vars/generate: improve output when vars are updated
fixes #2076 - print old and new value if possible - also inform the user if something hasn't changed
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
import logging
|
||||||
import shutil
|
import shutil
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
@@ -5,6 +6,15 @@ from pathlib import Path
|
|||||||
|
|
||||||
from clan_cli.machines import machines
|
from clan_cli.machines import machines
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def string_repr(value: bytes) -> str:
|
||||||
|
try:
|
||||||
|
return value.decode()
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
return "<binary blob>"
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Prompt:
|
class Prompt:
|
||||||
@@ -50,10 +60,7 @@ class Var:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def printable_value(self) -> str:
|
def printable_value(self) -> str:
|
||||||
try:
|
return string_repr(self.value)
|
||||||
return self.value.decode()
|
|
||||||
except UnicodeDecodeError:
|
|
||||||
return "<binary blob>"
|
|
||||||
|
|
||||||
def set(self, value: bytes) -> None:
|
def set(self, value: bytes) -> None:
|
||||||
self._store.set(self.generator, self.name, value, self.shared, self.deployed)
|
self._store.set(self.generator, self.name, value, self.shared, self.deployed)
|
||||||
@@ -128,6 +135,16 @@ class StoreBase(ABC):
|
|||||||
shared: bool = False,
|
shared: bool = False,
|
||||||
deployed: bool = True,
|
deployed: bool = True,
|
||||||
) -> Path | None:
|
) -> Path | None:
|
||||||
|
if self.exists(generator_name, var_name, shared):
|
||||||
|
if self.is_secret_store:
|
||||||
|
old_val = None
|
||||||
|
old_val_str = "********"
|
||||||
|
else:
|
||||||
|
old_val = self.get(generator_name, var_name, shared)
|
||||||
|
old_val_str = string_repr(old_val)
|
||||||
|
else:
|
||||||
|
old_val = None
|
||||||
|
old_val_str = "<not set>"
|
||||||
directory = self.directory(generator_name, var_name, shared)
|
directory = self.directory(generator_name, var_name, shared)
|
||||||
# delete directory
|
# delete directory
|
||||||
if directory.exists():
|
if directory.exists():
|
||||||
@@ -135,6 +152,19 @@ class StoreBase(ABC):
|
|||||||
# re-create directory
|
# re-create directory
|
||||||
directory.mkdir(parents=True, exist_ok=True)
|
directory.mkdir(parents=True, exist_ok=True)
|
||||||
new_file = self._set(generator_name, var_name, value, shared, deployed)
|
new_file = self._set(generator_name, var_name, value, shared, deployed)
|
||||||
|
if self.is_secret_store:
|
||||||
|
print(f"Updated secret var {generator_name}/{var_name}\n")
|
||||||
|
else:
|
||||||
|
if value != old_val:
|
||||||
|
print(
|
||||||
|
f"Updated var {generator_name}/{var_name}\n"
|
||||||
|
f" old: {old_val_str}\n"
|
||||||
|
f" new: {string_repr(value)}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
f"Var {generator_name}/{var_name} remains unchanged: {old_val_str}"
|
||||||
|
)
|
||||||
return new_file
|
return new_file
|
||||||
|
|
||||||
def get_all(self) -> list[Var]:
|
def get_all(self) -> list[Var]:
|
||||||
|
|||||||
@@ -223,7 +223,7 @@ def get_closure(
|
|||||||
return minimal_closure([generator_name], generators)
|
return minimal_closure([generator_name], generators)
|
||||||
|
|
||||||
|
|
||||||
def _generate_vars_for_machine(
|
def generate_vars_for_machine(
|
||||||
machine: Machine,
|
machine: Machine,
|
||||||
generator_name: str | None,
|
generator_name: str | None,
|
||||||
regenerate: bool,
|
regenerate: bool,
|
||||||
@@ -254,7 +254,7 @@ def generate_vars(
|
|||||||
for machine in machines:
|
for machine in machines:
|
||||||
errors = []
|
errors = []
|
||||||
try:
|
try:
|
||||||
was_regenerated |= _generate_vars_for_machine(
|
was_regenerated |= generate_vars_for_machine(
|
||||||
machine, generator_name, regenerate
|
machine, generator_name, regenerate
|
||||||
)
|
)
|
||||||
machine.flush_caches()
|
machine.flush_caches()
|
||||||
|
|||||||
@@ -7,23 +7,38 @@ from clan_cli.completions import add_dynamic_completer, complete_machines
|
|||||||
from clan_cli.machines.machines import Machine
|
from clan_cli.machines.machines import Machine
|
||||||
from clan_cli.vars.get import get_var
|
from clan_cli.vars.get import get_var
|
||||||
|
|
||||||
|
from ._types import Var
|
||||||
from .prompt import ask
|
from .prompt import ask
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def set_command(machine: str, var_id: str, flake: FlakeId) -> None:
|
def set_var(
|
||||||
|
machine: str | Machine, var: str | Var, value: bytes, flake: FlakeId
|
||||||
|
) -> None:
|
||||||
|
if isinstance(machine, str):
|
||||||
|
_machine = Machine(name=machine, flake=flake)
|
||||||
|
else:
|
||||||
|
_machine = machine
|
||||||
|
if isinstance(var, str):
|
||||||
|
_var = get_var(_machine, var)
|
||||||
|
else:
|
||||||
|
_var = var
|
||||||
|
_var.set(value)
|
||||||
|
|
||||||
|
|
||||||
|
def set_via_stdin(machine: str, var_id: str, flake: FlakeId) -> None:
|
||||||
_machine = Machine(name=machine, flake=flake)
|
_machine = Machine(name=machine, flake=flake)
|
||||||
var = get_var(_machine, var_id)
|
var = get_var(_machine, var_id)
|
||||||
if sys.stdin.isatty():
|
if sys.stdin.isatty():
|
||||||
new_value = ask(var.id, "hidden").encode("utf-8")
|
new_value = ask(var.id, "hidden").encode("utf-8")
|
||||||
else:
|
else:
|
||||||
new_value = sys.stdin.buffer.read()
|
new_value = sys.stdin.buffer.read()
|
||||||
var.set(new_value)
|
set_var(_machine, var, new_value, flake)
|
||||||
|
|
||||||
|
|
||||||
def _set_command(args: argparse.Namespace) -> None:
|
def _set_command(args: argparse.Namespace) -> None:
|
||||||
set_command(args.machine, args.var_id, args.flake)
|
set_via_stdin(args.machine, args.var_id, args.flake)
|
||||||
|
|
||||||
|
|
||||||
def register_set_parser(parser: argparse.ArgumentParser) -> None:
|
def register_set_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
|
|||||||
@@ -14,10 +14,12 @@ from clan_cli.vars.check import check_vars
|
|||||||
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
|
||||||
from clan_cli.vars.secret_modules import password_store, sops
|
from clan_cli.vars.secret_modules import password_store, sops
|
||||||
|
from clan_cli.vars.set import set_var
|
||||||
from fixtures_flakes import generate_flake
|
from fixtures_flakes import generate_flake
|
||||||
from helpers import cli
|
from helpers import cli
|
||||||
from helpers.nixos_config import nested_dict
|
from helpers.nixos_config import nested_dict
|
||||||
from root import CLAN_CORE
|
from root import CLAN_CORE
|
||||||
|
from stdout import CaptureOutput
|
||||||
|
|
||||||
|
|
||||||
def test_dependencies_as_files() -> None:
|
def test_dependencies_as_files() -> None:
|
||||||
@@ -636,3 +638,81 @@ def test_default_value(
|
|||||||
)
|
)
|
||||||
).stdout.strip()
|
).stdout.strip()
|
||||||
assert json.loads(value_eval) == "hello"
|
assert json.loads(value_eval) == "hello"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.impure
|
||||||
|
def test_stdout_of_generate(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
temporary_home: Path,
|
||||||
|
capture_output: CaptureOutput,
|
||||||
|
) -> None:
|
||||||
|
config = nested_dict()
|
||||||
|
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
||||||
|
my_generator["files"]["my_value"]["secret"] = False
|
||||||
|
my_generator["script"] = "echo -n hello > $out/my_value"
|
||||||
|
my_secret_generator = config["clan"]["core"]["vars"]["generators"][
|
||||||
|
"my_secret_generator"
|
||||||
|
]
|
||||||
|
my_secret_generator["files"]["my_secret"]["secret"] = True
|
||||||
|
my_secret_generator["script"] = "echo -n hello > $out/my_secret"
|
||||||
|
flake = generate_flake(
|
||||||
|
temporary_home,
|
||||||
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
machine_configs={"my_machine": config},
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
|
)
|
||||||
|
monkeypatch.chdir(flake.path)
|
||||||
|
from clan_cli.vars.generate import generate_vars_for_machine
|
||||||
|
|
||||||
|
with capture_output as output:
|
||||||
|
generate_vars_for_machine(
|
||||||
|
Machine(name="my_machine", flake=FlakeId(str(flake.path))),
|
||||||
|
"my_generator",
|
||||||
|
regenerate=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert "Updated var my_generator/my_value" in output.out
|
||||||
|
assert "old: <not set>" in output.out
|
||||||
|
assert "new: hello" in output.out
|
||||||
|
set_var("my_machine", "my_generator/my_value", b"world", FlakeId(str(flake.path)))
|
||||||
|
with capture_output as output:
|
||||||
|
generate_vars_for_machine(
|
||||||
|
Machine(name="my_machine", flake=FlakeId(str(flake.path))),
|
||||||
|
"my_generator",
|
||||||
|
regenerate=True,
|
||||||
|
)
|
||||||
|
assert "Updated var my_generator/my_value" in output.out
|
||||||
|
assert "old: world" in output.out
|
||||||
|
assert "new: hello" in output.out
|
||||||
|
# check the output when nothing gets regenerated
|
||||||
|
with capture_output as output:
|
||||||
|
generate_vars_for_machine(
|
||||||
|
Machine(name="my_machine", flake=FlakeId(str(flake.path))),
|
||||||
|
"my_generator",
|
||||||
|
regenerate=True,
|
||||||
|
)
|
||||||
|
assert "Updated" not in output.out
|
||||||
|
assert "hello" in output.out
|
||||||
|
with capture_output as output:
|
||||||
|
generate_vars_for_machine(
|
||||||
|
Machine(name="my_machine", flake=FlakeId(str(flake.path))),
|
||||||
|
"my_secret_generator",
|
||||||
|
regenerate=False,
|
||||||
|
)
|
||||||
|
assert "Updated secret var my_secret_generator/my_secret" in output.out
|
||||||
|
assert "hello" not in output.out
|
||||||
|
set_var(
|
||||||
|
"my_machine",
|
||||||
|
"my_secret_generator/my_secret",
|
||||||
|
b"world",
|
||||||
|
FlakeId(str(flake.path)),
|
||||||
|
)
|
||||||
|
with capture_output as output:
|
||||||
|
generate_vars_for_machine(
|
||||||
|
Machine(name="my_machine", flake=FlakeId(str(flake.path))),
|
||||||
|
"my_secret_generator",
|
||||||
|
regenerate=True,
|
||||||
|
)
|
||||||
|
assert "Updated secret var my_secret_generator/my_secret" in output.out
|
||||||
|
assert "world" not in output.out
|
||||||
|
assert "hello" not in output.out
|
||||||
|
|||||||
Reference in New Issue
Block a user