add apply "machine" as an alias to clan machines create

I was a bit confused that I was able to list templates but not
apply them. Turns out that "apply" only supported disk templates
This commit is contained in:
Jörg Thalheim
2025-08-27 14:30:03 +02:00
committed by Mic92
parent 5962149e55
commit 758eacd27e
5 changed files with 156 additions and 1 deletions

View File

@@ -198,7 +198,7 @@ This subcommand provides an interface to templates provided by clan.
Examples:
$ clan templates list
List all the machines managed by Clan.
List all available templates
Usage differs based on the template type
@@ -227,6 +227,16 @@ Disk templates
Real world example
$ clan templates apply disk single-disk jon --set mainDisk "/dev/disk/by-id/nvme-WD_PC_SN740_SDDQNQD-512G-1201_232557804368"
---
Machine templates
$ clan templates apply machine [TEMPLATE] [MACHINE_NAME]
Will create a new machine [MACHINE_NAME] from the specified [TEMPLATE]
Real world example
$ clan templates apply machine flash-installer my-installer
"""
),
formatter_class=argparse.RawTextHelpFormatter,

View File

@@ -303,6 +303,27 @@ def complete_templates_clan(
return []
def complete_templates_machine(
_prefix: str,
parsed_args: argparse.Namespace,
**_kwargs: Any,
) -> Iterable[str]:
"""Provides completion functionality for machine templates"""
flake = (
clan_dir_result
if (clan_dir_result := clan_dir(getattr(parsed_args, "flake", None)))
is not None
else "."
)
list_all_templates = list_templates(Flake(flake))
machine_template_list = list_all_templates.builtins.get("machine")
if machine_template_list:
machine_templates = list(machine_template_list)
return dict.fromkeys(machine_templates, "machine")
return []
def complete_vars_for_machine(
_prefix: str,
parsed_args: argparse.Namespace,

View File

@@ -1,6 +1,7 @@
import argparse
from .apply_disk import register_apply_disk_template_parser
from .apply_machine import register_apply_machine_template_parser
def register_apply_parser(parser: argparse.ArgumentParser) -> None:
@@ -11,5 +12,7 @@ def register_apply_parser(parser: argparse.ArgumentParser) -> None:
required=True,
)
disk_parser = subparser.add_parser("disk", help="Apply a disk template")
machine_parser = subparser.add_parser("machine", help="Apply a machine template")
register_apply_disk_template_parser(disk_parser)
register_apply_machine_template_parser(machine_parser)

View File

@@ -0,0 +1,42 @@
import argparse
import logging
from clan_lib.nix_models.clan import InventoryMachine
from clan_lib.nix_models.clan import InventoryMachineDeploy as MachineDeploy
from clan_cli.machines.create import CreateOptions, create_machine
log = logging.getLogger(__name__)
def apply_command(args: argparse.Namespace) -> None:
"""Apply a machine template - actually an alias for machines create --template."""
# Create machine using the create_machine API directly
machine = InventoryMachine(
name=args.machine,
tags=[],
deploy=MachineDeploy(targetHost=None),
)
opts = CreateOptions(
clan_dir=args.flake,
machine=machine,
template=args.template,
)
create_machine(opts)
def register_apply_machine_template_parser(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"template",
type=str,
help="The name of the machine template to apply",
)
parser.add_argument(
"machine",
type=str,
help="The name of the machine to create from the template",
)
parser.set_defaults(func=apply_command)

View File

@@ -0,0 +1,79 @@
import json
import pytest
from clan_lib.errors import ClanError
from clan_lib.flake import Flake
from clan_lib.machines.machines import Machine
from clan_lib.templates.disk import set_machine_disk_schema
from clan_cli.tests.fixtures_flakes import FlakeForTest
from clan_cli.tests.helpers import cli
@pytest.mark.with_core
def test_templates_apply_machine_and_disk(
test_flake_with_core: FlakeForTest,
) -> None:
"""Test both machine template creation and disk template application."""
flake_path = str(test_flake_with_core.path)
cli.run(
[
"templates",
"apply",
"machine",
"new-machine",
"test-apply-machine",
"--flake",
flake_path,
]
)
# Verify machine was created
machine_dir = test_flake_with_core.path / "machines" / "test-apply-machine"
assert machine_dir.exists(), "Machine directory should be created"
assert (machine_dir / "configuration.nix").exists(), (
"Configuration file should exist"
)
facter_content = {
"disks": [
{
"name": "test-disk",
"path": "/dev/sda",
"size": 107374182400,
"type": "disk",
}
]
}
facter_path = machine_dir / "facter.json"
facter_path.write_text(json.dumps(facter_content, indent=2))
machine = Machine(name="test-apply-machine", flake=Flake(flake_path))
set_machine_disk_schema(
machine,
"single-disk",
{"mainDisk": "/dev/sda"},
force=False,
check_hw=False, # Skip hardware validation for test
)
# Verify disk template was applied by checking that disko.nix exists or was updated
disko_file = machine_dir / "disko.nix"
assert disko_file.exists(), "Disko configuration should be created"
# Verify error handling - try to create duplicate machine
# Since apply machine now uses machines create, it raises ClanError for duplicates
with pytest.raises(ClanError, match="already exists"):
cli.run(
[
"templates",
"apply",
"machine",
"new-machine",
"test-apply-machine", # Same name as existing
"--flake",
flake_path,
]
)