diff --git a/main.py b/main.py index 27b04b6..3dc4229 100644 --- a/main.py +++ b/main.py @@ -1,89 +1,112 @@ -import click +import click as cli from pathlib import Path, PurePath, PurePosixPath, PureWindowsPath, PosixPath, WindowsPath from whereami import PROJ_ROOT from custtypes import ExecutedPath, IdlePath import yaml as yams from anodes import RemoteNode, ControlNode # from typing import TypeAlias as Neotype -# from typing import Literal +from typing import Literal +from enum import Enum +from ansible_vault import Vault +from random import choice # import configparser as ini # from cerberus import Validator as constrain_by # import skansible_types as skato +class AnsibleScope(Enum): + INTERNAL = 0 + INVENTORY = 1 + GROUPVARS = 2 + HOSTVARS = 3 + ROLE = 4 + +# @TODO for 'Config' class, write methods to pull and push from Ansible YAML files # @NOTE https://docs.python.org/3/library/configparser.html#quick-start class Config: path: ExecutedPath = PROJ_ROOT / "config.ini" + _cnode = ControlNode(PROJ_ROOT) + __scope_paths = { + AnsibleScope.INTERNAL.name: path, + AnsibleScope.GROUPVARS.name: Path(str(_cnode.invvar_data[0])), + AnsibleScope.HOSTVARS.name: Path(str(_cnode.invvar_data[1])) + } -config = Config() + def __init__(self, filepath: str, anscope: AnsibleScope = AnsibleScope.INTERNAL.name, mode: str = "r+"): + filepath: ExecutedPath = self.__scope_paths[anscope] / filepath -@click.group() -def skansible(): + if filepath.exists(): + self.filepath = filepath + else: + raise FileNotFoundError + + self.mode: str = mode + self.file = None + self.model = None + + def __enter__(self): + self.file = open(str(self.filepath), self.mode) + self.model = yams.load(self.file.read()) + return self.model + + def __exit__(self, exc_type, exc_value, exc_traceback): + file_content = yams.dump(self.model) + self.file.write(file_content) + self.file.close() + +@cli.group() +@cli.pass_context +def skansible(ctx): + ctx.ensure_object(dict) + +@skansible.group(help="Replace conventionally-typed Ansible variables") +@cli.pass_context +def mod(ctx): + with Config(entity, AnsibleScope.HOSTVARS.name) as config: + ctx.obj["fqdn"] = config["fqdn"] + +@skansible.group(help="Remove conventionally-typed Ansible variables") +def rm(): pass -@click.group() -@click.argument("hostname", nargs=-1, type=str) -@click.option("-g", "--group", type=str, show_default=True) -@click.option("-p", "--password", type=str, prompt=True, prompt_required=False) -@click.option("-a", "--api", type=str) -@click.option("-k", "--key", multiple=True) -@click.option("-t", "--tag", multiple=True, type=str) -@click.option("-n", "--fqdn", type=(str, None)) -@click.option("-s", "--service", type=(str, None)) -def addhost(hostname, password, api, key, group = "ungrouped", service = None, fqdn = None, tags = None): - hosts_model = dict() - hosts_model[group]["hosts"] = dict() +@skansible.group(help="Append conventionally-typed entries to Ansible variables") +def append(): + pass - for name in hostname: - hosts_model[group]["hosts"][name] = None +@skansible.group(help="Initialize Ansible variable or object with defaults") +def init(): + pass - with open(str(PROJ_ROOT / "hosts.yml")) as hosts_file: - yams.dump(hosts_model, hosts_file) +# @skansible.group(help="Show Ansible assets") +# @cli.argument("-d", "--datatype", type=str, help="Specify type of Ansible object or variable to show") +# def show(datatype): +# pass - cnode = ControlNode() - if fqdn is not None: - rnode = RemoteNode(cnode, api, password, fqdn) - else: - if isinstance(hostname, (tuple, list)): - if len(hostname) > 0 and len(hostname) < 2: - rnode = RemoteNode(cnode, api, password, hostname[0]) - else: - raise ValueError - elif isinstance(hostname, str): - rnode = RemoteNode(cnode, api, password, hostname) - else: - raise TypeError +@mod.command("vps", help="Add 'vps_service' Ansible variable") +@cli.argument("entity", type=str, help="Specify FQDN/alias or host group to associate with the VPS") +@cli.option("-p", "--password", type=str, prompt=True, prompt_required=False, help="Prompt for password of root or administrative user of VPS") +@cli.option("-a", "--api", type=str, help="API key for host service providing VPS") +@cli.option("-k", "--pubkey", multiple=True, help="A file glob pattern for acquiring user's public SSH keys") +@cli.option("-k", "--privkey", multiple=True, help="A file glob pattern for acquiring user's private SSH keys") +@cli.option("-m", "--motd", multiple=True, help="Provide basenames for MOTD SSH scripts") +@cli.option("-t", "--tag", multiple=True, type=str) +@cli.option("-s", "--service", type=str) +@cli.option("-r", "--region", type=str) +@cli.option("-f", "--fate", type=str) +@cli.pass_context +# @TODO rewrite below command function, using 'ctx' parameter for values shared with sibling commands +def mod_vps(ctx, entity: str, password: str | None = None, api: str | None = None, pubkey: tuple[str] = None, privkey: tuple[str] = None, service: str | int | None = None, region: Literal["us-east"] | None = None, tags: tuple[str] = None, fate: Literal["disposal", "retention"] | None = None): + with Config(entity, AnsibleScope.HOSTVARS.name) as config: + raise NotImplementedError - if tag is not None: - if isinstance(tag, (tuple, list)) and len(tag) > 0: - rnode.add_tags(*tag) +@mod.command("host", help="Add host to Ansible inventory") +@cli.argument("hostname", multiple=True, type=str, help="Provide aliases / FQDN / IP addresses for host(s)") +@cli.option("-g", "--group", type=str, default="ungrouped", help="Provide group name given host(s) fall under") +@cli.pass_context +# @TODO rewrite below command function, using 'ctx' parameter for values shared with sibling commands +def mod_host(group, hostname): + with Config(entity, AnsibleScope.INVENTORY.name) as config: + raise NotImplementedError - if key is not None: - if isinstance(key, tuple) and len(key) > 0: - rnode.import_keys(key) - - if service is not None: - rnode.service = service - - if group == "ungrouped": - if isinstance(hostname, (tuple, list)): - if len(hostname) > 0 and len(hostname) < 2: - filepath = PurePath(str(cnode.invvar_data[1]), hostname[0]) - else: - raise ValueError - elif isinstance(hostname, str): - filepath = PurePath(str(cnode.invvar_data[1]), hostname) - else: - raise TypeError - else: - filepath = PurePath(str(cnode.invvar_data[0]), group) - - inv_model = rnode.itemize() - with open(str(filepath), "w") as inv_file: - yams.dump(inv_model, inv_file) - - print((hosts_model, inv_model)) - -skansible.add_command(addhost) if __name__ == "__main__": skansible()