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"
|
||||
|
||||
|
||||
@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
|
||||
def test_shared_vars_must_never_depend_on_machine_specific_vars(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
|
||||
@@ -66,6 +66,41 @@ class Generator:
|
||||
_public_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
|
||||
def key(self) -> GeneratorKey:
|
||||
if self.share:
|
||||
@@ -240,15 +275,12 @@ class Generator:
|
||||
name=gen_name,
|
||||
share=share,
|
||||
files=files,
|
||||
dependencies=[
|
||||
GeneratorKey(
|
||||
machine=None
|
||||
if generators_data[dep]["share"]
|
||||
else machine_name,
|
||||
name=dep,
|
||||
)
|
||||
for dep in gen_data["dependencies"]
|
||||
],
|
||||
dependencies=cls.validate_dependencies(
|
||||
gen_name,
|
||||
machine_name,
|
||||
gen_data["dependencies"],
|
||||
generators_data,
|
||||
),
|
||||
migrate_fact=gen_data.get("migrateFact"),
|
||||
validation_hash=gen_data.get("validationHash"),
|
||||
prompts=prompts,
|
||||
|
||||
Reference in New Issue
Block a user