nixos-rebuild{-ng,}: Don't eval nixos closure before validating image variants. (#395148)

This commit is contained in:
Thiago Kenji Okada 2025-04-07 09:12:10 +01:00 committed by GitHub
commit b569b62759
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 115 additions and 22 deletions

View File

@ -396,7 +396,7 @@ def execute(argv: list[str]) -> None:
raise NRError( raise NRError(
"please specify one of the following " "please specify one of the following "
+ "supported image variants via --image-variant:\n" + "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: match action:
@ -518,7 +518,19 @@ def execute(argv: list[str]) -> None:
"Done. The virtual machine can be started by running", vm_path "Done. The virtual machine can be started by running", vm_path
) )
case Action.BUILD_IMAGE: 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) print_result("Done. The disk image can be found in", disk_path)
case Action.EDIT: case Action.EDIT:

View File

@ -8,7 +8,7 @@ from typing import Any, Callable, ClassVar, Self, TypedDict, override
from .process import Remote, run_wrapper from .process import Remote, run_wrapper
type ImageVariants = dict[str, str] type ImageVariants = list[str]
class NRError(Exception): 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()) 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( def get_build_image_variants(
build_attr: BuildAttr, build_attr: BuildAttr,
instantiate_flags: Args | None = None, instantiate_flags: Args | None = None,
@ -287,7 +340,7 @@ def get_build_image_variants(
value = import {path}; value = import {path};
set = if builtins.isFunction value then value {{}} else value; set = if builtins.isFunction value then value {{}} else value;
in 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), *dict_to_flags(instantiate_flags),
], ],
@ -308,7 +361,7 @@ def get_build_image_variants_flake(
"--json", "--json",
flake.to_attr("config.system.build.images"), flake.to_attr("config.system.build.images"),
"--apply", "--apply",
"builtins.mapAttrs (n: v: v.passthru.filePath)", "builtins.attrNames",
*dict_to_flags(eval_flags), *dict_to_flags(eval_flags),
], ],
stdout=PIPE, stdout=PIPE,

View File

@ -347,12 +347,7 @@ def test_execute_nix_build_image_flake(mock_run: Mock, tmp_path: Path) -> None:
return CompletedProcess( return CompletedProcess(
[], [],
0, 0,
""" '"nixos-image-azure-25.05.20250102.6df2492-x86_64-linux.vhd"',
{
"azure": "nixos-image-azure-25.05.20250102.6df2492-x86_64-linux.vhd",
"vmware": "nixos-image-vmware-25.05.20250102.6df2492-x86_64-linux.vmdk"
}
""",
) )
elif args[0] == "nix": elif args[0] == "nix":
return CompletedProcess([], 0, str(config_path)) 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( mock_run.assert_has_calls(
[ [
call( call(
@ -382,7 +377,7 @@ def test_execute_nix_build_image_flake(mock_run: Mock, tmp_path: Path) -> None:
"--json", "--json",
"/path/to/config#nixosConfigurations.hostname.config.system.build.images", "/path/to/config#nixosConfigurations.hostname.config.system.build.images",
"--apply", "--apply",
"builtins.mapAttrs (n: v: v.passthru.filePath)", "builtins.attrNames",
], ],
check=True, check=True,
stdout=PIPE, stdout=PIPE,
@ -401,6 +396,17 @@ def test_execute_nix_build_image_flake(mock_run: Mock, tmp_path: Path) -> None:
stdout=PIPE, stdout=PIPE,
**DEFAULT_RUN_KWARGS, **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>; value = import <nixpkgs/nixos>;
set = if builtins.isFunction value then value {} else value; set = if builtins.isFunction value then value {} else value;
in in
builtins.mapAttrs (n: v: v.passthru.filePath) set.config.system.build.images builtins.attrNames set.config.system.build.images
"""), """),
], ],
stdout=PIPE, stdout=PIPE,
@ -376,7 +376,7 @@ def test_get_build_image_variants(mock_run: Mock, tmp_path: Path) -> None:
value = import "{tmp_path}"; value = import "{tmp_path}";
set = if builtins.isFunction value then value {{}} else value; set = if builtins.isFunction value then value {{}} else value;
in in
builtins.mapAttrs (n: v: v.passthru.filePath) set.preAttr.config.system.build.images builtins.attrNames set.preAttr.config.system.build.images
"""), """),
"--inst-flag", "--inst-flag",
], ],
@ -411,7 +411,7 @@ def test_get_build_image_variants_flake(mock_run: Mock) -> None:
"--json", "--json",
"flake.nix#myAttr.config.system.build.images", "flake.nix#myAttr.config.system.build.images",
"--apply", "--apply",
"builtins.mapAttrs (n: v: v.passthru.filePath)", "builtins.attrNames",
"--eval-flag", "--eval-flag",
], ],
stdout=PIPE, stdout=PIPE,

