mirror of
https://github.com/NixOS/nixpkgs.git
synced 2024-11-25 08:23:09 +00:00
nixos-rebuild-ng: init
This commit is contained in:
parent
3d8f220f71
commit
9e6ece3ee1
1
.github/labeler.yml
vendored
1
.github/labeler.yml
vendored
@ -293,6 +293,7 @@
|
|||||||
- any-glob-to-any-file:
|
- any-glob-to-any-file:
|
||||||
- nixos/**/*
|
- nixos/**/*
|
||||||
- pkgs/by-name/sw/switch-to-configuration-ng/**/*
|
- pkgs/by-name/sw/switch-to-configuration-ng/**/*
|
||||||
|
- pkgs/by-name/ni/nixos-rebuild-ng/**/*
|
||||||
- pkgs/os-specific/linux/nixos-rebuild/**/*
|
- pkgs/os-specific/linux/nixos-rebuild/**/*
|
||||||
|
|
||||||
"6.topic: nixos-container":
|
"6.topic: nixos-container":
|
||||||
|
@ -143,6 +143,8 @@ nixos/modules/installer/tools/nix-fallback-paths.nix @NixOS/nix-team @raitobeza
|
|||||||
/nixos/tests/amazon-ssm-agent.nix @arianvp
|
/nixos/tests/amazon-ssm-agent.nix @arianvp
|
||||||
/nixos/modules/system/boot/grow-partition.nix @arianvp
|
/nixos/modules/system/boot/grow-partition.nix @arianvp
|
||||||
|
|
||||||
|
# nixos-rebuild-ng
|
||||||
|
/pkgs/by-name/ni/nixos-rebuild-ng @thiagokokada
|
||||||
|
|
||||||
|
|
||||||
# Updaters
|
# Updaters
|
||||||
|
99
pkgs/by-name/ni/nixos-rebuild-ng/README.md
Normal file
99
pkgs/by-name/ni/nixos-rebuild-ng/README.md
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
# nixos-rebuild-ng
|
||||||
|
|
||||||
|
Work-in-Progress rewrite of
|
||||||
|
[`nixos-rebuild`](https://github.com/NixOS/nixpkgs/blob/master/pkgs/os-specific/linux/nixos-rebuild/nixos-rebuild.sh).
|
||||||
|
|
||||||
|
## Why the rewrite?
|
||||||
|
|
||||||
|
The current state of `nixos-rebuild` is dare: it is one of the most critical
|
||||||
|
piece of code we have in NixOS, but it has tons of issues:
|
||||||
|
- The code is written in Bash, and while this by itself is not necessary bad,
|
||||||
|
it means that it is difficult to do refactorings due to the lack of tooling
|
||||||
|
for the language
|
||||||
|
- The code itself is a hacky mess. Changing even one line of code can cause
|
||||||
|
issues that affects dozens of people
|
||||||
|
- Lack of proper testing (we do have some integration tests, but no unit tests
|
||||||
|
and coverage is probably pitiful)
|
||||||
|
- The code predates some of the improvements `nix` had over the years, e.g.: it
|
||||||
|
builds Flakes inside a temporary directory and read the resulting symlink
|
||||||
|
since the code seems to predate `--print-out-paths` flag
|
||||||
|
|
||||||
|
Given all of those above, improvements in the `nixos-rebuild` are difficult to
|
||||||
|
do. A full rewrite is probably the easier way to improve the situation since
|
||||||
|
this can be done in a separate package that will not break anyone. So this is
|
||||||
|
an attempt of the rewrite.
|
||||||
|
|
||||||
|
## Why Python?
|
||||||
|
|
||||||
|
- It is the language of choice for many critical things inside `nixpkgs`, like
|
||||||
|
the `NixOSTest` and `systemd-boot-builder.py` activation scripts
|
||||||
|
- It is a language with great tooling, e.g.: `mypy` for type checking, `ruff`
|
||||||
|
for linting, `pytest` for unit testing
|
||||||
|
- It is a scripting language that fits well with the scope of this project
|
||||||
|
- Python's standard library is great and it means we will need a low number of
|
||||||
|
external dependencies for this project. For example, `nixos-rebuild`
|
||||||
|
currently depends in `jq` for JSON parsing, while Python has `json` in
|
||||||
|
standard library
|
||||||
|
|
||||||
|
## Do's and Don'ts
|
||||||
|
|
||||||
|
- Do: be as much of a drop-in replacement as possible
|
||||||
|
- Do: fix obvious bugs
|
||||||
|
- Do: improvements that are non-breaking
|
||||||
|
- Don't: change logic in breaking ways even if this would be an improvement
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ pkgs, ... }:
|
||||||
|
{
|
||||||
|
environment.systemPackages = [ pkgs.nixos-rebuild-ng ];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
And use `nixos-rebuild-ng` instead of `nixos-rebuild`.
|
||||||
|
|
||||||
|
## Current caveats
|
||||||
|
|
||||||
|
- For now we will install it in `nixos-rebuild-ng` path by default, to avoid
|
||||||
|
conflicting with the current `nixos-rebuild`. This means you can keep both in
|
||||||
|
your system at the same time, but it also means that a few things like bash
|
||||||
|
completion are broken right now (since it looks at `nixos-rebuild` binary)
|
||||||
|
- `_NIXOS_REBUILD_EXEC` is **not** implemented yet, so different from
|
||||||
|
`nixos-rebuild`, this will use the current version of `nixos-rebuild-ng` in
|
||||||
|
your `PATH` to build/set profile/switch, while `nixos-rebuild` builds the new
|
||||||
|
version (the one that will be switched) and re-exec to it instead. This means
|
||||||
|
that in case of bugs in `nixos-rebuild-ng`, the only way that you will get
|
||||||
|
them fixed is **after** you switch to a new version
|
||||||
|
- `nix` bootstrap is also **not** implemented yet, so this means that you will
|
||||||
|
eval with an old version of Nix instead of a newer one. This is unlikely to
|
||||||
|
cause issues, because the build will happen in the daemon anyway (that is
|
||||||
|
only changed after the switch), and unless you are using bleeding edge `nix`
|
||||||
|
features you will probably have zero problems here. You can basically think
|
||||||
|
that using `nixos-rebuild-ng` is similar to running `nixos-rebuild --fast`
|
||||||
|
right now
|
||||||
|
- Ignore any performance advantages of the rewrite right now, because of the 2
|
||||||
|
caveats above
|
||||||
|
- `--target-host` and `--build-host` are not implemented yet and this is
|
||||||
|
probably the thing that will be most difficult to implement. Help here is
|
||||||
|
welcome
|
||||||
|
- Bugs in the profile manipulation can cause corruption of your profile that
|
||||||
|
may be difficult to fix, so right now I only recommend using
|
||||||
|
`nixos-rebuild-ng` if you are testing in a VM or in a filesystem with
|
||||||
|
snapshots like btrfs or ZFS. Those bugs are unlikely to be unfixable but the
|
||||||
|
errors can be difficult to understand. If you want to go anyway,
|
||||||
|
`nix-collect-garbage -d` and `nix store repair` are your friends
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
- [ ] Remote host/builders (via SSH)
|
||||||
|
- [ ] Improve nix arguments handling (e.g.: `nixFlags` vs `copyFlags` in the
|
||||||
|
old `nixos-rebuild`)
|
||||||
|
- [ ] `_NIXOS_REBUILD_EXEC`
|
||||||
|
- [ ] Port `nixos-rebuild.passthru.tests`
|
||||||
|
- [ ] Change module system to allow easier opt-in, like
|
||||||
|
`system.switch.enableNg` for `switch-to-configuration-ng`
|
||||||
|
- [ ] Improve documentation
|
||||||
|
- [ ] `nixos-rebuild repl` (calling old `nixos-rebuild` for now)
|
||||||
|
- [ ] `nix` build/bootstrap
|
||||||
|
- [ ] Reduce build closure
|
80
pkgs/by-name/ni/nixos-rebuild-ng/package.nix
Normal file
80
pkgs/by-name/ni/nixos-rebuild-ng/package.nix
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
{
|
||||||
|
lib,
|
||||||
|
installShellFiles,
|
||||||
|
nix,
|
||||||
|
nixos-rebuild,
|
||||||
|
python3,
|
||||||
|
withNgSuffix ? true,
|
||||||
|
}:
|
||||||
|
python3.pkgs.buildPythonApplication {
|
||||||
|
pname = "nixos-rebuild-ng";
|
||||||
|
version = "0.0.0";
|
||||||
|
src = ./src;
|
||||||
|
pyproject = true;
|
||||||
|
|
||||||
|
build-system = with python3.pkgs; [
|
||||||
|
setuptools
|
||||||
|
];
|
||||||
|
|
||||||
|
dependencies = with python3.pkgs; [
|
||||||
|
tabulate
|
||||||
|
types-tabulate
|
||||||
|
];
|
||||||
|
|
||||||
|
nativeBuildInputs = [
|
||||||
|
installShellFiles
|
||||||
|
];
|
||||||
|
|
||||||
|
propagatedBuildInputs = [
|
||||||
|
# Make sure that we use the Nix package we depend on, not something
|
||||||
|
# else from the PATH for nix-{env,instantiate,build}. This is
|
||||||
|
# important, because NixOS defaults the architecture of the rebuilt
|
||||||
|
# system to the architecture of the nix-* binaries used. So if on an
|
||||||
|
# amd64 system the user has an i686 Nix package in her PATH, then we
|
||||||
|
# would silently downgrade the whole system to be i686 NixOS on the
|
||||||
|
# next reboot.
|
||||||
|
# The binary will be included in the wrapper for Python.
|
||||||
|
nix
|
||||||
|
];
|
||||||
|
|
||||||
|
preBuild = ''
|
||||||
|
substituteInPlace nixos_rebuild/__init__.py \
|
||||||
|
--subst-var-by nixos_rebuild ${lib.getExe nixos-rebuild}
|
||||||
|
'';
|
||||||
|
|
||||||
|
postInstall =
|
||||||
|
''
|
||||||
|
installManPage ${nixos-rebuild}/share/man/man8/nixos-rebuild.8
|
||||||
|
|
||||||
|
installShellCompletion \
|
||||||
|
--bash ${nixos-rebuild}/share/bash-completion/completions/_nixos-rebuild
|
||||||
|
''
|
||||||
|
+ lib.optionalString withNgSuffix ''
|
||||||
|
mv $out/bin/nixos-rebuild $out/bin/nixos-rebuild-ng
|
||||||
|
'';
|
||||||
|
|
||||||
|
nativeCheckInputs = with python3.pkgs; [
|
||||||
|
pytestCheckHook
|
||||||
|
mypy
|
||||||
|
ruff
|
||||||
|
];
|
||||||
|
|
||||||
|
pytestFlagsArray = [ "-vv" ];
|
||||||
|
|
||||||
|
postCheck = ''
|
||||||
|
echo -e "\x1b[32m## run mypy\x1b[0m"
|
||||||
|
mypy nixos_rebuild tests
|
||||||
|
echo -e "\x1b[32m## run ruff\x1b[0m"
|
||||||
|
ruff check nixos_rebuild tests
|
||||||
|
echo -e "\x1b[32m## run ruff format\x1b[0m"
|
||||||
|
ruff format --check nixos_rebuild tests
|
||||||
|
'';
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
description = "Rebuild your NixOS configuration and switch to it, on local hosts and remote";
|
||||||
|
homepage = "https://github.com/NixOS/nixpkgs/tree/master/pkgs/by-name/ni/nixos-rebuild-ng";
|
||||||
|
license = lib.licenses.mit;
|
||||||
|
maintainers = [ lib.maintainers.thiagokokada ];
|
||||||
|
mainProgram = if withNgSuffix then "nixos-rebuild-ng" else "nixos-rebuild";
|
||||||
|
};
|
||||||
|
}
|
222
pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/__init__.py
Normal file
222
pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/__init__.py
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from subprocess import run
|
||||||
|
from typing import assert_never
|
||||||
|
|
||||||
|
from tabulate import tabulate
|
||||||
|
|
||||||
|
from .models import Action, Flake, NRError, Profile
|
||||||
|
from .nix import (
|
||||||
|
edit,
|
||||||
|
list_generations,
|
||||||
|
nixos_build,
|
||||||
|
nixos_build_flake,
|
||||||
|
rollback,
|
||||||
|
rollback_temporary_profile,
|
||||||
|
set_profile,
|
||||||
|
switch_to_configuration,
|
||||||
|
upgrade_channels,
|
||||||
|
)
|
||||||
|
from .utils import info
|
||||||
|
|
||||||
|
VERBOSE = False
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args(argv: list[str]) -> tuple[argparse.Namespace, list[str]]:
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
prog="nixos-rebuild",
|
||||||
|
description="Reconfigure a NixOS machine",
|
||||||
|
add_help=False,
|
||||||
|
allow_abbrev=False,
|
||||||
|
)
|
||||||
|
parser.add_argument("--help", action="store_true")
|
||||||
|
parser.add_argument("--file", "-f")
|
||||||
|
parser.add_argument("--attr", "-A")
|
||||||
|
parser.add_argument("--flake", nargs="?", const=True)
|
||||||
|
parser.add_argument("--no-flake", dest="flake", action="store_false")
|
||||||
|
parser.add_argument("--install-bootloader", action="store_true")
|
||||||
|
# TODO: add deprecated=True in Python >=3.13
|
||||||
|
parser.add_argument("--install-grub", action="store_true")
|
||||||
|
parser.add_argument("--profile-name", "-p", default="system")
|
||||||
|
parser.add_argument("--specialisation", "-c")
|
||||||
|
parser.add_argument("--rollback", action="store_true")
|
||||||
|
parser.add_argument("--upgrade", action="store_true")
|
||||||
|
parser.add_argument("--upgrade-all", action="store_true")
|
||||||
|
parser.add_argument("--json", action="store_true")
|
||||||
|
parser.add_argument("action", choices=Action.values(), nargs="?")
|
||||||
|
|
||||||
|
args, remainder = parser.parse_known_args(argv[1:])
|
||||||
|
|
||||||
|
global VERBOSE
|
||||||
|
# Manually parse verbose flag since this is a nix flag that also affect
|
||||||
|
# the script
|
||||||
|
VERBOSE = any(v == "--verbose" or v.startswith("-v") for v in remainder)
|
||||||
|
|
||||||
|
# https://github.com/NixOS/nixpkgs/blob/master/pkgs/os-specific/linux/nixos-rebuild/nixos-rebuild.sh#L56
|
||||||
|
if args.action == Action.DRY_RUN.value:
|
||||||
|
args.action = Action.DRY_BUILD.value
|
||||||
|
|
||||||
|
if args.install_grub:
|
||||||
|
info(
|
||||||
|
f"{parser.prog}: warning: --install-grub deprecated, use --install-bootloader instead"
|
||||||
|
)
|
||||||
|
args.install_bootloader = True
|
||||||
|
|
||||||
|
if args.action == Action.EDIT.value and (args.file or args.attr):
|
||||||
|
parser.error("--file and --attr are not supported with 'edit'")
|
||||||
|
|
||||||
|
if args.flake and (args.file or args.attr):
|
||||||
|
parser.error("--flake cannot be used with --file or --attr")
|
||||||
|
|
||||||
|
if args.help or args.action is None:
|
||||||
|
r = run(["man", "8", "nixos-rebuild"], check=False)
|
||||||
|
parser.exit(r.returncode)
|
||||||
|
|
||||||
|
return args, remainder
|
||||||
|
|
||||||
|
|
||||||
|
def execute(argv: list[str]) -> None:
|
||||||
|
args, nix_flags = parse_args(argv)
|
||||||
|
|
||||||
|
profile = Profile.from_name(args.profile_name)
|
||||||
|
flake = Flake.from_arg(args.flake)
|
||||||
|
|
||||||
|
if args.upgrade or args.upgrade_all:
|
||||||
|
upgrade_channels(bool(args.upgrade_all))
|
||||||
|
|
||||||
|
match action := Action(args.action):
|
||||||
|
case Action.SWITCH | Action.BOOT:
|
||||||
|
info("building the system configuration...")
|
||||||
|
if args.rollback:
|
||||||
|
path_to_config = rollback(profile)
|
||||||
|
elif flake:
|
||||||
|
path_to_config = nixos_build_flake(
|
||||||
|
"toplevel",
|
||||||
|
flake,
|
||||||
|
nix_flags,
|
||||||
|
no_link=True,
|
||||||
|
)
|
||||||
|
set_profile(profile, path_to_config)
|
||||||
|
else:
|
||||||
|
path_to_config = nixos_build(
|
||||||
|
"system",
|
||||||
|
args.attr,
|
||||||
|
args.file,
|
||||||
|
nix_flags,
|
||||||
|
no_out_link=True,
|
||||||
|
)
|
||||||
|
set_profile(profile, path_to_config)
|
||||||
|
switch_to_configuration(
|
||||||
|
path_to_config,
|
||||||
|
action,
|
||||||
|
specialisation=args.specialisation,
|
||||||
|
install_bootloader=args.install_bootloader,
|
||||||
|
)
|
||||||
|
case Action.TEST | Action.BUILD | Action.DRY_BUILD | Action.DRY_ACTIVATE:
|
||||||
|
info("building the system configuration...")
|
||||||
|
dry_run = action == Action.DRY_BUILD
|
||||||
|
if args.rollback and action in (Action.TEST, Action.BUILD):
|
||||||
|
maybe_path_to_config = rollback_temporary_profile(profile)
|
||||||
|
if maybe_path_to_config: # kinda silly but this makes mypy happy
|
||||||
|
path_to_config = maybe_path_to_config
|
||||||
|
else:
|
||||||
|
raise NRError("could not find previous generation")
|
||||||
|
elif flake:
|
||||||
|
path_to_config = nixos_build_flake(
|
||||||
|
"toplevel",
|
||||||
|
flake,
|
||||||
|
nix_flags,
|
||||||
|
keep_going=True,
|
||||||
|
dry_run=dry_run,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
path_to_config = nixos_build(
|
||||||
|
"system",
|
||||||
|
args.attr,
|
||||||
|
args.file,
|
||||||
|
nix_flags,
|
||||||
|
keep_going=True,
|
||||||
|
dry_run=dry_run,
|
||||||
|
)
|
||||||
|
if action in (Action.TEST, Action.DRY_ACTIVATE):
|
||||||
|
switch_to_configuration(
|
||||||
|
path_to_config,
|
||||||
|
action,
|
||||||
|
specialisation=args.specialisation,
|
||||||
|
)
|
||||||
|
case Action.BUILD_VM | Action.BUILD_VM_WITH_BOOTLOADER:
|
||||||
|
info("building the system configuration...")
|
||||||
|
attr = "vm" if action == Action.BUILD_VM else "vmWithBootLoader"
|
||||||
|
if flake:
|
||||||
|
path_to_config = nixos_build_flake(
|
||||||
|
attr,
|
||||||
|
flake,
|
||||||
|
nix_flags,
|
||||||
|
keep_going=True,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
path_to_config = nixos_build(
|
||||||
|
attr,
|
||||||
|
args.attr,
|
||||||
|
args.file,
|
||||||
|
nix_flags,
|
||||||
|
keep_going=True,
|
||||||
|
)
|
||||||
|
vm_path = next(path_to_config.glob("bin/run-*-vm"), "./result/bin/run-*-vm")
|
||||||
|
print(f"Done. The virtual machine can be started by running '{vm_path}'")
|
||||||
|
case Action.EDIT:
|
||||||
|
edit(flake, nix_flags)
|
||||||
|
case Action.DRY_RUN:
|
||||||
|
assert False, "DRY_RUN should be a DRY_BUILD alias"
|
||||||
|
case Action.LIST_GENERATIONS:
|
||||||
|
generations = list_generations(profile)
|
||||||
|
if args.json:
|
||||||
|
print(json.dumps(generations, indent=2))
|
||||||
|
else:
|
||||||
|
headers = {
|
||||||
|
"generation": "Generation",
|
||||||
|
"date": "Build-date",
|
||||||
|
"nixosVersion": "NixOS version",
|
||||||
|
"kernelVersion": "Kernel",
|
||||||
|
"configurationRevision": "Configuration Revision",
|
||||||
|
"specialisations": "Specialisation",
|
||||||
|
"current": "Current",
|
||||||
|
}
|
||||||
|
# Not exactly the same format as legacy nixos-rebuild but close
|
||||||
|
# enough
|
||||||
|
table = tabulate(
|
||||||
|
generations,
|
||||||
|
headers=headers,
|
||||||
|
tablefmt="plain",
|
||||||
|
numalign="left",
|
||||||
|
stralign="left",
|
||||||
|
disable_numparse=True,
|
||||||
|
)
|
||||||
|
print(table)
|
||||||
|
case Action.REPL:
|
||||||
|
# For now just redirect it to `nixos-rebuild` instead of
|
||||||
|
# duplicating the code
|
||||||
|
os.execv(
|
||||||
|
"@nixos_rebuild@",
|
||||||
|
argv,
|
||||||
|
)
|
||||||
|
case _:
|
||||||
|
assert_never(action)
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
try:
|
||||||
|
execute(sys.argv)
|
||||||
|
except (Exception, KeyboardInterrupt) as ex:
|
||||||
|
if VERBOSE:
|
||||||
|
raise ex
|
||||||
|
else:
|
||||||
|
sys.exit(str(ex))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
121
pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/models.py
Normal file
121
pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/models.py
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import platform
|
||||||
|
import re
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import Enum
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any, ClassVar, TypedDict, override
|
||||||
|
|
||||||
|
|
||||||
|
class NRError(Exception):
|
||||||
|
"nixos-rebuild general error."
|
||||||
|
|
||||||
|
def __init__(self, message: str):
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
@override
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"error: {self.message}"
|
||||||
|
|
||||||
|
|
||||||
|
class Action(Enum):
|
||||||
|
SWITCH = "switch"
|
||||||
|
BOOT = "boot"
|
||||||
|
TEST = "test"
|
||||||
|
BUILD = "build"
|
||||||
|
EDIT = "edit"
|
||||||
|
REPL = "repl"
|
||||||
|
DRY_BUILD = "dry-build"
|
||||||
|
DRY_RUN = "dry-run"
|
||||||
|
DRY_ACTIVATE = "dry-activate"
|
||||||
|
BUILD_VM = "build-vm"
|
||||||
|
BUILD_VM_WITH_BOOTLOADER = "build-vm-with-bootloader"
|
||||||
|
LIST_GENERATIONS = "list-generations"
|
||||||
|
|
||||||
|
@override
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def values() -> list[str]:
|
||||||
|
return [a.value for a in Action]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Flake:
|
||||||
|
path: Path
|
||||||
|
attr: str
|
||||||
|
_re: ClassVar[re.Pattern[str]] = re.compile(
|
||||||
|
r"^(?P<path>[^\#]*)\#?(?P<attr>[^\#\"]*)$"
|
||||||
|
)
|
||||||
|
|
||||||
|
@override
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return f"{self.path}#{self.attr}"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, flake_str: str, hostname: str | None = None) -> Flake:
|
||||||
|
m = cls._re.match(flake_str)
|
||||||
|
assert m is not None, f"got no matches for {flake_str}"
|
||||||
|
attr = m.group("attr")
|
||||||
|
if not attr:
|
||||||
|
attr = f"nixosConfigurations.{hostname or "default"}"
|
||||||
|
else:
|
||||||
|
attr = f"nixosConfigurations.{attr}"
|
||||||
|
return Flake(Path(m.group("path")), attr)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_arg(cls, flake_arg: Any) -> Flake | None:
|
||||||
|
hostname = platform.node()
|
||||||
|
match flake_arg:
|
||||||
|
case str(s):
|
||||||
|
return cls.parse(s, hostname)
|
||||||
|
case True:
|
||||||
|
return cls.parse(".", hostname)
|
||||||
|
case False:
|
||||||
|
return None
|
||||||
|
case _:
|
||||||
|
# Use /etc/nixos/flake.nix if it exists.
|
||||||
|
default_path = Path("/etc/nixos/flake.nix")
|
||||||
|
if default_path.exists():
|
||||||
|
# It can be a symlink to the actual flake.
|
||||||
|
if default_path.is_symlink():
|
||||||
|
default_path = default_path.readlink()
|
||||||
|
return cls.parse(str(default_path.parent), hostname)
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Generation:
|
||||||
|
id: int
|
||||||
|
timestamp: str # we may want to have a proper timestamp type in future
|
||||||
|
current: bool
|
||||||
|
|
||||||
|
|
||||||
|
# camelCase since this will be used as output for `--json` flag
|
||||||
|
class GenerationJson(TypedDict):
|
||||||
|
generation: int
|
||||||
|
date: str
|
||||||
|
nixosVersion: str
|
||||||
|
kernelVersion: str
|
||||||
|
configurationRevision: str
|
||||||
|
specialisations: list[str]
|
||||||
|
current: bool
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Profile:
|
||||||
|
name: str
|
||||||
|
path: Path
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_name(name: str = "system") -> Profile:
|
||||||
|
match name:
|
||||||
|
case "system":
|
||||||
|
return Profile(name, Path("/nix/var/nix/profiles/system"))
|
||||||
|
case _:
|
||||||
|
path = Path("/nix/var/nix/profiles/system-profiles") / name
|
||||||
|
path.parent.mkdir(mode=0o755, parents=True, exist_ok=True)
|
||||||
|
return Profile(name, path)
|
282
pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/nix.py
Normal file
282
pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/nix.py
Normal file
@ -0,0 +1,282 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from subprocess import PIPE, CalledProcessError, run
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
from .models import (
|
||||||
|
Action,
|
||||||
|
Flake,
|
||||||
|
Generation,
|
||||||
|
GenerationJson,
|
||||||
|
NRError,
|
||||||
|
Profile,
|
||||||
|
)
|
||||||
|
from .utils import dict_to_flags
|
||||||
|
|
||||||
|
FLAKE_FLAGS: Final = ["--extra-experimental-features", "nix-command flakes"]
|
||||||
|
|
||||||
|
|
||||||
|
def edit(flake: Flake | None, nix_flags: list[str] | None = None) -> None:
|
||||||
|
"Try to find and open NixOS configuration file in editor."
|
||||||
|
if flake:
|
||||||
|
run(
|
||||||
|
["nix", *FLAKE_FLAGS, "edit", *(nix_flags or []), "--", str(flake)],
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
if nix_flags:
|
||||||
|
raise NRError("'edit' does not support extra Nix flags")
|
||||||
|
nixos_config = Path(
|
||||||
|
os.getenv("NIXOS_CONFIG")
|
||||||
|
or run(
|
||||||
|
["nix-instantiate", "--find-file", "nixos-config"],
|
||||||
|
text=True,
|
||||||
|
stdout=PIPE,
|
||||||
|
check=False,
|
||||||
|
).stdout.strip()
|
||||||
|
or "/etc/nixos/default.nix"
|
||||||
|
)
|
||||||
|
if nixos_config.is_dir():
|
||||||
|
nixos_config /= "default.nix"
|
||||||
|
|
||||||
|
if nixos_config.exists():
|
||||||
|
run([os.getenv("EDITOR", "nano"), nixos_config], check=False)
|
||||||
|
else:
|
||||||
|
raise NRError("cannot find NixOS config file")
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_generation_from_nix_store(path: Path, profile: Profile) -> Generation:
|
||||||
|
entry_id = path.name.split("-")[1]
|
||||||
|
current = path.name == profile.path.readlink().name
|
||||||
|
timestamp = datetime.fromtimestamp(path.stat().st_ctime).strftime(
|
||||||
|
"%Y-%m-%d %H:%M:%S"
|
||||||
|
)
|
||||||
|
|
||||||
|
return Generation(
|
||||||
|
id=int(entry_id),
|
||||||
|
timestamp=timestamp,
|
||||||
|
current=current,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_generation_from_nix_env(line: str) -> Generation:
|
||||||
|
parts = line.split()
|
||||||
|
|
||||||
|
entry_id = parts[0]
|
||||||
|
timestamp = f"{parts[1]} {parts[2]}"
|
||||||
|
current = "(current)" in parts
|
||||||
|
|
||||||
|
return Generation(
|
||||||
|
id=int(entry_id),
|
||||||
|
timestamp=timestamp,
|
||||||
|
current=current,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_generations(profile: Profile, lock_profile: bool = False) -> list[Generation]:
|
||||||
|
"""Get all NixOS generations from profile.
|
||||||
|
|
||||||
|
Includes generation ID (e.g.: 1, 2), timestamp (e.g.: when it was created)
|
||||||
|
and if this is the current active profile or not.
|
||||||
|
|
||||||
|
If `lock_profile = True` this command will need root to run successfully.
|
||||||
|
"""
|
||||||
|
if not profile.path.exists():
|
||||||
|
raise NRError(f"no profile '{profile.name}' found")
|
||||||
|
|
||||||
|
result = []
|
||||||
|
if lock_profile:
|
||||||
|
# Using `nix-env --list-generations` needs root to lock the profile
|
||||||
|
# TODO: do we actually need to lock profile for e.g.: rollback?
|
||||||
|
# https://github.com/NixOS/nix/issues/5144
|
||||||
|
r = run(
|
||||||
|
["nix-env", "-p", profile.path, "--list-generations"],
|
||||||
|
text=True,
|
||||||
|
stdout=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
for line in r.stdout.splitlines():
|
||||||
|
result.append(_parse_generation_from_nix_env(line))
|
||||||
|
else:
|
||||||
|
for p in profile.path.parent.glob("system-*-link"):
|
||||||
|
result.append(_parse_generation_from_nix_store(p, profile))
|
||||||
|
return sorted(result, key=lambda d: d.id)
|
||||||
|
|
||||||
|
|
||||||
|
def list_generations(profile: Profile) -> list[GenerationJson]:
|
||||||
|
"""Get all NixOS generations from profile, including extra information.
|
||||||
|
|
||||||
|
Includes OS information like the commit, kernel version, configuration
|
||||||
|
revision and specialisations.
|
||||||
|
|
||||||
|
Will be formatted in a way that is expected by the output of
|
||||||
|
`nixos-rebuild list-generations --json`.
|
||||||
|
"""
|
||||||
|
generations = get_generations(profile)
|
||||||
|
result = []
|
||||||
|
for generation in reversed(generations):
|
||||||
|
generation_path = (
|
||||||
|
profile.path.parent / f"{profile.path.name}-{generation.id}-link"
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
nixos_version = (generation_path / "nixos-version").read_text().strip()
|
||||||
|
except IOError:
|
||||||
|
nixos_version = "Unknown"
|
||||||
|
try:
|
||||||
|
kernel_version = next(
|
||||||
|
(generation_path / "kernel-modules/lib/modules").iterdir()
|
||||||
|
).name
|
||||||
|
except IOError:
|
||||||
|
kernel_version = "Unknown"
|
||||||
|
specialisations = [
|
||||||
|
s.name for s in (generation_path / "specialisation").glob("*") if s.is_dir()
|
||||||
|
]
|
||||||
|
try:
|
||||||
|
configuration_revision = run(
|
||||||
|
[generation_path / "sw/bin/nixos-version", "--configuration-revision"],
|
||||||
|
capture_output=True,
|
||||||
|
check=True,
|
||||||
|
text=True,
|
||||||
|
).stdout.strip()
|
||||||
|
except (CalledProcessError, IOError):
|
||||||
|
configuration_revision = "Unknown"
|
||||||
|
|
||||||
|
result.append(
|
||||||
|
GenerationJson(
|
||||||
|
generation=generation.id,
|
||||||
|
date=generation.timestamp,
|
||||||
|
nixosVersion=nixos_version,
|
||||||
|
kernelVersion=kernel_version,
|
||||||
|
configurationRevision=configuration_revision,
|
||||||
|
specialisations=specialisations,
|
||||||
|
current=generation.current,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def nixos_build(
|
||||||
|
attr: str,
|
||||||
|
pre_attr: str | None,
|
||||||
|
file: str | None,
|
||||||
|
nix_flags: list[str] | None = None,
|
||||||
|
**kwargs: bool | str,
|
||||||
|
) -> Path:
|
||||||
|
"""Build NixOS attribute using classic Nix.
|
||||||
|
|
||||||
|
It will by default build `<nixpkgs/nixos>` with `attr`, however it
|
||||||
|
optionally supports building from an external file and custom attributes
|
||||||
|
paths.
|
||||||
|
|
||||||
|
Returns the built attribute as path.
|
||||||
|
"""
|
||||||
|
if pre_attr or file:
|
||||||
|
run_args = [
|
||||||
|
"nix-build",
|
||||||
|
file or "default.nix",
|
||||||
|
"--attr",
|
||||||
|
f"{'.'.join(x for x in [pre_attr, attr] if x)}",
|
||||||
|
]
|
||||||
|
else:
|
||||||
|
run_args = ["nix-build", "<nixpkgs/nixos>", "--attr", attr]
|
||||||
|
run_args += dict_to_flags(kwargs) + (nix_flags or [])
|
||||||
|
r = run(run_args, check=True, text=True, stdout=PIPE)
|
||||||
|
return Path(r.stdout.strip())
|
||||||
|
|
||||||
|
|
||||||
|
def nixos_build_flake(
|
||||||
|
attr: str,
|
||||||
|
flake: Flake,
|
||||||
|
nix_flags: list[str] | None = None,
|
||||||
|
**kwargs: bool | str,
|
||||||
|
) -> Path:
|
||||||
|
"""Build NixOS attribute using Flakes.
|
||||||
|
|
||||||
|
Returns the built attribute as path.
|
||||||
|
"""
|
||||||
|
run_args = [
|
||||||
|
"nix",
|
||||||
|
*FLAKE_FLAGS,
|
||||||
|
"build",
|
||||||
|
"--print-out-paths",
|
||||||
|
f"{flake}.config.system.build.{attr}",
|
||||||
|
]
|
||||||
|
run_args += dict_to_flags(kwargs) + (nix_flags or [])
|
||||||
|
r = run(run_args, check=True, text=True, stdout=PIPE)
|
||||||
|
return Path(r.stdout.strip())
|
||||||
|
|
||||||
|
|
||||||
|
def rollback(profile: Profile) -> Path:
|
||||||
|
"Rollback Nix profile, like one created by `nixos-rebuild switch`."
|
||||||
|
run(["nix-env", "--rollback", "-p", profile.path], check=True)
|
||||||
|
# Rollback config PATH is the own profile
|
||||||
|
return profile.path
|
||||||
|
|
||||||
|
|
||||||
|
def rollback_temporary_profile(profile: Profile) -> Path | None:
|
||||||
|
"Rollback a temporary Nix profile, like one created by `nixos-rebuild test`."
|
||||||
|
generations = get_generations(profile, lock_profile=True)
|
||||||
|
previous_gen_id = None
|
||||||
|
for generation in generations:
|
||||||
|
if not generation.current:
|
||||||
|
previous_gen_id = generation.id
|
||||||
|
|
||||||
|
if previous_gen_id:
|
||||||
|
return profile.path.parent / f"{profile.name}-{previous_gen_id}-link"
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def set_profile(profile: Profile, path_to_config: Path) -> None:
|
||||||
|
"Set a path as the current active Nix profile."
|
||||||
|
run(["nix-env", "-p", profile.path, "--set", path_to_config], check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def switch_to_configuration(
|
||||||
|
path_to_config: Path,
|
||||||
|
action: Action,
|
||||||
|
install_bootloader: bool = False,
|
||||||
|
specialisation: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
"""Call `<config>/bin/switch-to-configuration <action>`.
|
||||||
|
|
||||||
|
Expects a built path to run, like one generated with `nixos_build` or
|
||||||
|
`nixos_build_flake` functions.
|
||||||
|
"""
|
||||||
|
if specialisation:
|
||||||
|
if action not in (Action.SWITCH, Action.TEST):
|
||||||
|
raise NRError(
|
||||||
|
"'--specialisation' can only be used with 'switch' and 'test'"
|
||||||
|
)
|
||||||
|
path_to_config = path_to_config / f"specialisation/{specialisation}"
|
||||||
|
|
||||||
|
if not path_to_config.exists():
|
||||||
|
raise NRError(f"specialisation not found: {specialisation}")
|
||||||
|
|
||||||
|
run(
|
||||||
|
[path_to_config / "bin/switch-to-configuration", str(action)],
|
||||||
|
env={
|
||||||
|
"NIXOS_INSTALL_BOOTLOADER": "1" if install_bootloader else "0",
|
||||||
|
"LOCALE_ARCHIVE": os.getenv("LOCALE_ARCHIVE", ""),
|
||||||
|
},
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade_channels(all: bool = False) -> None:
|
||||||
|
"""Upgrade channels for classic Nix.
|
||||||
|
|
||||||
|
It will either upgrade just the `nixos` channel (including any channel
|
||||||
|
that has a `.update-on-nixos-rebuild` file) or all.
|
||||||
|
"""
|
||||||
|
for channel_path in Path("/nix/var/nix/profiles/per-user/root/channels/").glob("*"):
|
||||||
|
if (
|
||||||
|
all
|
||||||
|
or channel_path.name == "nixos"
|
||||||
|
or (channel_path / ".update-on-nixos-rebuild").exists()
|
||||||
|
):
|
||||||
|
run(["nix-channel", "--update", channel_path.name], check=False)
|
28
pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/utils.py
Normal file
28
pkgs/by-name/ni/nixos-rebuild-ng/src/nixos_rebuild/utils.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from functools import partial
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
info = partial(print, file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
|
def dict_to_flags(d: dict[str, Any]) -> list[str]:
|
||||||
|
flags = []
|
||||||
|
for key, value in d.items():
|
||||||
|
flag = f"--{'-'.join(key.split('_'))}"
|
||||||
|
match value:
|
||||||
|
case None | False:
|
||||||
|
pass
|
||||||
|
case True:
|
||||||
|
flags.append(flag)
|
||||||
|
case int():
|
||||||
|
flags.append(f"-{key[0] * value}")
|
||||||
|
case str():
|
||||||
|
flags.append(flag)
|
||||||
|
flags.append(value)
|
||||||
|
case list():
|
||||||
|
flags.append(flag)
|
||||||
|
for v in value:
|
||||||
|
flags.append(v)
|
||||||
|
return flags
|
51
pkgs/by-name/ni/nixos-rebuild-ng/src/pyproject.toml
Normal file
51
pkgs/by-name/ni/nixos-rebuild-ng/src/pyproject.toml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
[build-system]
|
||||||
|
requires = ["setuptools"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "nixos-rebuild-ng"
|
||||||
|
version = "0.0.0"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
nixos-rebuild = "nixos_rebuild:main"
|
||||||
|
|
||||||
|
[tool.mypy]
|
||||||
|
# `--strict` config, but explicit options to avoid breaking build when mypy is
|
||||||
|
# updated
|
||||||
|
warn_unused_configs = true
|
||||||
|
disallow_any_generics = true
|
||||||
|
disallow_subclassing_any = true
|
||||||
|
disallow_untyped_calls = true
|
||||||
|
disallow_untyped_defs = true
|
||||||
|
disallow_incomplete_defs = true
|
||||||
|
check_untyped_defs = true
|
||||||
|
disallow_untyped_decorators = true
|
||||||
|
warn_redundant_casts = true
|
||||||
|
warn_unused_ignores = true
|
||||||
|
warn_return_any = true
|
||||||
|
# no_implicit_reexport = true
|
||||||
|
strict_equality = true
|
||||||
|
extra_checks = true
|
||||||
|
|
||||||
|
# extra options not included in `--strict`
|
||||||
|
enable_error_code = ["explicit-override", "mutable-override"]
|
||||||
|
|
||||||
|
[[tool.mypy.overrides]]
|
||||||
|
module = "pytest.*"
|
||||||
|
ignore_missing_imports = true
|
||||||
|
|
||||||
|
[tool.ruff.lint]
|
||||||
|
extend-select = [
|
||||||
|
# ensure imports are sorted
|
||||||
|
"I",
|
||||||
|
# require 'from __future__ import annotations'
|
||||||
|
"FA102",
|
||||||
|
# require `check` argument for `subprocess.run`
|
||||||
|
"PLW1510",
|
||||||
|
]
|
||||||
|
|
||||||
|
[tool.ruff.lint.per-file-ignores]
|
||||||
|
"tests/" = ["FA102"]
|
||||||
|
|
||||||
|
[tool.pytest.ini_options]
|
||||||
|
addopts = ["--import-mode=importlib"]
|
13
pkgs/by-name/ni/nixos-rebuild-ng/src/tests/helpers.py
Normal file
13
pkgs/by-name/ni/nixos-rebuild-ng/src/tests/helpers.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from types import ModuleType
|
||||||
|
from typing import Any, Callable
|
||||||
|
|
||||||
|
|
||||||
|
def get_qualified_name(
|
||||||
|
method: Callable[..., Any],
|
||||||
|
module: ModuleType | None = None,
|
||||||
|
) -> str:
|
||||||
|
module_name = getattr(module, "__name__", method.__module__)
|
||||||
|
method_name = getattr(method, "__qualname__", method.__name__)
|
||||||
|
return f"{module_name}.{method_name}"
|
270
pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_main.py
Normal file
270
pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_main.py
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
import textwrap
|
||||||
|
from pathlib import Path
|
||||||
|
from subprocess import PIPE, CompletedProcess
|
||||||
|
from typing import Any
|
||||||
|
from unittest.mock import call, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import nixos_rebuild as nr
|
||||||
|
|
||||||
|
from .helpers import get_qualified_name
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(autouse=True)
|
||||||
|
def setup(monkeypatch: Any) -> None:
|
||||||
|
monkeypatch.setenv("LOCALE_ARCHIVE", "/locale")
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_args() -> None:
|
||||||
|
with pytest.raises(SystemExit) as e:
|
||||||
|
nr.parse_args(["nixos-rebuild", "unknown-action"])
|
||||||
|
assert e.value.code == 2
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit) as e:
|
||||||
|
nr.parse_args(["nixos-rebuild", "test", "--flake", "--file", "abc"])
|
||||||
|
assert e.value.code == 2
|
||||||
|
|
||||||
|
with pytest.raises(SystemExit) as e:
|
||||||
|
nr.parse_args(["nixos-rebuild", "edit", "--attr", "attr"])
|
||||||
|
assert e.value.code == 2
|
||||||
|
|
||||||
|
r1, remainder = nr.parse_args(
|
||||||
|
[
|
||||||
|
"nixos-rebuild",
|
||||||
|
"switch",
|
||||||
|
"--install-grub",
|
||||||
|
"--flake",
|
||||||
|
"/etc/nixos",
|
||||||
|
"--extra",
|
||||||
|
"flag",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert remainder == ["--extra", "flag"]
|
||||||
|
assert r1.flake == "/etc/nixos"
|
||||||
|
assert r1.install_bootloader is True
|
||||||
|
assert r1.install_grub is True
|
||||||
|
assert r1.profile_name == "system"
|
||||||
|
assert r1.action == "switch"
|
||||||
|
|
||||||
|
r2, remainder = nr.parse_args(
|
||||||
|
[
|
||||||
|
"nixos-rebuild",
|
||||||
|
"dry-run",
|
||||||
|
"--flake",
|
||||||
|
"--no-flake",
|
||||||
|
"-f",
|
||||||
|
"foo",
|
||||||
|
"--attr",
|
||||||
|
"bar",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
assert remainder == []
|
||||||
|
assert r2.flake is False
|
||||||
|
assert r2.action == "dry-build"
|
||||||
|
assert r2.file == "foo"
|
||||||
|
assert r2.attr == "bar"
|
||||||
|
|
||||||
|
|
||||||
|
@patch(get_qualified_name(nr.nix.run, nr.nix), autospec=True)
|
||||||
|
def test_execute_nix_boot(mock_run: Any, tmp_path: Path) -> None:
|
||||||
|
config_path = tmp_path / "test"
|
||||||
|
config_path.touch()
|
||||||
|
mock_run.side_effect = [
|
||||||
|
# nixos_build
|
||||||
|
CompletedProcess([], 0, str(config_path)),
|
||||||
|
# set_profile
|
||||||
|
CompletedProcess([], 0),
|
||||||
|
# switch_to_configuration
|
||||||
|
CompletedProcess([], 0),
|
||||||
|
]
|
||||||
|
|
||||||
|
nr.execute(["nixos-rebuild", "boot", "--no-flake", "-vvv"])
|
||||||
|
|
||||||
|
assert nr.VERBOSE is True
|
||||||
|
assert mock_run.call_count == 3
|
||||||
|
mock_run.assert_has_calls(
|
||||||
|
[
|
||||||
|
call(
|
||||||
|
[
|
||||||
|
"nix-build",
|
||||||
|
"<nixpkgs/nixos>",
|
||||||
|
"--attr",
|
||||||
|
"system",
|
||||||
|
"--no-out-link",
|
||||||
|
"-vvv",
|
||||||
|
],
|
||||||
|
check=True,
|
||||||
|
text=True,
|
||||||
|
stdout=PIPE,
|
||||||
|
),
|
||||||
|
call(
|
||||||
|
[
|
||||||
|
"nix-env",
|
||||||
|
"-p",
|
||||||
|
Path("/nix/var/nix/profiles/system"),
|
||||||
|
"--set",
|
||||||
|
config_path,
|
||||||
|
],
|
||||||
|
check=True,
|
||||||
|
),
|
||||||
|
call(
|
||||||
|
[config_path / "bin/switch-to-configuration", "boot"],
|
||||||
|
env={"NIXOS_INSTALL_BOOTLOADER": "0", "LOCALE_ARCHIVE": "/locale"},
|
||||||
|
check=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(get_qualified_name(nr.nix.run, nr.nix), autospec=True)
|
||||||
|
def test_execute_nix_switch_flake(mock_run: Any, tmp_path: Path) -> None:
|
||||||
|
config_path = tmp_path / "test"
|
||||||
|
config_path.touch()
|
||||||
|
mock_run.side_effect = [
|
||||||
|
# nixos_build_flake
|
||||||
|
CompletedProcess([], 0, str(config_path)),
|
||||||
|
# set_profile
|
||||||
|
CompletedProcess([], 0),
|
||||||
|
# switch_to_configuration
|
||||||
|
CompletedProcess([], 0),
|
||||||
|
]
|
||||||
|
|
||||||
|
nr.execute(
|
||||||
|
[
|
||||||
|
"nixos-rebuild",
|
||||||
|
"switch",
|
||||||
|
"--flake",
|
||||||
|
"/path/to/config#hostname",
|
||||||
|
"--install-bootloader",
|
||||||
|
"--verbose",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert nr.VERBOSE is True
|
||||||
|
assert mock_run.call_count == 3
|
||||||
|
mock_run.assert_has_calls(
|
||||||
|
[
|
||||||
|
call(
|
||||||
|
[
|
||||||
|
"nix",
|
||||||
|
"--extra-experimental-features",
|
||||||
|
"nix-command flakes",
|
||||||
|
"build",
|
||||||
|
"--print-out-paths",
|
||||||
|
"/path/to/config#nixosConfigurations.hostname.config.system.build.toplevel",
|
||||||
|
"--no-link",
|
||||||
|
"--verbose",
|
||||||
|
],
|
||||||
|
check=True,
|
||||||
|
text=True,
|
||||||
|
stdout=PIPE,
|
||||||
|
),
|
||||||
|
call(
|
||||||
|
[
|
||||||
|
"nix-env",
|
||||||
|
"-p",
|
||||||
|
Path("/nix/var/nix/profiles/system"),
|
||||||
|
"--set",
|
||||||
|
config_path,
|
||||||
|
],
|
||||||
|
check=True,
|
||||||
|
),
|
||||||
|
call(
|
||||||
|
[config_path / "bin/switch-to-configuration", "switch"],
|
||||||
|
env={"NIXOS_INSTALL_BOOTLOADER": "1", "LOCALE_ARCHIVE": "/locale"},
|
||||||
|
check=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(get_qualified_name(nr.nix.run, nr.nix), autospec=True)
|
||||||
|
def test_execute_switch_rollback(mock_run: Any) -> None:
|
||||||
|
nr.execute(["nixos-rebuild", "switch", "--rollback", "--install-bootloader"])
|
||||||
|
|
||||||
|
assert nr.VERBOSE is False
|
||||||
|
assert mock_run.call_count == 2
|
||||||
|
mock_run.assert_has_calls(
|
||||||
|
[
|
||||||
|
call(
|
||||||
|
[
|
||||||
|
"nix-env",
|
||||||
|
"--rollback",
|
||||||
|
"-p",
|
||||||
|
Path("/nix/var/nix/profiles/system"),
|
||||||
|
],
|
||||||
|
check=True,
|
||||||
|
),
|
||||||
|
call(
|
||||||
|
[
|
||||||
|
Path("/nix/var/nix/profiles/system/bin/switch-to-configuration"),
|
||||||
|
"switch",
|
||||||
|
],
|
||||||
|
env={"NIXOS_INSTALL_BOOTLOADER": "1", "LOCALE_ARCHIVE": "/locale"},
|
||||||
|
check=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(get_qualified_name(nr.nix.run, nr.nix), 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,
|
||||||
|
) -> None:
|
||||||
|
mock_run.side_effect = [
|
||||||
|
# rollback_temporary_profile
|
||||||
|
CompletedProcess(
|
||||||
|
[],
|
||||||
|
0,
|
||||||
|
stdout=textwrap.dedent("""\
|
||||||
|
2082 2024-11-07 22:58:56
|
||||||
|
2083 2024-11-07 22:59:41
|
||||||
|
2084 2024-11-07 23:54:17 (current)
|
||||||
|
"""),
|
||||||
|
),
|
||||||
|
# switch_to_configuration
|
||||||
|
CompletedProcess([], 0),
|
||||||
|
]
|
||||||
|
|
||||||
|
nr.execute(
|
||||||
|
[
|
||||||
|
"nixos-rebuild",
|
||||||
|
"test",
|
||||||
|
"--rollback",
|
||||||
|
"--profile-name",
|
||||||
|
"foo",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert nr.VERBOSE is False
|
||||||
|
assert mock_run.call_count == 2
|
||||||
|
mock_run.assert_has_calls(
|
||||||
|
[
|
||||||
|
call(
|
||||||
|
[
|
||||||
|
"nix-env",
|
||||||
|
"-p",
|
||||||
|
Path("/nix/var/nix/profiles/system-profiles/foo"),
|
||||||
|
"--list-generations",
|
||||||
|
],
|
||||||
|
text=True,
|
||||||
|
stdout=True,
|
||||||
|
check=True,
|
||||||
|
),
|
||||||
|
call(
|
||||||
|
[
|
||||||
|
Path(
|
||||||
|
"/nix/var/nix/profiles/system-profiles/foo-2083-link/bin/switch-to-configuration"
|
||||||
|
),
|
||||||
|
"test",
|
||||||
|
],
|
||||||
|
env={"NIXOS_INSTALL_BOOTLOADER": "0", "LOCALE_ARCHIVE": "/locale"},
|
||||||
|
check=True,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
)
|
100
pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_models.py
Normal file
100
pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_models.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import platform
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from nixos_rebuild import models as m
|
||||||
|
|
||||||
|
from .helpers import get_qualified_name
|
||||||
|
|
||||||
|
|
||||||
|
def test_flake_parse() -> None:
|
||||||
|
assert m.Flake.parse("/path/to/flake#attr") == m.Flake(
|
||||||
|
Path("/path/to/flake"), "nixosConfigurations.attr"
|
||||||
|
)
|
||||||
|
assert m.Flake.parse("/path/ to /flake", "hostname") == m.Flake(
|
||||||
|
Path("/path/ to /flake"), "nixosConfigurations.hostname"
|
||||||
|
)
|
||||||
|
assert m.Flake.parse("/path/to/flake", "hostname") == m.Flake(
|
||||||
|
Path("/path/to/flake"), "nixosConfigurations.hostname"
|
||||||
|
)
|
||||||
|
assert m.Flake.parse(".#attr") == m.Flake(Path("."), "nixosConfigurations.attr")
|
||||||
|
assert m.Flake.parse("#attr") == m.Flake(Path("."), "nixosConfigurations.attr")
|
||||||
|
assert m.Flake.parse(".", None) == m.Flake(Path("."), "nixosConfigurations.default")
|
||||||
|
assert m.Flake.parse("", "") == m.Flake(Path("."), "nixosConfigurations.default")
|
||||||
|
|
||||||
|
|
||||||
|
@patch(get_qualified_name(platform.node), autospec=True)
|
||||||
|
def test_flake_from_arg(mock_node: Any) -> None:
|
||||||
|
mock_node.return_value = "hostname"
|
||||||
|
|
||||||
|
# Flake string
|
||||||
|
assert m.Flake.from_arg("/path/to/flake#attr") == m.Flake(
|
||||||
|
Path("/path/to/flake"), "nixosConfigurations.attr"
|
||||||
|
)
|
||||||
|
|
||||||
|
# False
|
||||||
|
assert m.Flake.from_arg(False) is None
|
||||||
|
|
||||||
|
# True
|
||||||
|
assert m.Flake.from_arg(True) == m.Flake(Path("."), "nixosConfigurations.hostname")
|
||||||
|
|
||||||
|
# None when we do not have /etc/nixos/flake.nix
|
||||||
|
with patch(
|
||||||
|
get_qualified_name(m.Path.exists, m),
|
||||||
|
autospec=True,
|
||||||
|
return_value=False,
|
||||||
|
):
|
||||||
|
assert m.Flake.from_arg(None) is None
|
||||||
|
|
||||||
|
# None when we have a file in /etc/nixos/flake.nix
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
get_qualified_name(m.Path.exists, m),
|
||||||
|
autospec=True,
|
||||||
|
return_value=True,
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
get_qualified_name(m.Path.is_symlink, m),
|
||||||
|
autospec=True,
|
||||||
|
return_value=False,
|
||||||
|
),
|
||||||
|
):
|
||||||
|
assert m.Flake.from_arg(None) == m.Flake(
|
||||||
|
Path("/etc/nixos"), "nixosConfigurations.hostname"
|
||||||
|
)
|
||||||
|
|
||||||
|
with (
|
||||||
|
patch(
|
||||||
|
get_qualified_name(m.Path.exists, m),
|
||||||
|
autospec=True,
|
||||||
|
return_value=True,
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
get_qualified_name(m.Path.is_symlink, m),
|
||||||
|
autospec=True,
|
||||||
|
return_value=True,
|
||||||
|
),
|
||||||
|
patch(
|
||||||
|
get_qualified_name(m.Path.readlink, m),
|
||||||
|
autospec=True,
|
||||||
|
return_value=Path("/path/to/flake.nix"),
|
||||||
|
),
|
||||||
|
):
|
||||||
|
assert m.Flake.from_arg(None) == m.Flake(
|
||||||
|
Path("/path/to"), "nixosConfigurations.hostname"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(get_qualified_name(m.Path.mkdir, m), autospec=True)
|
||||||
|
def test_profile_from_name(mock_mkdir: Any) -> None:
|
||||||
|
assert m.Profile.from_name("system") == m.Profile(
|
||||||
|
"system",
|
||||||
|
Path("/nix/var/nix/profiles/system"),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert m.Profile.from_name("something") == m.Profile(
|
||||||
|
"something",
|
||||||
|
Path("/nix/var/nix/profiles/system-profiles/something"),
|
||||||
|
)
|
||||||
|
mock_mkdir.assert_called_once()
|
327
pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_nix.py
Normal file
327
pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_nix.py
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
import textwrap
|
||||||
|
from pathlib import Path
|
||||||
|
from subprocess import PIPE, CompletedProcess
|
||||||
|
from typing import Any
|
||||||
|
from unittest.mock import ANY, call, patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
import nixos_rebuild.nix as n
|
||||||
|
from nixos_rebuild import models as m
|
||||||
|
|
||||||
|
from .helpers import get_qualified_name
|
||||||
|
|
||||||
|
|
||||||
|
@patch(get_qualified_name(n.run, n), autospec=True)
|
||||||
|
def test_edit(mock_run: Any, monkeypatch: Any, tmpdir: Any) -> None:
|
||||||
|
# Flake
|
||||||
|
flake = m.Flake.parse(".#attr")
|
||||||
|
n.edit(flake, ["--commit-lock-file"])
|
||||||
|
mock_run.assert_called_with(
|
||||||
|
[
|
||||||
|
"nix",
|
||||||
|
"--extra-experimental-features",
|
||||||
|
"nix-command flakes",
|
||||||
|
"edit",
|
||||||
|
"--commit-lock-file",
|
||||||
|
"--",
|
||||||
|
".#nixosConfigurations.attr",
|
||||||
|
],
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Classic
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
default_nix = tmpdir.join("default.nix")
|
||||||
|
default_nix.write("{}")
|
||||||
|
|
||||||
|
mp.setenv("NIXOS_CONFIG", str(tmpdir))
|
||||||
|
mp.setenv("EDITOR", "editor")
|
||||||
|
|
||||||
|
n.edit(None)
|
||||||
|
mock_run.assert_called_with(["editor", default_nix], check=False)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_generations_from_nix_store(tmp_path: Path) -> None:
|
||||||
|
nixos_path = tmp_path / "nixos-system"
|
||||||
|
nixos_path.mkdir()
|
||||||
|
|
||||||
|
(tmp_path / "system").symlink_to(tmp_path / "system-2-link")
|
||||||
|
# In the "wrong" order on purpose to make sure we are sorting the results
|
||||||
|
(tmp_path / "system-1-link").symlink_to(nixos_path)
|
||||||
|
(tmp_path / "system-3-link").symlink_to(nixos_path)
|
||||||
|
(tmp_path / "system-2-link").symlink_to(nixos_path)
|
||||||
|
|
||||||
|
assert n.get_generations(
|
||||||
|
m.Profile("system", tmp_path / "system"),
|
||||||
|
lock_profile=False,
|
||||||
|
) == [
|
||||||
|
m.Generation(id=1, current=False, timestamp=ANY),
|
||||||
|
m.Generation(id=2, current=True, timestamp=ANY),
|
||||||
|
m.Generation(id=3, current=False, timestamp=ANY),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
get_qualified_name(n.run, n),
|
||||||
|
autospec=True,
|
||||||
|
return_value=CompletedProcess(
|
||||||
|
[],
|
||||||
|
0,
|
||||||
|
stdout=textwrap.dedent("""\
|
||||||
|
2082 2024-11-07 22:58:56
|
||||||
|
2083 2024-11-07 22:59:41
|
||||||
|
2084 2024-11-07 23:54:17 (current)
|
||||||
|
"""),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_get_generations_from_nix_env(mock_run: Any, tmp_path: Path) -> None:
|
||||||
|
path = tmp_path / "test"
|
||||||
|
path.touch()
|
||||||
|
|
||||||
|
assert n.get_generations(m.Profile("system", path), lock_profile=True) == [
|
||||||
|
m.Generation(id=2082, current=False, timestamp="2024-11-07 22:58:56"),
|
||||||
|
m.Generation(id=2083, current=False, timestamp="2024-11-07 22:59:41"),
|
||||||
|
m.Generation(id=2084, current=True, timestamp="2024-11-07 23:54:17"),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
get_qualified_name(n.get_generations),
|
||||||
|
autospec=True,
|
||||||
|
return_value=[
|
||||||
|
m.Generation(
|
||||||
|
id=1,
|
||||||
|
timestamp="2024-11-07 23:54:17",
|
||||||
|
current=False,
|
||||||
|
),
|
||||||
|
m.Generation(
|
||||||
|
id=2,
|
||||||
|
timestamp="2024-11-07 23:54:17",
|
||||||
|
current=True,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_list_generations(mock_get_generations: Any, tmp_path: Path) -> None:
|
||||||
|
# Probably better to test this function in a real system, this test is
|
||||||
|
# mostly to make sure it doesn't break horribly
|
||||||
|
assert n.list_generations(m.Profile("system", tmp_path)) == [
|
||||||
|
{
|
||||||
|
"configurationRevision": "Unknown",
|
||||||
|
"current": True,
|
||||||
|
"date": "2024-11-07 23:54:17",
|
||||||
|
"generation": 2,
|
||||||
|
"kernelVersion": "Unknown",
|
||||||
|
"nixosVersion": "Unknown",
|
||||||
|
"specialisations": [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"configurationRevision": "Unknown",
|
||||||
|
"current": False,
|
||||||
|
"date": "2024-11-07 23:54:17",
|
||||||
|
"generation": 1,
|
||||||
|
"kernelVersion": "Unknown",
|
||||||
|
"nixosVersion": "Unknown",
|
||||||
|
"specialisations": [],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
get_qualified_name(n.run, n),
|
||||||
|
autospec=True,
|
||||||
|
return_value=CompletedProcess([], 0, stdout=" \n/path/to/file\n "),
|
||||||
|
)
|
||||||
|
def test_nixos_build_flake(mock_run: Any) -> None:
|
||||||
|
flake = m.Flake.parse(".#hostname")
|
||||||
|
|
||||||
|
assert n.nixos_build_flake(
|
||||||
|
"toplevel",
|
||||||
|
flake,
|
||||||
|
["--nix-flag", "foo"],
|
||||||
|
no_link=True,
|
||||||
|
) == Path("/path/to/file")
|
||||||
|
mock_run.assert_called_with(
|
||||||
|
[
|
||||||
|
"nix",
|
||||||
|
"--extra-experimental-features",
|
||||||
|
"nix-command flakes",
|
||||||
|
"build",
|
||||||
|
"--print-out-paths",
|
||||||
|
".#nixosConfigurations.hostname.config.system.build.toplevel",
|
||||||
|
"--no-link",
|
||||||
|
"--nix-flag",
|
||||||
|
"foo",
|
||||||
|
],
|
||||||
|
check=True,
|
||||||
|
text=True,
|
||||||
|
stdout=PIPE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
get_qualified_name(n.run, n),
|
||||||
|
autospec=True,
|
||||||
|
return_value=CompletedProcess([], 0, stdout=" \n/path/to/file\n "),
|
||||||
|
)
|
||||||
|
def test_nixos_build(mock_run: Any, monkeypatch: Any) -> None:
|
||||||
|
assert n.nixos_build("attr", None, None, ["--nix-flag", "foo"]) == Path(
|
||||||
|
"/path/to/file"
|
||||||
|
)
|
||||||
|
mock_run.assert_called_with(
|
||||||
|
["nix-build", "<nixpkgs/nixos>", "--attr", "attr", "--nix-flag", "foo"],
|
||||||
|
check=True,
|
||||||
|
text=True,
|
||||||
|
stdout=PIPE,
|
||||||
|
)
|
||||||
|
|
||||||
|
n.nixos_build("attr", "preAttr", "file")
|
||||||
|
mock_run.assert_called_with(
|
||||||
|
["nix-build", "file", "--attr", "preAttr.attr"],
|
||||||
|
check=True,
|
||||||
|
text=True,
|
||||||
|
stdout=PIPE,
|
||||||
|
)
|
||||||
|
|
||||||
|
n.nixos_build("attr", None, "file", no_out_link=True)
|
||||||
|
mock_run.assert_called_with(
|
||||||
|
["nix-build", "file", "--attr", "attr", "--no-out-link"],
|
||||||
|
check=True,
|
||||||
|
text=True,
|
||||||
|
stdout=PIPE,
|
||||||
|
)
|
||||||
|
|
||||||
|
n.nixos_build("attr", "preAttr", None, no_out_link=False, keep_going=True)
|
||||||
|
mock_run.assert_called_with(
|
||||||
|
["nix-build", "default.nix", "--attr", "preAttr.attr", "--keep-going"],
|
||||||
|
check=True,
|
||||||
|
text=True,
|
||||||
|
stdout=PIPE,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(get_qualified_name(n.run, n), autospec=True)
|
||||||
|
def test_rollback(mock_run: Any, tmp_path: Path) -> None:
|
||||||
|
path = tmp_path / "test"
|
||||||
|
path.touch()
|
||||||
|
|
||||||
|
profile = m.Profile("system", path)
|
||||||
|
|
||||||
|
assert n.rollback(profile) == profile.path
|
||||||
|
mock_run.assert_called_with(["nix-env", "--rollback", "-p", path], check=True)
|
||||||
|
|
||||||
|
|
||||||
|
def test_rollback_temporary_profile(tmp_path: Path) -> None:
|
||||||
|
path = tmp_path / "test"
|
||||||
|
path.touch()
|
||||||
|
profile = m.Profile("system", path)
|
||||||
|
|
||||||
|
with patch(get_qualified_name(n.run, n), autospec=True) as mock_run:
|
||||||
|
mock_run.return_value = CompletedProcess(
|
||||||
|
[],
|
||||||
|
0,
|
||||||
|
stdout=textwrap.dedent("""\
|
||||||
|
2082 2024-11-07 22:58:56
|
||||||
|
2083 2024-11-07 22:59:41
|
||||||
|
2084 2024-11-07 23:54:17 (current)
|
||||||
|
"""),
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
n.rollback_temporary_profile(m.Profile("system", path))
|
||||||
|
== path.parent / "system-2083-link"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
n.rollback_temporary_profile(m.Profile("foo", path))
|
||||||
|
== path.parent / "foo-2083-link"
|
||||||
|
)
|
||||||
|
|
||||||
|
with patch(get_qualified_name(n.run, n), autospec=True) as mock_run:
|
||||||
|
mock_run.return_value = CompletedProcess([], 0, stdout="")
|
||||||
|
assert n.rollback_temporary_profile(profile) is None
|
||||||
|
|
||||||
|
|
||||||
|
@patch(get_qualified_name(n.run, n), autospec=True)
|
||||||
|
def test_set_profile(mock_run: Any) -> None:
|
||||||
|
profile_path = Path("/path/to/profile")
|
||||||
|
config_path = Path("/path/to/config")
|
||||||
|
n.set_profile(m.Profile("system", profile_path), config_path)
|
||||||
|
|
||||||
|
mock_run.assert_called_with(
|
||||||
|
["nix-env", "-p", profile_path, "--set", config_path], check=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(get_qualified_name(n.run, n), autospec=True)
|
||||||
|
def test_switch_to_configuration(mock_run: Any, monkeypatch: Any) -> None:
|
||||||
|
profile_path = Path("/path/to/profile")
|
||||||
|
config_path = Path("/path/to/config")
|
||||||
|
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setenv("LOCALE_ARCHIVE", "")
|
||||||
|
|
||||||
|
n.switch_to_configuration(
|
||||||
|
profile_path,
|
||||||
|
m.Action.SWITCH,
|
||||||
|
specialisation=None,
|
||||||
|
install_bootloader=False,
|
||||||
|
)
|
||||||
|
mock_run.assert_called_with(
|
||||||
|
[profile_path / "bin/switch-to-configuration", "switch"],
|
||||||
|
env={"NIXOS_INSTALL_BOOTLOADER": "0", "LOCALE_ARCHIVE": ""},
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(m.NRError) as e:
|
||||||
|
n.switch_to_configuration(
|
||||||
|
config_path,
|
||||||
|
m.Action.BOOT,
|
||||||
|
specialisation="special",
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
str(e.value)
|
||||||
|
== "error: '--specialisation' can only be used with 'switch' and 'test'"
|
||||||
|
)
|
||||||
|
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setenv("LOCALE_ARCHIVE", "/path/to/locale")
|
||||||
|
mp.setattr(Path, Path.exists.__name__, lambda self: True)
|
||||||
|
|
||||||
|
n.switch_to_configuration(
|
||||||
|
Path("/path/to/config"),
|
||||||
|
m.Action.TEST,
|
||||||
|
install_bootloader=True,
|
||||||
|
specialisation="special",
|
||||||
|
)
|
||||||
|
mock_run.assert_called_with(
|
||||||
|
[
|
||||||
|
config_path / "specialisation/special/bin/switch-to-configuration",
|
||||||
|
"test",
|
||||||
|
],
|
||||||
|
env={"NIXOS_INSTALL_BOOTLOADER": "1", "LOCALE_ARCHIVE": "/path/to/locale"},
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@patch(
|
||||||
|
get_qualified_name(n.Path.glob, n),
|
||||||
|
autospec=True,
|
||||||
|
return_value=[
|
||||||
|
Path("/nix/var/nix/profiles/per-user/root/channels/nixos"),
|
||||||
|
Path("/nix/var/nix/profiles/per-user/root/channels/nixos-hardware"),
|
||||||
|
Path("/nix/var/nix/profiles/per-user/root/channels/home-manager"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_upgrade_channels(mock_glob: Any) -> None:
|
||||||
|
with patch(get_qualified_name(n.run, n), autospec=True) as mock_run:
|
||||||
|
n.upgrade_channels(False)
|
||||||
|
mock_run.assert_called_with(["nix-channel", "--update", "nixos"], check=False)
|
||||||
|
|
||||||
|
with patch(get_qualified_name(n.run, n), autospec=True) as mock_run:
|
||||||
|
n.upgrade_channels(True)
|
||||||
|
mock_run.assert_has_calls(
|
||||||
|
[
|
||||||
|
call(["nix-channel", "--update", "nixos"], check=False),
|
||||||
|
call(["nix-channel", "--update", "nixos-hardware"], check=False),
|
||||||
|
call(["nix-channel", "--update", "home-manager"], check=False),
|
||||||
|
]
|
||||||
|
)
|
23
pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_utils.py
Normal file
23
pkgs/by-name/ni/nixos-rebuild-ng/src/tests/test_utils.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
from nixos_rebuild import utils as u
|
||||||
|
|
||||||
|
|
||||||
|
def test_dict_to_flags() -> None:
|
||||||
|
r = u.dict_to_flags(
|
||||||
|
{
|
||||||
|
"test_flag_1": True,
|
||||||
|
"test_flag_2": False,
|
||||||
|
"test_flag_3": "value",
|
||||||
|
"test_flag_4": ["v1", "v2"],
|
||||||
|
"test_flag_5": None,
|
||||||
|
"verbose": 5,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
assert r == [
|
||||||
|
"--test-flag-1",
|
||||||
|
"--test-flag-3",
|
||||||
|
"value",
|
||||||
|
"--test-flag-4",
|
||||||
|
"v1",
|
||||||
|
"v2",
|
||||||
|
"-vvvvv",
|
||||||
|
]
|
Loading…
Reference in New Issue
Block a user