Merge pull request 'Make Generator validation more dynamic' (#3052) from tangential/clan-core:dynamic-vars-generator-validation into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3052
This commit is contained in:
@@ -154,7 +154,9 @@ class FlakeCacheEntry:
|
||||
self.value = value
|
||||
|
||||
def insert(
|
||||
self, value: str | float | dict[str, Any] | list[Any], selectors: list[Selector]
|
||||
self,
|
||||
value: str | float | dict[str, Any] | list[Any] | None,
|
||||
selectors: list[Selector],
|
||||
) -> None:
|
||||
selector: Selector
|
||||
if selectors == []:
|
||||
@@ -244,6 +246,12 @@ class FlakeCacheEntry:
|
||||
if self.value != value:
|
||||
msg = "value mismatch in cache, something is fishy"
|
||||
raise TypeError(msg)
|
||||
|
||||
elif value is None:
|
||||
if self.value is not None:
|
||||
msg = "value mismatch in cache, something is fishy"
|
||||
raise TypeError(msg)
|
||||
|
||||
else:
|
||||
msg = f"Cannot insert value of type {type(value)} into cache"
|
||||
raise TypeError(msg)
|
||||
|
||||
@@ -39,7 +39,6 @@ class Generator:
|
||||
name: str
|
||||
files: list[Var] = field(default_factory=list)
|
||||
share: bool = False
|
||||
validation: str | None = None
|
||||
prompts: list[Prompt] = field(default_factory=list)
|
||||
dependencies: list[str] = field(default_factory=list)
|
||||
|
||||
@@ -62,7 +61,6 @@ class Generator:
|
||||
name=data["name"],
|
||||
share=data["share"],
|
||||
files=[Var.from_json(data["name"], f) for f in data["files"].values()],
|
||||
validation=data["validationHash"],
|
||||
dependencies=data["dependencies"],
|
||||
migrate_fact=data["migrateFact"],
|
||||
prompts=[Prompt.from_json(p) for p in data["prompts"].values()],
|
||||
@@ -76,6 +74,13 @@ class Generator:
|
||||
)
|
||||
return final_script
|
||||
|
||||
@property
|
||||
def validation(self) -> str | None:
|
||||
assert self._machine is not None
|
||||
return self._machine.eval_nix(
|
||||
f'config.clan.core.vars.generators."{self.name}".validationHash'
|
||||
)
|
||||
|
||||
|
||||
def bubblewrap_cmd(generator: str, tmpdir: Path) -> list[str]:
|
||||
test_store = nix_test_store()
|
||||
@@ -253,6 +258,8 @@ def execute_generator(
|
||||
machine.flake_dir,
|
||||
f"Update vars via generator {generator.name} for machine {machine.name}",
|
||||
)
|
||||
if len(files_to_commit) > 0:
|
||||
machine.flush_caches()
|
||||
|
||||
|
||||
def _ask_prompts(
|
||||
@@ -456,8 +463,6 @@ def generate_vars_for_machine(
|
||||
public_vars_store=machine.public_vars_store,
|
||||
prompt_values=_ask_prompts(generator),
|
||||
)
|
||||
# flush caches to make sure the new secrets are available in evaluation
|
||||
machine.flush_caches()
|
||||
return True
|
||||
|
||||
|
||||
|
||||
@@ -17,6 +17,14 @@ def test_select() -> None:
|
||||
assert not test_cache.is_cached(["x", "z", 1])
|
||||
|
||||
|
||||
def test_insert() -> None:
|
||||
test_cache = FlakeCacheEntry({}, [])
|
||||
# Inserting the same thing twice should succeed
|
||||
test_cache.insert(None, ["nix"])
|
||||
test_cache.insert(None, ["nix"])
|
||||
assert test_cache.select(["nix"]) is None
|
||||
|
||||
|
||||
def test_out_path() -> None:
|
||||
testdict = {"x": {"y": [123, 345, 456], "z": "/nix/store/bla"}}
|
||||
test_cache = FlakeCacheEntry(testdict, [])
|
||||
|
||||
@@ -919,3 +919,75 @@ def test_invalidation(
|
||||
str(machine.flake.path), machine.name, "my_generator/my_value"
|
||||
).printable_value
|
||||
assert value2 == value2_new
|
||||
|
||||
|
||||
@pytest.mark.with_core
|
||||
def test_dynamic_invalidation(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
flake: ClanFlake,
|
||||
) -> None:
|
||||
gen_prefix = "config.clan.core.vars.generators"
|
||||
|
||||
machine = Machine(name="my_machine", flake=Flake(str(flake.path)))
|
||||
|
||||
config = flake.machines[machine.name]
|
||||
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 $RANDOM > $out/my_value"
|
||||
|
||||
dependent_generator = config["clan"]["core"]["vars"]["generators"][
|
||||
"dependent_generator"
|
||||
]
|
||||
dependent_generator["files"]["my_value"]["secret"] = False
|
||||
dependent_generator["dependencies"] = ["my_generator"]
|
||||
dependent_generator["script"] = "echo -n $RANDOM > $out/my_value"
|
||||
|
||||
flake.refresh()
|
||||
|
||||
# this is an abuse
|
||||
custom_nix = flake.path / "machines" / machine.name / "hardware-configuration.nix"
|
||||
custom_nix.write_text("""
|
||||
{ config, ... }: let
|
||||
p = config.clan.core.vars.generators.my_generator.files.my_value.path;
|
||||
in {
|
||||
clan.core.vars.generators.dependent_generator.validation = if builtins.pathExists p then builtins.readFile p else null;
|
||||
}
|
||||
""")
|
||||
|
||||
flake.refresh()
|
||||
machine.flush_caches()
|
||||
monkeypatch.chdir(flake.path)
|
||||
|
||||
# before generating, dependent generator validation should be empty; see bogus hardware-configuration.nix above
|
||||
# we have to avoid `*.files.value` in this initial select because the generators haven't been run yet
|
||||
generators_0 = machine.eval_nix(f"{gen_prefix}.*.{{validationHash}}")
|
||||
assert generators_0["dependent_generator"]["validationHash"] is None
|
||||
|
||||
# generate both my_generator and (the dependent) dependent_generator
|
||||
cli.run(["vars", "generate", "--flake", str(flake.path), machine.name])
|
||||
machine.flush_caches()
|
||||
|
||||
# after generating once, dependent generator validation should be set
|
||||
generators_1 = machine.eval_nix(gen_prefix)
|
||||
assert generators_1["dependent_generator"]["validationHash"] is not None
|
||||
|
||||
# after generating once, neither generator should want to run again because `clan vars generate` should have re-evaluated the dependent generator's validationHash after executing the parent generator but before executing the dependent generator
|
||||
# this ensures that validation can depend on parent generators while still only requiring a single pass
|
||||
cli.run(["vars", "generate", "--flake", str(flake.path), machine.name])
|
||||
machine.flush_caches()
|
||||
|
||||
generators_2 = machine.eval_nix(gen_prefix)
|
||||
assert (
|
||||
generators_1["dependent_generator"]["validationHash"]
|
||||
== generators_2["dependent_generator"]["validationHash"]
|
||||
)
|
||||
assert (
|
||||
generators_1["my_generator"]["files"]["my_value"]["value"]
|
||||
== generators_2["my_generator"]["files"]["my_value"]["value"]
|
||||
)
|
||||
assert (
|
||||
generators_1["dependent_generator"]["files"]["my_value"]["value"]
|
||||
== generators_2["dependent_generator"]["files"]["my_value"]["value"]
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user