From c5b16124efa4628abe5995fb11eca3372ec9e8a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=B6rg=20Thalheim?= Date: Thu, 24 Aug 2023 16:58:22 +0200 Subject: [PATCH] add machine subcommand --- pkgs/clan-cli/clan_cli/machines/__init__.py | 16 +++++++++++-- pkgs/clan-cli/clan_cli/machines/create.py | 13 +++++++++++ pkgs/clan-cli/clan_cli/machines/delete.py | 17 ++++++++++++++ pkgs/clan-cli/clan_cli/machines/folders.py | 11 +++++++++ pkgs/clan-cli/clan_cli/machines/list.py | 25 +++++++++++++++++++++ pkgs/clan-cli/clan_cli/machines/types.py | 22 ++++++++++++++++++ pkgs/clan-cli/clan_cli/secrets/groups.py | 3 +-- pkgs/clan-cli/clan_cli/secrets/machines.py | 3 +-- pkgs/clan-cli/clan_cli/secrets/types.py | 19 ---------------- 9 files changed, 104 insertions(+), 25 deletions(-) create mode 100644 pkgs/clan-cli/clan_cli/machines/create.py create mode 100644 pkgs/clan-cli/clan_cli/machines/delete.py create mode 100644 pkgs/clan-cli/clan_cli/machines/folders.py create mode 100644 pkgs/clan-cli/clan_cli/machines/list.py create mode 100644 pkgs/clan-cli/clan_cli/machines/types.py diff --git a/pkgs/clan-cli/clan_cli/machines/__init__.py b/pkgs/clan-cli/clan_cli/machines/__init__.py index ed51575f..fb12327e 100644 --- a/pkgs/clan-cli/clan_cli/machines/__init__.py +++ b/pkgs/clan-cli/clan_cli/machines/__init__.py @@ -1,6 +1,9 @@ # !/usr/bin/env python3 import argparse +from .create import register_create_parser +from .delete import register_delete_parser +from .list import register_list_parser from .update import register_update_parser @@ -13,5 +16,14 @@ def register_parser(parser: argparse.ArgumentParser) -> None: required=True, ) - groups_parser = subparser.add_parser("update", help="Update a machine") - register_update_parser(groups_parser) + update_parser = subparser.add_parser("update", help="Update a machine") + register_update_parser(update_parser) + + create_parser = subparser.add_parser("create", help="Create a machine") + register_create_parser(create_parser) + + delete_parser = subparser.add_parser("delete", help="Delete a machine") + register_delete_parser(delete_parser) + + list_parser = subparser.add_parser("list", help="List machines") + register_list_parser(list_parser) diff --git a/pkgs/clan-cli/clan_cli/machines/create.py b/pkgs/clan-cli/clan_cli/machines/create.py new file mode 100644 index 00000000..7e59a785 --- /dev/null +++ b/pkgs/clan-cli/clan_cli/machines/create.py @@ -0,0 +1,13 @@ +import argparse + +from .folders import machine_folder + + +def create_command(args: argparse.Namespace) -> None: + folder = machine_folder(args.host) + folder.mkdir(parents=True, exist_ok=True) + + +def register_create_parser(parser: argparse.ArgumentParser) -> None: + parser.add_argument("host", type=str) + parser.set_defaults(func=create_command) diff --git a/pkgs/clan-cli/clan_cli/machines/delete.py b/pkgs/clan-cli/clan_cli/machines/delete.py new file mode 100644 index 00000000..20dc3c08 --- /dev/null +++ b/pkgs/clan-cli/clan_cli/machines/delete.py @@ -0,0 +1,17 @@ +import argparse + +from ..errors import ClanError +from .folders import machine_folder + + +def delete_command(args: argparse.Namespace) -> None: + folder = machine_folder(args.host) + if folder.exists(): + folder.rmdir() + else: + raise ClanError(f"Machine {args.host} does not exist") + + +def register_delete_parser(parser: argparse.ArgumentParser) -> None: + parser.add_argument("host", type=str) + parser.set_defaults(func=delete_command) diff --git a/pkgs/clan-cli/clan_cli/machines/folders.py b/pkgs/clan-cli/clan_cli/machines/folders.py new file mode 100644 index 00000000..b7a5af2a --- /dev/null +++ b/pkgs/clan-cli/clan_cli/machines/folders.py @@ -0,0 +1,11 @@ +from pathlib import Path + +from ..dirs import get_clan_flake_toplevel + + +def machines_folder() -> Path: + return get_clan_flake_toplevel() / "machines" + + +def machine_folder(machine: str) -> Path: + return machines_folder() / machine diff --git a/pkgs/clan-cli/clan_cli/machines/list.py b/pkgs/clan-cli/clan_cli/machines/list.py new file mode 100644 index 00000000..dc4755f6 --- /dev/null +++ b/pkgs/clan-cli/clan_cli/machines/list.py @@ -0,0 +1,25 @@ +import argparse +import os + +from .folders import machines_folder +from .types import validate_hostname + + +def list_machines() -> list[str]: + path = machines_folder() + if not path.exists(): + return [] + objs: list[str] = [] + for f in os.listdir(path): + if validate_hostname(f): + objs.append(f) + return objs + + +def list_command(args: argparse.Namespace) -> None: + for machine in list_machines(): + print(machine) + + +def register_list_parser(parser: argparse.ArgumentParser) -> None: + parser.set_defaults(func=list_command) diff --git a/pkgs/clan-cli/clan_cli/machines/types.py b/pkgs/clan-cli/clan_cli/machines/types.py new file mode 100644 index 00000000..5c9ce484 --- /dev/null +++ b/pkgs/clan-cli/clan_cli/machines/types.py @@ -0,0 +1,22 @@ +import argparse +import re + +VALID_HOSTNAME = re.compile(r"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", re.IGNORECASE) + + +def validate_hostname(hostname: str) -> bool: + if len(hostname) > 63: + return False + return VALID_HOSTNAME.match(hostname) is not None + + +def machine_name_type(arg_value: str) -> str: + if len(arg_value) > 63: + raise argparse.ArgumentTypeError( + "Machine name must be less than 63 characters long" + ) + if not VALID_HOSTNAME.match(arg_value): + raise argparse.ArgumentTypeError( + "Invalid character in machine name. Allowed characters are a-z, 0-9, ., -, and _. Must not start with a number" + ) + return arg_value diff --git a/pkgs/clan-cli/clan_cli/secrets/groups.py b/pkgs/clan-cli/clan_cli/secrets/groups.py index 2d13614a..37b3cb2a 100644 --- a/pkgs/clan-cli/clan_cli/secrets/groups.py +++ b/pkgs/clan-cli/clan_cli/secrets/groups.py @@ -3,15 +3,14 @@ import os from pathlib import Path from ..errors import ClanError +from ..machines.types import machine_name_type, validate_hostname from . import secrets from .folders import sops_groups_folder, sops_machines_folder, sops_users_folder from .types import ( VALID_USER_NAME, group_name_type, - machine_name_type, secret_name_type, user_name_type, - validate_hostname, ) diff --git a/pkgs/clan-cli/clan_cli/secrets/machines.py b/pkgs/clan-cli/clan_cli/secrets/machines.py index ebb9fb8a..80a07e6d 100644 --- a/pkgs/clan-cli/clan_cli/secrets/machines.py +++ b/pkgs/clan-cli/clan_cli/secrets/machines.py @@ -1,13 +1,12 @@ import argparse +from ..machines.types import machine_name_type, validate_hostname from . import secrets from .folders import list_objects, remove_object, sops_machines_folder from .sops import write_key from .types import ( - machine_name_type, public_or_private_age_key_type, secret_name_type, - validate_hostname, ) diff --git a/pkgs/clan-cli/clan_cli/secrets/types.py b/pkgs/clan-cli/clan_cli/secrets/types.py index 00053765..874f8ad1 100644 --- a/pkgs/clan-cli/clan_cli/secrets/types.py +++ b/pkgs/clan-cli/clan_cli/secrets/types.py @@ -9,13 +9,6 @@ from .sops import get_public_key VALID_SECRET_NAME = re.compile(r"^[a-zA-Z0-9._-]+$") VALID_USER_NAME = re.compile(r"^[a-z_]([a-z0-9_-]{0,31})?$") -VALID_HOSTNAME = re.compile(r"^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", re.IGNORECASE) - - -def validate_hostname(hostname: str) -> bool: - if len(hostname) > 63: - return False - return VALID_HOSTNAME.match(hostname) is not None def secret_name_type(arg_value: str) -> str: @@ -26,18 +19,6 @@ def secret_name_type(arg_value: str) -> str: return arg_value -def machine_name_type(arg_value: str) -> str: - if len(arg_value) > 63: - raise argparse.ArgumentTypeError( - "Machine name must be less than 63 characters long" - ) - if not VALID_SECRET_NAME.match(arg_value): - raise argparse.ArgumentTypeError( - "Invalid character in machine name. Allowed characters are a-z, 0-9, ., -, and _. Must not start with a number" - ) - return arg_value - - def public_or_private_age_key_type(arg_value: str) -> str: if os.path.isfile(arg_value): arg_value = Path(arg_value).read_text().strip()