Chore(machine/templates): simplify template args for machines command

This commit is contained in:
Johannes Kirschbauer
2025-06-11 15:57:46 +02:00
parent 43bc5f0812
commit b80395af44
3 changed files with 51 additions and 120 deletions

View File

@@ -2,7 +2,6 @@ import argparse
import logging import logging
import re import re
from dataclasses import dataclass from dataclasses import dataclass
from pathlib import Path
from clan_lib.api import API from clan_lib.api import API
from clan_lib.dirs import get_clan_flake_toplevel_or_env from clan_lib.dirs import get_clan_flake_toplevel_or_env
@@ -13,15 +12,9 @@ from clan_lib.nix_models.clan import InventoryMachine
from clan_lib.nix_models.clan import InventoryMachineDeploy as MachineDeploy from clan_lib.nix_models.clan import InventoryMachineDeploy as MachineDeploy
from clan_lib.persist.inventory_store import InventoryStore from clan_lib.persist.inventory_store import InventoryStore
from clan_lib.persist.util import set_value_by_path from clan_lib.persist.util import set_value_by_path
from clan_lib.templates import ( from clan_lib.templates.handler import machine_template
InputPrio,
TemplateName,
get_template,
)
from clan_lib.templates.filesystem import copy_from_nixstore
from clan_cli.completions import add_dynamic_completer, complete_tags from clan_cli.completions import add_dynamic_completer, complete_tags
from clan_cli.machines.list import list_full_machines
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -30,9 +23,8 @@ log = logging.getLogger(__name__)
class CreateOptions: class CreateOptions:
clan_dir: Flake clan_dir: Flake
machine: InventoryMachine machine: InventoryMachine
template: str = "new-machine"
target_host: str | None = None target_host: str | None = None
input_prio: InputPrio | None = None
template_name: str | None = None
@API.register @API.register
@@ -54,42 +46,12 @@ def create_machine(
description = "Import machine only works on local clans" description = "Import machine only works on local clans"
raise ClanError(msg, description=description) raise ClanError(msg, description=description)
if not opts.template_name:
opts.template_name = "new-machine"
clan_dir = opts.clan_dir.path clan_dir = opts.clan_dir.path
# TODO(@Qubasa): make this a proper template handler
# i.e. with_template (use context manager)
# And move the checks and template handling into the template handler
template = get_template(
TemplateName(opts.template_name),
"machine",
input_prio=opts.input_prio,
clan_dir=opts.clan_dir,
)
log.info(f"Found template '{template.name}' in '{template.input_variant}'")
machine_name = opts.machine.get("name") machine_name = opts.machine.get("name")
if opts.template_name in list_full_machines( if not machine_name:
Flake(str(clan_dir)) msg = "Machine name is required"
) and not opts.machine.get("name"): raise ClanError(msg, location="Create Machine")
msg = f"{opts.template_name} is already defined in {clan_dir}"
raise ClanError(msg)
machine_name = machine_name if machine_name else opts.template_name
src = Path(template.src["path"])
if not src.exists():
msg = f"Template {template} does not exist"
raise ClanError(msg)
if not src.is_dir():
msg = f"Template {template} is not a directory"
raise ClanError(msg)
dst = clan_dir / "machines"
dst.mkdir(exist_ok=True)
dst /= machine_name
# TODO: Move this into nix code # TODO: Move this into nix code
hostname_regex = r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)$" hostname_regex = r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)$"
@@ -97,20 +59,10 @@ def create_machine(
msg = "Machine name must be a valid hostname" msg = "Machine name must be a valid hostname"
raise ClanError(msg, location="Create Machine") raise ClanError(msg, location="Create Machine")
if dst.exists(): with machine_template(
msg = f"Machine {machine_name} already exists in {clan_dir}" flake=opts.clan_dir, template_ident=opts.template, dst_machine_name=machine_name
description = "Please remove the existing machine folder" ) as _machine_dir:
raise ClanError(msg, description=description) # Write to the inventory if persist is true
# TODO(@Qubasa): move this into the template handler
if not (src / "configuration.nix").exists():
msg = f"Template machine '{opts.template_name}' does not contain a configuration.nix"
description = "Template machine must contain a configuration.nix"
raise ClanError(msg, description=description)
# TODO(@Qubasa): move this into the template handler
copy_from_nixstore(src, dst)
if _persist: if _persist:
target_host = opts.target_host target_host = opts.target_host
new_machine = opts.machine new_machine = opts.machine
@@ -133,16 +85,14 @@ def create_machine(
) )
inventory_store.write(inventory, message=f"machine '{machine_name}'") inventory_store.write(inventory, message=f"machine '{machine_name}'")
opts.clan_dir.invalidate_cache()
# Commit at the end in that order to avoid committing halve-baked machines
# TODO: automatic rollbacks if something goes wrong
if commit: if commit:
commit_file( commit_file(
clan_dir / "machines" / machine_name, clan_dir / "machines" / machine_name,
repo_dir=clan_dir, repo_dir=clan_dir,
commit_message=f"Add machine {machine_name}", commit_message=f"Add machine {machine_name}",
) )
# Invalidate the cache since this modified the flake
opts.clan_dir.invalidate_cache()
def create_command(args: argparse.Namespace) -> None: def create_command(args: argparse.Namespace) -> None:
@@ -159,25 +109,15 @@ def create_command(args: argparse.Namespace) -> None:
) )
raise ClanError(msg, description=description) raise ClanError(msg, description=description)
if len(args.input) == 0:
args.input = ["clan", "clan-core"]
if args.no_self:
input_prio = InputPrio.try_inputs(tuple(args.input))
else:
input_prio = InputPrio.try_self_then_inputs(tuple(args.input))
machine = InventoryMachine( machine = InventoryMachine(
name=args.machine_name, name=args.machine_name,
tags=args.tags, tags=args.tags,
deploy=MachineDeploy(targetHost=args.target_host), deploy=MachineDeploy(targetHost=args.target_host),
) )
opts = CreateOptions( opts = CreateOptions(
input_prio=input_prio,
clan_dir=clan_dir, clan_dir=clan_dir,
machine=machine, machine=machine,
template_name=args.template_name, template=args.template,
target_host=args.target_host,
) )
create_machine(opts) create_machine(opts)
@@ -196,29 +136,15 @@ def register_create_parser(parser: argparse.ArgumentParser) -> None:
help="Tags to associate with the machine. Can be used to assign multiple machines to services.", help="Tags to associate with the machine. Can be used to assign multiple machines to services.",
) )
add_dynamic_completer(tag_parser, complete_tags) add_dynamic_completer(tag_parser, complete_tags)
parser.add_argument(
"--template-name",
type=str,
help="The name of the template machine to import",
)
parser.add_argument( parser.add_argument(
"--target-host", "--target-host",
type=str, type=str,
help="Address of the machine to install and update, in the format of user@host:1234", help="Address of the machine to install and update, in the format of user@host:1234",
) )
parser.add_argument( parser.add_argument(
"--input", "-t",
"--template",
type=str, type=str,
help="""Flake input name to use as template source help="Reference to the template to use for the machine. In the format '<flake_ref>#template_name'",
can be specified multiple times, inputs are tried in order of definition default="new-machine",
Example: --input clan --input clan-core
""",
action="append",
default=[],
)
parser.add_argument(
"--no-self",
help="Do not look into own flake for templates",
action="store_true",
default=False,
) )

