forked from clan/clan-core
Merge pull request 'refactor-machine' (#1691) from refactor-machine into main
This commit is contained in:
commit
044cf3923e
@ -2,6 +2,7 @@ import argparse
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from ..clan_uri import FlakeId
|
||||||
from ..completions import (
|
from ..completions import (
|
||||||
add_dynamic_completer,
|
add_dynamic_completer,
|
||||||
complete_backup_providers_for_machine,
|
complete_backup_providers_for_machine,
|
||||||
@ -40,7 +41,7 @@ def create_backup(machine: Machine, provider: str | None = None) -> None:
|
|||||||
def create_command(args: argparse.Namespace) -> None:
|
def create_command(args: argparse.Namespace) -> None:
|
||||||
if args.flake is None:
|
if args.flake is None:
|
||||||
raise ClanError("Could not find clan flake toplevel directory")
|
raise ClanError("Could not find clan flake toplevel directory")
|
||||||
machine = Machine(name=args.machine, flake=args.flake)
|
machine = Machine(name=args.machine, flake=FlakeId(args.flake))
|
||||||
create_backup(machine=machine, provider=args.provider)
|
create_backup(machine=machine, provider=args.provider)
|
||||||
|
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import json
|
|||||||
import subprocess
|
import subprocess
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from ..clan_uri import FlakeId
|
||||||
from ..completions import (
|
from ..completions import (
|
||||||
add_dynamic_completer,
|
add_dynamic_completer,
|
||||||
complete_backup_providers_for_machine,
|
complete_backup_providers_for_machine,
|
||||||
@ -55,7 +56,7 @@ def list_backups(machine: Machine, provider: str | None = None) -> list[Backup]:
|
|||||||
def list_command(args: argparse.Namespace) -> None:
|
def list_command(args: argparse.Namespace) -> None:
|
||||||
if args.flake is None:
|
if args.flake is None:
|
||||||
raise ClanError("Could not find clan flake toplevel directory")
|
raise ClanError("Could not find clan flake toplevel directory")
|
||||||
machine = Machine(name=args.machine, flake=args.flake)
|
machine = Machine(name=args.machine, flake=FlakeId(args.flake))
|
||||||
backups = list_backups(machine=machine, provider=args.provider)
|
backups = list_backups(machine=machine, provider=args.provider)
|
||||||
for backup in backups:
|
for backup in backups:
|
||||||
print(backup.name)
|
print(backup.name)
|
||||||
|
@ -2,6 +2,7 @@ import argparse
|
|||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
|
|
||||||
|
from ..clan_uri import FlakeId
|
||||||
from ..completions import (
|
from ..completions import (
|
||||||
add_dynamic_completer,
|
add_dynamic_completer,
|
||||||
complete_backup_providers_for_machine,
|
complete_backup_providers_for_machine,
|
||||||
@ -86,7 +87,7 @@ def restore_backup(
|
|||||||
def restore_command(args: argparse.Namespace) -> None:
|
def restore_command(args: argparse.Namespace) -> None:
|
||||||
if args.flake is None:
|
if args.flake is None:
|
||||||
raise ClanError("Could not find clan flake toplevel directory")
|
raise ClanError("Could not find clan flake toplevel directory")
|
||||||
machine = Machine(name=args.machine, flake=args.flake)
|
machine = Machine(name=args.machine, flake=FlakeId(args.flake))
|
||||||
restore_backup(
|
restore_backup(
|
||||||
machine=machine,
|
machine=machine,
|
||||||
provider=args.provider,
|
provider=args.provider,
|
||||||
|
@ -2,6 +2,7 @@ import argparse
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ..clan_uri import FlakeId
|
||||||
from ..cmd import run
|
from ..cmd import run
|
||||||
from ..dirs import machine_gcroot
|
from ..dirs import machine_gcroot
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
@ -45,7 +46,7 @@ def inspect_flake(flake_url: str | Path, machine_name: str) -> FlakeConfig:
|
|||||||
f"Machine {machine_name} not found in {flake_url}. Available machines: {', '.join(machines)}"
|
f"Machine {machine_name} not found in {flake_url}. Available machines: {', '.join(machines)}"
|
||||||
)
|
)
|
||||||
|
|
||||||
machine = Machine(machine_name, flake_url)
|
machine = Machine(machine_name, FlakeId(flake_url))
|
||||||
vm = inspect_vm(machine)
|
vm = inspect_vm(machine)
|
||||||
|
|
||||||
# Make symlink to gcroots from vm.machine_icon
|
# Make symlink to gcroots from vm.machine_icon
|
||||||
@ -102,16 +103,16 @@ def inspect_flake(flake_url: str | Path, machine_name: str) -> FlakeConfig:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class InspectOptions:
|
class InspectOptions:
|
||||||
machine: str
|
machine: str
|
||||||
flake: Path
|
flake: FlakeId
|
||||||
|
|
||||||
|
|
||||||
def inspect_command(args: argparse.Namespace) -> None:
|
def inspect_command(args: argparse.Namespace) -> None:
|
||||||
inspect_options = InspectOptions(
|
inspect_options = InspectOptions(
|
||||||
machine=args.machine,
|
machine=args.machine,
|
||||||
flake=args.flake or Path.cwd(),
|
flake=FlakeId(args.flake or Path.cwd()),
|
||||||
)
|
)
|
||||||
res = inspect_flake(
|
res = inspect_flake(
|
||||||
flake_url=inspect_options.flake, machine_name=inspect_options.machine
|
flake_url=str(inspect_options.flake), machine_name=inspect_options.machine
|
||||||
)
|
)
|
||||||
print("Clan name:", res.clan_name)
|
print("Clan name:", res.clan_name)
|
||||||
print("Icon:", res.icon)
|
print("Icon:", res.icon)
|
||||||
|
@ -9,10 +9,13 @@ from .errors import ClanError
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FlakeId:
|
class FlakeId:
|
||||||
|
# FIXME: this is such a footgun if you accidnetally pass a string
|
||||||
_value: str | Path
|
_value: str | Path
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"{self._value}" # The __str__ method returns a custom string representation
|
return str(
|
||||||
|
self._value
|
||||||
|
) # The __str__ method returns a custom string representation
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def path(self) -> Path:
|
def path(self) -> Path:
|
||||||
@ -24,9 +27,6 @@ class FlakeId:
|
|||||||
assert isinstance(self._value, str)
|
assert isinstance(self._value, str)
|
||||||
return self._value
|
return self._value
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"ClanUrl({self._value})"
|
|
||||||
|
|
||||||
def is_local(self) -> bool:
|
def is_local(self) -> bool:
|
||||||
return isinstance(self._value, Path)
|
return isinstance(self._value, Path)
|
||||||
|
|
||||||
@ -34,26 +34,14 @@ class FlakeId:
|
|||||||
return isinstance(self._value, str)
|
return isinstance(self._value, str)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class MachineData:
|
|
||||||
flake_id: FlakeId
|
|
||||||
name: str = "defaultVM"
|
|
||||||
|
|
||||||
def get_id(self) -> str:
|
|
||||||
return f"{self.flake_id}#{self.name}"
|
|
||||||
|
|
||||||
|
|
||||||
# Define the ClanURI class
|
# Define the ClanURI class
|
||||||
|
@dataclass
|
||||||
class ClanURI:
|
class ClanURI:
|
||||||
_orig_uri: str
|
flake: FlakeId
|
||||||
_components: urllib.parse.ParseResult
|
machine_name: str
|
||||||
flake_id: FlakeId
|
|
||||||
_machines: list[MachineData]
|
|
||||||
|
|
||||||
# Initialize the class with a clan:// URI
|
# Initialize the class with a clan:// URI
|
||||||
def __init__(self, uri: str) -> None:
|
def __init__(self, uri: str) -> None:
|
||||||
self._machines = []
|
|
||||||
|
|
||||||
# users might copy whitespace along with the uri
|
# users might copy whitespace along with the uri
|
||||||
uri = uri.strip()
|
uri = uri.strip()
|
||||||
self._orig_uri = uri
|
self._orig_uri = uri
|
||||||
@ -67,21 +55,16 @@ class ClanURI:
|
|||||||
|
|
||||||
# Parse the URI into components
|
# Parse the URI into components
|
||||||
# url://netloc/path;parameters?query#fragment
|
# url://netloc/path;parameters?query#fragment
|
||||||
self._components = urllib.parse.urlparse(nested_uri)
|
components: urllib.parse.ParseResult = urllib.parse.urlparse(nested_uri)
|
||||||
|
|
||||||
# Replace the query string in the components with the new query string
|
# Replace the query string in the components with the new query string
|
||||||
clean_comps = self._components._replace(
|
clean_comps = components._replace(query=components.query, fragment="")
|
||||||
query=self._components.query, fragment=""
|
|
||||||
)
|
|
||||||
|
|
||||||
# Parse the URL into a ClanUrl object
|
# Parse the URL into a ClanUrl object
|
||||||
self.flake_id = self._parse_url(clean_comps)
|
self.flake = self._parse_url(clean_comps)
|
||||||
|
self.machine_name = "defaultVM"
|
||||||
if self._components.fragment == "":
|
if components.fragment:
|
||||||
self._machines.append(MachineData(flake_id=self.flake_id))
|
self.machine_name = components.fragment
|
||||||
return
|
|
||||||
|
|
||||||
self._machines.append(self._parse_machine_query(self._components.fragment))
|
|
||||||
|
|
||||||
def _parse_url(self, comps: urllib.parse.ParseResult) -> FlakeId:
|
def _parse_url(self, comps: urllib.parse.ParseResult) -> FlakeId:
|
||||||
comb = (
|
comb = (
|
||||||
@ -100,22 +83,8 @@ class ClanURI:
|
|||||||
|
|
||||||
return flake_id
|
return flake_id
|
||||||
|
|
||||||
def _parse_machine_query(self, machine_frag: str) -> MachineData:
|
|
||||||
comp = urllib.parse.urlparse(machine_frag)
|
|
||||||
machine_name = comp.path
|
|
||||||
|
|
||||||
machine = MachineData(flake_id=self.flake_id, name=machine_name)
|
|
||||||
return machine
|
|
||||||
|
|
||||||
@property
|
|
||||||
def machine(self) -> MachineData:
|
|
||||||
return self._machines[0]
|
|
||||||
|
|
||||||
def get_orig_uri(self) -> str:
|
|
||||||
return self._orig_uri
|
|
||||||
|
|
||||||
def get_url(self) -> str:
|
def get_url(self) -> str:
|
||||||
return str(self.flake_id)
|
return str(self.flake)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_str(
|
def from_str(
|
||||||
@ -133,9 +102,3 @@ class ClanURI:
|
|||||||
clan_uri += f"#{machine_name}"
|
clan_uri += f"#{machine_name}"
|
||||||
|
|
||||||
return cls(clan_uri)
|
return cls(clan_uri)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
|
||||||
return self.get_orig_uri()
|
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
|
||||||
return f"ClanURI({self})"
|
|
||||||
|
@ -208,8 +208,7 @@ def complete_secrets(
|
|||||||
"""
|
"""
|
||||||
Provides completion functionality for clan secrets
|
Provides completion functionality for clan secrets
|
||||||
"""
|
"""
|
||||||
from pathlib import Path
|
from .clan_uri import FlakeId
|
||||||
|
|
||||||
from .secrets.secrets import ListSecretsOptions, list_secrets
|
from .secrets.secrets import ListSecretsOptions, list_secrets
|
||||||
|
|
||||||
if (clan_dir_result := clan_dir(None)) is not None:
|
if (clan_dir_result := clan_dir(None)) is not None:
|
||||||
@ -218,11 +217,11 @@ def complete_secrets(
|
|||||||
flake = "."
|
flake = "."
|
||||||
|
|
||||||
options = ListSecretsOptions(
|
options = ListSecretsOptions(
|
||||||
flake=Path(flake),
|
flake=FlakeId(flake),
|
||||||
pattern=None,
|
pattern=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
secrets = list_secrets(options.flake, options.pattern)
|
secrets = list_secrets(options.flake.path, options.pattern)
|
||||||
|
|
||||||
secrets_dict = {name: "secret" for name in secrets}
|
secrets_dict = {name: "secret" for name in secrets}
|
||||||
return secrets_dict
|
return secrets_dict
|
||||||
|
@ -57,10 +57,13 @@ class ClanError(Exception):
|
|||||||
self.description = description
|
self.description = description
|
||||||
self.location = location or "Unknown location"
|
self.location = location or "Unknown location"
|
||||||
self.msg = msg or ""
|
self.msg = msg or ""
|
||||||
|
exception_msg = ""
|
||||||
|
if location:
|
||||||
|
exception_msg += f"{location}: \n"
|
||||||
|
exception_msg += self.msg
|
||||||
|
|
||||||
if self.description:
|
if self.description:
|
||||||
exception_msg = f"{self.location}: \n{self.msg} - {self.description}"
|
exception_msg = f" - {self.description}"
|
||||||
else:
|
|
||||||
exception_msg = f"{self.location}: \n{self.msg}"
|
|
||||||
super().__init__(exception_msg)
|
super().__init__(exception_msg)
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ import argparse
|
|||||||
import importlib
|
import importlib
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from ..clan_uri import FlakeId
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
from ..completions import add_dynamic_completer, complete_machines
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ def check_secrets(machine: Machine, service: None | str = None) -> bool:
|
|||||||
def check_command(args: argparse.Namespace) -> None:
|
def check_command(args: argparse.Namespace) -> None:
|
||||||
machine = Machine(
|
machine = Machine(
|
||||||
name=args.machine,
|
name=args.machine,
|
||||||
flake=args.flake,
|
flake=FlakeId(args.flake),
|
||||||
)
|
)
|
||||||
check_secrets(machine, service=args.service)
|
check_secrets(machine, service=args.service)
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import importlib
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
|
from ..clan_uri import FlakeId
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
from ..completions import add_dynamic_completer, complete_machines
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
|
|
||||||
@ -26,7 +27,7 @@ def get_all_facts(machine: Machine) -> dict:
|
|||||||
|
|
||||||
|
|
||||||
def get_command(args: argparse.Namespace) -> None:
|
def get_command(args: argparse.Namespace) -> None:
|
||||||
machine = Machine(name=args.machine, flake=args.flake)
|
machine = Machine(name=args.machine, flake=FlakeId(args.flake))
|
||||||
|
|
||||||
# the raw_facts are bytestrings making them not json serializable
|
# the raw_facts are bytestrings making them not json serializable
|
||||||
raw_facts = get_all_facts(machine)
|
raw_facts = get_all_facts(machine)
|
||||||
|
@ -12,9 +12,13 @@ class FactStore(FactStoreBase):
|
|||||||
self.works_remotely = False
|
self.works_remotely = False
|
||||||
|
|
||||||
def set(self, service: str, name: str, value: bytes) -> Path | None:
|
def set(self, service: str, name: str, value: bytes) -> Path | None:
|
||||||
if isinstance(self.machine.flake, Path):
|
if self.machine.flake.is_local():
|
||||||
fact_path = (
|
fact_path = (
|
||||||
self.machine.flake / "machines" / self.machine.name / "facts" / name
|
self.machine.flake.path
|
||||||
|
/ "machines"
|
||||||
|
/ self.machine.name
|
||||||
|
/ "facts"
|
||||||
|
/ name
|
||||||
)
|
)
|
||||||
fact_path.parent.mkdir(parents=True, exist_ok=True)
|
fact_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
fact_path.touch()
|
fact_path.touch()
|
||||||
|
@ -4,6 +4,7 @@ import logging
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
from ..clan_uri import FlakeId
|
||||||
from ..cmd import Log, run
|
from ..cmd import Log, run
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
from ..completions import add_dynamic_completer, complete_machines
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
@ -44,7 +45,7 @@ def upload_secrets(machine: Machine) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def upload_command(args: argparse.Namespace) -> None:
|
def upload_command(args: argparse.Namespace) -> None:
|
||||||
machine = Machine(name=args.machine, flake=args.flake)
|
machine = Machine(name=args.machine, flake=FlakeId(args.flake))
|
||||||
upload_secrets(machine)
|
upload_secrets(machine)
|
||||||
|
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ from pathlib import Path
|
|||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
|
from .clan_uri import FlakeId
|
||||||
from .cmd import Log, run
|
from .cmd import Log, run
|
||||||
from .completions import add_dynamic_completer, complete_machines
|
from .completions import add_dynamic_completer, complete_machines
|
||||||
from .errors import ClanError
|
from .errors import ClanError
|
||||||
@ -87,7 +88,7 @@ def flash_machine(
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class FlashOptions:
|
class FlashOptions:
|
||||||
flake: Path
|
flake: FlakeId
|
||||||
machine: str
|
machine: str
|
||||||
disks: dict[str, str]
|
disks: dict[str, str]
|
||||||
ssh_keys_path: list[Path]
|
ssh_keys_path: list[Path]
|
||||||
@ -119,7 +120,7 @@ class AppendDiskAction(argparse.Action):
|
|||||||
|
|
||||||
def flash_command(args: argparse.Namespace) -> None:
|
def flash_command(args: argparse.Namespace) -> None:
|
||||||
opts = FlashOptions(
|
opts = FlashOptions(
|
||||||
flake=args.flake,
|
flake=FlakeId(args.flake),
|
||||||
machine=args.machine,
|
machine=args.machine,
|
||||||
disks=args.disk,
|
disks=args.disk,
|
||||||
ssh_keys_path=args.ssh_pubkey,
|
ssh_keys_path=args.ssh_pubkey,
|
||||||
|
@ -82,7 +82,7 @@ def add_all_to_history(uri: ClanURI) -> list[HistoryEntry]:
|
|||||||
def add_history(uri: ClanURI) -> HistoryEntry:
|
def add_history(uri: ClanURI) -> HistoryEntry:
|
||||||
user_history_file().parent.mkdir(parents=True, exist_ok=True)
|
user_history_file().parent.mkdir(parents=True, exist_ok=True)
|
||||||
history = list_history()
|
history = list_history()
|
||||||
new_entry = _add_maschine_to_history_list(uri.get_url(), uri.machine.name, history)
|
new_entry = _add_maschine_to_history_list(uri.get_url(), uri.machine_name, history)
|
||||||
write_history_file(history)
|
write_history_file(history)
|
||||||
return new_entry
|
return new_entry
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ def update_history() -> list[HistoryEntry]:
|
|||||||
url=str(entry.flake.flake_url),
|
url=str(entry.flake.flake_url),
|
||||||
machine_name=entry.flake.flake_attr,
|
machine_name=entry.flake.flake_attr,
|
||||||
)
|
)
|
||||||
flake = inspect_flake(uri.get_url(), uri.machine.name)
|
flake = inspect_flake(uri.get_url(), uri.machine_name)
|
||||||
flake.flake_url = str(flake.flake_url)
|
flake.flake_url = str(flake.flake_url)
|
||||||
entry = HistoryEntry(
|
entry = HistoryEntry(
|
||||||
flake=flake, last_used=datetime.datetime.now().isoformat()
|
flake=flake, last_used=datetime.datetime.now().isoformat()
|
||||||
|
@ -7,6 +7,7 @@ from dataclasses import dataclass
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
from ..clan_uri import FlakeId
|
||||||
from ..cmd import Log, run
|
from ..cmd import Log, run
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
from ..completions import add_dynamic_completer, complete_machines
|
||||||
from ..facts.generate import generate_facts
|
from ..facts.generate import generate_facts
|
||||||
@ -90,7 +91,7 @@ def install_nixos(
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class InstallOptions:
|
class InstallOptions:
|
||||||
flake: Path
|
flake: FlakeId
|
||||||
machine: str
|
machine: str
|
||||||
target_host: str
|
target_host: str
|
||||||
kexec: str | None
|
kexec: str | None
|
||||||
@ -123,7 +124,7 @@ def install_command(args: argparse.Namespace) -> None:
|
|||||||
password = None
|
password = None
|
||||||
|
|
||||||
opts = InstallOptions(
|
opts = InstallOptions(
|
||||||
flake=args.flake,
|
flake=FlakeId(args.flake),
|
||||||
machine=args.machine,
|
machine=args.machine,
|
||||||
target_host=target_host,
|
target_host=target_host,
|
||||||
kexec=args.kexec,
|
kexec=args.kexec,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ..clan_uri import FlakeId
|
||||||
from ..cmd import run
|
from ..cmd import run
|
||||||
from ..nix import nix_build, nix_config
|
from ..nix import nix_build, nix_config
|
||||||
from .machines import Machine
|
from .machines import Machine
|
||||||
@ -21,8 +22,8 @@ def get_all_machines(flake_dir: Path, nix_options: list[str]) -> list[Machine]:
|
|||||||
machines.append(
|
machines.append(
|
||||||
Machine(
|
Machine(
|
||||||
name=name,
|
name=name,
|
||||||
flake=flake_dir,
|
flake=FlakeId(flake_dir),
|
||||||
deployment_info=machine_data,
|
cached_deployment=machine_data,
|
||||||
nix_options=nix_options,
|
nix_options=nix_options,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -34,5 +35,7 @@ def get_selected_machines(
|
|||||||
) -> list[Machine]:
|
) -> list[Machine]:
|
||||||
machines = []
|
machines = []
|
||||||
for name in machine_names:
|
for name in machine_names:
|
||||||
machines.append(Machine(name=name, flake=flake_dir, nix_options=nix_options))
|
machines.append(
|
||||||
|
Machine(name=name, flake=FlakeId(flake_dir), nix_options=nix_options)
|
||||||
|
)
|
||||||
return machines
|
return machines
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from clan_cli.clan_uri import ClanURI, MachineData
|
from clan_cli.clan_uri import FlakeId
|
||||||
|
|
||||||
from ..cmd import run_no_stdout
|
from ..cmd import run_no_stdout
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
@ -14,118 +15,83 @@ from ..ssh import Host, parse_deployment_address
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class Machine:
|
class Machine:
|
||||||
name: str
|
name: str
|
||||||
flake: str | Path
|
flake: FlakeId
|
||||||
data: MachineData
|
nix_options: list[str] = field(default_factory=list)
|
||||||
nix_options: list[str]
|
cached_deployment: None | dict = None
|
||||||
eval_cache: dict[str, str]
|
|
||||||
build_cache: dict[str, Path]
|
|
||||||
_flake_path: Path | None
|
|
||||||
_deployment_info: None | dict
|
|
||||||
|
|
||||||
def __init__(
|
_eval_cache: dict[str, str] = field(default_factory=dict)
|
||||||
self,
|
_build_cache: dict[str, Path] = field(default_factory=dict)
|
||||||
name: str,
|
|
||||||
flake: Path | str,
|
|
||||||
deployment_info: dict | None = None,
|
|
||||||
nix_options: list[str] = [],
|
|
||||||
machine: MachineData | None = None,
|
|
||||||
) -> None:
|
|
||||||
"""
|
|
||||||
Creates a Machine
|
|
||||||
@name: the name of the machine
|
|
||||||
@clan_dir: the directory of the clan, optional, if not set it will be determined from the current working directory
|
|
||||||
@machine_json: can be optionally used to skip evaluation of the machine, location of the json file with machine data
|
|
||||||
"""
|
|
||||||
if machine is None:
|
|
||||||
uri = ClanURI.from_str(str(flake), name)
|
|
||||||
machine = uri.machine
|
|
||||||
self.flake: str | Path = machine.flake_id._value
|
|
||||||
self.name: str = machine.name
|
|
||||||
self.data: MachineData = machine
|
|
||||||
else:
|
|
||||||
self.data: MachineData = machine
|
|
||||||
|
|
||||||
self.eval_cache: dict[str, str] = {}
|
|
||||||
self.build_cache: dict[str, Path] = {}
|
|
||||||
self._flake_path: Path | None = None
|
|
||||||
self._deployment_info: None | dict = deployment_info
|
|
||||||
self.nix_options = nix_options
|
|
||||||
|
|
||||||
def flush_caches(self) -> None:
|
def flush_caches(self) -> None:
|
||||||
self._deployment_info = None
|
self.cached_deployment = None
|
||||||
self._flake_path = None
|
self._build_cache.clear()
|
||||||
self.build_cache.clear()
|
self._eval_cache.clear()
|
||||||
self.eval_cache.clear()
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"Machine(name={self.data.name}, flake={self.data.flake_id})"
|
return f"Machine(name={self.name}, flake={self.flake})"
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return str(self)
|
return str(self)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def deployment_info(self) -> dict:
|
def deployment(self) -> dict:
|
||||||
if self._deployment_info is not None:
|
if self.cached_deployment is not None:
|
||||||
return self._deployment_info
|
return self.cached_deployment
|
||||||
self._deployment_info = json.loads(
|
deployment = json.loads(
|
||||||
self.build_nix("config.system.clan.deployment.file").read_text()
|
self.build_nix("config.system.clan.deployment.file").read_text()
|
||||||
)
|
)
|
||||||
return self._deployment_info
|
self.cached_deployment = deployment
|
||||||
|
return deployment
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_host_address(self) -> str:
|
def target_host_address(self) -> str:
|
||||||
# deploymentAddress is deprecated.
|
# deploymentAddress is deprecated.
|
||||||
val = self.deployment_info.get("targetHost") or self.deployment_info.get(
|
val = self.deployment.get("targetHost") or self.deployment.get(
|
||||||
"deploymentAddress"
|
"deploymentAddress"
|
||||||
)
|
)
|
||||||
if val is None:
|
if val is None:
|
||||||
msg = f"the 'clan.networking.targetHost' nixos option is not set for machine '{self.data.name}'"
|
msg = f"the 'clan.networking.targetHost' nixos option is not set for machine '{self.name}'"
|
||||||
raise ClanError(msg)
|
raise ClanError(msg)
|
||||||
return val
|
return val
|
||||||
|
|
||||||
@target_host_address.setter
|
@target_host_address.setter
|
||||||
def target_host_address(self, value: str) -> None:
|
def target_host_address(self, value: str) -> None:
|
||||||
self.deployment_info["targetHost"] = value
|
self.deployment["targetHost"] = value
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def secret_facts_module(self) -> str:
|
def secret_facts_module(self) -> str:
|
||||||
return self.deployment_info["facts"]["secretModule"]
|
return self.deployment["facts"]["secretModule"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def public_facts_module(self) -> str:
|
def public_facts_module(self) -> str:
|
||||||
return self.deployment_info["facts"]["publicModule"]
|
return self.deployment["facts"]["publicModule"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def facts_data(self) -> dict[str, dict[str, Any]]:
|
def facts_data(self) -> dict[str, dict[str, Any]]:
|
||||||
if self.deployment_info["facts"]["services"]:
|
if self.deployment["facts"]["services"]:
|
||||||
return self.deployment_info["facts"]["services"]
|
return self.deployment["facts"]["services"]
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def secrets_upload_directory(self) -> str:
|
def secrets_upload_directory(self) -> str:
|
||||||
return self.deployment_info["facts"]["secretUploadDirectory"]
|
return self.deployment["facts"]["secretUploadDirectory"]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def flake_dir(self) -> Path:
|
def flake_dir(self) -> Path:
|
||||||
if self._flake_path:
|
if self.flake.is_local():
|
||||||
return self._flake_path
|
return self.flake.path
|
||||||
|
elif self.flake.is_remote():
|
||||||
if self.data.flake_id.is_local():
|
return Path(nix_metadata(self.flake.url)["path"])
|
||||||
self._flake_path = self.data.flake_id.path
|
|
||||||
elif self.data.flake_id.is_remote():
|
|
||||||
self._flake_path = Path(nix_metadata(self.data.flake_id.url)["path"])
|
|
||||||
else:
|
else:
|
||||||
raise ClanError(f"Unsupported flake url: {self.data.flake_id}")
|
raise ClanError(f"Unsupported flake url: {self.flake}")
|
||||||
|
|
||||||
assert self._flake_path is not None
|
|
||||||
return self._flake_path
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_host(self) -> Host:
|
def target_host(self) -> Host:
|
||||||
return parse_deployment_address(
|
return parse_deployment_address(
|
||||||
self.data.name, self.target_host_address, meta={"machine": self}
|
self.name, self.target_host_address, meta={"machine": self}
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -134,12 +100,12 @@ class Machine:
|
|||||||
The host where the machine is built and deployed from.
|
The host where the machine is built and deployed from.
|
||||||
Can be the same as the target host.
|
Can be the same as the target host.
|
||||||
"""
|
"""
|
||||||
build_host = self.deployment_info.get("buildHost")
|
build_host = self.deployment.get("buildHost")
|
||||||
if build_host is None:
|
if build_host is None:
|
||||||
return self.target_host
|
return self.target_host
|
||||||
# enable ssh agent forwarding to allow the build host to access the target host
|
# enable ssh agent forwarding to allow the build host to access the target host
|
||||||
return parse_deployment_address(
|
return parse_deployment_address(
|
||||||
self.data.name,
|
self.name,
|
||||||
build_host,
|
build_host,
|
||||||
forward_agent=True,
|
forward_agent=True,
|
||||||
meta={"machine": self, "target_host": self.target_host},
|
meta={"machine": self, "target_host": self.target_host},
|
||||||
@ -198,7 +164,7 @@ class Machine:
|
|||||||
args += [
|
args += [
|
||||||
"--expr",
|
"--expr",
|
||||||
f"""
|
f"""
|
||||||
((builtins.getFlake "{url}").clanInternals.machinesFunc."{system}"."{self.data.name}" {{
|
((builtins.getFlake "{url}").clanInternals.machinesFunc."{system}"."{self.name}" {{
|
||||||
extraConfig = builtins.fromJSON (builtins.readFile (builtins.fetchTree {{
|
extraConfig = builtins.fromJSON (builtins.readFile (builtins.fetchTree {{
|
||||||
type = "file";
|
type = "file";
|
||||||
url = if (builtins.compareVersions builtins.nixVersion "2.19") == -1 then "{file_info["path"]}" else "file:{file_info["path"]}";
|
url = if (builtins.compareVersions builtins.nixVersion "2.19") == -1 then "{file_info["path"]}" else "file:{file_info["path"]}";
|
||||||
@ -213,9 +179,7 @@ class Machine:
|
|||||||
else:
|
else:
|
||||||
flake = f"path:{self.flake_dir}"
|
flake = f"path:{self.flake_dir}"
|
||||||
|
|
||||||
args += [
|
args += [f'{flake}#clanInternals.machines."{system}".{self.name}.{attr}']
|
||||||
f'{flake}#clanInternals.machines."{system}".{self.data.name}.{attr}'
|
|
||||||
]
|
|
||||||
args += nix_options + self.nix_options
|
args += nix_options + self.nix_options
|
||||||
|
|
||||||
if method == "eval":
|
if method == "eval":
|
||||||
@ -239,12 +203,12 @@ class Machine:
|
|||||||
eval a nix attribute of the machine
|
eval a nix attribute of the machine
|
||||||
@attr: the attribute to get
|
@attr: the attribute to get
|
||||||
"""
|
"""
|
||||||
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, impure, nix_options)
|
||||||
if isinstance(output, str):
|
if isinstance(output, str):
|
||||||
self.eval_cache[attr] = output
|
self._eval_cache[attr] = output
|
||||||
return output
|
return output
|
||||||
else:
|
else:
|
||||||
raise ClanError("eval_nix returned not a string")
|
raise ClanError("eval_nix returned not a string")
|
||||||
@ -262,12 +226,12 @@ class Machine:
|
|||||||
@attr: the attribute to get
|
@attr: the attribute to get
|
||||||
"""
|
"""
|
||||||
|
|
||||||
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, impure, nix_options)
|
||||||
if isinstance(output, Path):
|
if isinstance(output, Path):
|
||||||
self.build_cache[attr] = output
|
self._build_cache[attr] = output
|
||||||
return output
|
return output
|
||||||
else:
|
else:
|
||||||
raise ClanError("build_nix returned not a Path")
|
raise ClanError("build_nix returned not a Path")
|
||||||
|
@ -5,6 +5,7 @@ import os
|
|||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from ..clan_uri import FlakeId
|
||||||
from ..cmd import run
|
from ..cmd import run
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
from ..completions import add_dynamic_completer, complete_machines
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
@ -80,7 +81,7 @@ def upload_sources(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def deploy_nixos(machines: MachineGroup) -> None:
|
def deploy_machine(machines: MachineGroup) -> None:
|
||||||
"""
|
"""
|
||||||
Deploy to all hosts in parallel
|
Deploy to all hosts in parallel
|
||||||
"""
|
"""
|
||||||
@ -137,7 +138,7 @@ def update(args: argparse.Namespace) -> None:
|
|||||||
machines = []
|
machines = []
|
||||||
if len(args.machines) == 1 and args.target_host is not None:
|
if len(args.machines) == 1 and args.target_host is not None:
|
||||||
machine = Machine(
|
machine = Machine(
|
||||||
name=args.machines[0], flake=args.flake, nix_options=args.option
|
name=args.machines[0], flake=FlakeId(args.flake), nix_options=args.option
|
||||||
)
|
)
|
||||||
machine.target_host_address = args.target_host
|
machine.target_host_address = args.target_host
|
||||||
machines.append(machine)
|
machines.append(machine)
|
||||||
@ -149,7 +150,7 @@ def update(args: argparse.Namespace) -> None:
|
|||||||
if len(args.machines) == 0:
|
if len(args.machines) == 0:
|
||||||
ignored_machines = []
|
ignored_machines = []
|
||||||
for machine in get_all_machines(args.flake, args.option):
|
for machine in get_all_machines(args.flake, args.option):
|
||||||
if machine.deployment_info.get("requireExplicitUpdate", False):
|
if machine.deployment.get("requireExplicitUpdate", False):
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
machine.build_host
|
machine.build_host
|
||||||
@ -170,7 +171,7 @@ def update(args: argparse.Namespace) -> None:
|
|||||||
else:
|
else:
|
||||||
machines = get_selected_machines(args.flake, args.option, args.machines)
|
machines = get_selected_machines(args.flake, args.option, args.machines)
|
||||||
|
|
||||||
deploy_nixos(MachineGroup(machines))
|
deploy_machine(MachineGroup(machines))
|
||||||
|
|
||||||
|
|
||||||
def register_update_parser(parser: argparse.ArgumentParser) -> None:
|
def register_update_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
@ -190,4 +191,9 @@ def register_update_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
type=str,
|
type=str,
|
||||||
help="address of the machine to update, in the format of user@host:1234",
|
help="address of the machine to update, in the format of user@host:1234",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--darwin",
|
||||||
|
type=str,
|
||||||
|
help="Hack to deploy darwin machines. This will be removed in the future when we have full darwin integration.",
|
||||||
|
)
|
||||||
parser.set_defaults(func=update)
|
parser.set_defaults(func=update)
|
||||||
|
@ -9,6 +9,7 @@ from pathlib import Path
|
|||||||
from typing import IO
|
from typing import IO
|
||||||
|
|
||||||
from .. import tty
|
from .. import tty
|
||||||
|
from ..clan_uri import FlakeId
|
||||||
from ..completions import (
|
from ..completions import (
|
||||||
add_dynamic_completer,
|
add_dynamic_completer,
|
||||||
complete_groups,
|
complete_groups,
|
||||||
@ -263,16 +264,16 @@ def list_secrets(flake_dir: Path, pattern: str | None = None) -> list[str]:
|
|||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class ListSecretsOptions:
|
class ListSecretsOptions:
|
||||||
flake: Path
|
flake: FlakeId
|
||||||
pattern: str | None
|
pattern: str | None
|
||||||
|
|
||||||
|
|
||||||
def list_command(args: argparse.Namespace) -> None:
|
def list_command(args: argparse.Namespace) -> None:
|
||||||
options = ListSecretsOptions(
|
options = ListSecretsOptions(
|
||||||
flake=args.flake,
|
flake=FlakeId(args.flake),
|
||||||
pattern=args.pattern,
|
pattern=args.pattern,
|
||||||
)
|
)
|
||||||
lst = list_secrets(options.flake, options.pattern)
|
lst = list_secrets(options.flake.path, options.pattern)
|
||||||
if len(lst) > 0:
|
if len(lst) > 0:
|
||||||
print("\n".join(lst))
|
print("\n".join(lst))
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ import json
|
|||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
from ..clan_uri import FlakeId
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
from ..completions import add_dynamic_completer, complete_machines
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
|
|
||||||
@ -12,7 +13,7 @@ class VmConfig:
|
|||||||
machine_name: str
|
machine_name: str
|
||||||
machine_icon: Path
|
machine_icon: Path
|
||||||
machine_description: str
|
machine_description: str
|
||||||
flake_url: str | Path
|
flake_url: FlakeId
|
||||||
clan_name: str
|
clan_name: str
|
||||||
|
|
||||||
cores: int
|
cores: int
|
||||||
@ -23,19 +24,19 @@ class VmConfig:
|
|||||||
|
|
||||||
def inspect_vm(machine: Machine) -> VmConfig:
|
def inspect_vm(machine: Machine) -> VmConfig:
|
||||||
data = json.loads(machine.eval_nix("config.clan.core.vm.inspect"))
|
data = json.loads(machine.eval_nix("config.clan.core.vm.inspect"))
|
||||||
return VmConfig(flake_url=str(machine.flake), **data)
|
return VmConfig(flake_url=machine.flake, **data)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class InspectOptions:
|
class InspectOptions:
|
||||||
machine: str
|
machine: str
|
||||||
flake: Path
|
flake: FlakeId
|
||||||
|
|
||||||
|
|
||||||
def inspect_command(args: argparse.Namespace) -> None:
|
def inspect_command(args: argparse.Namespace) -> None:
|
||||||
inspect_options = InspectOptions(
|
inspect_options = InspectOptions(
|
||||||
machine=args.machine,
|
machine=args.machine,
|
||||||
flake=args.flake or Path.cwd(),
|
flake=FlakeId(args.flake or Path.cwd()),
|
||||||
)
|
)
|
||||||
|
|
||||||
machine = Machine(inspect_options.machine, inspect_options.flake)
|
machine = Machine(inspect_options.machine, inspect_options.flake)
|
||||||
|
@ -7,6 +7,7 @@ from contextlib import ExitStack
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
|
from ..clan_uri import FlakeId
|
||||||
from ..cmd import Log, run
|
from ..cmd import Log, run
|
||||||
from ..completions import add_dynamic_completer, complete_machines
|
from ..completions import add_dynamic_completer, complete_machines
|
||||||
from ..dirs import module_root, user_cache_dir, vm_state_dir
|
from ..dirs import module_root, user_cache_dir, vm_state_dir
|
||||||
@ -198,7 +199,7 @@ def run_command(
|
|||||||
option: list[str] = [],
|
option: list[str] = [],
|
||||||
**kwargs: dict[str, str],
|
**kwargs: dict[str, str],
|
||||||
) -> None:
|
) -> None:
|
||||||
machine_obj: Machine = Machine(machine, flake)
|
machine_obj: Machine = Machine(machine, FlakeId(flake))
|
||||||
|
|
||||||
vm: VmConfig = inspect_vm(machine=machine_obj)
|
vm: VmConfig = inspect_vm(machine=machine_obj)
|
||||||
|
|
||||||
|
@ -21,13 +21,13 @@ def test_get_url() -> None:
|
|||||||
def test_local_uri() -> None:
|
def test_local_uri() -> None:
|
||||||
# Create a ClanURI object from a local URI
|
# Create a ClanURI object from a local URI
|
||||||
uri = ClanURI("clan://file:///home/user/Downloads")
|
uri = ClanURI("clan://file:///home/user/Downloads")
|
||||||
assert uri.flake_id.path == Path("/home/user/Downloads")
|
assert uri.flake.path == Path("/home/user/Downloads")
|
||||||
|
|
||||||
|
|
||||||
def test_is_remote() -> None:
|
def test_is_remote() -> None:
|
||||||
# Create a ClanURI object from a remote URI
|
# Create a ClanURI object from a remote URI
|
||||||
uri = ClanURI("clan://https://example.com")
|
uri = ClanURI("clan://https://example.com")
|
||||||
assert uri.flake_id.url == "https://example.com"
|
assert uri.flake.url == "https://example.com"
|
||||||
|
|
||||||
|
|
||||||
def test_direct_local_path() -> None:
|
def test_direct_local_path() -> None:
|
||||||
@ -46,44 +46,36 @@ def test_remote_with_clanparams() -> None:
|
|||||||
# Create a ClanURI object from a remote URI with parameters
|
# Create a ClanURI object from a remote URI with parameters
|
||||||
uri = ClanURI("clan://https://example.com")
|
uri = ClanURI("clan://https://example.com")
|
||||||
|
|
||||||
assert uri.machine.name == "defaultVM"
|
assert uri.machine_name == "defaultVM"
|
||||||
assert uri.flake_id.url == "https://example.com"
|
assert uri.flake.url == "https://example.com"
|
||||||
|
|
||||||
|
|
||||||
def test_from_str_remote() -> None:
|
def test_from_str_remote() -> None:
|
||||||
uri = ClanURI.from_str(url="https://example.com", machine_name="myVM")
|
uri = ClanURI.from_str(url="https://example.com", machine_name="myVM")
|
||||||
assert uri.get_url() == "https://example.com"
|
assert uri.get_url() == "https://example.com"
|
||||||
assert uri.get_orig_uri() == "clan://https://example.com#myVM"
|
assert uri.machine_name == "myVM"
|
||||||
assert uri.machine.name == "myVM"
|
assert uri.flake.url == "https://example.com"
|
||||||
assert len(uri._machines) == 1
|
|
||||||
assert uri.flake_id.url == "https://example.com"
|
|
||||||
|
|
||||||
|
|
||||||
def test_from_str_local() -> None:
|
def test_from_str_local() -> None:
|
||||||
uri = ClanURI.from_str(url="~/Projects/democlan", machine_name="myVM")
|
uri = ClanURI.from_str(url="~/Projects/democlan", machine_name="myVM")
|
||||||
assert uri.get_url().endswith("/Projects/democlan")
|
assert uri.get_url().endswith("/Projects/democlan")
|
||||||
assert uri.get_orig_uri() == "clan://~/Projects/democlan#myVM"
|
assert uri.machine_name == "myVM"
|
||||||
assert uri.machine.name == "myVM"
|
assert uri.flake.is_local()
|
||||||
assert len(uri._machines) == 1
|
assert str(uri.flake).endswith("/Projects/democlan") # type: ignore
|
||||||
assert uri.flake_id.is_local()
|
|
||||||
assert str(uri.flake_id).endswith("/Projects/democlan") # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
def test_from_str_local_no_machine() -> None:
|
def test_from_str_local_no_machine() -> None:
|
||||||
uri = ClanURI.from_str("~/Projects/democlan")
|
uri = ClanURI.from_str("~/Projects/democlan")
|
||||||
assert uri.get_url().endswith("/Projects/democlan")
|
assert uri.get_url().endswith("/Projects/democlan")
|
||||||
assert uri.get_orig_uri() == "clan://~/Projects/democlan"
|
assert uri.machine_name == "defaultVM"
|
||||||
assert uri.machine.name == "defaultVM"
|
assert uri.flake.is_local()
|
||||||
assert len(uri._machines) == 1
|
assert str(uri.flake).endswith("/Projects/democlan") # type: ignore
|
||||||
assert uri.flake_id.is_local()
|
|
||||||
assert str(uri.flake_id).endswith("/Projects/democlan") # type: ignore
|
|
||||||
|
|
||||||
|
|
||||||
def test_from_str_local_no_machine2() -> None:
|
def test_from_str_local_no_machine2() -> None:
|
||||||
uri = ClanURI.from_str("~/Projects/democlan#syncthing-peer1")
|
uri = ClanURI.from_str("~/Projects/democlan#syncthing-peer1")
|
||||||
assert uri.get_url().endswith("/Projects/democlan")
|
assert uri.get_url().endswith("/Projects/democlan")
|
||||||
assert uri.get_orig_uri() == "clan://~/Projects/democlan#syncthing-peer1"
|
assert uri.machine_name == "syncthing-peer1"
|
||||||
assert uri.machine.name == "syncthing-peer1"
|
assert uri.flake.is_local()
|
||||||
assert len(uri._machines) == 1
|
assert str(uri.flake).endswith("/Projects/democlan") # type: ignore
|
||||||
assert uri.flake_id.is_local()
|
|
||||||
assert str(uri.flake_id).endswith("/Projects/democlan") # type: ignore
|
|
||||||
|
@ -6,7 +6,6 @@ from cli import Cli
|
|||||||
from fixtures_flakes import FlakeForTest
|
from fixtures_flakes import FlakeForTest
|
||||||
from pytest import CaptureFixture
|
from pytest import CaptureFixture
|
||||||
|
|
||||||
from clan_cli.clan_uri import ClanURI
|
|
||||||
from clan_cli.dirs import user_history_file
|
from clan_cli.dirs import user_history_file
|
||||||
from clan_cli.history.add import HistoryEntry
|
from clan_cli.history.add import HistoryEntry
|
||||||
|
|
||||||
@ -19,11 +18,10 @@ def test_history_add(
|
|||||||
test_flake_with_core: FlakeForTest,
|
test_flake_with_core: FlakeForTest,
|
||||||
) -> None:
|
) -> None:
|
||||||
cli = Cli()
|
cli = Cli()
|
||||||
uri = ClanURI.from_str(str(test_flake_with_core.path), "vm1")
|
|
||||||
cmd = [
|
cmd = [
|
||||||
"history",
|
"history",
|
||||||
"add",
|
"add",
|
||||||
str(uri),
|
f"clan://{test_flake_with_core.path}#vm1",
|
||||||
]
|
]
|
||||||
cli.run(cmd)
|
cli.run(cmd)
|
||||||
|
|
||||||
@ -39,7 +37,6 @@ def test_history_list(
|
|||||||
test_flake_with_core: FlakeForTest,
|
test_flake_with_core: FlakeForTest,
|
||||||
) -> None:
|
) -> None:
|
||||||
cli = Cli()
|
cli = Cli()
|
||||||
uri = ClanURI.from_str(str(test_flake_with_core.path), "vm1")
|
|
||||||
cmd = [
|
cmd = [
|
||||||
"history",
|
"history",
|
||||||
"list",
|
"list",
|
||||||
@ -48,6 +45,6 @@ def test_history_list(
|
|||||||
cli.run(cmd)
|
cli.run(cmd)
|
||||||
assert str(test_flake_with_core.path) not in capsys.readouterr().out
|
assert str(test_flake_with_core.path) not in capsys.readouterr().out
|
||||||
|
|
||||||
cli.run(["history", "add", str(uri)])
|
cli.run(["history", "add", f"clan://{test_flake_with_core.path}#vm1"])
|
||||||
cli.run(cmd)
|
cli.run(cmd)
|
||||||
assert str(test_flake_with_core.path) in capsys.readouterr().out
|
assert str(test_flake_with_core.path) in capsys.readouterr().out
|
||||||
|
@ -6,6 +6,7 @@ from cli import Cli
|
|||||||
from fixtures_flakes import FlakeForTest
|
from fixtures_flakes import FlakeForTest
|
||||||
from validator import is_valid_age_key, is_valid_ssh_key
|
from validator import is_valid_age_key, is_valid_ssh_key
|
||||||
|
|
||||||
|
from clan_cli.clan_uri import FlakeId
|
||||||
from clan_cli.facts.secret_modules.sops import SecretStore
|
from clan_cli.facts.secret_modules.sops import SecretStore
|
||||||
from clan_cli.machines.facts import machine_get_fact
|
from clan_cli.machines.facts import machine_get_fact
|
||||||
from clan_cli.machines.machines import Machine
|
from clan_cli.machines.machines import Machine
|
||||||
@ -48,7 +49,7 @@ def test_generate_secret(
|
|||||||
)
|
)
|
||||||
cmd = ["facts", "generate", "--flake", str(test_flake_with_core.path), "vm1"]
|
cmd = ["facts", "generate", "--flake", str(test_flake_with_core.path), "vm1"]
|
||||||
cli.run(cmd)
|
cli.run(cmd)
|
||||||
store1 = SecretStore(Machine(name="vm1", flake=test_flake_with_core.path))
|
store1 = SecretStore(Machine(name="vm1", flake=FlakeId(test_flake_with_core.path)))
|
||||||
|
|
||||||
assert store1.exists("", "age.key")
|
assert store1.exists("", "age.key")
|
||||||
assert store1.exists("", "zerotier-identity-secret")
|
assert store1.exists("", "zerotier-identity-secret")
|
||||||
@ -76,7 +77,7 @@ def test_generate_secret(
|
|||||||
secrets_folder / "vm1-zerotier-identity-secret" / "machines" / "vm1"
|
secrets_folder / "vm1-zerotier-identity-secret" / "machines" / "vm1"
|
||||||
).exists()
|
).exists()
|
||||||
|
|
||||||
store2 = SecretStore(Machine(name="vm2", flake=test_flake_with_core.path))
|
store2 = SecretStore(Machine(name="vm2", flake=FlakeId(test_flake_with_core.path)))
|
||||||
|
|
||||||
assert store2.exists("", "password")
|
assert store2.exists("", "password")
|
||||||
assert store2.exists("", "password-hash")
|
assert store2.exists("", "password-hash")
|
||||||
|
@ -6,6 +6,7 @@ from cli import Cli
|
|||||||
from fixtures_flakes import FlakeForTest
|
from fixtures_flakes import FlakeForTest
|
||||||
from validator import is_valid_ssh_key
|
from validator import is_valid_ssh_key
|
||||||
|
|
||||||
|
from clan_cli.clan_uri import FlakeId
|
||||||
from clan_cli.facts.secret_modules.password_store import SecretStore
|
from clan_cli.facts.secret_modules.password_store import SecretStore
|
||||||
from clan_cli.machines.facts import machine_get_fact
|
from clan_cli.machines.facts import machine_get_fact
|
||||||
from clan_cli.machines.machines import Machine
|
from clan_cli.machines.machines import Machine
|
||||||
@ -48,7 +49,9 @@ def test_upload_secret(
|
|||||||
)
|
)
|
||||||
cli.run(["facts", "generate", "vm1"])
|
cli.run(["facts", "generate", "vm1"])
|
||||||
|
|
||||||
store = SecretStore(Machine(name="vm1", flake=test_flake_with_core_and_pass.path))
|
store = SecretStore(
|
||||||
|
Machine(name="vm1", flake=FlakeId(test_flake_with_core_and_pass.path))
|
||||||
|
)
|
||||||
|
|
||||||
network_id = machine_get_fact(
|
network_id = machine_get_fact(
|
||||||
test_flake_with_core_and_pass.path, "vm1", "zerotier-network-id"
|
test_flake_with_core_and_pass.path, "vm1", "zerotier-network-id"
|
||||||
|
Loading…
Reference in New Issue
Block a user