View File

@ -835,28 +835,50 @@ if [ -z "$rollback" ]; then
"let "let
value = import \"$(realpath $buildFile)\"; value = import \"$(realpath $buildFile)\";
set = if builtins.isFunction value then value {} else value; set = if builtins.isFunction value then value {} else value;
in builtins.mapAttrs (n: v: v.passthru.filePath) set.${attr:+$attr.}config.system.build.images" \ in builtins.attrNames set.${attr:+$attr.}config.system.build.images" \
"${extraBuildFlags[@]}" "${extraBuildFlags[@]}"
)" )"
elif [[ -z $flake ]]; then elif [[ -z $flake ]]; then
variants="$( variants="$(
runCmd nix-instantiate --eval --strict --json --expr \ runCmd nix-instantiate --eval --strict --json --expr \
"with import <nixpkgs/nixos> {}; builtins.mapAttrs (n: v: v.passthru.filePath) config.system.build.images" \ "with import <nixpkgs/nixos> {}; builtins.attrNames config.system.build.images" \
"${extraBuildFlags[@]}" "${extraBuildFlags[@]}"
)" )"
else else
variants="$( variants="$(
runCmd nix "${flakeFlags[@]}" eval --json \ runCmd nix "${flakeFlags[@]}" eval --json \
"$flake#$flakeAttr.config.system.build.images" \ "$flake#$flakeAttr.config.system.build.images" \
--apply "builtins.mapAttrs (n: v: v.passthru.filePath)" "${evalArgs[@]}" "${extraBuildFlags[@]}" --apply "builtins.attrNames" "${evalArgs[@]}" "${extraBuildFlags[@]}"
)" )"
fi fi
if ! echo "$variants" | jq -e --arg variant "$imageVariant" "keys | any(. == \$variant)" > /dev/null; then if ! echo "$variants" | jq -e --arg variant "$imageVariant" "any(. == \$variant)" > /dev/null; then
echo -e "Please specify one of the following supported image variants via --image-variant:\n" >&2 echo -e "Please specify one of the following supported image variants via --image-variant:\n" >&2
echo "$variants" | jq -r '. | keys | join ("\n")' echo "$variants" | jq -r 'join ("\n")'
exit 1 exit 1
fi fi
imageName="$(echo "$variants" | jq -r --arg variant "$imageVariant" ".[\$variant]")"
if [[ -z $buildingAttribute ]]; then
imageName="$(
runCmd nix-instantiate --eval --strict --json --expr \
"let
value = import \"$(realpath $buildFile)\";
set = if builtins.isFunction value then value {} else value;
in set.${attr:+$attr.}config.system.build.images.$imageVariant.v.passthru.filePath" \
"${extraBuildFlags[@]}"
)"
elif [[ -z $flake ]]; then
imageName="$(
runCmd nix-instantiate --eval --strict --json --expr \
"with import <nixpkgs/nixos> {}; config.system.build.images.$imageVariant.passthru.filePath" \
"${extraBuildFlags[@]}"
)"
else
imageName="$(
runCmd nix "${flakeFlags[@]}" eval --json \
"$flake#$flakeAttr.config.system.build.images.$imageVariant.passthru.filePath" \
"${evalArgs[@]}" "${extraBuildFlags[@]}"
)"
fi
if [[ -z $buildingAttribute ]]; then if [[ -z $buildingAttribute ]]; then
pathToConfig="$(nixBuild $buildFile -A "${attr:+$attr.}config.system.build.images.${imageVariant}" "${extraBuildFlags[@]}")" pathToConfig="$(nixBuild $buildFile -A "${attr:+$attr.}config.system.build.images.${imageVariant}" "${extraBuildFlags[@]}")"