diff --git a/pkgs/clan-cli/clan_cli/facts/generate.py b/pkgs/clan-cli/clan_cli/facts/generate.py index 5731d970..e27dcc3a 100644 --- a/pkgs/clan-cli/clan_cli/facts/generate.py +++ b/pkgs/clan-cli/clan_cli/facts/generate.py @@ -36,101 +36,101 @@ def generate_service_facts( public_facts_store: FactStoreBase, tmpdir: Path, prompt: Callable[[str], str], -) -> None: +) -> bool: service_dir = tmpdir / service # check if all secrets exist and generate them if at least one is missing needs_regeneration = not check_secrets(machine, service=service) log.debug(f"{service} needs_regeneration: {needs_regeneration}") - if needs_regeneration: - if not isinstance(machine.flake, Path): - msg = f"flake is not a Path: {machine.flake}" - msg += "fact/secret generation is only supported for local flakes" + if not needs_regeneration: + return False + if not isinstance(machine.flake, Path): + msg = f"flake is not a Path: {machine.flake}" + msg += "fact/secret generation is only supported for local flakes" - env = os.environ.copy() - facts_dir = service_dir / "facts" - facts_dir.mkdir(parents=True) - env["facts"] = str(facts_dir) - secrets_dir = service_dir / "secrets" - secrets_dir.mkdir(parents=True) - env["secrets"] = str(secrets_dir) - # compatibility for old outputs.nix users - if isinstance(machine.facts_data[service]["generator"], str): - generator = machine.facts_data[service]["generator"] + env = os.environ.copy() + facts_dir = service_dir / "facts" + facts_dir.mkdir(parents=True) + env["facts"] = str(facts_dir) + secrets_dir = service_dir / "secrets" + secrets_dir.mkdir(parents=True) + env["secrets"] = str(secrets_dir) + # compatibility for old outputs.nix users + if isinstance(machine.facts_data[service]["generator"], str): + generator = machine.facts_data[service]["generator"] + else: + generator = machine.facts_data[service]["generator"]["finalScript"] + if machine.facts_data[service]["generator"]["prompt"]: + prompt_value = prompt(machine.facts_data[service]["generator"]["prompt"]) + env["prompt_value"] = prompt_value + # fmt: off + cmd = nix_shell( + [ + "nixpkgs#bash", + "nixpkgs#bubblewrap", + ], + [ + "bwrap", + "--ro-bind", "/nix/store", "/nix/store", + "--tmpfs", "/usr/lib/systemd", + "--dev", "/dev", + "--bind", str(facts_dir), str(facts_dir), + "--bind", str(secrets_dir), str(secrets_dir), + "--unshare-all", + "--unshare-user", + "--uid", "1000", + "--", + "bash", "-c", generator + ], + ) + # fmt: on + run( + cmd, + env=env, + ) + files_to_commit = [] + # store secrets + for secret in machine.facts_data[service]["secret"]: + if isinstance(secret, str): + # TODO: This is the old NixOS module, can be dropped everyone has updated. + secret_name = secret + groups = [] else: - generator = machine.facts_data[service]["generator"]["finalScript"] - if machine.facts_data[service]["generator"]["prompt"]: - prompt_value = prompt( - machine.facts_data[service]["generator"]["prompt"] - ) - env["prompt_value"] = prompt_value - # fmt: off - cmd = nix_shell( - [ - "nixpkgs#bash", - "nixpkgs#bubblewrap", - ], - [ - "bwrap", - "--ro-bind", "/nix/store", "/nix/store", - "--tmpfs", "/usr/lib/systemd", - "--dev", "/dev", - "--bind", str(facts_dir), str(facts_dir), - "--bind", str(secrets_dir), str(secrets_dir), - "--unshare-all", - "--unshare-user", - "--uid", "1000", - "--", - "bash", "-c", generator - ], - ) - # fmt: on - run( - cmd, - env=env, - ) - files_to_commit = [] - # store secrets - for secret in machine.facts_data[service]["secret"]: - if isinstance(secret, str): - # TODO: This is the old NixOS module, can be dropped everyone has updated. - secret_name = secret - groups = [] - else: - secret_name = secret["name"] - groups = secret.get("groups", []) + secret_name = secret["name"] + groups = secret.get("groups", []) - secret_file = secrets_dir / secret_name - if not secret_file.is_file(): - msg = f"did not generate a file for '{secret_name}' when running the following command:\n" - msg += generator - raise ClanError(msg) - secret_path = secret_facts_store.set( - service, secret_name, secret_file.read_bytes(), groups - ) - if secret_path: - files_to_commit.append(secret_path) - - # store facts - for name in machine.facts_data[service]["public"]: - fact_file = facts_dir / name - if not fact_file.is_file(): - msg = f"did not generate a file for '{name}' when running the following command:\n" - msg += machine.facts_data[service]["generator"] - raise ClanError(msg) - fact_file = public_facts_store.set(service, name, fact_file.read_bytes()) - if fact_file: - files_to_commit.append(fact_file) - commit_files( - files_to_commit, - machine.flake_dir, - f"Update facts/secrets for service {service} in machine {machine.name}", + secret_file = secrets_dir / secret_name + if not secret_file.is_file(): + msg = f"did not generate a file for '{secret_name}' when running the following command:\n" + msg += generator + raise ClanError(msg) + secret_path = secret_facts_store.set( + service, secret_name, secret_file.read_bytes(), groups ) + if secret_path: + files_to_commit.append(secret_path) + + # store facts + for name in machine.facts_data[service]["public"]: + fact_file = facts_dir / name + if not fact_file.is_file(): + msg = f"did not generate a file for '{name}' when running the following command:\n" + msg += machine.facts_data[service]["generator"] + raise ClanError(msg) + fact_file = public_facts_store.set(service, name, fact_file.read_bytes()) + if fact_file: + files_to_commit.append(fact_file) + commit_files( + files_to_commit, + machine.flake_dir, + f"Update facts/secrets for service {service} in machine {machine.name}", + ) + return True def generate_facts( machine: Machine, prompt: None | Callable[[str], str] = None, -) -> None: +) -> bool: secret_facts_module = importlib.import_module(machine.secret_facts_module) secret_facts_store = secret_facts_module.SecretStore(machine=machine) @@ -145,10 +145,11 @@ def generate_facts( prompt = prompt_func + was_regenerated = False with TemporaryDirectory() as tmp: tmpdir = Path(tmp) for service in machine.facts_data: - generate_service_facts( + was_regenerated |= generate_service_facts( machine=machine, service=service, secret_facts_store=secret_facts_store, @@ -157,7 +158,12 @@ def generate_facts( prompt=prompt, ) - print("successfully generated secrets") + if was_regenerated: + # flush caches to make sure the new secrets are available in evaluation + machine.flush_caches() + else: + print("All secrets and facts are already up to date") + return was_regenerated def generate_command(args: argparse.Namespace) -> None: diff --git a/pkgs/clan-cli/clan_cli/machines/machines.py b/pkgs/clan-cli/clan_cli/machines/machines.py index 708faae1..9c8bf265 100644 --- a/pkgs/clan-cli/clan_cli/machines/machines.py +++ b/pkgs/clan-cli/clan_cli/machines/machines.py @@ -81,6 +81,12 @@ class Machine: self.vm: QMPWrapper = QMPWrapper(state_dir) + def flush_caches(self) -> None: + self._deployment_info = None + self._flake_path = None + self.build_cache.clear() + self.eval_cache.clear() + def __str__(self) -> str: return f"Machine(name={self.data.name}, flake={self.data.flake_id})" diff --git a/pkgs/clan-cli/clan_cli/machines/update.py b/pkgs/clan-cli/clan_cli/machines/update.py index 3e06e545..5c31b014 100644 --- a/pkgs/clan-cli/clan_cli/machines/update.py +++ b/pkgs/clan-cli/clan_cli/machines/update.py @@ -96,6 +96,11 @@ def deploy_nixos(hosts: HostGroup) -> None: ssh_arg = f"-p {h.port}" if h.port else "" env = os.environ.copy() env["NIX_SSHOPTS"] = ssh_arg + machine: Machine = h.meta["machine"] + + generate_facts(machine) + upload_secrets(machine) + path = upload_sources(".", target) if h.host_key_check != HostKeyCheck.STRICT: @@ -105,11 +110,6 @@ def deploy_nixos(hosts: HostGroup) -> None: ssh_arg += " -i " + h.key if h.key else "" - machine: Machine = h.meta["machine"] - - generate_facts(machine) - upload_secrets(machine) - extra_args = h.meta.get("extra_args", []) cmd = [ "nixos-rebuild",