Merge pull request 'API: refactor into resource oriented names' (#4223) from api-cleanup into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4223
This commit is contained in:
hsjobeki
2025-07-06 19:11:31 +00:00
20 changed files with 116 additions and 197 deletions

View File

@@ -33,27 +33,6 @@ export const createModulesQuery = (
}, },
})); }));
export const tagsQuery = (uri: string | undefined) =>
useQuery<string[]>(() => ({
queryKey: [uri, "tags"],
placeholderData: [],
queryFn: async () => {
if (!uri) return [];
const response = await callApi("get_inventory", {
flake: { identifier: uri },
}).promise;
if (response.status === "error") {
console.error("Failed to fetch data");
} else {
const machines = response.data.machines || {};
const tags = Object.values(machines).flatMap((m) => m.tags || []);
return tags;
}
return [];
},
}));
export const machinesQuery = (uri: string | undefined) => export const machinesQuery = (uri: string | undefined) =>
useQuery<string[]>(() => ({ useQuery<string[]>(() => ({
queryKey: [uri, "machines"], queryKey: [uri, "machines"],
@@ -61,7 +40,7 @@ export const machinesQuery = (uri: string | undefined) =>
queryFn: async () => { queryFn: async () => {
if (!uri) return []; if (!uri) return [];
const response = await callApi("get_inventory", { const response = await callApi("list_machines", {
flake: { identifier: uri }, flake: { identifier: uri },
}).promise; }).promise;
if (response.status === "error") { if (response.status === "error") {

View File

@@ -66,7 +66,7 @@ export const CreateClan = () => {
} }
// Will generate a key if it doesn't exist, and add a user to the clan // Will generate a key if it doesn't exist, and add a user to the clan
const k = await callApi("keygen", { const k = await callApi("create_secrets_user", {
flake_dir: target_dir[0], flake_dir: target_dir[0],
}).promise; }).promise;

View File

@@ -110,7 +110,7 @@ export const Flash = () => {
const keymapQuery = createQuery(() => ({ const keymapQuery = createQuery(() => ({
queryKey: ["list_keymaps"], queryKey: ["list_keymaps"],
queryFn: async () => { queryFn: async () => {
const result = await callApi("list_possible_keymaps", {}).promise; const result = await callApi("list_keymaps", {}).promise;
if (result.status === "error") throw new Error("Failed to fetch data"); if (result.status === "error") throw new Error("Failed to fetch data");
return result.data; return result.data;
}, },
@@ -120,7 +120,7 @@ export const Flash = () => {
const langQuery = createQuery(() => ({ const langQuery = createQuery(() => ({
queryKey: ["list_languages"], queryKey: ["list_languages"],
queryFn: async () => { queryFn: async () => {
const result = await callApi("list_possible_languages", {}).promise; const result = await callApi("list_languages", {}).promise;
if (result.status === "error") throw new Error("Failed to fetch data"); if (result.status === "error") throw new Error("Failed to fetch data");
return result.data; return result.data;
}, },

View File

@@ -1,5 +1,5 @@
import { BackButton } from "@/src/components/BackButton"; import { BackButton } from "@/src/components/BackButton";
import { createModulesQuery, machinesQuery, tagsQuery } from "@/src/queries"; import { createModulesQuery, machinesQuery } from "@/src/queries";
import { useParams } from "@solidjs/router"; import { useParams } from "@solidjs/router";
import { For, Match, Switch } from "solid-js"; import { For, Match, Switch } from "solid-js";
import { ModuleInfo } from "./list"; import { ModuleInfo } from "./list";
@@ -34,28 +34,11 @@ interface AddModuleProps {
const AddModule = (props: AddModuleProps) => { const AddModule = (props: AddModuleProps) => {
const { activeClanURI } = useClanContext(); const { activeClanURI } = useClanContext();
const tags = tagsQuery(activeClanURI());
const machines = machinesQuery(activeClanURI()); const machines = machinesQuery(activeClanURI());
return ( return (
<div> <div>
<div>Add to your clan</div> <div>Add to your clan</div>
<Switch fallback="loading"> <Switch fallback="loading">Removed</Switch>
<Match when={tags.data}>
{(tags) => (
<For each={Object.keys(props.data.roles)}>
{(role) => (
<>
<div class="text-neutral-600">{role}s</div>
<RoleForm
avilableTags={tags()}
availableMachines={machines.data || []}
/>
</>
)}
</For>
)}
</Match>
</Switch>
</div> </div>
); );
}; };

View File

@@ -62,7 +62,6 @@ const Details = (props: DetailsProps) => {
navigate(`/modules/add/${props.id}`); navigate(`/modules/add/${props.id}`);
// const uri = activeURI(); // const uri = activeURI();
// if (!uri) return; // if (!uri) return;
// const res = await callApi("get_inventory", { base_path: uri });
// if (res.status === "error") { // if (res.status === "error") {
// toast.error("Failed to fetch inventory"); // toast.error("Failed to fetch inventory");
// return; // return;

View File

@@ -65,7 +65,7 @@ export const CreateClan = () => {
} }
// Will generate a key if it doesn't exist, and add a user to the clan // Will generate a key if it doesn't exist, and add a user to the clan
const k = await callApi("keygen", { const k = await callApi("create_secrets_user", {
flake_dir: target_dir[0], flake_dir: target_dir[0],
}).promise; }).promise;

View File

@@ -17,7 +17,7 @@ from clan_cli.vars.generate import generate_vars
from clan_cli.vars.upload import populate_secret_vars from clan_cli.vars.upload import populate_secret_vars
from .automount import pause_automounting from .automount import pause_automounting
from .list import list_possible_keymaps, list_possible_languages from .list import list_keymaps, list_languages
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -59,7 +59,7 @@ def flash_machine(
generate_vars([machine]) generate_vars([machine])
if system_config.language: if system_config.language:
if system_config.language not in list_possible_languages(): if system_config.language not in list_languages():
msg = ( msg = (
f"Language '{system_config.language}' is not a valid language. " f"Language '{system_config.language}' is not a valid language. "
f"Run 'clan flash list languages' to see a list of possible languages." f"Run 'clan flash list languages' to see a list of possible languages."
@@ -68,7 +68,7 @@ def flash_machine(
system_config_nix["i18n"] = {"defaultLocale": system_config.language} system_config_nix["i18n"] = {"defaultLocale": system_config.language}
if system_config.keymap: if system_config.keymap:
if system_config.keymap not in list_possible_keymaps(): if system_config.keymap not in list_keymaps():
msg = ( msg = (
f"Keymap '{system_config.keymap}' is not a valid keymap. " f"Keymap '{system_config.keymap}' is not a valid keymap. "
f"Run 'clan flash list keymaps' to see a list of possible keymaps." f"Run 'clan flash list keymaps' to see a list of possible keymaps."

View File

@@ -2,6 +2,7 @@ import argparse
import logging import logging
import os import os
from pathlib import Path from pathlib import Path
from typing import TypedDict
from clan_lib.api import API from clan_lib.api import API
from clan_lib.cmd import Log, RunOpts, run from clan_lib.cmd import Log, RunOpts, run
@@ -11,8 +12,17 @@ from clan_lib.nix import nix_build
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
class FlashOptions(TypedDict):
languages: list[str]
keymaps: list[str]
@API.register @API.register
def list_possible_languages() -> list[str]: def show_flash_options() -> FlashOptions:
return {"languages": list_languages(), "keymaps": list_keymaps()}
def list_languages() -> list[str]:
cmd = nix_build(["nixpkgs#glibcLocales"]) cmd = nix_build(["nixpkgs#glibcLocales"])
result = run(cmd, RunOpts(log=Log.STDERR, error_msg="Failed to find glibc locales")) result = run(cmd, RunOpts(log=Log.STDERR, error_msg="Failed to find glibc locales"))
locale_file = Path(result.stdout.strip()) / "share" / "i18n" / "SUPPORTED" locale_file = Path(result.stdout.strip()) / "share" / "i18n" / "SUPPORTED"
@@ -37,8 +47,7 @@ def list_possible_languages() -> list[str]:
return languages return languages
@API.register def list_keymaps() -> list[str]:
def list_possible_keymaps() -> list[str]:
cmd = nix_build(["nixpkgs#kbd"]) cmd = nix_build(["nixpkgs#kbd"])
result = run(cmd, RunOpts(log=Log.STDERR, error_msg="Failed to find kbdinfo")) result = run(cmd, RunOpts(log=Log.STDERR, error_msg="Failed to find kbdinfo"))
keymaps_dir = Path(result.stdout.strip()) / "share" / "keymaps" keymaps_dir = Path(result.stdout.strip()) / "share" / "keymaps"
@@ -61,11 +70,11 @@ def list_possible_keymaps() -> list[str]:
def list_command(args: argparse.Namespace) -> None: def list_command(args: argparse.Namespace) -> None:
if args.cmd == "languages": if args.cmd == "languages":
languages = list_possible_languages() languages = list_languages()
for language in languages: for language in languages:
print(language) print(language)
elif args.cmd == "keymaps": elif args.cmd == "keymaps":
keymaps = list_possible_keymaps() keymaps = list_keymaps()
for keymap in keymaps: for keymap in keymaps:
print(keymap) print(keymap)

View File

@@ -10,11 +10,11 @@ from clan_cli.tests.helpers import cli
from clan_cli.vars.check import check_vars from clan_cli.vars.check import check_vars
from clan_cli.vars.generate import ( from clan_cli.vars.generate import (
Generator, Generator,
generate_vars_for_machine, create_machine_vars,
generate_vars_for_machine_interactive, create_machine_vars_interactive,
get_generators_closure, get_machine_generators,
) )
from clan_cli.vars.get import get_var from clan_cli.vars.get import get_machine_var
from clan_cli.vars.graph import all_missing_closure, requested_closure from clan_cli.vars.graph import all_missing_closure, requested_closure
from clan_cli.vars.list import stringify_all_vars from clan_cli.vars.list import stringify_all_vars
from clan_cli.vars.public_modules import in_repo from clan_cli.vars.public_modules import in_repo
@@ -172,13 +172,13 @@ def test_generate_public_and_secret_vars(
in commit_message in commit_message
) )
assert ( assert (
get_var( get_machine_var(
str(machine.flake.path), machine.name, "my_generator/my_value" str(machine.flake.path), machine.name, "my_generator/my_value"
).printable_value ).printable_value
== "public" == "public"
) )
assert ( assert (
get_var( get_machine_var(
str(machine.flake.path), machine.name, "my_shared_generator/my_shared_value" str(machine.flake.path), machine.name, "my_shared_generator/my_shared_value"
).printable_value ).printable_value
== "shared" == "shared"
@@ -668,7 +668,7 @@ def test_api_set_prompts(
monkeypatch.chdir(flake.path) monkeypatch.chdir(flake.path)
generate_vars_for_machine( create_machine_vars(
machine_name="my_machine", machine_name="my_machine",
base_dir=flake.path, base_dir=flake.path,
generators=["my_generator"], generators=["my_generator"],
@@ -682,7 +682,7 @@ def test_api_set_prompts(
store = in_repo.FactStore(machine) store = in_repo.FactStore(machine)
assert store.exists(Generator("my_generator"), "prompt1") assert store.exists(Generator("my_generator"), "prompt1")
assert store.get(Generator("my_generator"), "prompt1").decode() == "input1" assert store.get(Generator("my_generator"), "prompt1").decode() == "input1"
generate_vars_for_machine( create_machine_vars(
machine_name="my_machine", machine_name="my_machine",
base_dir=flake.path, base_dir=flake.path,
generators=["my_generator"], generators=["my_generator"],
@@ -694,7 +694,7 @@ def test_api_set_prompts(
) )
assert store.get(Generator("my_generator"), "prompt1").decode() == "input2" assert store.get(Generator("my_generator"), "prompt1").decode() == "input2"
generators = get_generators_closure( generators = get_machine_generators(
machine_name="my_machine", machine_name="my_machine",
base_dir=flake.path, base_dir=flake.path,
full_closure=True, full_closure=True,
@@ -727,11 +727,11 @@ def test_stdout_of_generate(
flake_.refresh() flake_.refresh()
monkeypatch.chdir(flake_.path) monkeypatch.chdir(flake_.path)
flake = Flake(str(flake_.path)) flake = Flake(str(flake_.path))
from clan_cli.vars.generate import generate_vars_for_machine_interactive from clan_cli.vars.generate import create_machine_vars_interactive
# with capture_output as output: # with capture_output as output:
with caplog.at_level(logging.INFO): with caplog.at_level(logging.INFO):
generate_vars_for_machine_interactive( create_machine_vars_interactive(
Machine(name="my_machine", flake=flake), Machine(name="my_machine", flake=flake),
"my_generator", "my_generator",
regenerate=False, regenerate=False,
@@ -744,7 +744,7 @@ def test_stdout_of_generate(
set_var("my_machine", "my_generator/my_value", b"world", flake) set_var("my_machine", "my_generator/my_value", b"world", flake)
with caplog.at_level(logging.INFO): with caplog.at_level(logging.INFO):
generate_vars_for_machine_interactive( create_machine_vars_interactive(
Machine(name="my_machine", flake=flake), Machine(name="my_machine", flake=flake),
"my_generator", "my_generator",
regenerate=True, regenerate=True,
@@ -755,7 +755,7 @@ def test_stdout_of_generate(
caplog.clear() caplog.clear()
# check the output when nothing gets regenerated # check the output when nothing gets regenerated
with caplog.at_level(logging.INFO): with caplog.at_level(logging.INFO):
generate_vars_for_machine_interactive( create_machine_vars_interactive(
Machine(name="my_machine", flake=flake), Machine(name="my_machine", flake=flake),
"my_generator", "my_generator",
regenerate=True, regenerate=True,
@@ -764,7 +764,7 @@ def test_stdout_of_generate(
assert "hello" in caplog.text assert "hello" in caplog.text
caplog.clear() caplog.clear()
with caplog.at_level(logging.INFO): with caplog.at_level(logging.INFO):
generate_vars_for_machine_interactive( create_machine_vars_interactive(
Machine(name="my_machine", flake=flake), Machine(name="my_machine", flake=flake),
"my_secret_generator", "my_secret_generator",
regenerate=False, regenerate=False,
@@ -779,7 +779,7 @@ def test_stdout_of_generate(
Flake(str(flake.path)), Flake(str(flake.path)),
) )
with caplog.at_level(logging.INFO): with caplog.at_level(logging.INFO):
generate_vars_for_machine_interactive( create_machine_vars_interactive(
Machine(name="my_machine", flake=flake), Machine(name="my_machine", flake=flake),
"my_secret_generator", "my_secret_generator",
regenerate=True, regenerate=True,
@@ -869,7 +869,7 @@ def test_fails_when_files_are_left_from_other_backend(
flake.refresh() flake.refresh()
monkeypatch.chdir(flake.path) monkeypatch.chdir(flake.path)
for generator in ["my_secret_generator", "my_value_generator"]: for generator in ["my_secret_generator", "my_value_generator"]:
generate_vars_for_machine_interactive( create_machine_vars_interactive(
Machine(name="my_machine", flake=Flake(str(flake.path))), Machine(name="my_machine", flake=Flake(str(flake.path))),
generator, generator,
regenerate=False, regenerate=False,
@@ -886,13 +886,13 @@ def test_fails_when_files_are_left_from_other_backend(
# This should raise an error # This should raise an error
if generator == "my_secret_generator": if generator == "my_secret_generator":
with pytest.raises(ClanError): with pytest.raises(ClanError):
generate_vars_for_machine_interactive( create_machine_vars_interactive(
Machine(name="my_machine", flake=Flake(str(flake.path))), Machine(name="my_machine", flake=Flake(str(flake.path))),
generator, generator,
regenerate=False, regenerate=False,
) )
else: else:
generate_vars_for_machine_interactive( create_machine_vars_interactive(
Machine(name="my_machine", flake=Flake(str(flake.path))), Machine(name="my_machine", flake=Flake(str(flake.path))),
generator, generator,
regenerate=False, regenerate=False,
@@ -900,7 +900,9 @@ def test_fails_when_files_are_left_from_other_backend(
@pytest.mark.with_core @pytest.mark.with_core
def test_keygen(monkeypatch: pytest.MonkeyPatch, flake: ClanFlake) -> None: def test_create_sops_age_secrets(
monkeypatch: pytest.MonkeyPatch, flake: ClanFlake
) -> None:
monkeypatch.chdir(flake.path) monkeypatch.chdir(flake.path)
cli.run(["vars", "keygen", "--flake", str(flake.path), "--user", "user"]) cli.run(["vars", "keygen", "--flake", str(flake.path), "--user", "user"])
# check public key exists # check public key exists
@@ -930,12 +932,12 @@ def test_invalidation(
monkeypatch.chdir(flake.path) monkeypatch.chdir(flake.path)
cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"])
machine = Machine(name="my_machine", flake=Flake(str(flake.path))) machine = Machine(name="my_machine", flake=Flake(str(flake.path)))
value1 = get_var( value1 = get_machine_var(
str(machine.flake.path), machine.name, "my_generator/my_value" str(machine.flake.path), machine.name, "my_generator/my_value"
).printable_value ).printable_value
# generate again and make sure nothing changes without the invalidation data being set # generate again and make sure nothing changes without the invalidation data being set
cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"])
value1_new = get_var( value1_new = get_machine_var(
str(machine.flake.path), machine.name, "my_generator/my_value" str(machine.flake.path), machine.name, "my_generator/my_value"
).printable_value ).printable_value
assert value1 == value1_new assert value1 == value1_new
@@ -944,13 +946,13 @@ def test_invalidation(
flake.refresh() flake.refresh()
# generate again and make sure the value changes # generate again and make sure the value changes
cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"])
value2 = get_var( value2 = get_machine_var(
str(machine.flake.path), machine.name, "my_generator/my_value" str(machine.flake.path), machine.name, "my_generator/my_value"
).printable_value ).printable_value
assert value1 != value2 assert value1 != value2
# generate again without changing invalidation data -> value should not change # generate again without changing invalidation data -> value should not change
cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"]) cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"])
value2_new = get_var( value2_new = get_machine_var(
str(machine.flake.path), machine.name, "my_generator/my_value" str(machine.flake.path), machine.name, "my_generator/my_value"
).printable_value ).printable_value
assert value2 == value2_new assert value2 == value2_new

View File

@@ -89,9 +89,11 @@ class Generator:
deploy=file_data["deploy"], deploy=file_data["deploy"],
owner=file_data["owner"], owner=file_data["owner"],
group=file_data["group"], group=file_data["group"],
mode=file_data["mode"] mode=(
if isinstance(file_data["mode"], int) file_data["mode"]
else int(file_data["mode"], 8), if isinstance(file_data["mode"], int)
else int(file_data["mode"], 8)
),
needed_for=file_data["neededFor"], needed_for=file_data["neededFor"],
) )
files.append(var) files.append(var)
@@ -421,7 +423,7 @@ def get_closure(
@API.register @API.register
def get_generators_closure( def get_machine_generators(
machine_name: str, machine_name: str,
base_dir: Path, base_dir: Path,
full_closure: bool = False, full_closure: bool = False,
@@ -459,7 +461,7 @@ def _generate_vars_for_machine(
@API.register @API.register
def generate_vars_for_machine( def create_machine_vars(
machine_name: str, machine_name: str,
generators: list[str], generators: list[str],
all_prompt_values: dict[str, dict[str, str]], all_prompt_values: dict[str, dict[str, str]],
@@ -484,7 +486,7 @@ def generate_vars_for_machine(
) )
def generate_vars_for_machine_interactive( def create_machine_vars_interactive(
machine: "Machine", machine: "Machine",
generator_name: str | None, generator_name: str | None,
regenerate: bool, regenerate: bool,
@@ -538,7 +540,7 @@ def generate_vars(
for machine in machines: for machine in machines:
errors = [] errors = []
try: try:
was_regenerated |= generate_vars_for_machine_interactive( was_regenerated |= create_machine_vars_interactive(
machine, machine,
generator_name, generator_name,
regenerate, regenerate,

View File

@@ -3,19 +3,17 @@ import logging
import sys import sys
from clan_cli.completions import add_dynamic_completer, complete_machines from clan_cli.completions import add_dynamic_completer, complete_machines
from clan_lib.api import API
from clan_lib.errors import ClanError from clan_lib.errors import ClanError
from clan_lib.flake import Flake from clan_lib.flake import Flake
from .generate import Var from .generate import Var
from .list import get_vars from .list import get_machine_vars
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@API.register def get_machine_var(base_dir: str, machine_name: str, var_id: str) -> Var:
def get_var(base_dir: str, machine_name: str, var_id: str) -> Var: vars_ = get_machine_vars(base_dir=base_dir, machine_name=machine_name)
vars_ = get_vars(base_dir=base_dir, machine_name=machine_name)
results = [] results = []
for var in vars_: for var in vars_:
if var.id == var_id: if var.id == var_id:
@@ -41,7 +39,7 @@ def get_var(base_dir: str, machine_name: str, var_id: str) -> Var:
def get_command(machine_name: str, var_id: str, flake: Flake) -> None: def get_command(machine_name: str, var_id: str, flake: Flake) -> None:
var = get_var(str(flake.path), machine_name, var_id) var = get_machine_var(str(flake.path), machine_name, var_id)
if not var.exists: if not var.exists:
msg = f"Var {var.id} has not been generated yet" msg = f"Var {var.id} has not been generated yet"
raise ClanError(msg) raise ClanError(msg)

View File

@@ -13,11 +13,16 @@ log = logging.getLogger(__name__)
@API.register @API.register
def keygen(flake_dir: Path, user: str | None = None, force: bool = False) -> None: def create_secrets_user(
flake_dir: Path, user: str | None = None, force: bool = False
) -> None:
"""
initialize sops keys for vars
"""
if user is None: if user is None:
user = os.getenv("USER", None) user = os.getenv("USER", None)
if not user: if not user:
msg = "No user provided and $USER is not set. Please provide a user via --user." msg = "No user provided and environment variable: '$USER' is not set. Please provide an explizit username via argument"
raise ClanError(msg) raise ClanError(msg)
pub_keys = maybe_get_admin_public_keys() pub_keys = maybe_get_admin_public_keys()
if not pub_keys: if not pub_keys:
@@ -34,7 +39,7 @@ def keygen(flake_dir: Path, user: str | None = None, force: bool = False) -> Non
def _command( def _command(
args: argparse.Namespace, args: argparse.Namespace,
) -> None: ) -> None:
keygen( create_secrets_user(
flake_dir=args.flake.path, flake_dir=args.flake.path,
user=args.user, user=args.user,
force=args.force, force=args.force,

View File

@@ -2,19 +2,15 @@ import argparse
import logging import logging
from clan_cli.completions import add_dynamic_completer, complete_machines from clan_cli.completions import add_dynamic_completer, complete_machines
from clan_lib.api import API
from clan_lib.errors import ClanError
from clan_lib.flake import Flake from clan_lib.flake import Flake
from clan_lib.machines.machines import Machine from clan_lib.machines.machines import Machine
from ._types import GeneratorUpdate from .generate import Var
from .generate import Generator, Prompt, Var, execute_generator
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@API.register def get_machine_vars(base_dir: str, machine_name: str) -> list[Var]:
def get_vars(base_dir: str, machine_name: str) -> list[Var]:
machine = Machine(name=machine_name, flake=Flake(base_dir)) machine = Machine(name=machine_name, flake=Flake(base_dir))
pub_store = machine.public_vars_store pub_store = machine.public_vars_store
sec_store = machine.secret_vars_store sec_store = machine.secret_vars_store
@@ -32,70 +28,12 @@ def get_vars(base_dir: str, machine_name: str) -> list[Var]:
return all_vars return all_vars
def _get_previous_value(
machine: Machine,
generator: Generator,
prompt: Prompt,
) -> str | None:
if not prompt.persist:
return None
pub_store = machine.public_vars_store
if pub_store.exists(generator, prompt.name):
return pub_store.get(generator, prompt.name).decode()
sec_store = machine.secret_vars_store
if sec_store.exists(generator, prompt.name):
return sec_store.get(generator, prompt.name).decode()
return None
@API.register
def get_generators(base_dir: str, machine_name: str) -> list[Generator]:
from clan_cli.vars.generate import Generator
machine = Machine(name=machine_name, flake=Flake(base_dir))
generators: list[Generator] = Generator.generators_from_flake(
machine_name, machine.flake
)
for generator in generators:
for prompt in generator.prompts:
prompt.previous_value = _get_previous_value(machine, generator, prompt)
return generators
# TODO: Ensure generator dependencies are met (executed in correct order etc.)
# TODO: for missing prompts, default to existing values
# TODO: raise error if mandatory prompt not provided
@API.register
def set_prompts(
base_dir: str, machine_name: str, updates: list[GeneratorUpdate]
) -> None:
from clan_cli.vars.generate import Generator
machine = Machine(name=machine_name, flake=Flake(base_dir))
for update in updates:
generators = Generator.generators_from_flake(machine_name, machine.flake)
for generator in generators:
if generator.name == update.generator:
break
else:
msg = f"Generator '{update.generator}' not found in machine {machine.name}"
raise ClanError(msg)
execute_generator(
machine,
generator,
secret_vars_store=machine.secret_vars_store,
public_vars_store=machine.public_vars_store,
prompt_values=update.prompt_values,
)
def stringify_vars(_vars: list[Var]) -> str: def stringify_vars(_vars: list[Var]) -> str:
return "\n".join([str(var) for var in _vars]) return "\n".join([str(var) for var in _vars])
def stringify_all_vars(machine: Machine) -> str: def stringify_all_vars(machine: Machine) -> str:
return stringify_vars(get_vars(str(machine.flake), machine.name)) return stringify_vars(get_machine_vars(str(machine.flake), machine.name))
def list_command(args: argparse.Namespace) -> None: def list_command(args: argparse.Namespace) -> None:

View File

@@ -3,7 +3,7 @@ import logging
import sys import sys
from clan_cli.completions import add_dynamic_completer, complete_machines from clan_cli.completions import add_dynamic_completer, complete_machines
from clan_cli.vars.get import get_var from clan_cli.vars.get import get_machine_var
from clan_cli.vars.prompt import PromptType from clan_cli.vars.prompt import PromptType
from clan_lib.flake import Flake from clan_lib.flake import Flake
from clan_lib.git import commit_files from clan_lib.git import commit_files
@@ -21,7 +21,7 @@ def set_var(machine: str | Machine, var: str | Var, value: bytes, flake: Flake)
else: else:
_machine = machine _machine = machine
if isinstance(var, str): if isinstance(var, str):
_var = get_var(str(flake.path), _machine.name, var) _var = get_machine_var(str(flake.path), _machine.name, var)
else: else:
_var = var _var = var
path = _var.set(value) path = _var.set(value)
@@ -35,7 +35,7 @@ def set_var(machine: str | Machine, var: str | Var, value: bytes, flake: Flake)
def set_via_stdin(machine_name: str, var_id: str, flake: Flake) -> None: def set_via_stdin(machine_name: str, var_id: str, flake: Flake) -> None:
machine = Machine(name=machine_name, flake=flake) machine = Machine(name=machine_name, flake=flake)
var = get_var(str(flake.path), machine_name, var_id) var = get_machine_var(str(flake.path), machine_name, var_id)
if sys.stdin.isatty(): if sys.stdin.isatty():
new_value = ask( new_value = ask(
var.id, var.id,

View File

@@ -10,7 +10,7 @@ from clan_lib.api.modules import Frontmatter, extract_frontmatter
from clan_lib.dirs import TemplateType, clan_templates from clan_lib.dirs import TemplateType, clan_templates
from clan_lib.errors import ClanError from clan_lib.errors import ClanError
from clan_lib.git import commit_file from clan_lib.git import commit_file
from clan_lib.machines.hardware import HardwareConfig, show_machine_hardware_config from clan_lib.machines.hardware import HardwareConfig, get_machine_hardware_config
from clan_lib.machines.machines import Machine from clan_lib.machines.machines import Machine
log = logging.getLogger(__name__) log = logging.getLogger(__name__)
@@ -137,7 +137,7 @@ def set_machine_disk_schema(
Set the disk placeholders of the template Set the disk placeholders of the template
""" """
# Assert the hw-config must exist before setting the disk # Assert the hw-config must exist before setting the disk
hw_config = show_machine_hardware_config(machine) hw_config = get_machine_hardware_config(machine)
hw_config_path = hw_config.config_path(machine) hw_config_path = hw_config.config_path(machine)
if not hw_config_path.exists(): if not hw_config_path.exists():

View File

@@ -10,14 +10,3 @@ Which is an abstraction over the inventory
Interacting with 'clan_lib.inventory' is NOT recommended and will be removed Interacting with 'clan_lib.inventory' is NOT recommended and will be removed
""" """
from clan_lib.api import API
from clan_lib.flake import Flake
from clan_lib.persist.inventory_store import InventorySnapshot, InventoryStore
@API.register
def get_inventory(flake: Flake) -> InventorySnapshot:
inventory_store = InventoryStore(flake)
inventory = inventory_store.read()
return inventory

View File

@@ -9,7 +9,7 @@ from clan_cli.secrets.secrets import (
list_secrets, list_secrets,
) )
from clan_lib import inventory from clan_lib.persist.inventory_store import InventoryStore
from clan_lib.api import API from clan_lib.api import API
from clan_lib.dirs import specific_machine_dir from clan_lib.dirs import specific_machine_dir
from clan_lib.machines.machines import Machine from clan_lib.machines.machines import Machine
@@ -19,7 +19,7 @@ log = logging.getLogger(__name__)
@API.register @API.register
def delete_machine(machine: Machine) -> None: def delete_machine(machine: Machine) -> None:
inventory_store = inventory.InventoryStore(machine.flake) inventory_store = InventoryStore(machine.flake)
try: try:
inventory_store.delete( inventory_store.delete(
{f"machines.{machine.name}"}, {f"machines.{machine.name}"},

View File

@@ -3,6 +3,7 @@ import logging
from dataclasses import dataclass from dataclasses import dataclass
from enum import Enum from enum import Enum
from pathlib import Path from pathlib import Path
from typing import TypedDict
from clan_lib.api import API from clan_lib.api import API
from clan_lib.cmd import RunOpts, run from clan_lib.cmd import RunOpts, run
@@ -40,19 +41,7 @@ class HardwareConfig(Enum):
return HardwareConfig.NONE return HardwareConfig.NONE
@API.register def get_machine_target_platform(machine: Machine) -> str | None:
def show_machine_hardware_config(machine: Machine) -> HardwareConfig:
"""
Show hardware information for a machine returns None if none exist.
"""
return HardwareConfig.detect_type(machine)
@API.register
def show_machine_hardware_platform(machine: Machine) -> str | None:
"""
Show hardware information for a machine returns None if none exist.
"""
config = nix_config() config = nix_config()
system = config["system"] system = config["system"]
cmd = nix_eval( cmd = nix_eval(
@@ -132,7 +121,7 @@ def generate_machine_hardware_info(
f"machines/{opts.machine}/{hw_file.name}: update hardware configuration", f"machines/{opts.machine}/{hw_file.name}: update hardware configuration",
) )
try: try:
show_machine_hardware_platform(opts.machine) get_machine_target_platform(opts.machine)
if backup_file: if backup_file:
backup_file.unlink(missing_ok=True) backup_file.unlink(missing_ok=True)
except ClanCmdError as e: except ClanCmdError as e:
@@ -150,3 +139,29 @@ def generate_machine_hardware_info(
) from e ) from e
return opts.backend return opts.backend
def get_machine_hardware_config(machine: Machine) -> HardwareConfig:
"""
Detect and return the full hardware configuration for the given machine.
Returns:
HardwareConfig: Structured hardware information, or None if unavailable.
"""
return HardwareConfig.detect_type(machine)
class MachineHardwareBrief(TypedDict):
hardware_config: HardwareConfig
platform: str | None
@API.register
def describe_machine_hardware(machine: Machine) -> MachineHardwareBrief:
"""
Return a high-level summary of hardware config and platform type.
"""
return {
"hardware_config": get_machine_hardware_config(machine),
"platform": get_machine_target_platform(machine),
}

View File

@@ -129,7 +129,7 @@ class Machine:
return self.flake.path return self.flake.path
def target_host(self) -> Remote: def target_host(self) -> Remote:
remote = get_host(self.name, self.flake, field="targetHost") remote = get_machine_host(self.name, self.flake, field="targetHost")
if remote is None: if remote is None:
msg = f"'targetHost' is not set for machine '{self.name}'" msg = f"'targetHost' is not set for machine '{self.name}'"
raise ClanError( raise ClanError(
@@ -144,7 +144,7 @@ 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.
""" """
remote = get_host(self.name, self.flake, field="buildHost") remote = get_machine_host(self.name, self.flake, field="buildHost")
if remote: if remote:
data = remote.data data = remote.data
@@ -176,7 +176,7 @@ class RemoteSource:
@API.register @API.register
def get_host( def get_machine_host(
name: str, flake: Flake, field: Literal["targetHost", "buildHost"] name: str, flake: Flake, field: Literal["targetHost", "buildHost"]
) -> RemoteSource | None: ) -> RemoteSource | None:
""" """

View File

@@ -14,7 +14,7 @@ from clan_cli.machines.create import create_machine
from clan_cli.secrets.key import generate_key from clan_cli.secrets.key import generate_key
from clan_cli.secrets.sops import maybe_get_admin_public_keys from clan_cli.secrets.sops import maybe_get_admin_public_keys
from clan_cli.secrets.users import add_user from clan_cli.secrets.users import add_user
from clan_cli.vars.generate import generate_vars_for_machine, get_generators_closure from clan_cli.vars.generate import create_machine_vars, get_machine_generators
from clan_lib.api.disk import hw_main_disk_options, set_machine_disk_schema from clan_lib.api.disk import hw_main_disk_options, set_machine_disk_schema
from clan_lib.api.modules import list_modules from clan_lib.api.modules import list_modules
@@ -22,7 +22,7 @@ from clan_lib.cmd import RunOpts, run
from clan_lib.dirs import specific_machine_dir from clan_lib.dirs import specific_machine_dir
from clan_lib.errors import ClanError from clan_lib.errors import ClanError
from clan_lib.flake import Flake from clan_lib.flake import Flake
from clan_lib.inventory import InventoryStore from clan_lib.persist.inventory_store import InventoryStore
from clan_lib.machines.machines import Machine from clan_lib.machines.machines import Machine
from clan_lib.nix import nix_command from clan_lib.nix import nix_command
from clan_lib.nix_models.clan import ( from clan_lib.nix_models.clan import (
@@ -221,7 +221,7 @@ def test_clan_create_api(
# Invalidate cache because of new inventory # Invalidate cache because of new inventory
clan_dir_flake.invalidate_cache() clan_dir_flake.invalidate_cache()
generators = get_generators_closure(machine.name, machine.flake.path) generators = get_machine_generators(machine.name, machine.flake.path)
all_prompt_values = {} all_prompt_values = {}
for generator in generators: for generator in generators:
prompt_values = {} prompt_values = {}
@@ -234,7 +234,7 @@ def test_clan_create_api(
raise ClanError(msg) raise ClanError(msg)
all_prompt_values[generator.name] = prompt_values all_prompt_values[generator.name] = prompt_values
generate_vars_for_machine( create_machine_vars(
machine_name=machine.name, machine_name=machine.name,
base_dir=machine.flake.path, base_dir=machine.flake.path,
generators=[gen.name for gen in generators], generators=[gen.name for gen in generators],