import json import shutil from dataclasses import dataclass from io import StringIO from pathlib import Path import pytest from age_keys import SopsSetup from clan_cli.clan_uri import FlakeId from clan_cli.errors import ClanError from clan_cli.machines.machines import Machine from clan_cli.nix import nix_eval, run from clan_cli.vars.check import check_vars from clan_cli.vars.generate import generate_vars_for_machine from clan_cli.vars.list import stringify_all_vars from clan_cli.vars.public_modules import in_repo from clan_cli.vars.secret_modules import password_store, sops from clan_cli.vars.set import set_var from fixtures_flakes import generate_flake, set_machine_settings from helpers import cli from helpers.nixos_config import nested_dict from root import CLAN_CORE from stdout import CaptureOutput def test_dependencies_as_files(temp_dir: Path) -> None: from clan_cli.vars.generate import dependencies_as_dir decrypted_dependencies = { "gen_1": { "var_1a": b"var_1a", "var_1b": b"var_1b", }, "gen_2": { "var_2a": b"var_2a", "var_2b": b"var_2b", }, } dependencies_as_dir(decrypted_dependencies, temp_dir) assert temp_dir.is_dir() assert (temp_dir / "gen_1" / "var_1a").read_bytes() == b"var_1a" assert (temp_dir / "gen_1" / "var_1b").read_bytes() == b"var_1b" assert (temp_dir / "gen_2" / "var_2a").read_bytes() == b"var_2a" assert (temp_dir / "gen_2" / "var_2b").read_bytes() == b"var_2b" # ensure the files are not world readable assert (temp_dir / "gen_1" / "var_1a").stat().st_mode & 0o777 == 0o600 assert (temp_dir / "gen_1" / "var_1b").stat().st_mode & 0o777 == 0o600 assert (temp_dir / "gen_2" / "var_2a").stat().st_mode & 0o777 == 0o600 assert (temp_dir / "gen_2" / "var_2b").stat().st_mode & 0o777 == 0o600 def test_required_generators() -> None: from clan_cli.vars.graph import all_missing_closure, requested_closure @dataclass class Generator: dependencies: list[str] exists: bool # result is already on disk generators = { "gen_1": Generator([], True), "gen_2": Generator(["gen_1"], False), "gen_2a": Generator(["gen_2"], False), "gen_2b": Generator(["gen_2"], True), } assert requested_closure(["gen_1"], generators) == [ "gen_1", "gen_2", "gen_2a", "gen_2b", ] assert requested_closure(["gen_2"], generators) == ["gen_2", "gen_2a", "gen_2b"] assert requested_closure(["gen_2a"], generators) == ["gen_2", "gen_2a", "gen_2b"] assert requested_closure(["gen_2b"], generators) == ["gen_2", "gen_2a", "gen_2b"] assert all_missing_closure(generators) == ["gen_2", "gen_2a", "gen_2b"] @pytest.mark.impure def test_generate_public_var( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, ) -> None: config = nested_dict() config["nixpkgs"]["hostPlatform"] = "x86_64-linux" my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"] my_generator["files"]["my_value"]["secret"] = False my_generator["script"] = "echo hello > $out/my_value" flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) machine = Machine(name="my_machine", flake=FlakeId(str(flake.path))) assert not check_vars(machine) vars_text = stringify_all_vars(machine) assert "my_generator/my_value: " in vars_text cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) assert check_vars(machine) store = in_repo.FactStore( Machine(name="my_machine", flake=FlakeId(str(flake.path))) ) assert store.exists("my_generator", "my_value") assert store.get("my_generator", "my_value").decode() == "hello\n" vars_text = stringify_all_vars(machine) assert "my_generator/my_value: hello" in vars_text vars_eval = run( nix_eval( [ f"{flake.path}#nixosConfigurations.my_machine.config.clan.core.vars.generators.my_generator.files.my_value.value", ] ) ).stdout.strip() assert json.loads(vars_eval) == "hello\n" @pytest.mark.impure def test_generate_secret_var_sops( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, sops_setup: SopsSetup, ) -> None: config = nested_dict() config["nixpkgs"]["hostPlatform"] = "x86_64-linux" my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"] my_generator["files"]["my_secret"]["secret"] = True my_generator["script"] = "echo hello > $out/my_secret" flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) sops_setup.init() machine = Machine(name="my_machine", flake=FlakeId(str(flake.path))) assert not check_vars(machine) vars_text = stringify_all_vars(machine) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) assert check_vars(machine) assert "my_generator/my_secret: " in vars_text in_repo_store = in_repo.FactStore( Machine(name="my_machine", flake=FlakeId(str(flake.path))) ) assert not in_repo_store.exists("my_generator", "my_secret") sops_store = sops.SecretStore( Machine(name="my_machine", flake=FlakeId(str(flake.path))) ) assert sops_store.exists("my_generator", "my_secret") assert sops_store.get("my_generator", "my_secret").decode() == "hello\n" vars_text = stringify_all_vars(machine) assert "my_generator/my_secret" in vars_text # test regeneration works cli.run( ["vars", "generate", "--flake", str(flake.path), "my_machine", "--regenerate"] ) @pytest.mark.impure def test_generate_secret_var_sops_with_default_group( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, sops_setup: SopsSetup, ) -> None: config = nested_dict() config["nixpkgs"]["hostPlatform"] = "x86_64-linux" config["clan"]["core"]["sops"]["defaultGroups"] = ["my_group"] my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"] my_generator["files"]["my_secret"]["secret"] = True my_generator["script"] = "echo hello > $out/my_secret" flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) sops_setup.init() cli.run(["secrets", "groups", "add-user", "my_group", sops_setup.user]) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) in_repo_store = in_repo.FactStore( Machine(name="my_machine", flake=FlakeId(str(flake.path))) ) assert not in_repo_store.exists("my_generator", "my_secret") sops_store = sops.SecretStore( Machine(name="my_machine", flake=FlakeId(str(flake.path))) ) assert sops_store.exists("my_generator", "my_secret") assert sops_store.get("my_generator", "my_secret").decode() == "hello\n" @pytest.mark.impure def test_generated_shared_secret_sops( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, sops_setup: SopsSetup, ) -> None: m1_config = nested_dict() m1_config["nixpkgs"]["hostPlatform"] = "x86_64-linux" shared_generator = m1_config["clan"]["core"]["vars"]["generators"][ "my_shared_generator" ] shared_generator["share"] = True shared_generator["files"]["my_shared_secret"]["secret"] = True shared_generator["script"] = "echo hello > $out/my_shared_secret" m2_config = nested_dict() m2_config["nixpkgs"]["hostPlatform"] = "x86_64-linux" m2_config["clan"]["core"]["vars"]["generators"]["my_shared_generator"] = ( shared_generator.copy() ) flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"machine1": m1_config, "machine2": m2_config}, ) monkeypatch.chdir(flake.path) sops_setup.init() machine1 = Machine(name="machine1", flake=FlakeId(str(flake.path))) machine2 = Machine(name="machine2", flake=FlakeId(str(flake.path))) cli.run(["vars", "generate", "--flake", str(flake.path), "machine1"]) assert check_vars(machine1) cli.run(["vars", "generate", "--flake", str(flake.path), "machine2"]) assert check_vars(machine2) assert check_vars(machine2) m1_sops_store = sops.SecretStore(machine1) m2_sops_store = sops.SecretStore(machine2) assert m1_sops_store.exists("my_shared_generator", "my_shared_secret", shared=True) assert m2_sops_store.exists("my_shared_generator", "my_shared_secret", shared=True) assert m1_sops_store.machine_has_access( "my_shared_generator", "my_shared_secret", shared=True ) assert m2_sops_store.machine_has_access( "my_shared_generator", "my_shared_secret", shared=True ) @pytest.mark.impure def test_generate_secret_var_password_store( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, test_root: Path, ) -> None: config = nested_dict() config["nixpkgs"]["hostPlatform"] = "x86_64-linux" config["clan"]["core"]["vars"]["settings"]["secretStore"] = "password-store" my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"] my_generator["files"]["my_secret"]["secret"] = True my_generator["script"] = "echo hello > $out/my_secret" my_shared_generator = config["clan"]["core"]["vars"]["generators"][ "my_shared_generator" ] my_shared_generator["share"] = True my_shared_generator["files"]["my_shared_secret"]["secret"] = True my_shared_generator["script"] = "echo hello > $out/my_shared_secret" flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) gnupghome = temporary_home / "gpg" shutil.copytree(test_root / "data" / "gnupg-home", gnupghome) monkeypatch.setenv("GNUPGHOME", str(gnupghome)) password_store_dir = temporary_home / "pass" shutil.copytree(test_root / "data" / "password-store", password_store_dir) monkeypatch.setenv("PASSWORD_STORE_DIR", str(temporary_home / "pass")) machine = Machine(name="my_machine", flake=FlakeId(str(flake.path))) assert not check_vars(machine) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) assert check_vars(machine) store = password_store.SecretStore( Machine(name="my_machine", flake=FlakeId(str(flake.path))) ) assert store.exists("my_generator", "my_secret", shared=False) assert not store.exists("my_generator", "my_secret", shared=True) assert store.exists("my_shared_generator", "my_shared_secret", shared=True) assert not store.exists("my_shared_generator", "my_shared_secret", shared=False) assert store.get("my_generator", "my_secret", shared=False).decode() == "hello\n" vars_text = stringify_all_vars(machine) assert "my_generator/my_secret" in vars_text @pytest.mark.impure def test_generate_secret_for_multiple_machines( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, sops_setup: SopsSetup, ) -> None: machine1_config = nested_dict() machine1_generator = machine1_config["clan"]["core"]["vars"]["generators"][ "my_generator" ] machine1_generator["files"]["my_secret"]["secret"] = True machine1_generator["files"]["my_value"]["secret"] = False machine1_generator["script"] = ( "echo machine1 > $out/my_secret && echo machine1 > $out/my_value" ) machine2_config = nested_dict() machine2_generator = machine2_config["clan"]["core"]["vars"]["generators"][ "my_generator" ] machine2_generator["files"]["my_secret"]["secret"] = True machine2_generator["files"]["my_value"]["secret"] = False machine2_generator["script"] = ( "echo machine2 > $out/my_secret && echo machine2 > $out/my_value" ) flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"machine1": machine1_config, "machine2": machine2_config}, ) monkeypatch.chdir(flake.path) sops_setup.init() cli.run(["vars", "generate", "--flake", str(flake.path)]) # check if public vars have been created correctly in_repo_store1 = in_repo.FactStore( Machine(name="machine1", flake=FlakeId(str(flake.path))) ) in_repo_store2 = in_repo.FactStore( Machine(name="machine2", flake=FlakeId(str(flake.path))) ) assert in_repo_store1.exists("my_generator", "my_value") assert in_repo_store2.exists("my_generator", "my_value") assert in_repo_store1.get("my_generator", "my_value").decode() == "machine1\n" assert in_repo_store2.get("my_generator", "my_value").decode() == "machine2\n" # check if secret vars have been created correctly sops_store1 = sops.SecretStore( Machine(name="machine1", flake=FlakeId(str(flake.path))) ) sops_store2 = sops.SecretStore( Machine(name="machine2", flake=FlakeId(str(flake.path))) ) assert sops_store1.exists("my_generator", "my_secret") assert sops_store2.exists("my_generator", "my_secret") assert sops_store1.get("my_generator", "my_secret").decode() == "machine1\n" assert sops_store2.get("my_generator", "my_secret").decode() == "machine2\n" @pytest.mark.impure def test_dependant_generators( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, ) -> None: config = nested_dict() parent_gen = config["clan"]["core"]["vars"]["generators"]["parent_generator"] parent_gen["files"]["my_value"]["secret"] = False parent_gen["script"] = "echo hello > $out/my_value" child_gen = config["clan"]["core"]["vars"]["generators"]["child_generator"] child_gen["files"]["my_value"]["secret"] = False child_gen["dependencies"] = ["parent_generator"] child_gen["script"] = "cat $in/parent_generator/my_value > $out/my_value" flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) in_repo_store = in_repo.FactStore( Machine(name="my_machine", flake=FlakeId(str(flake.path))) ) assert in_repo_store.exists("parent_generator", "my_value") assert in_repo_store.get("parent_generator", "my_value").decode() == "hello\n" assert in_repo_store.exists("child_generator", "my_value") assert in_repo_store.get("child_generator", "my_value").decode() == "hello\n" @pytest.mark.impure @pytest.mark.parametrize( ("prompt_type", "input_value"), [ ("line", "my input"), ("multiline", "my\nmultiline\ninput\n"), # The hidden type cannot easily be tested, as getpass() reads from /dev/tty directly # ("hidden", "my hidden input"), ], ) def test_prompt( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, prompt_type: str, input_value: str, ) -> None: config = nested_dict() my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"] my_generator["files"]["my_value"]["secret"] = False my_generator["prompts"]["prompt1"]["description"] = "dream2nix" my_generator["prompts"]["prompt1"]["createFile"] = False my_generator["prompts"]["prompt1"]["type"] = prompt_type my_generator["script"] = "cat $prompts/prompt1 > $out/my_value" flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) monkeypatch.setattr("sys.stdin", StringIO(input_value)) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) in_repo_store = in_repo.FactStore( Machine(name="my_machine", flake=FlakeId(str(flake.path))) ) assert in_repo_store.exists("my_generator", "my_value") assert in_repo_store.get("my_generator", "my_value").decode() == input_value @pytest.mark.impure def test_share_flag( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, sops_setup: SopsSetup, ) -> None: config = nested_dict() config["nixpkgs"]["hostPlatform"] = "x86_64-linux" shared_generator = config["clan"]["core"]["vars"]["generators"]["shared_generator"] shared_generator["share"] = True shared_generator["files"]["my_secret"]["secret"] = True shared_generator["files"]["my_value"]["secret"] = False shared_generator["script"] = ( "echo hello > $out/my_secret && echo hello > $out/my_value" ) unshared_generator = config["clan"]["core"]["vars"]["generators"][ "unshared_generator" ] unshared_generator["share"] = False unshared_generator["files"]["my_secret"]["secret"] = True unshared_generator["files"]["my_value"]["secret"] = False unshared_generator["script"] = ( "echo hello > $out/my_secret && echo hello > $out/my_value" ) flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) sops_setup.init() machine = Machine(name="my_machine", flake=FlakeId(str(flake.path))) assert not check_vars(machine) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) assert check_vars(machine) sops_store = sops.SecretStore( Machine(name="my_machine", flake=FlakeId(str(flake.path))) ) in_repo_store = in_repo.FactStore( Machine(name="my_machine", flake=FlakeId(str(flake.path))) ) # check secrets stored correctly assert sops_store.exists("shared_generator", "my_secret", shared=True) assert not sops_store.exists("shared_generator", "my_secret", shared=False) assert sops_store.exists("unshared_generator", "my_secret", shared=False) assert not sops_store.exists("unshared_generator", "my_secret", shared=True) # check values stored correctly assert in_repo_store.exists("shared_generator", "my_value", shared=True) assert not in_repo_store.exists("shared_generator", "my_value", shared=False) assert in_repo_store.exists("unshared_generator", "my_value", shared=False) assert not in_repo_store.exists("unshared_generator", "my_value", shared=True) vars_eval = run( nix_eval( [ f"{flake.path}#nixosConfigurations.my_machine.config.clan.core.vars.generators.shared_generator.files.my_value.value", ] ) ).stdout.strip() assert json.loads(vars_eval) == "hello\n" @pytest.mark.impure def test_prompt_create_file( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, sops_setup: SopsSetup, ) -> None: """ Test that the createFile flag in the prompt configuration works as expected """ config = nested_dict() my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"] my_generator["prompts"]["prompt1"]["createFile"] = True my_generator["prompts"]["prompt2"]["createFile"] = False flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) sops_setup.init() monkeypatch.setattr("sys.stdin", StringIO("input1\ninput2\n")) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) sops_store = sops.SecretStore( Machine(name="my_machine", flake=FlakeId(str(flake.path))) ) assert sops_store.exists("my_generator", "prompt1") assert not sops_store.exists("my_generator", "prompt2") assert sops_store.get("my_generator", "prompt1").decode() == "input1" @pytest.mark.impure def test_api_get_prompts( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, ) -> None: from clan_cli.vars.list import get_prompts config = nested_dict() my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"] my_generator["prompts"]["prompt1"]["type"] = "line" my_generator["files"]["prompt1"]["secret"] = False flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) monkeypatch.setattr("sys.stdin", StringIO("input1")) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) machine = Machine(name="my_machine", flake=FlakeId(str(flake.path))) api_prompts = get_prompts(machine) assert len(api_prompts) == 1 assert api_prompts[0].name == "my_generator" assert api_prompts[0].prompts[0].name == "prompt1" assert api_prompts[0].prompts[0].previous_value == "input1" @pytest.mark.impure def test_api_set_prompts( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, ) -> None: from clan_cli.vars._types import GeneratorUpdate from clan_cli.vars.list import set_prompts config = nested_dict() my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"] my_generator["prompts"]["prompt1"]["type"] = "line" my_generator["files"]["prompt1"]["secret"] = False flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) machine = Machine(name="my_machine", flake=FlakeId(str(flake.path))) set_prompts( machine, [ GeneratorUpdate( generator="my_generator", prompt_values={"prompt1": "input1"}, ) ], ) store = in_repo.FactStore(machine) assert store.exists("my_generator", "prompt1") assert store.get("my_generator", "prompt1").decode() == "input1" set_prompts( machine, [ GeneratorUpdate( generator="my_generator", prompt_values={"prompt1": "input2"}, ) ], ) assert store.get("my_generator", "prompt1").decode() == "input2" @pytest.mark.impure def test_commit_message( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, sops_setup: SopsSetup, ) -> None: config = nested_dict() my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"] my_generator["files"]["my_value"]["secret"] = False my_generator["script"] = "echo hello > $out/my_value" my_secret_generator = config["clan"]["core"]["vars"]["generators"][ "my_secret_generator" ] my_secret_generator["files"]["my_secret"]["secret"] = True my_secret_generator["script"] = "echo hello > $out/my_secret" flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) sops_setup.init() cli.run( [ "vars", "generate", "--flake", str(flake.path), "my_machine", "--service", "my_generator", ] ) # get last commit message commit_message = run( ["git", "log", "-1", "--pretty=%B"], ).stdout.strip() assert ( commit_message == "Update vars via generator my_generator for machine my_machine" ) cli.run( [ "vars", "generate", "--flake", str(flake.path), "my_machine", "--service", "my_secret_generator", ] ) commit_message = run( ["git", "log", "-1", "--pretty=%B"], ).stdout.strip() assert ( commit_message == "Update vars via generator my_secret_generator for machine my_machine" ) @pytest.mark.impure def test_default_value( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, ) -> None: config = nested_dict() config["nixpkgs"]["hostPlatform"] = "x86_64-linux" my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"] my_generator["files"]["my_value"]["secret"] = False my_generator["files"]["my_value"]["value"]["_type"] = "override" my_generator["files"]["my_value"]["value"]["priority"] = 1000 # mkDefault my_generator["files"]["my_value"]["value"]["content"] = "foo" my_generator["script"] = "echo -n hello > $out/my_value" flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) # ensure evaluating the default value works without generating the value value_eval = run( nix_eval( [ f"{flake.path}#nixosConfigurations.my_machine.config.clan.core.vars.generators.my_generator.files.my_value.value", ] ) ).stdout.strip() assert json.loads(value_eval) == "foo" # generate cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) # ensure the value is set correctly value_eval = run( nix_eval( [ f"{flake.path}#nixosConfigurations.my_machine.config.clan.core.vars.generators.my_generator.files.my_value.value", ] ) ).stdout.strip() assert json.loads(value_eval) == "hello" @pytest.mark.impure def test_stdout_of_generate( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, capture_output: CaptureOutput, sops_setup: SopsSetup, ) -> None: config = nested_dict() config["nixpkgs"]["hostPlatform"] = "x86_64-linux" my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"] my_generator["files"]["my_value"]["secret"] = False my_generator["script"] = "echo -n hello > $out/my_value" my_secret_generator = config["clan"]["core"]["vars"]["generators"][ "my_secret_generator" ] my_secret_generator["files"]["my_secret"]["secret"] = True my_secret_generator["script"] = "echo -n hello > $out/my_secret" flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) sops_setup.init() from clan_cli.vars.generate import generate_vars_for_machine with capture_output as output: generate_vars_for_machine( Machine(name="my_machine", flake=FlakeId(str(flake.path))), "my_generator", regenerate=False, ) assert "Updated var my_generator/my_value" in output.out assert "old: " in output.out assert "new: hello" in output.out set_var("my_machine", "my_generator/my_value", b"world", FlakeId(str(flake.path))) with capture_output as output: generate_vars_for_machine( Machine(name="my_machine", flake=FlakeId(str(flake.path))), "my_generator", regenerate=True, ) assert "Updated var my_generator/my_value" in output.out assert "old: world" in output.out assert "new: hello" in output.out # check the output when nothing gets regenerated with capture_output as output: generate_vars_for_machine( Machine(name="my_machine", flake=FlakeId(str(flake.path))), "my_generator", regenerate=True, ) assert "Updated" not in output.out assert "hello" in output.out with capture_output as output: generate_vars_for_machine( Machine(name="my_machine", flake=FlakeId(str(flake.path))), "my_secret_generator", regenerate=False, ) assert "Updated secret var my_secret_generator/my_secret" in output.out assert "hello" not in output.out set_var( "my_machine", "my_secret_generator/my_secret", b"world", FlakeId(str(flake.path)), ) with capture_output as output: generate_vars_for_machine( Machine(name="my_machine", flake=FlakeId(str(flake.path))), "my_secret_generator", regenerate=True, ) assert "Updated secret var my_secret_generator/my_secret" in output.out assert "world" not in output.out assert "hello" not in output.out @pytest.mark.impure def test_migration_skip( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, sops_setup: SopsSetup, ) -> None: config = nested_dict() config["nixpkgs"]["hostPlatform"] = "x86_64-linux" my_service = config["clan"]["core"]["facts"]["services"]["my_service"] my_service["secret"]["my_value"] = {} my_service["generator"]["script"] = "echo -n hello > $secrets/my_value" my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"] # the var to migrate to is mistakenly marked as not secret (migration should fail) my_generator["files"]["my_value"]["secret"] = False my_generator["migrateFact"] = "my_service" my_generator["script"] = "echo -n world > $out/my_value" flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) sops_setup.init() cli.run(["facts", "generate", "--flake", str(flake.path), "my_machine"]) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) in_repo_store = in_repo.FactStore( Machine(name="my_machine", flake=FlakeId(str(flake.path))) ) assert in_repo_store.exists("my_generator", "my_value") assert in_repo_store.get("my_generator", "my_value").decode() == "world" @pytest.mark.impure def test_migration( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, sops_setup: SopsSetup, ) -> None: config = nested_dict() config["nixpkgs"]["hostPlatform"] = "x86_64-linux" my_service = config["clan"]["core"]["facts"]["services"]["my_service"] my_service["public"]["my_value"] = {} my_service["generator"]["script"] = "echo -n hello > $facts/my_value" my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"] my_generator["files"]["my_value"]["secret"] = False my_generator["migrateFact"] = "my_service" my_generator["script"] = "echo -n world > $out/my_value" flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) sops_setup.init() cli.run(["facts", "generate", "--flake", str(flake.path), "my_machine"]) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) in_repo_store = in_repo.FactStore( Machine(name="my_machine", flake=FlakeId(str(flake.path))) ) assert in_repo_store.exists("my_generator", "my_value") assert in_repo_store.get("my_generator", "my_value").decode() == "hello" @pytest.mark.impure def test_fails_when_files_are_left_from_other_backend( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, sops_setup: SopsSetup, ) -> None: config = nested_dict() config["nixpkgs"]["hostPlatform"] = "x86_64-linux" my_secret_generator = config["clan"]["core"]["vars"]["generators"][ "my_secret_generator" ] my_secret_generator["files"]["my_secret"]["secret"] = True my_secret_generator["script"] = "echo hello > $out/my_secret" my_value_generator = config["clan"]["core"]["vars"]["generators"][ "my_value_generator" ] my_value_generator["files"]["my_value"]["secret"] = False my_value_generator["script"] = "echo hello > $out/my_value" flake = generate_flake( temporary_home, flake_template=CLAN_CORE / "templates" / "minimal", machine_configs={"my_machine": config}, ) monkeypatch.chdir(flake.path) sops_setup.init() for generator in ["my_secret_generator", "my_value_generator"]: generate_vars_for_machine( Machine(name="my_machine", flake=FlakeId(str(flake.path))), generator, regenerate=False, ) my_secret_generator["files"]["my_secret"]["secret"] = False my_value_generator["files"]["my_value"]["secret"] = True set_machine_settings(flake.path, "my_machine", config) monkeypatch.chdir(flake.path) for generator in ["my_secret_generator", "my_value_generator"]: with pytest.raises(ClanError): generate_vars_for_machine( Machine(name="my_machine", flake=FlakeId(str(flake.path))), generator, regenerate=False, ) @pytest.mark.impure def test_keygen( monkeypatch: pytest.MonkeyPatch, temporary_home: Path, ) -> None: monkeypatch.chdir(temporary_home) cli.run(["vars", "keygen", "--flake", str(temporary_home), "--user", "user"]) # check public key exists assert (temporary_home / "sops" / "users" / "user").is_dir() # check private key exists assert (temporary_home / ".config" / "sops" / "age" / "keys.txt").is_file() # it should still work, even if the keys already exist import shutil shutil.rmtree(temporary_home / "sops" / "users" / "user") cli.run(["vars", "keygen", "--flake", str(temporary_home), "--user", "user"]) # check public key exists assert (temporary_home / "sops" / "users" / "user").is_dir()