View File

@@ -41,7 +41,7 @@ def random_hostname() -> str:
def morph_machine( def morph_machine(
flake: Flake, template_name: str, ask_confirmation: bool, name: str | None = None flake: Flake, template: str, ask_confirmation: bool, name: str | None = None
) -> None: ) -> None:
cmd = nix_command( cmd = nix_command(
[ [
@@ -70,7 +70,7 @@ def morph_machine(
name = random_hostname() name = random_hostname()
create_opts = CreateOptions( create_opts = CreateOptions(
template_name=template_name, template=template,
machine=InventoryMachine(name=name), machine=InventoryMachine(name=name),
clan_dir=Flake(str(flakedir)), clan_dir=Flake(str(flakedir)),
) )
@@ -149,7 +149,7 @@ def morph_command(args: argparse.Namespace) -> None:
morph_machine( morph_machine(
flake=Flake(str(args.flake)), flake=Flake(str(args.flake)),
template_name=args.template_name, template=args.template,
ask_confirmation=args.confirm_firing, ask_confirmation=args.confirm_firing,
name=args.name, name=args.name,
) )
@@ -159,7 +159,7 @@ def register_morph_parser(parser: argparse.ArgumentParser) -> None:
parser.set_defaults(func=morph_command) parser.set_defaults(func=morph_command)
parser.add_argument( parser.add_argument(
"template_name", "template",
default="new-machine", default="new-machine",
type=str, type=str,
help="The name of the template to use", help="The name of the template to use",

View File

@@ -15,7 +15,7 @@ log = logging.getLogger(__name__)
@contextmanager @contextmanager
def with_machine_template( def machine_template(
flake: Flake, template_ident: str, dst_machine_name: str flake: Flake, template_ident: str, dst_machine_name: str
) -> Iterator[Path]: ) -> Iterator[Path]:
""" """
@@ -28,7 +28,7 @@ def with_machine_template(
Example usage: Example usage:
>>> with with_machine_template( >>> with machine_template(
... Flake("/home/johannes/git/clan-core"), ".#new-machine", "my-machine" ... Flake("/home/johannes/git/clan-core"), ".#new-machine", "my-machine"
... ) as machine_path: ... ) as machine_path:
... # Use `machine_path` here if you want to access the created machine directory ... # Use `machine_path` here if you want to access the created machine directory
@@ -46,7 +46,10 @@ def with_machine_template(
) )
# Get the clan template from the specifier # Get the clan template from the specifier
[flake_ref, template_selector] = transform_url("machine", template_ident, local_path=flake) [flake_ref, template_selector] = transform_url(
"machine", template_ident, local_path=flake
)
template_flake = Flake(flake_ref) template_flake = Flake(flake_ref)
template = template_flake.select(template_selector) template = template_flake.select(template_selector)
@@ -82,12 +85,14 @@ def with_machine_template(
dst_machine_dir = specific_machine_dir(tmp_machine) dst_machine_dir = specific_machine_dir(tmp_machine)
dst_machine_dir.mkdir(exist_ok=True, parents=True)
copy_from_nixstore(src_path, dst_machine_dir) copy_from_nixstore(src_path, dst_machine_dir)
try: try:
yield dst_machine_dir yield dst_machine_dir
except Exception as e: except Exception as e:
log.error(f"An error occurred inside the 'with_machine_template' context: {e}") log.error(f"An error occurred inside the 'machine_template' context: {e}")
# Ensure that the directory is removed to avoid half-created machines # Ensure that the directory is removed to avoid half-created machines
# Everything in the with block is considered part of the context # Everything in the with block is considered part of the context