nixos-rebuild-ng: don't eval closure before validating variant

Because we want to be able to list variants even if one of them might
not eval correctly.

The eager evaluation was caused by us querying for the resulting image
file name to early and is fixed by calling nix-instantiate/nix eval
twice now, once for the variants, once for the image file name.

fixes #394626
This commit is contained in:
phaer 2025-04-01 13:17:34 +02:00
parent 8e5ac55fb1
commit ab4f5ac2f1
5 changed files with 87 additions and 16 deletions

View File

@ -396,7 +396,7 @@ def execute(argv: list[str]) -> None:
raise NRError(
"please specify one of the following "
+ "supported image variants via --image-variant:\n"
+ "\n".join(f"- {v}" for v in variants.keys())
+ "\n".join(f"- {v}" for v in variants)
)
match action:
@ -518,7 +518,19 @@ def execute(argv: list[str]) -> None:
"Done. The virtual machine can be started by running", vm_path
)
case Action.BUILD_IMAGE:
disk_path = path_to_config / variants[args.image_variant]
if flake:
image_name = nix.get_build_image_name_flake(
flake,
args.image_variant,
eval_flags=flake_common_flags,
)
else:
image_name = nix.get_build_image_name(
build_attr,
args.image_variant,
instantiate_flags=flake_common_flags,
)
disk_path = path_to_config / image_name
print_result("Done. The disk image can be found in", disk_path)
case Action.EDIT:

View File

@ -8,7 +8,7 @@ from typing import Any, Callable, ClassVar, Self, TypedDict, override
from .process import Remote, run_wrapper
type ImageVariants = dict[str, str]
type ImageVariants = list[str]
class NRError(Exception):

View File

@ -266,6 +266,59 @@ def find_file(file: str, nix_flags: Args | None = None) -> Path | None:
return Path(r.stdout.strip())
def get_build_image_name(
build_attr: BuildAttr,
image_variant: str,
instantiate_flags: Args | None = None,
) -> str:
path = (
f'"{build_attr.path.resolve()}"'
if isinstance(build_attr.path, Path)
else build_attr.path
)
r = run_wrapper(
[
"nix-instantiate",
"--eval",
"--strict",
"--json",
"--expr",
textwrap.dedent(f"""
let
value = import {path};
set = if builtins.isFunction value then value {{}} else value;
in
set.{build_attr.to_attr("config.system.build.images", image_variant, "passthru", "filePath")}
"""),
*dict_to_flags(instantiate_flags),
],
stdout=PIPE,
)
j: str = json.loads(r.stdout.strip())
return j
def get_build_image_name_flake(
flake: Flake,
image_variant: str,
eval_flags: Args | None = None,
) -> str:
r = run_wrapper(
[
"nix",
"eval",
"--json",
flake.to_attr(
"config.system.build.images", image_variant, "passthru", "filePath"
),
*dict_to_flags(eval_flags),
],
stdout=PIPE,
)
j: str = json.loads(r.stdout.strip())
return j
def get_build_image_variants(
build_attr: BuildAttr,
instantiate_flags: Args | None = None,
@ -287,7 +340,7 @@ def get_build_image_variants(
value = import {path};
set = if builtins.isFunction value then value {{}} else value;
in
builtins.mapAttrs (n: v: v.passthru.filePath) set.{build_attr.to_attr("config.system.build.images")}
builtins.attrNames set.{build_attr.to_attr("config.system.build.images")}
"""),
*dict_to_flags(instantiate_flags),
],
@ -308,7 +361,7 @@ def get_build_image_variants_flake(
"--json",
flake.to_attr("config.system.build.images"),
"--apply",
"builtins.mapAttrs (n: v: v.passthru.filePath)",
"builtins.attrNames",
*dict_to_flags(eval_flags),
],
stdout=PIPE,

View File

@ -347,12 +347,7 @@ def test_execute_nix_build_image_flake(mock_run: Mock, tmp_path: Path) -> None:
return CompletedProcess(
[],
0,
"""
{
"azure": "nixos-image-azure-25.05.20250102.6df2492-x86_64-linux.vhd",
"vmware": "nixos-image-vmware-25.05.20250102.6df2492-x86_64-linux.vmdk"
}
""",
'"nixos-image-azure-25.05.20250102.6df2492-x86_64-linux.vhd"',
)
elif args[0] == "nix":
return CompletedProcess([], 0, str(config_path))
@ -372,7 +367,7 @@ def test_execute_nix_build_image_flake(mock_run: Mock, tmp_path: Path) -> None:
]
)
assert mock_run.call_count == 2
assert mock_run.call_count == 3
mock_run.assert_has_calls(
[
call(
@ -382,7 +377,7 @@ def test_execute_nix_build_image_flake(mock_run: Mock, tmp_path: Path) -> None:
"--json",
"/path/to/config#nixosConfigurations.hostname.config.system.build.images",
"--apply",
"builtins.mapAttrs (n: v: v.passthru.filePath)",
"builtins.attrNames",
],
check=True,
stdout=PIPE,
@ -401,6 +396,17 @@ def test_execute_nix_build_image_flake(mock_run: Mock, tmp_path: Path) -> None:
stdout=PIPE,
**DEFAULT_RUN_KWARGS,
),
call(
[
"nix",
"eval",
"--json",
"/path/to/config#nixosConfigurations.hostname.config.system.build.images.azure.passthru.filePath",
],
check=True,
stdout=PIPE,
**DEFAULT_RUN_KWARGS,
),
]
)

View File

@ -353,7 +353,7 @@ def test_get_build_image_variants(mock_run: Mock, tmp_path: Path) -> None:
value = import <nixpkgs/nixos>;
set = if builtins.isFunction value then value {} else value;
in
builtins.mapAttrs (n: v: v.passthru.filePath) set.config.system.build.images
builtins.attrNames set.config.system.build.images
"""),
],
stdout=PIPE,
@ -376,7 +376,7 @@ def test_get_build_image_variants(mock_run: Mock, tmp_path: Path) -> None:
value = import "{tmp_path}";
set = if builtins.isFunction value then value {{}} else value;
in
builtins.mapAttrs (n: v: v.passthru.filePath) set.preAttr.config.system.build.images
builtins.attrNames set.preAttr.config.system.build.images
"""),
"--inst-flag",
],
@ -411,7 +411,7 @@ def test_get_build_image_variants_flake(mock_run: Mock) -> None:
"--json",
"flake.nix#myAttr.config.system.build.images",
"--apply",
"builtins.mapAttrs (n: v: v.passthru.filePath)",
"builtins.attrNames",
"--eval-flag",
],
stdout=PIPE,