Merge pull request 'machines/create: fix handle defaults' (#4129) from update-templates-services into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4129
This commit is contained in:
hsjobeki
2025-06-27 18:59:39 +00:00

View File

@@ -2,6 +2,7 @@ import argparse
import logging import logging
import re import re
from dataclasses import dataclass from dataclasses import dataclass
from typing import TypeVar, cast
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
@@ -27,6 +28,41 @@ class CreateOptions:
target_host: str | None = None target_host: str | None = None
T = TypeVar("T")
def merge_objects(obj1: T, obj2: T) -> T:
"""
Updates values in obj2 by values of Obj1
The output contains values for all keys of Obj1 and Obj2 together
Lists are deduplicated and appended almost like in the nix module system.
"""
result = {}
msg = f"cannot update non-dictionary values: {obj2} by {obj1}"
if not isinstance(obj1, dict):
raise ClanError(msg)
if not isinstance(obj2, dict):
raise ClanError(msg)
all_keys = set(obj1.keys()).union(obj2.keys())
for key in all_keys:
val1 = obj1.get(key)
val2 = obj2.get(key)
if isinstance(val1, dict) and isinstance(val2, dict):
result[key] = merge_objects(val1, val2)
elif isinstance(val1, list) and isinstance(val2, list):
result[key] = list(dict.fromkeys(val2 + val1)) # type: ignore
elif key in obj1:
result[key] = val1 # type: ignore
elif key in obj2:
result[key] = val2 # type: ignore
return cast(T, result)
@API.register @API.register
def create_machine( def create_machine(
opts: CreateOptions, opts: CreateOptions,
@@ -66,19 +102,27 @@ def create_machine(
dst_machine_name=machine_name, dst_machine_name=machine_name,
) as _machine_dir: ) as _machine_dir:
# Write to the inventory if persist is true # Write to the inventory if persist is true
target_host = opts.target_host
new_machine = opts.machine
new_machine["deploy"] = {"targetHost": target_host} # type: ignore
inventory_store = InventoryStore(opts.clan_dir) inventory_store = InventoryStore(opts.clan_dir)
inventory = inventory_store.read() inventory = inventory_store.read()
if machine_name in inventory.get("machines", {}): if machine_name in inventory.get("machines", {}):
msg = f"Machine {machine_name} already exists in inventory" msg = f"Machine {machine_name} already exists in inventory"
description = ( description = (
"Please delete the existing machine or import with a different name" "Please delete the existing machine or import with a different name"
) )
raise ClanError(msg, description=description) raise ClanError(msg, description=description)
# Committing the machines directory can add the machine with
# defaults to the eval result of inventory
if commit:
commit_file(
clan_dir / "machines" / machine_name,
repo_dir=clan_dir,
commit_message=f"Add machine {machine_name}",
)
opts.clan_dir.invalidate_cache()
inventory = inventory_store.read()
curr_machine = inventory.get("machines", {}).get(machine_name, {})
new_machine = merge_objects(opts.machine, curr_machine)
set_value_by_path( set_value_by_path(
inventory, inventory,
@@ -87,12 +131,6 @@ def create_machine(
) )
inventory_store.write(inventory, message=f"machine '{machine_name}'") inventory_store.write(inventory, message=f"machine '{machine_name}'")
if commit:
commit_file(
clan_dir / "machines" / machine_name,
repo_dir=clan_dir,
commit_message=f"Add machine {machine_name}",
)
# Invalidate the cache since this modified the flake # Invalidate the cache since this modified the flake
opts.clan_dir.invalidate_cache() opts.clan_dir.invalidate_cache()