clan-cli: Fix nixos-anywhere for systems that can not be build locally

This commit is contained in:
Qubasa
2024-11-08 14:13:37 +07:00
committed by Luis Hebendanz
parent 382e27a20b
commit 9fc4e4c7d4
3 changed files with 84 additions and 18 deletions

View File

@@ -0,0 +1,47 @@
from dataclasses import dataclass, fields
from typing import Any
@dataclass
class HostPlatform:
config: str
darwinArch: str
darwinMinVersion: str
darwinSdkVersion: str
is32bit: bool
is64bit: bool
isx86_64: bool
isAarch: bool
isDarwin: bool
isFreeBSD: bool
isLinux: bool
isMacOS: bool
isWindows: bool
isAndroid: bool
linuxArch: str
qemuArch: str
system: str
ubootArch: str
# ruff: noqa: N815
@staticmethod
def from_dict(data: dict[str, Any]) -> "HostPlatform":
"""
Factory method that creates an instance of HostPlatform from a dictionary.
Extra fields in the dictionary are ignored.
Args:
data (dict): A dictionary containing values for initializing HostPlatform.
Returns:
HostPlatform: An instance of the HostPlatform class.
"""
# Dynamically extract field names from the dataclass
valid_keys = {field.name for field in fields(HostPlatform)}
# Filter the dictionary to only include items with keys that are valid
filtered_data = {k: v for k, v in data.items() if k in valid_keys}
# Pass the filtered data to the HostPlatform constructor
return HostPlatform(**filtered_data)

View File

