pkgs/cli/vars: Add dependency validation
Add explicit dependency validation to vars, so that proper error
messages can be surfaced to the user.
Instead of:
```
Traceback (most recent call last):
File "/home/lhebendanz/Projects/clan-core/pkgs/clan-cli/clan_lib/async_run/__init__.py", line 154, in run
self.result = AsyncResult(_result=self.function(*self.args, **self.kwargs))
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/lhebendanz/Projects/clan-core/pkgs/clan-cli/clan_cli/machines/update.py", line 62, in run_update_wit
h_network
run_machine_update(
~~~~~~~~~~~~~~~~~~^
machine=machine,
^^^^^^^^^^^^^^^^
...<2 lines>...
upload_inputs=upload_inputs,
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/lhebendanz/Projects/clan-core/pkgs/clan-cli/clan_lib/machines/update.py", line 158, in run_machine_u
pdate
run_generators([machine], generators=None, full_closure=False)
~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/lhebendanz/Projects/clan-core/pkgs/clan-cli/clan_lib/vars/generate.py", line 156, in run_generators
all_generators = get_generators(machines, full_closure=True)
File "/home/lhebendanz/Projects/clan-core/pkgs/clan-cli/clan_lib/vars/generate.py", line 50, in get_generators
all_generators_list = Generator.get_machine_generators(
all_machines,
flake,
include_previous_values=include_previous_values,
)
File "/home/lhebendanz/Projects/clan-core/pkgs/clan-cli/clan_cli/vars/generator.py", line 246, in get_machine_ge
nerators
if generators_data[dep]["share"]
~~~~~~~~~~~~~~~^^^^^
KeyError: 'bla'
```
We now get:
```
$> Generator 'my_generator' on machine 'my_machine' depends on generator 'non_existing_generator', but 'non_existing_generator' does not exist
```
Closes: #5698
This commit is contained in:
@@ -766,6 +766,28 @@ def test_prompt(
|
|||||||
assert sops_store.get(my_generator, "prompt_persist").decode() == "prompt_persist"
|
assert sops_store.get(my_generator, "prompt_persist").decode() == "prompt_persist"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.with_core
|
||||||
|
def test_non_existing_dependency_raises_error(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
flake_with_sops: ClanFlake,
|
||||||
|
) -> None:
|
||||||
|
"""Ensure that a generator with a non-existing dependency raises a clear error."""
|
||||||
|
flake = flake_with_sops
|
||||||
|
|
||||||
|
config = flake.machines["my_machine"] = create_test_machine_config()
|
||||||
|
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
||||||
|
my_generator["files"]["my_value"]["secret"] = False
|
||||||
|
my_generator["script"] = 'echo "$RANDOM" > "$out"/my_value'
|
||||||
|
my_generator["dependencies"] = ["non_existing_generator"]
|
||||||
|
flake.refresh()
|
||||||
|
monkeypatch.chdir(flake.path)
|
||||||
|
with pytest.raises(
|
||||||
|
ClanError,
|
||||||
|
match="Generator 'my_generator' on machine 'my_machine' depends on generator 'non_existing_generator', but 'non_existing_generator' does not exist",
|
||||||
|
):
|
||||||
|
cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.with_core
|
@pytest.mark.with_core
|
||||||
def test_shared_vars_must_never_depend_on_machine_specific_vars(
|
def test_shared_vars_must_never_depend_on_machine_specific_vars(
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
|||||||
@@ -66,6 +66,41 @@ class Generator:
|
|||||||
_public_store: "StoreBase | None" = None
|
_public_store: "StoreBase | None" = None
|
||||||
_secret_store: "StoreBase | None" = None
|
_secret_store: "StoreBase | None" = None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def validate_dependencies(
|
||||||
|
generator_name: str,
|
||||||
|
machine_name: str,
|
||||||
|
dependencies: list[str],
|
||||||
|
generators_data: dict[str, dict],
|
||||||
|
) -> list[GeneratorKey]:
|
||||||
|
"""Validate and build dependency keys for a generator.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
generator_name: Name of the generator that has dependencies
|
||||||
|
machine_name: Name of the machine the generator belongs to
|
||||||
|
dependencies: List of dependency generator names
|
||||||
|
generators_data: Dictionary of all available generators for this machine
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List of GeneratorKey objects
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ClanError: If a dependency does not exist
|
||||||
|
|
||||||
|
"""
|
||||||
|
deps_list = []
|
||||||
|
for dep in dependencies:
|
||||||
|
if dep not in generators_data:
|
||||||
|
msg = f"Generator '{generator_name}' on machine '{machine_name}' depends on generator '{dep}', but '{dep}' does not exist. Please check your configuration."
|
||||||
|
raise ClanError(msg)
|
||||||
|
deps_list.append(
|
||||||
|
GeneratorKey(
|
||||||
|
machine=None if generators_data[dep]["share"] else machine_name,
|
||||||
|
name=dep,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return deps_list
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def key(self) -> GeneratorKey:
|
def key(self) -> GeneratorKey:
|
||||||
if self.share:
|
if self.share:
|
||||||
@@ -240,15 +275,12 @@ class Generator:
|
|||||||
name=gen_name,
|
name=gen_name,
|
||||||
share=share,
|
share=share,
|
||||||
files=files,
|
files=files,
|
||||||
dependencies=[
|
dependencies=cls.validate_dependencies(
|
||||||
GeneratorKey(
|
gen_name,
|
||||||
machine=None
|
machine_name,
|
||||||
if generators_data[dep]["share"]
|
gen_data["dependencies"],
|
||||||
else machine_name,
|
generators_data,
|
||||||
name=dep,
|
),
|
||||||
)
|
|
||||||
for dep in gen_data["dependencies"]
|
|
||||||
],
|
|
||||||
migrate_fact=gen_data.get("migrateFact"),
|
migrate_fact=gen_data.get("migrateFact"),
|
||||||
validation_hash=gen_data.get("validationHash"),
|
validation_hash=gen_data.get("validationHash"),
|
||||||
prompts=prompts,
|
prompts=prompts,
|
||||||
|
|||||||
Reference in New Issue
Block a user