clan-cli: Fix nixos-anywhere for systems that can not be build locally
This commit is contained in:
47
pkgs/clan-cli/clan_cli/machines/host_platform.py
Normal file
47
pkgs/clan-cli/clan_cli/machines/host_platform.py
Normal 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)
|
||||||
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user