From cd248b69db0a66e808844a6006424a42d28744f2 Mon Sep 17 00:00:00 2001 From: Louis Opter Date: Fri, 7 Feb 2025 11:31:38 +0000 Subject: [PATCH] clan-cli: machines delete: delete the machine's vars and secrets When a machine is deleted with `clan machines delete`, remove its vars and legacy secrets, and update any secrets that reference the machine's key. This command is a superset of `clan secrets machine delete`, and I am wondering if we could remove the `clan secrets machine` subcommand, unless there is an use case for having a machine defined without its key, and any secrets/vars? Note: - This deletes the `ListSecretsOptions` dataclass, as it did not seem to bring any value, especially since `list_secrets` was receiving its individual members instead of the whole dataclass. We can always bring it back if complexity grows to demand it. --- pkgs/clan-cli/clan_cli/completions.py | 9 ++---- pkgs/clan-cli/clan_cli/machines/delete.py | 38 +++++++++++++++++++++-- pkgs/clan-cli/clan_cli/secrets/secrets.py | 23 +++++--------- 3 files changed, 45 insertions(+), 25 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/completions.py b/pkgs/clan-cli/clan_cli/completions.py index 2bb64aa83..30e329089 100644 --- a/pkgs/clan-cli/clan_cli/completions.py +++ b/pkgs/clan-cli/clan_cli/completions.py @@ -208,16 +208,11 @@ def complete_secrets( Provides completion functionality for clan secrets """ from .clan_uri import Flake - from .secrets.secrets import ListSecretsOptions, list_secrets + from .secrets.secrets import list_secrets flake = clan_dir_result if (clan_dir_result := clan_dir(None)) is not None else "." - options = ListSecretsOptions( - flake=Flake(flake), - pattern=None, - ) - - secrets = list_secrets(options.flake.path, options.pattern) + secrets = list_secrets(Flake(flake).path) secrets_dict = dict.fromkeys(secrets, "secret") return secrets_dict diff --git a/pkgs/clan-cli/clan_cli/machines/delete.py b/pkgs/clan-cli/clan_cli/machines/delete.py index 18adc78c4..b24de1392 100644 --- a/pkgs/clan-cli/clan_cli/machines/delete.py +++ b/pkgs/clan-cli/clan_cli/machines/delete.py @@ -1,25 +1,57 @@ import argparse import logging import shutil +from pathlib import Path +from clan_cli import inventory from clan_cli.api import API from clan_cli.clan_uri import Flake from clan_cli.completions import add_dynamic_completer, complete_machines from clan_cli.dirs import specific_machine_dir -from clan_cli.inventory import delete +from clan_cli.secrets.folders import sops_secrets_folder +from clan_cli.secrets.machines import has_machine as secrets_has_machine +from clan_cli.secrets.machines import remove_machine as secrets_machine_remove +from clan_cli.secrets.secrets import ( + list_secrets, +) +from clan_cli.vars.list import ( + public_store as vars_public_store, +) +from clan_cli.vars.list import ( + secret_store as vars_secret_store, +) + +from .machines import Machine log = logging.getLogger(__name__) @API.register def delete_machine(flake: Flake, name: str) -> None: - delete(str(flake.path), {f"machines.{name}"}) + inventory.delete(str(flake.path), {f"machines.{name}"}) + + changed_paths: list[Path] = [] - # Remove the machine directory folder = specific_machine_dir(flake.path, name) if folder.exists(): + changed_paths.append(folder) shutil.rmtree(folder) + # louis@(2025-02-04): clean-up legacy (pre-vars) secrets: + sops_folder = sops_secrets_folder(flake.path) + filter_fn = lambda secret_name: secret_name.startswith(f"{name}-") + for secret_name in list_secrets(flake.path, filter_fn): + secret_path = sops_folder / secret_name + changed_paths.append(secret_path) + shutil.rmtree(secret_path) + + machine = Machine(name, flake) + changed_paths.extend(vars_public_store(machine).delete_store()) + changed_paths.extend(vars_secret_store(machine).delete_store()) + # Remove the machine's key, and update secrets & vars that referenced it: + if secrets_has_machine(flake.path, name): + secrets_machine_remove(flake.path, name) + def delete_command(args: argparse.Namespace) -> None: delete_machine(args.flake, args.name) diff --git a/pkgs/clan-cli/clan_cli/secrets/secrets.py b/pkgs/clan-cli/clan_cli/secrets/secrets.py index e3af24955..ab9aa0713 100644 --- a/pkgs/clan-cli/clan_cli/secrets/secrets.py +++ b/pkgs/clan-cli/clan_cli/secrets/secrets.py @@ -6,7 +6,6 @@ import os import shutil import sys from collections.abc import Callable -from dataclasses import dataclass from pathlib import Path from typing import IO @@ -18,7 +17,6 @@ from clan_cli.completions import ( complete_users, ) from clan_cli.errors import ClanError -from clan_cli.flake import Flake from clan_cli.git import commit_files from . import sops @@ -325,31 +323,26 @@ def has_secret(secret_path: Path) -> bool: return (secret_path / "secret").exists() -def list_secrets(flake_dir: Path, pattern: str | None = None) -> list[str]: +def list_secrets( + flake_dir: Path, filter_fn: Callable[[str], bool] | None = None +) -> list[str]: path = sops_secrets_folder(flake_dir) def validate(name: str) -> bool: return ( VALID_SECRET_NAME.match(name) is not None and has_secret(sops_secrets_folder(flake_dir) / name) - and (pattern is None or pattern in name) + and (filter_fn is None or filter_fn(name) is True) ) return list_objects(path, validate) -@dataclass -class ListSecretsOptions: - flake: Flake - pattern: str | None - - def list_command(args: argparse.Namespace) -> None: - options = ListSecretsOptions( - flake=args.flake, - pattern=args.pattern, - ) - lst = list_secrets(options.flake.path, options.pattern) + def filter_fn(name: str) -> bool: + return args.pattern in name + + lst = list_secrets(args.flake.path, filter_fn if args.pattern else None) if len(lst) > 0: print("\n".join(lst))