@@ -15,6 +15,7 @@ from clan_cli.completions import (
complete_machines, complete_machines,
complete_target_host, complete_target_host,
) )
from clan_cli.errors import ClanError
from clan_cli.facts.generate import generate_facts from clan_cli.facts.generate import generate_facts
from clan_cli.machines.hardware import HardwareConfig from clan_cli.machines.hardware import HardwareConfig
from clan_cli.machines.machines import Machine from clan_cli.machines.machines import Machine
@@ -25,10 +26,6 @@ from clan_cli.vars.generate import generate_vars
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class ClanError(Exception):
pass
@dataclass @dataclass
class InstallOptions: class InstallOptions:
# flake to install # flake to install
@@ -85,9 +82,6 @@ def install_machine(opts: InstallOptions) -> None:
if opts.no_reboot: if opts.no_reboot:
cmd.append("--no-reboot") cmd.append("--no-reboot")
if opts.build_on_remote:
cmd.append("--build-on-remote")
if opts.update_hardware_config is not HardwareConfig.NONE: if opts.update_hardware_config is not HardwareConfig.NONE:
cmd.extend( cmd.extend(
[ [
@@ -108,6 +102,10 @@ def install_machine(opts: InstallOptions) -> None:
"IdentitiesOnly=yes", "IdentitiesOnly=yes",
] ]
if not machine.can_build_locally or opts.build_on_remote:
log.info("Architecture mismatch. Building on remote machine")
cmd.append("--build-on-remote")
if machine.target_host.port: if machine.target_host.port:
cmd += ["--ssh-port", str(machine.target_host.port)] cmd += ["--ssh-port", str(machine.target_host.port)]
if opts.kexec: if opts.kexec:

View File

@@ -12,6 +12,7 @@ from clan_cli.cmd import run_no_stdout
from clan_cli.errors import ClanError from clan_cli.errors import ClanError
from clan_cli.facts import public_modules as facts_public_modules from clan_cli.facts import public_modules as facts_public_modules
from clan_cli.facts import secret_modules as facts_secret_modules from clan_cli.facts import secret_modules as facts_secret_modules
from clan_cli.machines import host_platform
from clan_cli.nix import nix_build, nix_config, nix_eval, nix_metadata from clan_cli.nix import nix_build, nix_config, nix_eval, nix_metadata
from clan_cli.ssh import Host, HostKeyCheck, parse_deployment_address from clan_cli.ssh import Host, HostKeyCheck, parse_deployment_address
from clan_cli.vars.public_modules import FactStoreBase from clan_cli.vars.public_modules import FactStoreBase
@@ -46,6 +47,35 @@ class Machine:
def __repr__(self) -> str: def __repr__(self) -> str:
return str(self) return str(self)
@property
def host_platform(self) -> host_platform.HostPlatform:
# We filter out function attributes because they are not serializable.
attr = f"""
(let
machine = ((builtins.getFlake "{self.flake}").nixosConfigurations.{self.name});
lib = machine.lib;
removeFunctionAttrs = attrset:
lib.filterAttrs (name: value: lib.isFunction value == false && name != "parsed") attrset;
in
{{ x = removeFunctionAttrs machine.pkgs.stdenv.hostPlatform; }}).x
"""
if attr in self._eval_cache:
output = self._eval_cache[attr]
else:
output = run_no_stdout(
nix_eval(["--impure", "--expr", attr])
).stdout.strip()
self._eval_cache[attr] = output
value = json.loads(output)
return host_platform.HostPlatform.from_dict(value)
@property
def can_build_locally(self) -> bool:
# TODO: We could also use the function pkgs.stdenv.hostPlatform.canExecute
# but this is good enough for now.
output = nix_config()
return self.host_platform.system == output["system"]
@property @property
def deployment(self) -> dict: def deployment(self) -> dict:
if self.cached_deployment is not None: if self.cached_deployment is not None:
@@ -173,7 +203,6 @@ class Machine:
method: Literal["eval", "build"], method: Literal["eval", "build"],
attr: str, attr: str,
extra_config: None | dict = None, extra_config: None | dict = None,
impure: bool = False,
nix_options: list[str] | None = None, nix_options: list[str] | None = None,
) -> str | Path: ) -> str | Path:
""" """
@@ -215,12 +244,6 @@ class Machine:
"dirtyRevision" in metadata "dirtyRevision" in metadata
or "dirtyRev" in metadata["locks"]["nodes"]["clan-core"]["locked"] or "dirtyRev" in metadata["locks"]["nodes"]["clan-core"]["locked"]
): ):
# if not impure:
# raise ClanError(
# "The machine has a dirty revision, and impure mode is not allowed"
# )
# else:
# args += ["--impure"]
args += ["--impure"] args += ["--impure"]
args += [ args += [
@@ -254,7 +277,6 @@ class Machine:
attr: str, attr: str,
refresh: bool = False, refresh: bool = False,
extra_config: None | dict = None, extra_config: None | dict = None,
impure: bool = False,
nix_options: list[str] | None = None, nix_options: list[str] | None = None,
) -> str: ) -> str:
""" """
@@ -266,7 +288,7 @@ class Machine:
if attr in self._eval_cache and not refresh and extra_config is None: if attr in self._eval_cache and not refresh and extra_config is None:
return self._eval_cache[attr] return self._eval_cache[attr]
output = self.nix("eval", attr, extra_config, impure, nix_options) output = self.nix("eval", attr, extra_config, nix_options)
if isinstance(output, str): if isinstance(output, str):
self._eval_cache[attr] = output self._eval_cache[attr] = output
return output return output
@@ -278,7 +300,6 @@ class Machine:
attr: str, attr: str,
refresh: bool = False, refresh: bool = False,
extra_config: None | dict = None, extra_config: None | dict = None,
impure: bool = False,
nix_options: list[str] | None = None, nix_options: list[str] | None = None,
) -> Path: ) -> Path:
""" """
@@ -291,7 +312,7 @@ class Machine:
if attr in self._build_cache and not refresh and extra_config is None: if attr in self._build_cache and not refresh and extra_config is None:
return self._build_cache[attr] return self._build_cache[attr]
output = self.nix("build", attr, extra_config, impure, nix_options) output = self.nix("build", attr, extra_config, nix_options)
if isinstance(output, Path): if isinstance(output, Path):
self._build_cache[attr] = output self._build_cache[attr] = output
return output return output