diff --git a/Makefile b/Makefile index 97eefeb7..4d363d84 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ ROLES_DIR := ./roles APPLICATIONS_OUT := ./group_vars/all/11_applications.yml -APPLICATIONS_SCRIPT := ./cli/generate_defaults_applications.py +APPLICATIONS_SCRIPT := ./cli/generate-applications-defaults.py INCLUDES_OUT := ./tasks/include-docker-roles.yml -INCLUDES_SCRIPT := ./cli/generate_role_includes.py +INCLUDES_SCRIPT := ./cli/generate-role-includes.py .PHONY: build install test diff --git a/cli/fix-tabs.py b/cli/fix-tabs.py new file mode 100644 index 00000000..0785232a --- /dev/null +++ b/cli/fix-tabs.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +import os +import argparse +from pathlib import Path + +FILES_FIXED = [] + +def fix_tabs_in_file(file_path): + """Replaces tab characters with two spaces in the specified file.""" + with open(file_path, "r", encoding="utf-8") as f: + lines = f.readlines() + + if any('\t' in line for line in lines): + fixed_lines = [line.replace('\t', ' ') for line in lines] + with open(file_path, "w", encoding="utf-8") as f: + f.writelines(fixed_lines) + FILES_FIXED.append(str(file_path)) + +def find_yml_files(path): + """Yield all .yml files under a given path recursively.""" + for file in path.rglob("*.yml"): + if file.is_file(): + yield file + +def main(): + parser = argparse.ArgumentParser( + description="Fix tab characters in all .yml files under a given path (recursively)." + ) + parser.add_argument( + "path", + nargs="?", + default="roles", + help="Base path to search for .yml files (default: ./roles)" + ) + args = parser.parse_args() + + base_path = Path(args.path).resolve() + + if not base_path.exists(): + print(f"โŒ Path does not exist: {base_path}") + exit(1) + + print(f"๐Ÿ” Searching for .yml files under: {base_path}\n") + + for yml_file in find_yml_files(base_path): + fix_tabs_in_file(yml_file) + + if FILES_FIXED: + print("โœ… Fixed tab characters in the following files:") + for f in FILES_FIXED: + print(f" - {f}") + else: + print("โœ… No tabs found in any .yml files.") + +if __name__ == "__main__": + main() diff --git a/cli/fix_tabs.py b/cli/fix_tabs.py deleted file mode 100644 index 9ce14abc..00000000 --- a/cli/fix_tabs.py +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/env python3 - -import os -from pathlib import Path - -ROLES_DIR = Path("roles") # Adjust this if needed -FILES_FIXED = [] - -def fix_tabs_in_file(file_path): - with open(file_path, "r") as f: - lines = f.readlines() - - if any('\t' in line for line in lines): - fixed_lines = [line.replace('\t', ' ') for line in lines] - with open(file_path, "w") as f: - f.writelines(fixed_lines) - FILES_FIXED.append(str(file_path)) - -def main(): - for role_dir in sorted(ROLES_DIR.iterdir()): - if not role_dir.is_dir(): - continue - - vars_main = role_dir / "vars" / "main.yml" - if vars_main.exists(): - fix_tabs_in_file(vars_main) - - if FILES_FIXED: - print("โœ… Fixed tab characters in the following files:") - for f in FILES_FIXED: - print(f" - {f}") - else: - print("โœ… No tabs found in any vars/main.yml files.") - -if __name__ == "__main__": - main() diff --git a/cli/generate_defaults_applications.py b/cli/generate-applications-defaults similarity index 100% rename from cli/generate_defaults_applications.py rename to cli/generate-applications-defaults diff --git a/cli/generate_vaulted_credentials.py b/cli/generate-role-credentials.py similarity index 100% rename from cli/generate_vaulted_credentials.py rename to cli/generate-role-credentials.py diff --git a/cli/generate_role_includes.py b/cli/generate-role-includes.py similarity index 100% rename from cli/generate_role_includes.py rename to cli/generate-role-includes.py diff --git a/cli/playbook.py b/cli/playbook.py new file mode 100644 index 00000000..877fdca6 --- /dev/null +++ b/cli/playbook.py @@ -0,0 +1,83 @@ +#!/usr/bin/env python3 + +import argparse +import subprocess +import os +import datetime + +def run_ansible_playbook(inventory, playbook, modes, limit=None, password_file=None, verbose=0, skip_tests=False): + start_time = datetime.datetime.now().isoformat() + print(f"\nโ–ถ๏ธ Script started at: {start_time}\n") + print("\n๐Ÿ› ๏ธ Building project (make build)...\n") + subprocess.run(["make", "build"], check=True) + + if not skip_tests: + print("\n๐Ÿงช Running tests (make test)...\n") + subprocess.run(["make", "test"], check=True) + + cmd = ["ansible-playbook", "-i", inventory, playbook] + + if limit: + cmd.extend(["--limit", limit]) + + for key, value in modes.items(): + val = str(value).lower() if isinstance(value, bool) else str(value) + cmd.extend(["-e", f"{key}={val}"]) + + if password_file: + cmd.extend(["--vault-password-file", password_file]) + else: + cmd.extend(["--ask-vault-pass"]) + + if verbose: + cmd.append("-" + "v" * verbose) + + print("\n๐Ÿš€ Launching Ansible Playbook...\n") + subprocess.run(cmd, check=True) + + end_time = datetime.datetime.now().isoformat() + print(f"\nโœ… Script ended at: {end_time}\n") + +def main(): + script_dir = os.path.dirname(os.path.realpath(__file__)) + parser = argparse.ArgumentParser(description="Run Ansible Playbooks") + + parser.add_argument("inventory", help="Path to the inventory file") + parser.add_argument("--limit", help="Limit execution to a specific server") + parser.add_argument("--host-type", choices=["server", "personal-computer"], default="server") + parser.add_argument("--reset", action="store_true") + parser.add_argument("--test", action="store_true") + parser.add_argument("--update", action="store_true") + parser.add_argument("--backup", action="store_true") + parser.add_argument("--cleanup", action="store_true") + parser.add_argument("--debug", action="store_true") + parser.add_argument("--password-file") + parser.add_argument("--skip-tests", action="store_true") + parser.add_argument("-v", "--verbose", action="count", default=0) + + args = parser.parse_args() + + modes = { + "mode_reset": args.reset, + "mode_test": args.test, + "mode_update": args.update, + "mode_backup": args.backup, + "mode_cleanup": args.cleanup, + "enable_debug": args.debug, + "host_type": args.host_type + } + + playbook_file = os.path.join(os.path.dirname(script_dir), "playbook.yml") + + run_ansible_playbook( + inventory=args.inventory, + playbook=playbook_file, + modes=modes, + limit=args.limit, + password_file=args.password_file, + verbose=args.verbose, + skip_tests=args.skip_tests + ) + +if __name__ == "__main__": + main() diff --git a/cli/vault.py b/cli/vault.py new file mode 100644 index 00000000..383c6325 --- /dev/null +++ b/cli/vault.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 + +import argparse +import subprocess + +def run_ansible_vault(action, filename, password_file): + cmd = ["ansible-vault", action, filename, "--vault-password-file", password_file] + subprocess.run(cmd, check=True) + +def main(): + parser = argparse.ArgumentParser(description="Manage Ansible Vault") + parser.add_argument("action", choices=["edit", "decrypt", "encrypt"], help="Vault action") + parser.add_argument("filename", help="File to process") + parser.add_argument("--password-file", required=True, help="Path to the Vault password file") + args = parser.parse_args() + + run_ansible_vault(args.action, args.filename, args.password_file) + +if __name__ == "__main__": + main() diff --git a/main.py b/main.py index 8e07f9f1..31a5dffd 100755 --- a/main.py +++ b/main.py @@ -1,105 +1,69 @@ #!/usr/bin/env python3 import argparse -import subprocess import os -import datetime +import subprocess +import sys +from textwrap import indent -def run_ansible_vault(action, filename, password_file): - """Execute an ansible-vault command with the specified action on a file.""" - cmd = ["ansible-vault", action, filename, "--vault-password-file", password_file] - subprocess.run(cmd, check=True) +def list_cli_commands(cli_dir): + """List all available CLI script names in the given directory (without .py extension).""" + return sorted( + os.path.splitext(f.name)[0] for f in os.scandir(cli_dir) + if f.is_file() and f.name.endswith(".py") and not f.name.startswith("__") + ) -def run_ansible_playbook(inventory: str, playbook: str, modes: dict, limit: str = None, password_file: str = None, verbose: int = 0, skip_tests: bool = False): - start_time = datetime.datetime.now().isoformat() - print(f"\nโ–ถ๏ธ Script started at: {start_time}\n") - print("\n๐Ÿ› ๏ธ Building project (make build)...\n") - subprocess.run(["make", "build"], check=True) +def get_help_for_cli_command(cli_script): + """Return the --help output for a given CLI script path.""" + try: + result = subprocess.run( + [sys.executable, cli_script, "--help"], + capture_output=True, + text=True, + check=True + ) + return result.stdout.strip() + except subprocess.CalledProcessError as e: + return f"(โš ๏ธ Help not available: {e})" - if not skip_tests: - print("\n๐Ÿงช Running tests (make test)...\n") - subprocess.run(["make", "test"], check=True) - - """Execute an ansible-playbook command with optional parameters.""" - cmd = ["ansible-playbook", "-i", inventory, playbook] - - if limit: - cmd.extend(["--limit", limit]) - - if modes: - for key, value in modes.items(): - arg_value = f"{str(value).lower()}" if isinstance(value, bool) else f"{value}" - cmd.extend(["-e", f"{key}={arg_value}"]) - - if password_file: - cmd.extend(["--vault-password-file", password_file]) - else: - cmd.extend(["--ask-vault-pass"]) - - if verbose: - cmd.append("-" + "v" * verbose) - - print("\n๐Ÿš€ Launching Ansible Playbook...\n") - subprocess.run(cmd, check=True) +def build_full_help(cli_dir, available_cli_commands): + """Return a composed help string with help snippets from each CLI command.""" + help_output = ["Available CLI commands:\n"] + for cmd in available_cli_commands: + cli_path = os.path.join(cli_dir, f"{cmd}.py") + help_snippet = get_help_for_cli_command(cli_path) + help_output.append(f"๐Ÿ”น {cmd}\n{indent(help_snippet, ' ')}\n") + return "\n".join(help_output) - end_time = datetime.datetime.now().isoformat() - print(f"\nโœ… Script ended at: {end_time}\n") def main(): - # Change to script dir to execute all folders relative to their script_dir = os.path.dirname(os.path.realpath(__file__)) + cli_dir = os.path.join(script_dir, "cli") os.chdir(script_dir) - - parser = argparse.ArgumentParser(description="CyMaIS Ansible Deployment and Vault Management") - subparsers = parser.add_subparsers(dest="command", required=True) - # Vault subcommand parser - vault_parser = subparsers.add_parser("vault", help="Manage Ansible Vault") - vault_parser.add_argument("action", choices=["edit", "decrypt", "encrypt"], help="Vault action") - vault_parser.add_argument("filename", help="File to process") - vault_parser.add_argument("--password-file", required=True, help="Path to the Vault password file") + available_cli_commands = list_cli_commands(cli_dir) - # Playbook subcommand parser - playbook_parser = subparsers.add_parser("playbook", help="Run Ansible Playbooks") - playbook_parser.add_argument("inventory", help="Path to the inventory file") - playbook_parser.add_argument("--limit", help="Limit execution to a specific server") - playbook_parser.add_argument("--host-type", choices=["server", "personal-computer"], default="server", - help="Host type to run the playbook on; defaults to 'server'") - playbook_parser.add_argument("--reset", action="store_true", help="Enable reset mode") - playbook_parser.add_argument("--test", action="store_true", help="Enable test mode") - playbook_parser.add_argument("--update", action="store_true", help="Enable update mode") - playbook_parser.add_argument("--backup", action="store_true", help="Enable backup mode") - playbook_parser.add_argument("--cleanup", action="store_true", help="Enable cleanup mode") - playbook_parser.add_argument("--debug", action="store_true", help="Enable debugging output") - playbook_parser.add_argument("--password-file", help="Path to the Vault password file") - playbook_parser.add_argument("--skip-tests", action="store_true", help="Skip running make test before executing the playbook") - playbook_parser.add_argument("-v", "--verbose", action="count", default=0, - help=("Increase verbosity. This option can be specified multiple times " - "to increase the verbosity level (e.g., -vvv for more detailed debug output).")) + # Custom --help handler + if "--help" in sys.argv or "-h" in sys.argv: + parser = argparse.ArgumentParser( + description="CyMaIS CLI โ€“ proxy to tools in ./cli/", + formatter_class=argparse.RawDescriptionHelpFormatter + ) + parser.add_argument("cli_command", choices=available_cli_commands, help="The CLI command to run (proxied from ./cli/)") + parser.add_argument("cli_args", nargs=argparse.REMAINDER, help="Arguments to pass to the CLI script") + parser.print_help() + print() + print(build_full_help(cli_dir, available_cli_commands)) + sys.exit(0) + # Standard execution flow + parser = argparse.ArgumentParser(description="CyMaIS CLI โ€“ proxy to tools in ./cli/") + parser.add_argument("cli_command", choices=available_cli_commands, help="The CLI command to run (proxied from ./cli/)") + parser.add_argument("cli_args", nargs=argparse.REMAINDER, help="Arguments to pass to the CLI script") args = parser.parse_args() - if args.command == "vault": - run_ansible_vault(args.action, args.filename, args.password_file) - elif args.command == "playbook": - modes = { - "mode_reset": args.reset, - "mode_test": args.test, - "mode_update": args.update, - "mode_backup": args.backup, - "mode_cleanup": args.cleanup, - "enable_debug": args.debug, - "host_type": args.host_type - } - - run_ansible_playbook( - inventory=args.inventory, - playbook=f"{script_dir}/playbook.yml", - modes=modes, - limit=args.limit, - password_file=args.password_file, - verbose=args.verbose, - skip_tests=args.skip_tests - ) + cli_script_path = os.path.join(cli_dir, f"{args.cli_command}.py") + full_cmd = [sys.executable, cli_script_path] + args.cli_args + subprocess.run(full_cmd, check=True) if __name__ == "__main__": main()