Unverified Commit 2ff53fe6 authored by Thiago Kenji Okada's avatar Thiago Kenji Okada Committed by GitHub
Browse files

nixos-rebuild-ng: fix non-flake remote build evaluation (#381511)

parents 37af572f 5485a051
Loading
Loading
Loading
Loading
+8 −7
Original line number Diff line number Diff line
@@ -389,8 +389,6 @@ def execute(argv: list[str]) -> None:

            dry_run = action == Action.DRY_BUILD
            no_link = action in (Action.SWITCH, Action.BOOT)
            build_flags |= {"no_out_link": no_link, "dry_run": dry_run}
            flake_build_flags |= {"no_link": no_link, "dry_run": dry_run}
            rollback = bool(args.rollback)

            def validate_image_variant(variants: ImageVariants) -> None:
@@ -444,29 +442,32 @@ def execute(argv: list[str]) -> None:
                        flake,
                        build_host,
                        eval_flags=flake_common_flags,
                        flake_build_flags=flake_build_flags,
                        flake_build_flags=flake_build_flags
                        | {"no_link": no_link, "dry_run": dry_run},
                        copy_flags=copy_flags,
                    )
                case (_, False, None, Flake(_)):
                    path_to_config = nix.build_flake(
                        attr,
                        flake,
                        flake_build_flags=flake_build_flags,
                        flake_build_flags=flake_build_flags
                        | {"no_link": no_link, "dry_run": dry_run},
                    )
                case (_, False, Remote(_), None):
                    path_to_config = nix.build_remote(
                        attr,
                        build_attr,
                        build_host,
                        instantiate_flags=common_flags,
                        realise_flags=common_flags,
                        instantiate_flags=build_flags,
                        copy_flags=copy_flags,
                        build_flags=build_flags,
                    )
                case (_, False, None, None):
                    path_to_config = nix.build(
                        attr,
                        build_attr,
                        build_flags=build_flags,
                        build_flags=build_flags
                        | {"no_out_link": no_link, "dry_run": dry_run},
                    )
                case never:
                    # should never happen, but mypy is not smart enough to
+4 −4
Original line number Diff line number Diff line
@@ -8,7 +8,7 @@ from importlib.resources import files
from pathlib import Path
from string import Template
from subprocess import PIPE, CalledProcessError
from typing import Final
from typing import Final, Literal
from uuid import uuid4

from . import tmpdir
@@ -77,7 +77,7 @@ def build_remote(
    attr: str,
    build_attr: BuildAttr,
    build_host: Remote | None,
    build_flags: Args | None = None,
    realise_flags: Args | None = None,
    instantiate_flags: Args | None = None,
    copy_flags: Args | None = None,
) -> Path:
@@ -112,7 +112,7 @@ def build_remote(
                drv,
                "--add-root",
                remote_tmpdir / uuid4().hex,
                *dict_to_flags(build_flags),
                *dict_to_flags(realise_flags),
            ],
            remote=build_host,
            stdout=PIPE,
