This commit is contained in:
emdee 2023-12-26 00:06:43 +00:00
parent 06cffbdbd7
commit d6e3e05796
10 changed files with 455 additions and 13 deletions

0
__init__.py Normal file
View File

View File

@ -1,4 +1,6 @@
import sys import sys
from subprocess import Popen, PIPE
import gentooimgr.chroot import gentooimgr.chroot
def command(config, *args): def command(config, *args):

View File

@ -15,9 +15,8 @@ def older_than_a_day(fullpath):
return time.time() - filetime > (gentooimgr.config.DAY_IN_SECONDS * return time.time() - filetime > (gentooimgr.config.DAY_IN_SECONDS *
gentooimgr.config.DAYS) gentooimgr.config.DAYS)
def find_iso(download_dir): def find_iso(download_dir):
LOG.info(f"Looking for iso in {download_dir}")
name = None name = None
ext = None ext = None
found = [] found = []
@ -34,9 +33,10 @@ def make_iso_from_dir(mydir):
path to iso that was created or NoneType if mydir is not found path to iso that was created or NoneType if mydir is not found
""" """
if not os.path.exists(mydir): if not os.path.exists(mydir):
LOG.warn(f"\t:: dir not found {mydir}")
return return
print(f"\t:: Making ISO with dir of {mydir}") LOG.info(f"\t:: Making ISO with dir of {mydir}")
path = os.path.join(mydir, "..", "cloudgen.iso") path = os.path.join(mydir, "..", "cloudgen.iso")
proc = Popen(["mkisofs", proc = Popen(["mkisofs",
"--input-charset", "utf-8", "--input-charset", "utf-8",
@ -52,15 +52,17 @@ def make_iso_from_dir(mydir):
return path return path
def portage_from_dir(d, filename=None): def portage_from_dir(download_dir, filename=None):
"""Find portage file from directory. Will do a check in os.listdir() for portage*.tar.bz2. """Find portage file from directory. Will do a check in os.listdir() for portage*.tar.bz2.
If a filename is provided, this function either returns that filename assuming it exists in d, If a filename is provided, this function either returns that filename assuming it exists in d,
or return None. If filename is None, this looks through all entries for portage files and if or return None. If filename is None, this looks through all entries for portage files and if
only one exists, returns it, otherwise None. only one exists, returns it, otherwise None.
""" """
assert download_dir, f"empty {download_dir} for for portage"
LOG.info(f"Looking for portage in {download_dir}")
found = [] found = []
for f in os.listdir(d): for f in os.listdir(download_dir):
if filename is not None: if filename is not None:
if filename == f: if filename == f:
found.append(f) found.append(f)
@ -70,7 +72,7 @@ def portage_from_dir(d, filename=None):
if len(found) > 1: if len(found) > 1:
LOG.error("\tEE: More than one portage file exists, please specify the exact portage file with --portage [file] or remove all others\n") LOG.error("\tEE: More than one portage file exists, please specify the exact portage file with --portage [file] or remove all others\n")
LOG.error(''.join([f"\t{f}\n" for f in found])) LOG.error(''.join([f"\t{f}\n" for f in found]))
LOG.error(f"in {d}\n") LOG.error(f"in {download_dir}\n")
sys.exit(1) sys.exit(1)
return found[0] if found else None return found[0] if found else None

View File

@ -72,7 +72,7 @@
"syslog-ng": "default", "syslog-ng": "default",
"cronie": "default", "cronie": "default",
"acpid": "default", "acpid": "default",
"ntp": "default" "ntp": "default",
"qemu-guest-agent": "default" "qemu-guest-agent": "default"
}, },
"iso": null, "iso": null,

View File

@ -151,7 +151,7 @@ def step10_emerge_pkgs(args, cfg):
proc = Popen(["emerge", "-j1", single]) proc = Popen(["emerge", "-j1", single])
proc.communicate() proc.communicate()
LOG.info("KERNEL PACKAGES", packages.get("kernel")) LOG.info(f"KERNEL PACKAGES {packages.get('kernel')}")
if packages.get("kernel", []): if packages.get("kernel", []):
cmd = ["emerge", "-j", str(args.threads)] + packages.get("kernel", []) cmd = ["emerge", "-j", str(args.threads)] + packages.get("kernel", [])
proc = Popen(cmd) proc = Popen(cmd)

View File

@ -3,6 +3,8 @@ import os
import sys import sys
import argparse import argparse
from subprocess import Popen, PIPE from subprocess import Popen, PIPE
from gentooimgr import LOG
import gentooimgr.config import gentooimgr.config
import gentooimgr.common import gentooimgr.common
def create_image(args, config: dict, overwrite: bool = False) -> str: def create_image(args, config: dict, overwrite: bool = False) -> str:
@ -36,6 +38,8 @@ def run_image(
- mount_isos: list of iso paths to mount in qemu as disks. - mount_isos: list of iso paths to mount in qemu as disks.
""" """
iso = config.get("iso") iso = config.get("iso")
prefix = args.temporary_dir
LOG.info(f"iso from config {iso}")
if iso is None: if iso is None:
iso = gentooimgr.common.find_iso( iso = gentooimgr.common.find_iso(
os.path.join( os.path.join(
@ -43,6 +47,13 @@ def run_image(
".." ".."
) )
) )
LOG.info(f"iso from cwd {iso}")
if not iso:
prefix = config.get('temporary_dir')
iso = gentooimgr.common.find_iso(prefix)
LOG.info(f"iso from {prefix} {iso}")
assert iso, f"iso not found {iso}"
if isinstance(iso, list): if isinstance(iso, list):
assert len(iso), f"iso list is empty {iso}" assert len(iso), f"iso list is empty {iso}"
@ -55,6 +66,11 @@ def run_image(
qmounts.append("-drive") qmounts.append("-drive")
qmounts.append(f"file={i},media=cdrom") qmounts.append(f"file={i},media=cdrom")
assert image, f"image is empty {image}"
if not os.path.exists(image):
if os.path.exists(os.path.join(prefix, image)):
image = os.path.join(prefix, image)
assert os.path.exists(image), f"image not found {image}"
threads = args.threads threads = args.threads
cmd = [ cmd = [
"qemu-system-x86_64", "qemu-system-x86_64",
@ -65,7 +81,6 @@ def run_image(
"-drive", f"file={image},if=virtio,index=0", "-drive", f"file={image},if=virtio,index=0",
"-cdrom", iso, "-cdrom", iso,
"-net", "nic,model=virtio", "-net", "nic,model=virtio",
"-net", "user",
"-vga", "virtio", "-vga", "virtio",
"-cpu", "kvm64", "-cpu", "kvm64",
"-chardev", "file,id=charserial0,path=gentoo.log", "-chardev", "file,id=charserial0,path=gentoo.log",
@ -73,12 +88,14 @@ def run_image(
"-chardev", "pty,id=charserial1", "-chardev", "pty,id=charserial1",
"-device", "isa-serial,chardev=charserial1,id=serial1" "-device", "isa-serial,chardev=charserial1,id=serial1"
] ]
# "-net", "user",
# -net user: network backend 'user' is not compiled into this binary"
cmd += qmounts cmd += qmounts
print(cmd) LOG.info(cmd)
proc = Popen(cmd, stderr=PIPE, stdout=PIPE) proc = Popen(cmd, stderr=PIPE, stdout=PIPE)
stdout, stderr = proc.communicate() stdout, stderr = proc.communicate()
if stderr: if stderr:
sys.stderr.write(str(stderr)) LOG.error(str(stderr))
sys.stderr.write("\n")

View File

@ -23,6 +23,11 @@ def run(args, config: dict) -> None:
assert os.path.isfile(main_iso), f"iso not found {main_iso}" assert os.path.isfile(main_iso), f"iso not found {main_iso}"
LOG.info(args) LOG.info(args)
LOG.info(f'iso={args.iso}') LOG.info(f'iso={args.iso}')
if args.iso != config['iso']:
LOG.warn(f'iso={args.iso}')
config['iso'] = args.iso
else:
LOG.info(f'iso={args.iso}')
gentooimgr.qemu.run_image( gentooimgr.qemu.run_image(
args, args,
config, config,

View File

@ -17,14 +17,17 @@ Step 16: Sysconfig
Step 17: fstab Step 17: fstab
""" """
import os import os
import sys
import json import json
# from gentooimgr import LOG # from gentooimgr import LOG
import gentooimgr.config import gentooimgr.config
import gentooimgr.configs import gentooimgr.configs
from gentooimgr import install
def print_template(args, configjson): def print_template(args, configjson, prefix='/tmp'):
print(__doc__) print(__doc__)
sys.stderr.write(f"the last step to succeed is {install.getlaststep(prefix)}\n")
print(f"the last step to succeed is {install.getlaststep(prefix)}\n") print(f"the last step to succeed is {install.getlaststep(prefix)}\n")
print(f"""------------------------ STATUS ------------------------ print(f"""------------------------ STATUS ------------------------

189
library/ansible-keepassxc.py Executable file
View File

@ -0,0 +1,189 @@
#!/usr/bin/python
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import os
import traceback
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
IMPORT_ERR = None
try:
# import _argon2_xffi_bindings
import pykeepass as keepass
except ImportError:
IMPORT_ERR = traceback.format_exc()
DOCUMENTATION = r'''
---
module: ansible-keepassxc
short_description: Module to read credentials from KeePassXC
version_added: "0.0.1"
description: Module to read credentials from KeePassXC
options:
database:
description: Path to database file
required: true
type: str
password:
description: Database Password
required: true
type: str
keyfile:
description: Path to key file
required: false
type: str
entry:
description: Entry name for the attribute to fetch
required: true
type: str
group:
decription: Group name that the Entry belongs to
required: false
type: str
author:
- Jeremy Lumley (@jlumley)
'''
EXAMPLES = r'''
# Fetch the credentials for the server_1 entry in any group
- name: Fetch server_1 credentials
jlumley.jlumley.ansible-keepassxc:
database: "/secrets/db.kdbx"
password: "s3cure_p4550rd"
entry: "server_1"
# Fetch the reddit entry in the social group
- name: Fetching reddit credentials
jlumley.jlumley.ansible-keepassxc:
database: "/secrets/db.kdbx"
password: "sup3r_s3cure_p4550rd"
entry: "reddit"
group: "social"
# Fetch a custom strig attribute from the github entry
- name: Fetch Github API Token
jlumley.jlumley.ansible-keepassxc:
database: "/secrets/db.kdbx"
password: "d0pe_s3cure_p4550rd"
keyfile: "/secrets/top_secret_key"
entry: "github"
group: "development"
'''
RETURN = r'''
# Return values
username:
description: Username of entry if present
type: str
returned: always
sample: 's3cr3t_us3r'
password:
description: Password of entry if present
type: str
returned: always
sample: 's3cr3t_p455word'
url:
description: Url of entry if present
type: str
returned: always
sample: 'http://reddit.com'
custom_fields:
description: dictionary containing all custom fields
type: dict
returned: always
sample: False
no_log:
description: suppress logging of password
type: bool
returned: never
sample: False
'''
def run_module():
# define available arguments/parameters a user can pass to the module
module_args = dict(
database = dict(type='str', required=True),
password = dict(type='str', required=False,
default=os.environ.get('ANSIBLE_KEEPASSXC_PASSWORD')),
keyfile = dict(type='str', required=False, default=None),
entry = dict(type='str', required=True),
group = dict(type='str', required=False),
no_log = dict(type='bool', required=False, default=False),
)
# seed the result dict in the object
result = dict(
changed=False,
username='',
password='',
url='',
custom_fields={}
)
# Currently no support for a check_mode this maybe added later if
# functionality to modify the database is added later
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=False,
)
if IMPORT_ERR:
module.fail_json(
msg=missing_required_lib("pykeepass"),
exception=IMPORT_ERR
)
# unlock local keepass database
try:
kp = keepass.PyKeePass(
module.params['database'],
password=module.params['password'],
keyfile=module.params['keyfile'])
except keepass.exceptions.CredentialsError:
module.fail_json(msg='Invalid Credentials')
# find entry
entry = kp.find_entries(
title=module.params['entry'],
group=module.params['group']
)
# fail is entry is not present
if not entry:
module.fail_json(msg=f"Unable to find entry: {module.params['entry']}")
else:
entry = entry[0]
custom_field_keys = entry._get_string_field_keys(exclude_reserved=True)
custom_fields = dict()
for key in custom_field_keys:
custom_fields[key] = entry.get_custom_property(key)
result = dict (
changed=False,
username=entry.username,
password=entry.password,
url=entry.url,
custom_fields=custom_fields
)
# 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()

224
library/ansible_gentooimgr.py Executable file
View File

@ -0,0 +1,224 @@
#!/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
sys.path.append('/mnt/o/var/local/src/play_tox/src/ansible_gentooimgr')
# 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
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),
config=dict(type='str', default='cloud.json', required=False),
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)
except Exception as e:
result['message'] = str(e)
e = traceback.print_exc()
if e: result['original_message'] += f"{e}"
module.fail_json(msg='Exception', **result)
else:
result['message'] = str(retval)
# use whatever logic you need to determine whether or not this module
# made any modifications to your target
if dArgs['action'] in ['status']:
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()