Commit ab4f5ac2 authored by phaer's avatar phaer
Browse files

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
parent 8e5ac55f
Loading
Loading
Loading
Loading
+14 −2
Original line number Diff line number Diff line
@@ -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:
+1 −1
Original line number Diff line number Diff line
@@ -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):
+55 −2
Original line number Diff line number Diff line
@@ -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,
+14 −8
Original line number Diff line number Diff line
@@ -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,
            ),
        ]
    )

+3 −3
Original line number Diff line number Diff line
@@ -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,