Merge pull request 'vars: raise error when shared generators differ between machines' (#5425) from dave into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/5425
This commit is contained in:
DavHau
2025-10-08 07:20:03 +00:00
2 changed files with 76 additions and 0 deletions

View File

@@ -1335,6 +1335,46 @@ def test_cache_misses_for_vars_operations(
)
@pytest.mark.with_core
def test_shared_generator_conflicting_definition_raises_error(
monkeypatch: pytest.MonkeyPatch,
flake_with_sops: ClanFlake,
) -> None:
"""Test that vars generation raises an error when two machines have different
definitions for the same shared generator.
"""
flake = flake_with_sops
# Create machine1 with a shared generator
machine1_config = flake.machines["machine1"] = create_test_machine_config()
shared_gen1 = machine1_config["clan"]["core"]["vars"]["generators"][
"shared_generator"
]
shared_gen1["share"] = True
shared_gen1["files"]["file1"]["secret"] = False
shared_gen1["script"] = 'echo "test" > "$out"/file1'
# Create machine2 with the same shared generator but different files
machine2_config = flake.machines["machine2"] = create_test_machine_config()
shared_gen2 = machine2_config["clan"]["core"]["vars"]["generators"][
"shared_generator"
]
shared_gen2["share"] = True
shared_gen2["files"]["file2"]["secret"] = False # Different file name
shared_gen2["script"] = 'echo "test" > "$out"/file2'
flake.refresh()
monkeypatch.chdir(flake.path)
# Attempting to generate vars for both machines should raise an error
# because they have conflicting definitions for the same shared generator
with pytest.raises(
ClanError,
match=".*differ.*",
):
cli.run(["vars", "generate", "--flake", str(flake.path)])
@pytest.mark.with_core
def test_dynamic_invalidation(
monkeypatch: pytest.MonkeyPatch,

View File

@@ -144,6 +144,9 @@ class Generator:
flake.precache(cls.get_machine_selectors(machine_names))
generators = []
shared_generators_raw: dict[
str, tuple[str, dict, dict]
] = {} # name -> (machine_name, gen_data, files_data)
for machine_name in machine_names:
# Get all generator metadata in one select (safe fields only)
@@ -165,6 +168,38 @@ class Generator:
sec_store = machine.secret_vars_store
for gen_name, gen_data in generators_data.items():
# Check for conflicts in shared generator definitions using raw data
if gen_data["share"]:
if gen_name in shared_generators_raw:
prev_machine, prev_gen_data, prev_files_data = (
shared_generators_raw[gen_name]
)
# Compare raw data
prev_gen_files = prev_files_data.get(gen_name, {})
curr_gen_files = files_data.get(gen_name, {})
# Build list of differences with details
differences = []
if prev_gen_files != curr_gen_files:
differences.append("files")
if prev_gen_data.get("prompts") != gen_data.get("prompts"):
differences.append("prompts")
if prev_gen_data.get("dependencies") != gen_data.get(
"dependencies"
):
differences.append("dependencies")
if prev_gen_data.get("validationHash") != gen_data.get(
"validationHash"
):
differences.append("validation_hash")
if differences:
msg = f"Machines {prev_machine} and {machine_name} have different definitions for shared generator '{gen_name}' (differ in: {', '.join(differences)})"
raise ClanError(msg)
else:
shared_generators_raw[gen_name] = (
machine_name,
gen_data,
files_data,
)
# Build files from the files_data
files = []
gen_files = files_data.get(gen_name, {})
@@ -216,6 +251,7 @@ class Generator:
_public_store=pub_store,
_secret_store=sec_store,
)
generators.append(generator)
# TODO: This should be done in a non-mutable way.