@@ -554,7 +554,7 @@ def set_profile(

def switch_to_configuration(
    path_to_config: Path,
    action: Action,
    action: Literal[Action.SWITCH, Action.BOOT, Action.TEST, Action.DRY_ACTIVATE],
    target_host: Remote | None,
    sudo: bool,
    install_bootloader: bool = False,
+565 −366
Original line number Diff line number Diff line
import logging
import textwrap
import uuid
from pathlib import Path
from subprocess import PIPE, CompletedProcess
from typing import Any
from unittest.mock import ANY, call, patch
from unittest.mock import ANY, Mock, call, patch

import pytest

@@ -126,7 +127,7 @@ def test_parse_args() -> None:

@patch.dict(nr.process.os.environ, {}, clear=True)
@patch(get_qualified_name(nr.process.subprocess.run), autospec=True)
def test_execute_nix_boot(mock_run: Any, tmp_path: Path) -> None:
def test_execute_nix_boot(mock_run: Mock, tmp_path: Path) -> None:
    nixpkgs_path = tmp_path / "nixpkgs"
    nixpkgs_path.mkdir()
    config_path = tmp_path / "test"
@@ -146,9 +147,7 @@ def test_execute_nix_boot(mock_run: Any, tmp_path: Path) -> None:

    nr.execute(["nixos-rebuild", "boot", "--no-flake", "-vvv", "--no-reexec"])

    assert mock_run.call_count == 6
    mock_run.assert_has_calls(
        [
    assert mock_run.call_args_list == [
        call(
            ["nix-instantiate", "--find-file", "nixpkgs", "-vvv"],
            stdout=PIPE,
@@ -196,12 +195,11 @@ def test_execute_nix_boot(mock_run: Any, tmp_path: Path) -> None:
            **(DEFAULT_RUN_KWARGS | {"env": {"NIXOS_INSTALL_BOOTLOADER": "0"}}),
        ),
    ]
    )


@patch.dict(nr.process.os.environ, {}, clear=True)
@patch(get_qualified_name(nr.process.subprocess.run), autospec=True)
def test_execute_nix_build_vm(mock_run: Any, tmp_path: Path) -> None:
def test_execute_nix_build_vm(mock_run: Mock, tmp_path: Path) -> None:
    config_path = tmp_path / "test"
    config_path.touch()

@@ -226,9 +224,7 @@ def test_execute_nix_build_vm(mock_run: Any, tmp_path: Path) -> None:
        ]
    )

    assert mock_run.call_count == 1
    mock_run.assert_has_calls(
        [
    assert mock_run.call_args_list == [
        call(
            [
                "nix-build",
@@ -245,12 +241,11 @@ def test_execute_nix_build_vm(mock_run: Any, tmp_path: Path) -> None:
            **DEFAULT_RUN_KWARGS,
        )
    ]
    )


@patch.dict(nr.process.os.environ, {}, clear=True)
@patch(get_qualified_name(nr.process.subprocess.run), autospec=True)
def test_execute_nix_build_image_flake(mock_run: Any, tmp_path: Path) -> None:
def test_execute_nix_build_image_flake(mock_run: Mock, tmp_path: Path) -> None:
    config_path = tmp_path / "test"
    config_path.touch()

@@ -284,9 +279,7 @@ def test_execute_nix_build_image_flake(mock_run: Any, tmp_path: Path) -> None:
        ]
    )

    assert mock_run.call_count == 2
    mock_run.assert_has_calls(
        [
    assert mock_run.call_args_list == [
        call(
            [
                "nix",
@@ -314,12 +307,11 @@ def test_execute_nix_build_image_flake(mock_run: Any, tmp_path: Path) -> None:
            **DEFAULT_RUN_KWARGS,
        ),
    ]
    )


@patch.dict(nr.process.os.environ, {}, clear=True)
@patch(get_qualified_name(nr.process.subprocess.run), autospec=True)
def test_execute_nix_switch_flake(mock_run: Any, tmp_path: Path) -> None:
def test_execute_nix_switch_flake(mock_run: Mock, tmp_path: Path) -> None:
    config_path = tmp_path / "test"
    config_path.touch()

@@ -348,9 +340,7 @@ def test_execute_nix_switch_flake(mock_run: Any, tmp_path: Path) -> None:
        ]
    )

    assert mock_run.call_count == 3
    mock_run.assert_has_calls(
        [
    assert mock_run.call_args_list == [
        call(
            [
                "nix",
@@ -387,15 +377,207 @@ def test_execute_nix_switch_flake(mock_run: Any, tmp_path: Path) -> None:
            **(DEFAULT_RUN_KWARGS | {"env": {"NIXOS_INSTALL_BOOTLOADER": "1"}}),
        ),
    ]


@patch.dict(nr.process.os.environ, {}, clear=True)
@patch(get_qualified_name(nr.process.subprocess.run), autospec=True)
@patch(get_qualified_name(nr.cleanup_ssh, nr), autospec=True)
@patch(get_qualified_name(nr.nix.uuid4, nr.nix), autospec=True)
def test_execute_nix_switch_build_target_host(
    mock_uuid4: Mock,
    mock_cleanup_ssh: Mock,
    mock_run: Mock,
    tmp_path: Path,
) -> None:
    config_path = tmp_path / "test"
    config_path.touch()

    def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]:
        if args[0] == "nix":
            return CompletedProcess([], 0, str(config_path))
        elif args[0] == "nix-instantiate" and "--find-file" in args:
            return CompletedProcess([], 1)
        elif args[0] == "nix-instantiate":
            return CompletedProcess([], 0, str(config_path))
        elif args[0] == "ssh" and "nix-store" in args:
            return CompletedProcess([], 0, "/tmp/tmpdir/config")
        elif args[0] == "ssh" and "mktemp" in args:
            return CompletedProcess([], 0, "/tmp/tmpdir")
        elif args[0] == "ssh" and "readlink" in args:
            return CompletedProcess([], 0, str(config_path))
        else:
            return CompletedProcess([], 0)

    mock_run.side_effect = run_side_effect
    mock_uuid4.return_value = uuid.UUID(int=0)

    nr.execute(
        [
            "nixos-rebuild",
            "switch",
            "--no-flake",
            "--sudo",
            "--build-host",
            "user@build-host",
            "--target-host",
            "user@target-host",
            "--no-reexec",
            # https://github.com/NixOS/nixpkgs/issues/381457
            "-I",
            "nixos-config=./configuration.nix",
            "-I",
            "nixpkgs=$HOME/.nix-defexpr/channels/pinned_nixpkgs",
        ]
    )

    assert mock_run.call_args_list == [
        call(
            [
                "nix-instantiate",
                "--find-file",
                "nixpkgs",
                "--include",
                "nixos-config=./configuration.nix",
                "--include",
                "nixpkgs=$HOME/.nix-defexpr/channels/pinned_nixpkgs",
            ],
            check=False,
            stdout=PIPE,
            **DEFAULT_RUN_KWARGS,
        ),
        call(
            [
                "nix-instantiate",
                "<nixpkgs/nixos>",
                "--attr",
                "config.system.build.toplevel",
                "--add-root",
                nr.tmpdir.TMPDIR_PATH / "00000000000000000000000000000000",
                "--include",
                "nixos-config=./configuration.nix",
                "--include",
                "nixpkgs=$HOME/.nix-defexpr/channels/pinned_nixpkgs",
            ],
            check=True,
            stdout=PIPE,
            **DEFAULT_RUN_KWARGS,
        ),
        call(
            ["nix-copy-closure", "--to", "user@build-host", config_path],
            check=True,
            **DEFAULT_RUN_KWARGS,
        ),
        call(
            [
                "ssh",
                *nr.process.SSH_DEFAULT_OPTS,
                "user@build-host",
                "--",
                "mktemp",
                "-d",
                "-t",
                "nixos-rebuild.XXXXX",
            ],
            check=True,
            stdout=PIPE,
            **DEFAULT_RUN_KWARGS,
        ),
        call(
            [
                "ssh",
                *nr.process.SSH_DEFAULT_OPTS,
                "user@build-host",
                "--",
                "nix-store",
                "--realise",
                str(config_path),
                "--add-root",
                "/tmp/tmpdir/00000000000000000000000000000000",
            ],
            check=True,
            stdout=PIPE,
            **DEFAULT_RUN_KWARGS,
        ),
        call(
            [
                "ssh",
                *nr.process.SSH_DEFAULT_OPTS,
                "user@build-host",
                "--",
                "readlink",
                "-f",
                "/tmp/tmpdir/config",
            ],
            check=True,
            stdout=PIPE,
            **DEFAULT_RUN_KWARGS,
        ),
        call(
            [
                "ssh",
                *nr.process.SSH_DEFAULT_OPTS,
                "user@build-host",
                "--",
                "rm",
                "-rf",
                "/tmp/tmpdir",
            ],
            check=False,
            **DEFAULT_RUN_KWARGS,
        ),
        call(
            [
                "nix",
                "copy",
                "--from",
                "ssh://user@build-host",
                "--to",
                "ssh://user@target-host",
                config_path,
            ],
            check=True,
            **DEFAULT_RUN_KWARGS,
        ),
        call(
            [
                "ssh",
                *nr.process.SSH_DEFAULT_OPTS,
                "user@target-host",
                "--",
                "sudo",
                "nix-env",
                "-p",
                "/nix/var/nix/profiles/system",
                "--set",
                str(config_path),
            ],
            check=True,
            **DEFAULT_RUN_KWARGS,
        ),
        call(
            [
                "ssh",
                *nr.process.SSH_DEFAULT_OPTS,
                "user@target-host",
                "--",
                "sudo",
                "env",
                "NIXOS_INSTALL_BOOTLOADER=0",
                str(config_path / "bin/switch-to-configuration"),
                "switch",
            ],
            check=True,
            **DEFAULT_RUN_KWARGS,
        ),
    ]


@patch.dict(nr.process.os.environ, {}, clear=True)
@patch(get_qualified_name(nr.process.subprocess.run), autospec=True)
@patch(get_qualified_name(nr.cleanup_ssh, nr), autospec=True)
def test_execute_nix_switch_flake_target_host(
    mock_cleanup_ssh: Any,
    mock_run: Any,
    mock_cleanup_ssh: Mock,
    mock_run: Mock,
    tmp_path: Path,
) -> None:
    config_path = tmp_path / "test"
@@ -422,9 +604,7 @@ def test_execute_nix_switch_flake_target_host(
        ]
    )

    assert mock_run.call_count == 4
    mock_run.assert_has_calls(
        [
    assert mock_run.call_args_list == [
        call(
            [
                "nix",
@@ -469,22 +649,21 @@ def test_execute_nix_switch_flake_target_host(
                "sudo",
                "env",
                "NIXOS_INSTALL_BOOTLOADER=0",
                    f"{config_path / 'bin/switch-to-configuration'}",
                str(config_path / "bin/switch-to-configuration"),
                "switch",
            ],
            check=True,
            **DEFAULT_RUN_KWARGS,
        ),
    ]
    )


@patch.dict(nr.process.os.environ, {}, clear=True)
@patch(get_qualified_name(nr.process.subprocess.run), autospec=True)
@patch(get_qualified_name(nr.cleanup_ssh, nr), autospec=True)
def test_execute_nix_switch_flake_build_host(
    mock_cleanup_ssh: Any,
    mock_run: Any,
    mock_cleanup_ssh: Mock,
    mock_run: Mock,
    tmp_path: Path,
) -> None:
    config_path = tmp_path / "test"
@@ -493,7 +672,7 @@ def test_execute_nix_switch_flake_build_host(
    def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]:
        if args[0] == "nix" and "eval" in args:
            return CompletedProcess([], 0, str(config_path))
        if args[0] == "ssh" and "nix" in args:
        elif args[0] == "ssh" and "nix" in args:
            return CompletedProcess([], 0, str(config_path))
        else:
            return CompletedProcess([], 0)
@@ -512,9 +691,7 @@ def test_execute_nix_switch_flake_build_host(
        ]
    )

    assert mock_run.call_count == 6
    mock_run.assert_has_calls(
        [
    assert mock_run.call_args_list == [
        call(
            [
                "nix",
@@ -578,22 +755,54 @@ def test_execute_nix_switch_flake_build_host(
            **DEFAULT_RUN_KWARGS,
        ),
    ]
    )


@patch(get_qualified_name(nr.process.subprocess.run), autospec=True)
def test_execute_switch_rollback(mock_run: Any, tmp_path: Path) -> None:
def test_execute_switch_rollback(mock_run: Mock, tmp_path: Path) -> None:
    nixpkgs_path = tmp_path / "nixpkgs"
    nixpkgs_path.touch()

    def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]:
        if args[0] == "nix-instantiate":
            return CompletedProcess([], 0, str(nixpkgs_path))
        elif args[0] == "git":
            return CompletedProcess([], 0, "")
        else:
            return CompletedProcess([], 0)

    mock_run.side_effect = run_side_effect

    nr.execute(
        ["nixos-rebuild", "switch", "--rollback", "--install-bootloader", "--no-reexec"]
        [
            "nixos-rebuild",
            "switch",
            "--rollback",
            "--install-bootloader",
            "--no-reexec",
            "--no-flake",
        ]
    )

    assert mock_run.call_count >= 2
    # ignoring update_nixpkgs_rev calls
    mock_run.assert_has_calls(
    assert mock_run.call_args_list == [
        call(
            ["nix-instantiate", "--find-file", "nixpkgs"],
            check=False,
            stdout=PIPE,
            **DEFAULT_RUN_KWARGS,
        ),
        call(
            [
                "git",
                "-C",
                nixpkgs_path,
                "rev-parse",
                "--short",
                "HEAD",
            ],
            check=False,
            capture_output=True,
            **DEFAULT_RUN_KWARGS,
        ),
        call(
            [
                "nix-env",
@@ -613,11 +822,10 @@ def test_execute_switch_rollback(mock_run: Any, tmp_path: Path) -> None:
            **DEFAULT_RUN_KWARGS,
        ),
    ]
    )


@patch(get_qualified_name(nr.process.subprocess.run), autospec=True)
def test_execute_build(mock_run: Any, tmp_path: Path) -> None:
def test_execute_build(mock_run: Mock, tmp_path: Path) -> None:
    config_path = tmp_path / "test"
    config_path.touch()
    mock_run.side_effect = [
@@ -627,9 +835,7 @@ def test_execute_build(mock_run: Any, tmp_path: Path) -> None:

    nr.execute(["nixos-rebuild", "build", "--no-flake", "--no-reexec"])

    assert mock_run.call_count == 1
    mock_run.assert_has_calls(
        [
    assert mock_run.call_args_list == [
        call(
            [
                "nix-build",
@@ -642,11 +848,10 @@ def test_execute_build(mock_run: Any, tmp_path: Path) -> None:
            **DEFAULT_RUN_KWARGS,
        )
    ]
    )


@patch(get_qualified_name(nr.process.subprocess.run), autospec=True)
def test_execute_test_flake(mock_run: Any, tmp_path: Path) -> None:
def test_execute_test_flake(mock_run: Mock, tmp_path: Path) -> None:
    config_path = tmp_path / "test"
    config_path.touch()

@@ -662,9 +867,7 @@ def test_execute_test_flake(mock_run: Any, tmp_path: Path) -> None:
        ["nixos-rebuild", "test", "--flake", "github:user/repo#hostname", "--no-reexec"]
    )

    assert mock_run.call_count == 2
    mock_run.assert_has_calls(
        [
    assert mock_run.call_args_list == [
        call(
            [
                "nix",
@@ -684,16 +887,15 @@ def test_execute_test_flake(mock_run: Any, tmp_path: Path) -> None:
            **DEFAULT_RUN_KWARGS,
        ),
    ]
    )


@patch(get_qualified_name(nr.process.subprocess.run), autospec=True)
@patch(get_qualified_name(nr.nix.Path.exists, nr.nix), autospec=True, return_value=True)
@patch(get_qualified_name(nr.nix.Path.mkdir, nr.nix), autospec=True)
def test_execute_test_rollback(
    mock_path_mkdir: Any,
    mock_path_exists: Any,
    mock_run: Any,
    mock_path_mkdir: Mock,
    mock_path_exists: Mock,
    mock_run: Mock,
) -> None:
    def run_side_effect(args: list[str], **kwargs: Any) -> CompletedProcess[str]:
        if args[0] == "nix-env":
@@ -715,9 +917,7 @@ def test_execute_test_rollback(
        ["nixos-rebuild", "test", "--rollback", "--profile-name", "foo", "--no-reexec"]
    )

    assert mock_run.call_count == 2
    mock_run.assert_has_calls(
        [
    assert mock_run.call_args_list == [
        call(
            [
                "nix-env",
@@ -740,4 +940,3 @@ def test_execute_test_rollback(
            **DEFAULT_RUN_KWARGS,
        ),
    ]
    )
+12 −5
Original line number Diff line number Diff line
import platform
import subprocess
from pathlib import Path
from typing import Any
from unittest.mock import patch
from unittest.mock import Mock, patch

from pytest import MonkeyPatch

@@ -79,7 +78,9 @@ def test_flake_to_attr() -> None:


@patch(get_qualified_name(platform.node), autospec=True)
def test_flake_from_arg(mock_node: Any, monkeypatch: MonkeyPatch, tmpdir: Path) -> None:
def test_flake_from_arg(
    mock_node: Mock, monkeypatch: MonkeyPatch, tmpdir: Path
) -> None:
    mock_node.return_value = "hostname"

    # Flake string
@@ -117,9 +118,14 @@ def test_flake_from_arg(mock_node: Any, monkeypatch: MonkeyPatch, tmpdir: Path)
            autospec=True,
            return_value=False,
        ),
        patch(
            get_qualified_name(m.discover_git, m),
            autospec=True,
            return_value="/etc/nixos",
        ),
    ):
        assert m.Flake.from_arg(None, None) == m.Flake(
            Path("/etc/nixos"), "nixosConfigurations.hostname"
            "git+file:///etc/nixos", "nixosConfigurations.hostname"
        )

    with (
@@ -156,11 +162,12 @@ def test_flake_from_arg(mock_node: Any, monkeypatch: MonkeyPatch, tmpdir: Path)


@patch(get_qualified_name(m.Path.mkdir, m), autospec=True)
def test_profile_from_arg(mock_mkdir: Any) -> None:
def test_profile_from_arg(mock_mkdir: Mock) -> None:
    assert m.Profile.from_arg("system") == m.Profile(
        "system",
        Path("/nix/var/nix/profiles/system"),
    )
    mock_mkdir.assert_not_called()

    assert m.Profile.from_arg("something") == m.Profile(
        "something",
+167 −162

File changed.

Preview size limit exceeded, changes collapsed.

Loading