Merge pull request 'expose nixos-facter in cli' (#2172) from nixos-facter into main

This commit is contained in:
clan-bot
2024-09-29 15:07:37 +00:00
4 changed files with 70 additions and 91 deletions

View File

@@ -50,6 +50,7 @@
services.openssh.enable = true; services.openssh.enable = true;
users.users.root.openssh.authorizedKeys.keyFiles = [ ../lib/ssh/pubkey ]; users.users.root.openssh.authorizedKeys.keyFiles = [ ../lib/ssh/pubkey ];
system.nixos.variant_id = "installer"; system.nixos.variant_id = "installer";
environment.systemPackages = [ pkgs.nixos-facter ];
virtualisation.emptyDiskImages = [ 4096 ]; virtualisation.emptyDiskImages = [ 4096 ];
nix.settings = { nix.settings = {
substituters = lib.mkForce [ ]; substituters = lib.mkForce [ ];
@@ -101,6 +102,8 @@
client.fail("test -f test-flake/machines/test-install-machine/hardware-configuration.nix") client.fail("test -f test-flake/machines/test-install-machine/hardware-configuration.nix")
client.succeed("clan machines update-hardware-config --flake test-flake test-install-machine root@target>&2") client.succeed("clan machines update-hardware-config --flake test-flake test-install-machine root@target>&2")
client.succeed("test -f test-flake/machines/test-install-machine/hardware-configuration.nix") client.succeed("test -f test-flake/machines/test-install-machine/hardware-configuration.nix")
client.succeed("clan machines update-hardware-config --backend nixos-facter --flake test-flake test-install-machine root@target>&2")
client.succeed("test -f test-flake/machines/test-install-machine/facter.json")
client.succeed("clan machines install --debug --flake ${../..} --yes test-install-machine root@target >&2") client.succeed("clan machines install --debug --flake ${../..} --yes test-install-machine root@target >&2")
try: try:
target.shutdown() target.shutdown()

View File

@@ -414,13 +414,19 @@ def main() -> None:
args.func(args) args.func(args)
except ClanError as e: except ClanError as e:
if isinstance(e, ClanCmdError): if isinstance(e, ClanCmdError):
msg = ""
if e.cmd.msg: if e.cmd.msg:
log.fatal(e.cmd.msg) msg += f"{e.cmd.msg}: "
msg += f"command exited with code {e.cmd.returncode}: {e.cmd.command}"
log.error(msg) # noqa: TRY400
sys.exit(1) sys.exit(1)
log.fatal(e.msg) if not e.msg: # should not be empty, print stack trace
raise
msg = e.msg
if e.description: if e.description:
print(f"========> {e.description}", file=sys.stderr) msg += f": {e.description}"
log.error(msg) # noqa: TRY400
sys.exit(1) sys.exit(1)
except KeyboardInterrupt: except KeyboardInterrupt:
log.warning("Interrupted by user") log.warning("Interrupted by user")

View File

@@ -21,7 +21,7 @@ log = logging.getLogger(__name__)
@dataclass @dataclass
class HardwareReport: class HardwareReport:
file: Literal["nixos-generate-config", "nixos-facter"] backend: Literal["nixos-generate-config", "nixos-facter"]
hw_nix_file = "hardware-configuration.nix" hw_nix_file = "hardware-configuration.nix"
@@ -30,13 +30,13 @@ facter_file = "facter.json"
@API.register @API.register
def show_machine_hardware_info( def show_machine_hardware_info(
clan_dir: str | Path, machine_name: str clan_dir: Path, machine_name: str
) -> HardwareReport | None: ) -> HardwareReport | None:
""" """
Show hardware information for a machine returns None if none exist. Show hardware information for a machine returns None if none exist.
""" """
hw_file = Path(f"{clan_dir}/machines/{machine_name}/{hw_nix_file}") hw_file = Path(clan_dir) / "machines" / machine_name / hw_nix_file
is_template = hw_file.exists() and "throw" in hw_file.read_text() is_template = hw_file.exists() and "throw" in hw_file.read_text()
if hw_file.exists() and not is_template: if hw_file.exists() and not is_template:
@@ -49,9 +49,7 @@ def show_machine_hardware_info(
@API.register @API.register
def show_machine_deployment_target( def show_machine_deployment_target(clan_dir: Path, machine_name: str) -> str | None:
clan_dir: str | Path, machine_name: str
) -> str | None:
""" """
Show deployment target for a machine returns None if none exist. Show deployment target for a machine returns None if none exist.
""" """
@@ -73,9 +71,7 @@ def show_machine_deployment_target(
@API.register @API.register
def show_machine_hardware_platform( def show_machine_hardware_platform(clan_dir: Path, machine_name: str) -> str | None:
clan_dir: str | Path, machine_name: str
) -> str | None:
""" """
Show hardware information for a machine returns None if none exist. Show hardware information for a machine returns None if none exist.
""" """
@@ -85,7 +81,7 @@ def show_machine_hardware_platform(
[ [
f"{clan_dir}#clanInternals.machines.{system}.{machine_name}", f"{clan_dir}#clanInternals.machines.{system}.{machine_name}",
"--apply", "--apply",
"machine: { inherit (machine.config.nixpkgs.hostPlatform) system; }", "machine: { inherit (machine.pkgs) system; }",
"--json", "--json",
] ]
) )
@@ -96,37 +92,44 @@ def show_machine_hardware_platform(
return host_platform.get("system", None) return host_platform.get("system", None)
@dataclass
class HardwareGenerateOptions:
flake: FlakeId
machine: str
backend: Literal["nixos-generate-config", "nixos-facter"]
target_host: str | None = None
keyfile: str | None = None
password: str | None = None
@API.register @API.register
def generate_machine_hardware_info( def generate_machine_hardware_info(opts: HardwareGenerateOptions) -> HardwareReport:
clan_dir: FlakeId,
machine_name: str,
hostname: str | None = None,
password: str | None = None,
keyfile: str | None = None,
force: bool | None = False,
report_type: Literal[
"nixos-generate-config", "nixos-facter"
] = "nixos-generate-config",
) -> HardwareReport:
""" """
Generate hardware information for a machine Generate hardware information for a machine
and place the resulting *.nix file in the machine's directory. and place the resulting *.nix file in the machine's directory.
""" """
machine = Machine(machine_name, flake=clan_dir) machine = Machine(opts.machine, flake=opts.flake)
if hostname is not None: if opts.target_host is not None:
machine.target_host_address = hostname machine.target_host_address = opts.target_host
config_command = ( hw_file = opts.flake.path / "machines" / opts.machine
["nixos-facter"] if opts.backend == "nixos-generate-config":
if report_type == "nixos-facter" hw_file /= hw_nix_file
else [ else:
hw_file /= facter_file
hw_file.parent.mkdir(parents=True, exist_ok=True)
if opts.backend == "nixos-facter":
config_command = ["nixos-facter"]
else:
config_command = [
"nixos-generate-config", "nixos-generate-config",
# Filesystems are managed by disko # Filesystems are managed by disko
"--no-filesystems", "--no-filesystems",
"--show-hardware-config", "--show-hardware-config",
] ]
)
host = machine.target_host host = machine.target_host
target_host = f"{host.user or 'root'}@{host.host}" target_host = f"{host.user or 'root'}@{host.host}"
@@ -136,9 +139,9 @@ def generate_machine_hardware_info(
"nixpkgs#sshpass", "nixpkgs#sshpass",
], ],
[ [
*(["sshpass", "-p", f"{password}"] if password else []), *(["sshpass", "-p", opts.password] if opts.password else []),
"ssh", "ssh",
*(["-i", f"{keyfile}"] if keyfile else []), *(["-i", f"{opts.keyfile}"] if opts.keyfile else []),
# Disable known hosts file # Disable known hosts file
"-o", "-o",
"UserKnownHostsFile=/dev/null", "UserKnownHostsFile=/dev/null",
@@ -156,36 +159,15 @@ def generate_machine_hardware_info(
) )
out = run(cmd) out = run(cmd)
if out.returncode != 0: if out.returncode != 0:
log.error(f"Failed to inspect {machine_name}. Address: {hostname}")
log.error(out) log.error(out)
msg = f"Failed to inspect {machine_name}. Address: {hostname}" msg = f"Failed to inspect {opts.machine}. Address: {opts.target_host}"
raise ClanError(msg) raise ClanError(msg)
hw_file = Path(
f"{clan_dir}/machines/{machine_name}/{hw_nix_file if report_type == 'nixos-generate-config' else facter_file}"
)
hw_file.parent.mkdir(parents=True, exist_ok=True)
# Check if the hardware-configuration.nix file is a template
is_template = hw_file.exists() and "throw" in hw_file.read_text()
if hw_file.exists() and not force and not is_template:
msg = "File exists."
raise ClanError(
msg,
description="Hardware file already exists. To force overwrite the existing configuration use '--force'.",
location=f"{__name__} {hw_file}",
)
backup_file = None backup_file = None
if hw_file.exists() and force: if hw_file.exists():
# Backup the existing file
backup_file = hw_file.with_suffix(".bak") backup_file = hw_file.with_suffix(".bak")
hw_file.replace(backup_file) hw_file.replace(backup_file)
print(f"Backed up existing {hw_file} to {backup_file}") hw_file.write_text(out.stdout)
with hw_file.open("w") as f:
f.write(out.stdout)
print(f"Successfully generated: {hw_file}") print(f"Successfully generated: {hw_file}")
# try to evaluate the machine # try to evaluate the machine
@@ -193,11 +175,11 @@ def generate_machine_hardware_info(
commit_file( commit_file(
hw_file, hw_file,
clan_dir.path, opts.flake.path,
f"HW/report: Hardware configuration for {machine_name}", f"machines/{opts.machine}/{hw_file.name}: update hardware configuration",
) )
try: try:
show_machine_hardware_platform(clan_dir.path, machine_name) show_machine_hardware_platform(opts.flake.path, opts.machine)
except ClanCmdError as e: except ClanCmdError as e:
log.exception("Failed to evaluate hardware-configuration.nix") log.exception("Failed to evaluate hardware-configuration.nix")
# Restore the backup file # Restore the backup file
@@ -209,20 +191,10 @@ def generate_machine_hardware_info(
msg = "Invalid hardware-configuration.nix file" msg = "Invalid hardware-configuration.nix file"
raise ClanError( raise ClanError(
msg, msg,
description="The hardware-configuration.nix file is invalid. Please check the file and try again.", description=f"Configuration at '{hw_file}' is invalid. Please check the file and try again.",
location=f"{__name__} {hw_file}",
) from e ) from e
return HardwareReport(report_type) return HardwareReport(opts.backend)
@dataclass
class HardwareGenerateOptions:
flake: FlakeId
machine: str
target_host: str | None
password: str | None
force: bool | None
def update_hardware_config_command(args: argparse.Namespace) -> None: def update_hardware_config_command(args: argparse.Namespace) -> None:
@@ -231,14 +203,9 @@ def update_hardware_config_command(args: argparse.Namespace) -> None:
machine=args.machine, machine=args.machine,
target_host=args.target_host, target_host=args.target_host,
password=args.password, password=args.password,
force=args.force, backend=args.backend,
) )
hw_info = generate_machine_hardware_info( generate_machine_hardware_info(opts)
opts.flake, opts.machine, opts.target_host, opts.password
)
print("Successfully generated hardware information.")
print(f"Target: {opts.machine} ({opts.target_host})")
print(f"Type: {hw_info.file}")
def register_update_hardware_config(parser: argparse.ArgumentParser) -> None: def register_update_hardware_config(parser: argparse.ArgumentParser) -> None:
@@ -261,8 +228,9 @@ def register_update_hardware_config(parser: argparse.ArgumentParser) -> None:
required=False, required=False,
) )
machine_parser = parser.add_argument( machine_parser = parser.add_argument(
"--force", "--backend",
help="Will overwrite the hardware-configuration.nix file.", help="The type of hardware report to generate.",
action="store_true", choices=["nixos-generate-config", "nixos-facter"],
default="nixos-generate-config",
) )
add_dynamic_completer(machine_parser, complete_machines) add_dynamic_completer(machine_parser, complete_machines)

View File

@@ -81,7 +81,7 @@ const InstallMachine = (props: InstallMachineProps) => {
machine_name: props.name, machine_name: props.name,
}); });
if (result.status === "error") throw new Error("Failed to fetch data"); if (result.status === "error") throw new Error("Failed to fetch data");
return result.data?.file === "nixos-facter" || null; return result.data?.backend === "nixos-facter" || null;
} }
return null; return null;
}, },
@@ -153,11 +153,13 @@ const InstallMachine = (props: InstallMachineProps) => {
const loading_toast = toast.loading("Generating hardware report..."); const loading_toast = toast.loading("Generating hardware report...");
const r = await callApi("generate_machine_hardware_info", { const r = await callApi("generate_machine_hardware_info", {
clan_dir: { loc: curr_uri }, opts: {
machine_name: props.name, flake: { loc: curr_uri },
machine: props.name,
keyfile: props.sshKey?.name, keyfile: props.sshKey?.name,
hostname: props.targetHost, target_host: props.targetHost,
report_type: "nixos-facter", backend: "nixos-facter",
},
}); });
toast.dismiss(loading_toast); toast.dismiss(loading_toast);
hwInfoQuery.refetch(); hwInfoQuery.refetch();