2023-12-30 12:52:24 +00:00
|
|
|
#!/usr/bin/python3
|
|
|
|
|
|
|
|
from __future__ import (absolute_import, division, print_function)
|
|
|
|
__metaclass__ = type
|
|
|
|
|
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import logging
|
|
|
|
from argparse import Namespace
|
|
|
|
import pathlib
|
|
|
|
import traceback
|
|
|
|
|
|
|
|
# in the library
|
2023-12-31 03:19:26 +00:00
|
|
|
mod_path = ''
|
2023-12-30 12:52:24 +00:00
|
|
|
if os.environ.get('PLAY_ANSIBLE_SRC',''):
|
|
|
|
# running from source
|
|
|
|
mod_path = os.environ.get('PLAY_ANSIBLE_SRC','')
|
|
|
|
mod_path = os.path.join(mod_path, 'src', 'ansible_gentooimgr')
|
|
|
|
assert os.path.isdir(mod_path), f"parent {mod_path}"
|
|
|
|
assert os.path.isfile(os.path.join(mod_path, '__init__.py')),f"index {mod_path}"
|
|
|
|
assert os.path.isdir(os.path.join(mod_path, 'gentooimgr')), f"sub {mod_path}"
|
|
|
|
sys.path.append(mod_path)
|
|
|
|
else:
|
|
|
|
# in the library
|
|
|
|
mod_path = os.path.dirname(os.path.realpath('__file__'))
|
|
|
|
mod_path = os.path.join(mod_path, 'src', 'ansible_gentooimgr')
|
|
|
|
assert os.path.isdir(mod_path), f"parent {mod_path}"
|
|
|
|
assert os.path.isfile(os.path.join(mod_path, '__init__.py')),f"index {mod_path}"
|
|
|
|
assert os.path.isdir(os.path.join(mod_path, 'gentooimgr')), f"sub {mod_path}"
|
|
|
|
sys.path.append(mod_path)
|
|
|
|
try:
|
|
|
|
import gentooimgr
|
|
|
|
except Exception as e:
|
|
|
|
sys.stderr.write(f"{mod_path} {sys.path} {traceback.print_exc()}")
|
|
|
|
raise
|
|
|
|
import ansible
|
|
|
|
|
|
|
|
DOCUMENTATION = rf'''
|
|
|
|
---
|
|
|
|
module: gentooimgr
|
|
|
|
|
|
|
|
short_description: Gentoo Image Builder for Cloud and Turnkey ISO installers
|
|
|
|
|
|
|
|
|
|
|
|
version_added: "1.0.0"
|
|
|
|
|
|
|
|
description:
|
|
|
|
* This project enables easy access to building ``systemd`` or ``openrc`` -based images.
|
|
|
|
* Performs automatic download AND verification of the linux iso, stage3 tarball and portage.
|
|
|
|
* Caches the iso and stage3 .txt files for at most a day before redownloading and rechecking for new files
|
|
|
|
* Sane and readable cli commands to build, run and test.
|
|
|
|
* Step system to enable user to continue off at the same place if a step fails
|
|
|
|
* No heavy packages like rust included ** TODO
|
|
|
|
|
|
|
|
options:
|
|
|
|
action:
|
|
|
|
description: The action to be run by the image builder
|
|
|
|
choices:
|
|
|
|
- build
|
|
|
|
- run
|
|
|
|
- status
|
|
|
|
- install
|
|
|
|
- chroot
|
|
|
|
- unchroot
|
|
|
|
- command
|
|
|
|
- shrink
|
|
|
|
- kernel
|
|
|
|
required: true
|
|
|
|
# clean test
|
|
|
|
config:
|
|
|
|
default: cloud.json
|
|
|
|
description: init configuration file or or base.json or cloud.json
|
|
|
|
required: false
|
|
|
|
loglevel:
|
|
|
|
default: {logging.INFO}
|
|
|
|
description: python logging level <= 50, INFO=20
|
|
|
|
required: false
|
|
|
|
threads:
|
|
|
|
default: 1
|
|
|
|
description: Number of threads to use
|
|
|
|
required: false
|
|
|
|
profile:
|
|
|
|
default: openrc
|
|
|
|
description: The init system
|
|
|
|
choices:
|
|
|
|
- openrc
|
|
|
|
- systemd
|
|
|
|
required: false
|
|
|
|
kernel_dir:
|
|
|
|
default: /usr/src/linux
|
|
|
|
description: Where kernel is specified. By default uses the active linux kernel
|
|
|
|
required: false
|
|
|
|
portage:
|
|
|
|
description: Extract the specified portage tarball onto the filesystem
|
|
|
|
required: false
|
|
|
|
stage3:
|
|
|
|
description: Extract the specified stage3 package onto the filesystema
|
|
|
|
required: false
|
|
|
|
action_args:
|
|
|
|
default: []
|
|
|
|
description: Arguments for some of the actions - UNUSED!
|
|
|
|
required: false
|
|
|
|
temporary_dir:
|
|
|
|
description: Path to temporary directory for downloading files (20G)
|
|
|
|
required: false
|
|
|
|
qcow:
|
|
|
|
description: Path to file to serve as the base image
|
|
|
|
required: false
|
|
|
|
|
|
|
|
# Specify this value according to your collection
|
|
|
|
# in format of namespace.collection.doc_fragment_name
|
|
|
|
# extends_documentation_fragment:
|
|
|
|
# - my_namespace.my_collection.my_doc_fragment_name
|
|
|
|
|
|
|
|
author:
|
|
|
|
- Your Name (@yourGitHubHandle)
|
|
|
|
'''
|
|
|
|
|
|
|
|
#[-y DAYS]
|
|
|
|
# [-d DOWNLOAD_DIR]
|
|
|
|
# [-f]
|
|
|
|
# [--format FORMAT]
|
|
|
|
|
|
|
|
EXAMPLES = r'''
|
|
|
|
# Pass in a message
|
|
|
|
- name: Test with a message
|
|
|
|
my_namespace.my_collection.my_test:
|
|
|
|
name: hello world
|
|
|
|
|
|
|
|
# pass in a message and have changed true
|
|
|
|
- name: Test with a message and changed output
|
|
|
|
my_namespace.my_collection.my_test:
|
|
|
|
name: hello world
|
|
|
|
new: true
|
|
|
|
|
|
|
|
# fail the module
|
|
|
|
- name: Test failure of the module
|
|
|
|
my_namespace.my_collection.my_test:
|
|
|
|
name: fail me
|
|
|
|
'''
|
|
|
|
|
|
|
|
RETURN = r'''
|
|
|
|
# These are examples of possible return values, and in general should use other names for return values.
|
|
|
|
message:
|
|
|
|
description: The output message that the test module generates.
|
|
|
|
type: str
|
|
|
|
returned: always
|
|
|
|
sample: 'goodbye'
|
|
|
|
'''
|
|
|
|
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
|
|
|
|
|
|
|
|
def run_module():
|
|
|
|
# define available arguments/parameters a user can pass to the module
|
2023-12-31 03:19:26 +00:00
|
|
|
#? default config from __file__ ?
|
|
|
|
if mod_path and os.path.isdir(mod_path):
|
|
|
|
def_config = os.path.join(mod_path, 'configs', 'base.json')
|
|
|
|
else:
|
|
|
|
# WARN:
|
|
|
|
def_config = 'base.json'
|
2023-12-30 12:52:24 +00:00
|
|
|
module_args = dict(
|
|
|
|
action=dict(type='str', required=True),
|
|
|
|
loglevel=dict(type='int', required=False, default=logging.INFO),
|
|
|
|
threads=dict(type='int', required=False, default=1),
|
2024-01-01 01:04:40 +00:00
|
|
|
# Module error: required and default are mutually exclusive for config
|
|
|
|
config=dict(type='path', default=def_config),
|
2023-12-30 12:52:24 +00:00
|
|
|
profile=dict(type='str', required=False),
|
|
|
|
kernel_dir=dict(type='path', required=False),
|
|
|
|
portage=dict(type='path', required=False),
|
|
|
|
stage3=dict(type='path', required=False),
|
|
|
|
temporary_dir=dict(type='path', required=False, default=pathlib.Path(os.getcwd())),
|
|
|
|
download_dir=dict(type='path', required=False, default=pathlib.Path(os.getcwd())),
|
|
|
|
qcow=dict(type='path', required=False),
|
|
|
|
)
|
|
|
|
|
|
|
|
# seed the result dict in the object
|
|
|
|
# we primarily care about changed and state
|
|
|
|
# changed is if this module effectively modified the target
|
|
|
|
# state will include any data that you want your module to pass back
|
|
|
|
# for consumption, for example, in a subsequent task
|
|
|
|
result = dict(
|
|
|
|
changed=False,
|
|
|
|
original_message='',
|
|
|
|
message=''
|
|
|
|
)
|
|
|
|
|
|
|
|
# the AnsibleModule object will be our abstraction working with Ansible
|
|
|
|
# this includes instantiation, a couple of common attr would be the
|
|
|
|
# args/params passed to the execution, as well as if the module
|
|
|
|
# supports check mode
|
|
|
|
module = AnsibleModule(
|
|
|
|
argument_spec=module_args,
|
|
|
|
supports_check_mode=True
|
|
|
|
)
|
|
|
|
|
|
|
|
# if the user is working with this module in only check mode we do not
|
|
|
|
# want to make any changes to the environment, just return the current
|
|
|
|
# state with no modifications
|
|
|
|
if module.check_mode:
|
|
|
|
module.exit_json(**result)
|
|
|
|
|
|
|
|
# manipulate or modify the state as needed (this is going to be the
|
|
|
|
# part where your module will do what it needs to do)
|
|
|
|
# if module.params.get('thirsty'):
|
|
|
|
|
|
|
|
oargs = Namespace(**module.params)
|
|
|
|
# during the execution of the module, if there is an exception or a
|
|
|
|
# conditional state that effectively causes a failure, run
|
|
|
|
# AnsibleModule.fail_json() to pass in the message and the result
|
|
|
|
result['original_message'] = ""
|
|
|
|
try:
|
|
|
|
from gentooimgr.__main__ import main
|
|
|
|
retval = main(oargs)
|
2023-12-31 03:19:26 +00:00
|
|
|
# should be 0
|
|
|
|
# is stdout already in result? how can it be?
|
2023-12-30 12:52:24 +00:00
|
|
|
except Exception as e:
|
|
|
|
result['message'] = str(e)
|
2024-01-02 02:13:28 +00:00
|
|
|
result['original_message'] = f"{traceback.print_exc()}"
|
|
|
|
module.fail_json(msg=f'Exception {e.__class__}', **result)
|
2023-12-30 12:52:24 +00:00
|
|
|
else:
|
|
|
|
result['message'] = str(retval)
|
|
|
|
|
|
|
|
# use whatever logic you need to determine whether or not this module
|
|
|
|
# made any modifications to your target
|
2023-12-31 07:39:01 +00:00
|
|
|
# build run test chroot unchroot status clean kernel shrink
|
|
|
|
if oargs.action in ['status', '']:
|
2023-12-30 12:52:24 +00:00
|
|
|
result['changed'] = False
|
|
|
|
else:
|
|
|
|
result['changed'] = True
|
|
|
|
|
|
|
|
# in the event of a successful module execution, you will want to
|
|
|
|
# simple AnsibleModule.exit_json(), passing the key/value results
|
|
|
|
module.exit_json(**result)
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
run_module()
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|
|
|
|
|