Merge pull request 'Inventory: warning on undefined tags, instead of error.' (#2696) from hsjobeki/clan-core:hsjobeki-main into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/2696
This commit is contained in:
@@ -28,10 +28,10 @@ let
|
|||||||
);
|
);
|
||||||
in
|
in
|
||||||
if tagMembers == [ ] then
|
if tagMembers == [ ] then
|
||||||
throw ''
|
lib.warn ''
|
||||||
inventory.services.${serviceName}.${instanceName}: - ${roleName} tags: no machine with tag '${tag}' found.
|
inventory.services.${serviceName}.${instanceName}: - ${roleName} tags: no machine with tag '${tag}' found.
|
||||||
Available tags: ${builtins.toJSON (lib.unique availableTags)}
|
Available tags: ${builtins.toJSON (lib.unique availableTags)}
|
||||||
''
|
'' [ ]
|
||||||
else
|
else
|
||||||
acc ++ tagMembers
|
acc ++ tagMembers
|
||||||
) [ ] members.tags or [ ]);
|
) [ ] members.tags or [ ]);
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ in
|
|||||||
checks = {
|
checks = {
|
||||||
lib-inventory-eval = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
|
lib-inventory-eval = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
|
||||||
export HOME="$(realpath .)"
|
export HOME="$(realpath .)"
|
||||||
|
export NIX_ABORT_ON_WARN=1
|
||||||
nix-unit --eval-store "$HOME" \
|
nix-unit --eval-store "$HOME" \
|
||||||
--extra-experimental-features flakes \
|
--extra-experimental-features flakes \
|
||||||
${inputOverrides} \
|
${inputOverrides} \
|
||||||
|
|||||||
@@ -178,6 +178,8 @@ in
|
|||||||
msg = "Roles roleXYZ are not defined in the service borgbackup.";
|
msg = "Roles roleXYZ are not defined in the service borgbackup.";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
# Needs NIX_ABORT_ON_WARN=1
|
||||||
|
# So the lib.warn is turned into abort
|
||||||
test_inventory_tag_doesnt_exist =
|
test_inventory_tag_doesnt_exist =
|
||||||
let
|
let
|
||||||
configs = buildInventory {
|
configs = buildInventory {
|
||||||
@@ -201,8 +203,9 @@ in
|
|||||||
{
|
{
|
||||||
expr = configs;
|
expr = configs;
|
||||||
expectedError = {
|
expectedError = {
|
||||||
type = "ThrownError";
|
type = "Error";
|
||||||
msg = "no machine with tag '\\w+' found";
|
# TODO: Add warning matching in nix-unit
|
||||||
|
msg = ".*";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
test_inventory_disabled_service =
|
test_inventory_disabled_service =
|
||||||
|
|||||||
@@ -6,8 +6,17 @@ let
|
|||||||
in
|
in
|
||||||
{
|
{
|
||||||
perSystem =
|
perSystem =
|
||||||
{ pkgs, system, ... }:
|
|
||||||
{
|
{
|
||||||
|
pkgs,
|
||||||
|
system,
|
||||||
|
lib,
|
||||||
|
...
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
tests = import ./test.nix { inherit lib; };
|
||||||
|
in
|
||||||
|
{
|
||||||
|
legacyPackages.evalTests-values = tests;
|
||||||
checks = {
|
checks = {
|
||||||
lib-values-eval = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
|
lib-values-eval = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
|
||||||
export HOME="$(realpath .)"
|
export HOME="$(realpath .)"
|
||||||
@@ -15,7 +24,7 @@ in
|
|||||||
nix-unit --eval-store "$HOME" \
|
nix-unit --eval-store "$HOME" \
|
||||||
--extra-experimental-features flakes \
|
--extra-experimental-features flakes \
|
||||||
${inputOverrides} \
|
${inputOverrides} \
|
||||||
--flake ${self}#legacyPackages.${system}.evalTests-inventory
|
--flake ${self}#legacyPackages.${system}.evalTests-values
|
||||||
|
|
||||||
touch $out
|
touch $out
|
||||||
'';
|
'';
|
||||||
|
|||||||
@@ -82,43 +82,47 @@ class Webview:
|
|||||||
method_name: str = name,
|
method_name: str = name,
|
||||||
) -> None:
|
) -> None:
|
||||||
def thread_task() -> None:
|
def thread_task() -> None:
|
||||||
args = json.loads(req.decode())
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
log.debug(f"Calling {method_name}({args[0]})")
|
args = json.loads(req.decode())
|
||||||
# Initialize dataclasses from the payload
|
|
||||||
reconciled_arguments = {}
|
|
||||||
for k, v in args[0].items():
|
|
||||||
# Some functions expect to be called with dataclass instances
|
|
||||||
# But the js api returns dictionaries.
|
|
||||||
# Introspect the function and create the expected dataclass from dict dynamically
|
|
||||||
# Depending on the introspected argument_type
|
|
||||||
arg_class = api.get_method_argtype(method_name, k)
|
|
||||||
|
|
||||||
# TODO: rename from_dict into something like construct_checked_value
|
try:
|
||||||
# from_dict really takes Anything and returns an instance of the type/class
|
log.debug(f"Calling {method_name}({args[0]})")
|
||||||
reconciled_arguments[k] = from_dict(arg_class, v)
|
# Initialize dataclasses from the payload
|
||||||
|
reconciled_arguments = {}
|
||||||
|
for k, v in args[0].items():
|
||||||
|
# Some functions expect to be called with dataclass instances
|
||||||
|
# But the js api returns dictionaries.
|
||||||
|
# Introspect the function and create the expected dataclass from dict dynamically
|
||||||
|
# Depending on the introspected argument_type
|
||||||
|
arg_class = api.get_method_argtype(method_name, k)
|
||||||
|
|
||||||
reconciled_arguments["op_key"] = seq.decode()
|
# TODO: rename from_dict into something like construct_checked_value
|
||||||
# TODO: We could remove the wrapper in the MethodRegistry
|
# from_dict really takes Anything and returns an instance of the type/class
|
||||||
# and just call the method directly
|
reconciled_arguments[k] = from_dict(arg_class, v)
|
||||||
result = wrap_method(**reconciled_arguments)
|
|
||||||
success = True
|
reconciled_arguments["op_key"] = seq.decode()
|
||||||
|
# TODO: We could remove the wrapper in the MethodRegistry
|
||||||
|
# and just call the method directly
|
||||||
|
result = wrap_method(**reconciled_arguments)
|
||||||
|
success = True
|
||||||
|
except Exception as e:
|
||||||
|
log.exception(f"Error calling {method_name}")
|
||||||
|
result = str(e)
|
||||||
|
success = False
|
||||||
|
|
||||||
|
try:
|
||||||
|
serialized = json.dumps(
|
||||||
|
dataclass_to_dict(result), indent=4, ensure_ascii=False
|
||||||
|
)
|
||||||
|
except TypeError:
|
||||||
|
log.exception(f"Error serializing result for {method_name}")
|
||||||
|
raise
|
||||||
|
|
||||||
|
log.debug(f"Result for {method_name}: {serialized}")
|
||||||
|
self.return_(seq.decode(), 0 if success else 1, serialized)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
log.exception(f"Error calling {method_name}")
|
log.exception(f"Unhandled error in webview {method_name}")
|
||||||
result = str(e)
|
self.return_(seq.decode(), 1, str(e))
|
||||||
success = False
|
|
||||||
|
|
||||||
try:
|
|
||||||
serialized = json.dumps(
|
|
||||||
dataclass_to_dict(result), indent=4, ensure_ascii=False
|
|
||||||
)
|
|
||||||
except TypeError:
|
|
||||||
log.exception(f"Error serializing result for {method_name}")
|
|
||||||
raise
|
|
||||||
|
|
||||||
log.debug(f"Result for {method_name}: {serialized}")
|
|
||||||
self.return_(seq.decode(), 0 if success else 1, serialized)
|
|
||||||
|
|
||||||
thread = threading.Thread(target=thread_task)
|
thread = threading.Thread(target=thread_task)
|
||||||
thread.start()
|
thread.start()
|
||||||
|
|||||||
@@ -260,7 +260,11 @@ def _ask_prompts(
|
|||||||
prompt_values: dict[str, str] = {}
|
prompt_values: dict[str, str] = {}
|
||||||
for prompt in generator.prompts:
|
for prompt in generator.prompts:
|
||||||
var_id = f"{generator.name}/{prompt.name}"
|
var_id = f"{generator.name}/{prompt.name}"
|
||||||
prompt_values[prompt.name] = ask(var_id, prompt.prompt_type)
|
prompt_values[prompt.name] = ask(
|
||||||
|
var_id,
|
||||||
|
prompt.prompt_type,
|
||||||
|
prompt.description if prompt.description != prompt.name else None,
|
||||||
|
)
|
||||||
return prompt_values
|
return prompt_values
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import argparse
|
|||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from clan_cli.api import API
|
||||||
from clan_cli.clan_uri import FlakeId
|
from clan_cli.clan_uri import FlakeId
|
||||||
from clan_cli.completions import add_dynamic_completer, complete_machines
|
from clan_cli.completions import add_dynamic_completer, complete_machines
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
from clan_cli.machines.machines import Machine
|
|
||||||
|
|
||||||
from .generate import Var
|
from .generate import Var
|
||||||
from .list import get_vars
|
from .list import get_vars
|
||||||
@@ -13,8 +13,9 @@ from .list import get_vars
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_var(machine: Machine, var_id: str) -> Var:
|
@API.register
|
||||||
vars_ = get_vars(machine)
|
def get_var(base_dir: str, machine_name: str, var_id: str) -> Var:
|
||||||
|
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:
|
||||||
@@ -42,8 +43,7 @@ def get_var(machine: Machine, var_id: str) -> Var:
|
|||||||
|
|
||||||
|
|
||||||
def get_command(machine_name: str, var_id: str, flake: FlakeId) -> None:
|
def get_command(machine_name: str, var_id: str, flake: FlakeId) -> None:
|
||||||
machine = Machine(name=machine_name, flake=flake)
|
var = get_var(str(flake.path), machine_name, var_id)
|
||||||
var = get_var(machine, 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)
|
||||||
|
|||||||
@@ -25,7 +25,9 @@ def secret_store(machine: Machine) -> StoreBase:
|
|||||||
return secret_vars_module.SecretStore(machine=machine)
|
return secret_vars_module.SecretStore(machine=machine)
|
||||||
|
|
||||||
|
|
||||||
def get_vars(machine: Machine) -> list[Var]:
|
@API.register
|
||||||
|
def get_vars(base_dir: str, machine_name: str) -> list[Var]:
|
||||||
|
machine = Machine(name=machine_name, flake=FlakeId(base_dir))
|
||||||
pub_store = public_store(machine)
|
pub_store = public_store(machine)
|
||||||
sec_store = secret_store(machine)
|
sec_store = secret_store(machine)
|
||||||
all_vars = []
|
all_vars = []
|
||||||
@@ -58,7 +60,7 @@ def _get_previous_value(
|
|||||||
|
|
||||||
|
|
||||||
@API.register
|
@API.register
|
||||||
def get_prompts(base_dir: str, machine_name: str) -> list[Generator]:
|
def get_generators(base_dir: str, machine_name: str) -> list[Generator]:
|
||||||
machine = Machine(name=machine_name, flake=FlakeId(base_dir))
|
machine = Machine(name=machine_name, flake=FlakeId(base_dir))
|
||||||
generators: list[Generator] = machine.vars_generators
|
generators: list[Generator] = machine.vars_generators
|
||||||
for generator in generators:
|
for generator in generators:
|
||||||
@@ -96,7 +98,7 @@ def stringify_vars(_vars: list[Var]) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def stringify_all_vars(machine: Machine) -> str:
|
def stringify_all_vars(machine: Machine) -> str:
|
||||||
return stringify_vars(get_vars(machine))
|
return stringify_vars(get_vars(str(machine.flake), machine.name))
|
||||||
|
|
||||||
|
|
||||||
def list_command(args: argparse.Namespace) -> None:
|
def list_command(args: argparse.Namespace) -> None:
|
||||||
|
|||||||
@@ -37,16 +37,25 @@ class Prompt:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def ask(description: str, input_type: PromptType) -> str:
|
def ask(
|
||||||
|
ident: str,
|
||||||
|
input_type: PromptType,
|
||||||
|
label: str | None,
|
||||||
|
) -> str:
|
||||||
|
text = f"Enter the value for {ident}:"
|
||||||
|
if label:
|
||||||
|
text = f"{label}"
|
||||||
|
|
||||||
if MOCK_PROMPT_RESPONSE:
|
if MOCK_PROMPT_RESPONSE:
|
||||||
return next(MOCK_PROMPT_RESPONSE)
|
return next(MOCK_PROMPT_RESPONSE)
|
||||||
match input_type:
|
match input_type:
|
||||||
case PromptType.LINE:
|
case PromptType.LINE:
|
||||||
result = input(f"Enter the value for {description}: ")
|
result = input(f"{text}: ")
|
||||||
case PromptType.MULTILINE:
|
case PromptType.MULTILINE:
|
||||||
print(f"Enter the value for {description} (Finish with Ctrl-D): ")
|
print(f"{text} (Finish with Ctrl-D): ")
|
||||||
result = sys.stdin.read()
|
result = sys.stdin.read()
|
||||||
case PromptType.HIDDEN:
|
case PromptType.HIDDEN:
|
||||||
result = getpass(f"Enter the value for {description} (hidden): ")
|
result = getpass(f"{text} (hidden): ")
|
||||||
|
|
||||||
log.info("Input received. Processing...")
|
log.info("Input received. Processing...")
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ def set_var(
|
|||||||
else:
|
else:
|
||||||
_machine = machine
|
_machine = machine
|
||||||
if isinstance(var, str):
|
if isinstance(var, str):
|
||||||
_var = get_var(_machine, var)
|
_var = get_var(str(flake.path), _machine.name, var)
|
||||||
else:
|
else:
|
||||||
_var = var
|
_var = var
|
||||||
path = _var.set(value)
|
path = _var.set(value)
|
||||||
@@ -36,12 +36,17 @@ def set_var(
|
|||||||
|
|
||||||
|
|
||||||
def set_via_stdin(machine: str, var_id: str, flake: FlakeId) -> None:
|
def set_via_stdin(machine: str, var_id: str, flake: FlakeId) -> None:
|
||||||
_machine = Machine(name=machine, flake=flake)
|
var = get_var(str(flake.path), machine, var_id)
|
||||||
var = get_var(_machine, var_id)
|
|
||||||
if sys.stdin.isatty():
|
if sys.stdin.isatty():
|
||||||
new_value = ask(var.id, PromptType.HIDDEN).encode("utf-8")
|
new_value = ask(
|
||||||
|
var.id,
|
||||||
|
PromptType.HIDDEN,
|
||||||
|
None,
|
||||||
|
).encode("utf-8")
|
||||||
else:
|
else:
|
||||||
new_value = sys.stdin.buffer.read()
|
new_value = sys.stdin.buffer.read()
|
||||||
|
|
||||||
|
_machine = Machine(name=machine, flake=flake)
|
||||||
set_var(_machine, var, new_value, flake)
|
set_var(_machine, var, new_value, flake)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -170,9 +170,16 @@ def test_generate_public_and_secret_vars(
|
|||||||
"Update vars via generator my_shared_generator for machine my_machine"
|
"Update vars via generator my_shared_generator for machine my_machine"
|
||||||
in commit_message
|
in commit_message
|
||||||
)
|
)
|
||||||
assert get_var(machine, "my_generator/my_value").printable_value == "public"
|
|
||||||
assert (
|
assert (
|
||||||
get_var(machine, "my_shared_generator/my_shared_value").printable_value
|
get_var(
|
||||||
|
str(machine.flake.path), machine.name, "my_generator/my_value"
|
||||||
|
).printable_value
|
||||||
|
== "public"
|
||||||
|
)
|
||||||
|
assert (
|
||||||
|
get_var(
|
||||||
|
str(machine.flake.path), machine.name, "my_shared_generator/my_shared_value"
|
||||||
|
).printable_value
|
||||||
== "shared"
|
== "shared"
|
||||||
)
|
)
|
||||||
vars_text = stringify_all_vars(machine)
|
vars_text = stringify_all_vars(machine)
|
||||||
@@ -587,7 +594,7 @@ def test_api_set_prompts(
|
|||||||
flake: ClanFlake,
|
flake: ClanFlake,
|
||||||
) -> None:
|
) -> None:
|
||||||
from clan_cli.vars._types import GeneratorUpdate
|
from clan_cli.vars._types import GeneratorUpdate
|
||||||
from clan_cli.vars.list import get_prompts, set_prompts
|
from clan_cli.vars.list import get_generators, set_prompts
|
||||||
|
|
||||||
config = flake.machines["my_machine"]
|
config = flake.machines["my_machine"]
|
||||||
config["nixpkgs"]["hostPlatform"] = "x86_64-linux"
|
config["nixpkgs"]["hostPlatform"] = "x86_64-linux"
|
||||||
@@ -623,11 +630,11 @@ def test_api_set_prompts(
|
|||||||
)
|
)
|
||||||
assert store.get(Generator("my_generator"), "prompt1").decode() == "input2"
|
assert store.get(Generator("my_generator"), "prompt1").decode() == "input2"
|
||||||
|
|
||||||
api_prompts = get_prompts(**params)
|
generators = get_generators(**params)
|
||||||
assert len(api_prompts) == 1
|
assert len(generators) == 1
|
||||||
assert api_prompts[0].name == "my_generator"
|
assert generators[0].name == "my_generator"
|
||||||
assert api_prompts[0].prompts[0].name == "prompt1"
|
assert generators[0].prompts[0].name == "prompt1"
|
||||||
assert api_prompts[0].prompts[0].previous_value == "input2"
|
assert generators[0].prompts[0].previous_value == "input2"
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.with_core
|
@pytest.mark.with_core
|
||||||
@@ -843,19 +850,27 @@ 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=FlakeId(str(flake.path)))
|
machine = Machine(name="my_machine", flake=FlakeId(str(flake.path)))
|
||||||
value1 = get_var(machine, "my_generator/my_value").printable_value
|
value1 = get_var(
|
||||||
|
str(machine.flake.path), machine.name, "my_generator/my_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(machine, "my_generator/my_value").printable_value
|
value1_new = get_var(
|
||||||
|
str(machine.flake.path), machine.name, "my_generator/my_value"
|
||||||
|
).printable_value
|
||||||
assert value1 == value1_new
|
assert value1 == value1_new
|
||||||
# set the invalidation data of the generator
|
# set the invalidation data of the generator
|
||||||
my_generator["validation"] = 1
|
my_generator["validation"] = 1
|
||||||
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(machine, "my_generator/my_value").printable_value
|
value2 = get_var(
|
||||||
|
str(machine.flake.path), machine.name, "my_generator/my_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(machine, "my_generator/my_value").printable_value
|
value2_new = get_var(
|
||||||
|
str(machine.flake.path), machine.name, "my_generator/my_value"
|
||||||
|
).printable_value
|
||||||
assert value2 == value2_new
|
assert value2 == value2_new
|
||||||
|
|||||||
Reference in New Issue
Block a user