vars: fix regenerating a specific generator
This was broken after re-designing the API -> added a test
This commit is contained in:
@@ -550,7 +550,13 @@ const InstallSummary = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract generator names from prompt values
|
||||
// TODO: This is wrong. We need to extend run_generators to be able to compute
|
||||
// a sane closure over a list of provided generators.
|
||||
const generators = Object.keys(store.install.promptValues || {});
|
||||
|
||||
const runGenerators = client.fetch("run_generators", {
|
||||
generators: generators.length > 0 ? generators : undefined,
|
||||
prompt_values: store.install.promptValues,
|
||||
machines: [
|
||||
{
|
||||
|
||||
@@ -119,6 +119,28 @@ def test_generate_public_and_secret_vars(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
flake_with_sops: ClanFlake,
|
||||
) -> None:
|
||||
"""Test generation of public and secret vars with dependencies.
|
||||
|
||||
Generator dependency graph:
|
||||
|
||||
my_generator (standalone)
|
||||
├── my_value (public)
|
||||
├── my_secret (secret)
|
||||
└── value_with_default (public, has default)
|
||||
|
||||
my_shared_generator (shared=True)
|
||||
└── my_shared_value (public)
|
||||
↓
|
||||
dependent_generator (depends on my_shared_generator)
|
||||
└── my_secret (secret, copies from my_shared_value)
|
||||
|
||||
This test verifies:
|
||||
- Public and secret vars are stored correctly
|
||||
- Shared generators work across dependencies
|
||||
- Default values are handled properly
|
||||
- Regeneration with --regenerate updates all values
|
||||
- Regeneration with --regenerate --generator only updates specified generator
|
||||
"""
|
||||
flake = flake_with_sops
|
||||
|
||||
config = flake.machines["my_machine"]
|
||||
@@ -126,14 +148,13 @@ def test_generate_public_and_secret_vars(
|
||||
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
||||
my_generator["files"]["my_value"]["secret"] = False
|
||||
my_generator["files"]["my_secret"]["secret"] = True
|
||||
my_generator["script"] = (
|
||||
'echo -n public$RANDOM > "$out"/my_value; echo -n secret$RANDOM > "$out"/my_secret; echo -n non-default$RANDOM > "$out"/value_with_default'
|
||||
)
|
||||
|
||||
my_generator["files"]["value_with_default"]["secret"] = False
|
||||
my_generator["files"]["value_with_default"]["value"]["_type"] = "override"
|
||||
my_generator["files"]["value_with_default"]["value"]["priority"] = 1000 # mkDefault
|
||||
my_generator["files"]["value_with_default"]["value"]["content"] = "default_value"
|
||||
my_generator["script"] = (
|
||||
'echo -n public$RANDOM > "$out"/my_value; echo -n secret$RANDOM > "$out"/my_secret; echo -n non-default$RANDOM > "$out"/value_with_default'
|
||||
)
|
||||
|
||||
my_shared_generator = config["clan"]["core"]["vars"]["generators"][
|
||||
"my_shared_generator"
|
||||
@@ -257,6 +278,44 @@ def test_generate_public_and_secret_vars(
|
||||
assert shared_value != shared_value_new, (
|
||||
"Shared value should change after regeneration"
|
||||
)
|
||||
# test that after regenerating a shared generator, it and its dependents are regenerated
|
||||
cli.run(
|
||||
[
|
||||
"vars",
|
||||
"generate",
|
||||
"--flake",
|
||||
str(flake.path),
|
||||
"my_machine",
|
||||
"--regenerate",
|
||||
"--no-sandbox",
|
||||
"--generator",
|
||||
"my_shared_generator",
|
||||
]
|
||||
)
|
||||
# test that the shared generator is regenerated
|
||||
shared_value_after_regeneration = get_machine_var(
|
||||
machine, "my_shared_generator/my_shared_value"
|
||||
).printable_value
|
||||
assert shared_value_after_regeneration != shared_value_new, (
|
||||
"Shared value should change after regenerating my_shared_generator"
|
||||
)
|
||||
# test that the dependent generator is also regenerated (because it depends on my_shared_generator)
|
||||
secret_value_after_regeneration = sops_store.get(
|
||||
dependent_generator, "my_secret"
|
||||
).decode()
|
||||
assert secret_value_after_regeneration != secret_value_new, (
|
||||
"Dependent generator's secret should change after regenerating my_shared_generator"
|
||||
)
|
||||
assert secret_value_after_regeneration == shared_value_after_regeneration, (
|
||||
"Dependent generator's secret should match the new shared value"
|
||||
)
|
||||
# test that my_generator is NOT regenerated (it doesn't depend on my_shared_generator)
|
||||
public_value_after_regeneration = get_machine_var(
|
||||
machine, "my_generator/my_value"
|
||||
).printable_value
|
||||
assert public_value_after_regeneration == public_value_new, (
|
||||
"my_generator value should NOT change after regenerating only my_shared_generator"
|
||||
)
|
||||
|
||||
|
||||
# TODO: it doesn't actually test if the group has access
|
||||
|
||||
@@ -37,11 +37,8 @@ def generate_command(args: argparse.Namespace) -> None:
|
||||
|
||||
run_generators(
|
||||
machines,
|
||||
generators=args.generator
|
||||
if args.generator
|
||||
else "all"
|
||||
if args.regenerate
|
||||
else "minimal",
|
||||
generators=args.generator,
|
||||
full_closure=args.regenerate if args.regenerate is not None else False,
|
||||
no_sandbox=args.no_sandbox,
|
||||
)
|
||||
|
||||
|
||||
@@ -383,7 +383,7 @@ def run_command(
|
||||
machine_obj: Machine = Machine(args.machine, args.flake)
|
||||
|
||||
generate_facts([machine_obj])
|
||||
run_generators([machine_obj])
|
||||
run_generators([machine_obj], generators=None, full_closure=False)
|
||||
|
||||
vm: VmConfig = inspect_vm(machine=machine_obj)
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ def run_machine_flash(
|
||||
system_config_nix: dict[str, Any] = {}
|
||||
|
||||
generate_facts([machine])
|
||||
run_generators([machine])
|
||||
run_generators([machine], generators=None, full_closure=False)
|
||||
|
||||
if system_config.language:
|
||||
if system_config.language not in list_languages():
|
||||
|
||||
@@ -88,7 +88,7 @@ def run_machine_install(opts: InstallOptions, target_host: Remote) -> None:
|
||||
# Notify the UI about what we are doing
|
||||
notify_install_step("generators")
|
||||
generate_facts([machine])
|
||||
run_generators([machine])
|
||||
run_generators([machine], generators=None, full_closure=False)
|
||||
|
||||
with (
|
||||
TemporaryDirectory(prefix="nixos-install-") as _base_directory,
|
||||
|
||||
@@ -78,7 +78,7 @@ def morph_machine(
|
||||
|
||||
machine = Machine(name=name, flake=Flake(str(flakedir)))
|
||||
|
||||
run_generators([machine], generators="minimal")
|
||||
run_generators([machine], generators=None, full_closure=False)
|
||||
|
||||
machine.secret_vars_store.populate_dir(
|
||||
machine.name,
|
||||
|
||||
@@ -148,7 +148,7 @@ def run_machine_update(
|
||||
target_host_root = stack.enter_context(_target_host.become_root())
|
||||
|
||||
generate_facts([machine], service=None, regenerate=False)
|
||||
run_generators([machine], generators="minimal")
|
||||
run_generators([machine], generators=None, full_closure=False)
|
||||
|
||||
# Upload secrets to the target host using root
|
||||
upload_secrets(machine, target_host_root)
|
||||
|
||||
@@ -97,7 +97,8 @@ strategies (e.g., interactive CLI, GUI, or programmatic).
|
||||
@API.register
|
||||
def run_generators(
|
||||
machines: list[Machine],
|
||||
generators: str | list[str] = "minimal",
|
||||
generators: str | list[str] | None = None,
|
||||
full_closure: bool = False,
|
||||
prompt_values: dict[str, dict[str, str]] | PromptFunc = lambda g: g.ask_prompts(),
|
||||
no_sandbox: bool = False,
|
||||
) -> None:
|
||||
@@ -105,12 +106,13 @@ def run_generators(
|
||||
Args:
|
||||
machines: The machines to run generators for.
|
||||
generators: Can be:
|
||||
- str: Single generator name to run (ensuring dependencies are met)
|
||||
- None: Run all generators (with closure based on full_closure parameter)
|
||||
- str: Single generator name to run (with closure based on full_closure parameter)
|
||||
- list[str]: Specific generator names to run exactly as provided.
|
||||
Dependency generators are not added automatically in this case.
|
||||
The caller must ensure that all dependencies are included.
|
||||
- "all": Run all generators (full closure)
|
||||
- "minimal": Run only missing generators (minimal closure) (default)
|
||||
full_closure: Whether to include all dependencies (True) or only missing ones (False).
|
||||
Only used when generators is None or a string.
|
||||
prompt_values: A dictionary mapping generator names to their prompt values,
|
||||
or a function that returns prompt values for a generator.
|
||||
no_sandbox: Whether to disable sandboxing when executing the generator.
|
||||
@@ -119,16 +121,8 @@ def run_generators(
|
||||
executing the generator.
|
||||
"""
|
||||
for machine in machines:
|
||||
if generators == "all":
|
||||
generator_objects = get_generators(machine, full_closure=True)
|
||||
elif generators == "minimal":
|
||||
generator_objects = get_generators(machine, full_closure=False)
|
||||
elif isinstance(generators, str) and generators not in ["all", "minimal"]:
|
||||
# Single generator name - compute minimal closure for it
|
||||
generator_objects = get_generators(
|
||||
machine, full_closure=False, generator_name=generators
|
||||
)
|
||||
elif isinstance(generators, list):
|
||||
if isinstance(generators, list):
|
||||
# List of generator names - use them exactly as provided
|
||||
if len(generators) == 0:
|
||||
return
|
||||
# Create GeneratorKeys for this specific machine
|
||||
@@ -138,8 +132,10 @@ def run_generators(
|
||||
all_generators = get_generators(machine, full_closure=True)
|
||||
generator_objects = [g for g in all_generators if g.key in generator_keys]
|
||||
else:
|
||||
msg = f"Invalid generators argument: {generators}. Must be 'all', 'minimal', a generator name, or a list of generator names"
|
||||
raise ValueError(msg)
|
||||
# None or single string - use get_generators with closure parameter
|
||||
generator_objects = get_generators(
|
||||
machine, full_closure=full_closure, generator_name=generators
|
||||
)
|
||||
|
||||
# If prompt function provided, ask all prompts
|
||||
# TODO: make this more lazy and ask for every generator on execution
|
||||
|
||||
Reference in New Issue
Block a user