Added new type FlakeName
This commit is contained in:
@@ -11,6 +11,7 @@ from typing import Any, Optional, Tuple, get_origin
|
|||||||
|
|
||||||
from clan_cli.dirs import machine_settings_file, specific_flake_dir
|
from clan_cli.dirs import machine_settings_file, specific_flake_dir
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
|
from clan_cli.flakes.types import FlakeName
|
||||||
from clan_cli.git import commit_file
|
from clan_cli.git import commit_file
|
||||||
from clan_cli.nix import nix_eval
|
from clan_cli.nix import nix_eval
|
||||||
|
|
||||||
@@ -104,7 +105,7 @@ def cast(value: Any, type: Any, opt_description: str) -> Any:
|
|||||||
|
|
||||||
|
|
||||||
def options_for_machine(
|
def options_for_machine(
|
||||||
flake_name: str, machine_name: str, show_trace: bool = False
|
flake_name: FlakeName, machine_name: str, show_trace: bool = False
|
||||||
) -> dict:
|
) -> dict:
|
||||||
clan_dir = specific_flake_dir(flake_name)
|
clan_dir = specific_flake_dir(flake_name)
|
||||||
flags = []
|
flags = []
|
||||||
@@ -127,7 +128,7 @@ def options_for_machine(
|
|||||||
|
|
||||||
|
|
||||||
def read_machine_option_value(
|
def read_machine_option_value(
|
||||||
flake_name: str, machine_name: str, option: str, show_trace: bool = False
|
flake_name: FlakeName, machine_name: str, option: str, show_trace: bool = False
|
||||||
) -> str:
|
) -> str:
|
||||||
clan_dir = specific_flake_dir(flake_name)
|
clan_dir = specific_flake_dir(flake_name)
|
||||||
# use nix eval to read from .#nixosConfigurations.default.config.{option}
|
# use nix eval to read from .#nixosConfigurations.default.config.{option}
|
||||||
@@ -240,7 +241,7 @@ def find_option(
|
|||||||
|
|
||||||
|
|
||||||
def set_option(
|
def set_option(
|
||||||
flake_name: str,
|
flake_name: FlakeName,
|
||||||
option: str,
|
option: str,
|
||||||
value: Any,
|
value: Any,
|
||||||
options: dict,
|
options: dict,
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ from clan_cli.dirs import (
|
|||||||
from clan_cli.git import commit_file, find_git_repo_root
|
from clan_cli.git import commit_file, find_git_repo_root
|
||||||
from clan_cli.nix import nix_eval
|
from clan_cli.nix import nix_eval
|
||||||
|
|
||||||
|
from ..flakes.types import FlakeName
|
||||||
|
|
||||||
|
|
||||||
def verify_machine_config(
|
def verify_machine_config(
|
||||||
machine_name: str, config: Optional[dict] = None, flake: Optional[Path] = None
|
machine_name: str, config: Optional[dict] = None, flake: Optional[Path] = None
|
||||||
@@ -52,7 +54,8 @@ def verify_machine_config(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def config_for_machine(machine_name: str) -> dict:
|
|
||||||
|
def config_for_machine(flake_name: FlakeName, machine_name: str) -> dict:
|
||||||
# read the config from a json file located at {flake}/machines/{machine_name}/settings.json
|
# read the config from a json file located at {flake}/machines/{machine_name}/settings.json
|
||||||
if not specific_machine_dir(flake_name, machine_name).exists():
|
if not specific_machine_dir(flake_name, machine_name).exists():
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@@ -66,7 +69,9 @@ def config_for_machine(machine_name: str) -> dict:
|
|||||||
return json.load(f)
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
def set_config_for_machine(flake_name: str, machine_name: str, config: dict) -> None:
|
def set_config_for_machine(
|
||||||
|
flake_name: FlakeName, machine_name: str, config: dict
|
||||||
|
) -> None:
|
||||||
# write the config to a json file located at {flake}/machines/{machine_name}/settings.json
|
# write the config to a json file located at {flake}/machines/{machine_name}/settings.json
|
||||||
if not specific_machine_dir(flake_name, machine_name).exists():
|
if not specific_machine_dir(flake_name, machine_name).exists():
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
@@ -83,7 +88,7 @@ def set_config_for_machine(flake_name: str, machine_name: str, config: dict) ->
|
|||||||
commit_file(settings_path, repo_dir)
|
commit_file(settings_path, repo_dir)
|
||||||
|
|
||||||
|
|
||||||
def schema_for_machine(flake_name: str, machine_name: str) -> dict:
|
def schema_for_machine(flake_name: FlakeName, machine_name: str) -> dict:
|
||||||
flake = specific_flake_dir(flake_name)
|
flake = specific_flake_dir(flake_name)
|
||||||
|
|
||||||
# use nix eval to lib.evalModules .#nixosModules.machine-{machine_name}
|
# use nix eval to lib.evalModules .#nixosModules.machine-{machine_name}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from pathlib import Path
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from .errors import ClanError
|
from .errors import ClanError
|
||||||
|
from .flakes.types import FlakeName
|
||||||
|
|
||||||
|
|
||||||
def _get_clan_flake_toplevel() -> Path:
|
def _get_clan_flake_toplevel() -> Path:
|
||||||
@@ -68,22 +69,22 @@ def clan_flakes_dir() -> Path:
|
|||||||
return path.resolve()
|
return path.resolve()
|
||||||
|
|
||||||
|
|
||||||
def specific_flake_dir(name: str) -> Path:
|
def specific_flake_dir(flake_name: FlakeName) -> Path:
|
||||||
flake_dir = clan_flakes_dir() / name
|
flake_dir = clan_flakes_dir() / flake_name
|
||||||
if not flake_dir.exists():
|
if not flake_dir.exists():
|
||||||
raise ClanError(f"Flake {name} does not exist")
|
raise ClanError(f"Flake {flake_name} does not exist")
|
||||||
return flake_dir
|
return flake_dir
|
||||||
|
|
||||||
|
|
||||||
def machines_dir(flake_name: str) -> Path:
|
def machines_dir(flake_name: FlakeName) -> Path:
|
||||||
return specific_flake_dir(flake_name) / "machines"
|
return specific_flake_dir(flake_name) / "machines"
|
||||||
|
|
||||||
|
|
||||||
def specific_machine_dir(flake_name: str, machine: str) -> Path:
|
def specific_machine_dir(flake_name: FlakeName, machine: str) -> Path:
|
||||||
return machines_dir(flake_name) / machine
|
return machines_dir(flake_name) / machine
|
||||||
|
|
||||||
|
|
||||||
def machine_settings_file(flake_name: str, machine: str) -> Path:
|
def machine_settings_file(flake_name: FlakeName, machine: str) -> Path:
|
||||||
return specific_machine_dir(flake_name, machine) / "settings.json"
|
return specific_machine_dir(flake_name, machine) / "settings.json"
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
3
pkgs/clan-cli/clan_cli/flakes/types.py
Normal file
3
pkgs/clan-cli/clan_cli/flakes/types.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from typing import NewType
|
||||||
|
|
||||||
|
FlakeName = NewType("FlakeName", str)
|
||||||
@@ -5,12 +5,13 @@ from typing import Dict
|
|||||||
from ..async_cmd import CmdOut, run, runforcli
|
from ..async_cmd import CmdOut, run, runforcli
|
||||||
from ..dirs import specific_flake_dir, specific_machine_dir
|
from ..dirs import specific_flake_dir, specific_machine_dir
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
|
from ..flakes.types import FlakeName
|
||||||
from ..nix import nix_shell
|
from ..nix import nix_shell
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def create_machine(flake_name: str, machine_name: str) -> Dict[str, CmdOut]:
|
async def create_machine(flake_name: FlakeName, machine_name: str) -> Dict[str, CmdOut]:
|
||||||
folder = specific_machine_dir(flake_name, machine_name)
|
folder = specific_machine_dir(flake_name, machine_name)
|
||||||
folder.mkdir(parents=True, exist_ok=True)
|
folder.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
from ..dirs import specific_machine_dir
|
from ..dirs import specific_machine_dir
|
||||||
|
from ..flakes.types import FlakeName
|
||||||
|
|
||||||
|
|
||||||
def machine_has_fact(flake_name: str, machine: str, fact: str) -> bool:
|
def machine_has_fact(flake_name: FlakeName, machine: str, fact: str) -> bool:
|
||||||
return (specific_machine_dir(flake_name, machine) / "facts" / fact).exists()
|
return (specific_machine_dir(flake_name, machine) / "facts" / fact).exists()
|
||||||
|
|
||||||
|
|
||||||
def machine_get_fact(flake_name: str, machine: str, fact: str) -> str:
|
def machine_get_fact(flake_name: FlakeName, machine: str, fact: str) -> str:
|
||||||
return (specific_machine_dir(flake_name, machine) / "facts" / fact).read_text()
|
return (specific_machine_dir(flake_name, machine) / "facts" / fact).read_text()
|
||||||
|
|||||||
@@ -3,12 +3,13 @@ import logging
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
from ..dirs import machines_dir
|
from ..dirs import machines_dir
|
||||||
|
from ..flakes.types import FlakeName
|
||||||
from .types import validate_hostname
|
from .types import validate_hostname
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def list_machines(flake_name: str) -> list[str]:
|
def list_machines(flake_name: FlakeName) -> list[str]:
|
||||||
path = machines_dir(flake_name)
|
path = machines_dir(flake_name)
|
||||||
log.debug(f"Listing machines in {path}")
|
log.debug(f"Listing machines in {path}")
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
|
|||||||
@@ -5,14 +5,15 @@ from typing import Callable
|
|||||||
|
|
||||||
from ..dirs import specific_flake_dir
|
from ..dirs import specific_flake_dir
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
|
from ..flakes.types import FlakeName
|
||||||
|
|
||||||
|
|
||||||
def get_sops_folder(flake_name: str) -> Path:
|
def get_sops_folder(flake_name: FlakeName) -> Path:
|
||||||
return specific_flake_dir(flake_name) / "sops"
|
return specific_flake_dir(flake_name) / "sops"
|
||||||
|
|
||||||
|
|
||||||
def gen_sops_subfolder(subdir: str) -> Callable[[str], Path]:
|
def gen_sops_subfolder(subdir: str) -> Callable[[FlakeName], Path]:
|
||||||
def folder(flake_name: str) -> Path:
|
def folder(flake_name: FlakeName) -> Path:
|
||||||
return specific_flake_dir(flake_name) / "sops" / subdir
|
return specific_flake_dir(flake_name) / "sops" / subdir
|
||||||
|
|
||||||
return folder
|
return folder
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import os
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
|
from ..flakes.types import FlakeName
|
||||||
from ..machines.types import machine_name_type, validate_hostname
|
from ..machines.types import machine_name_type, validate_hostname
|
||||||
from . import secrets
|
from . import secrets
|
||||||
from .folders import (
|
from .folders import (
|
||||||
@@ -20,17 +21,17 @@ from .types import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def machines_folder(flake_name: str, group: str) -> Path:
|
def machines_folder(flake_name: FlakeName, group: str) -> Path:
|
||||||
return sops_groups_folder(flake_name) / group / "machines"
|
return sops_groups_folder(flake_name) / group / "machines"
|
||||||
|
|
||||||
|
|
||||||
def users_folder(flake_name: str, group: str) -> Path:
|
def users_folder(flake_name: FlakeName, group: str) -> Path:
|
||||||
return sops_groups_folder(flake_name) / group / "users"
|
return sops_groups_folder(flake_name) / group / "users"
|
||||||
|
|
||||||
|
|
||||||
class Group:
|
class Group:
|
||||||
def __init__(
|
def __init__(
|
||||||
self, flake_name: str, name: str, machines: list[str], users: list[str]
|
self, flake_name: FlakeName, name: str, machines: list[str], users: list[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.machines = machines
|
self.machines = machines
|
||||||
@@ -38,7 +39,7 @@ class Group:
|
|||||||
self.flake_name = flake_name
|
self.flake_name = flake_name
|
||||||
|
|
||||||
|
|
||||||
def list_groups(flake_name: str) -> list[Group]:
|
def list_groups(flake_name: FlakeName) -> list[Group]:
|
||||||
groups: list[Group] = []
|
groups: list[Group] = []
|
||||||
folder = sops_groups_folder(flake_name)
|
folder = sops_groups_folder(flake_name)
|
||||||
if not folder.exists():
|
if not folder.exists():
|
||||||
@@ -87,7 +88,7 @@ def list_directory(directory: Path) -> str:
|
|||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
def update_group_keys(flake_name: str, group: str) -> None:
|
def update_group_keys(flake_name: FlakeName, group: str) -> None:
|
||||||
for secret_ in secrets.list_secrets(flake_name):
|
for secret_ in secrets.list_secrets(flake_name):
|
||||||
secret = sops_secrets_folder(flake_name) / secret_
|
secret = sops_secrets_folder(flake_name) / secret_
|
||||||
if (secret / "groups" / group).is_symlink():
|
if (secret / "groups" / group).is_symlink():
|
||||||
@@ -98,7 +99,7 @@ def update_group_keys(flake_name: str, group: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def add_member(
|
def add_member(
|
||||||
flake_name: str, group_folder: Path, source_folder: Path, name: str
|
flake_name: FlakeName, group_folder: Path, source_folder: Path, name: str
|
||||||
) -> None:
|
) -> None:
|
||||||
source = source_folder / name
|
source = source_folder / name
|
||||||
if not source.exists():
|
if not source.exists():
|
||||||
@@ -117,7 +118,7 @@ def add_member(
|
|||||||
update_group_keys(flake_name, group_folder.parent.name)
|
update_group_keys(flake_name, group_folder.parent.name)
|
||||||
|
|
||||||
|
|
||||||
def remove_member(flake_name: str, group_folder: Path, name: str) -> None:
|
def remove_member(flake_name: FlakeName, group_folder: Path, name: str) -> None:
|
||||||
target = group_folder / name
|
target = group_folder / name
|
||||||
if not target.exists():
|
if not target.exists():
|
||||||
msg = f"{name} does not exist in group in {group_folder}: "
|
msg = f"{name} does not exist in group in {group_folder}: "
|
||||||
@@ -135,7 +136,7 @@ def remove_member(flake_name: str, group_folder: Path, name: str) -> None:
|
|||||||
os.rmdir(group_folder.parent)
|
os.rmdir(group_folder.parent)
|
||||||
|
|
||||||
|
|
||||||
def add_user(flake_name: str, group: str, name: str) -> None:
|
def add_user(flake_name: FlakeName, group: str, name: str) -> None:
|
||||||
add_member(
|
add_member(
|
||||||
flake_name, users_folder(flake_name, group), sops_users_folder(flake_name), name
|
flake_name, users_folder(flake_name, group), sops_users_folder(flake_name), name
|
||||||
)
|
)
|
||||||
@@ -145,7 +146,7 @@ def add_user_command(args: argparse.Namespace) -> None:
|
|||||||
add_user(args.flake, args.group, args.user)
|
add_user(args.flake, args.group, args.user)
|
||||||
|
|
||||||
|
|
||||||
def remove_user(flake_name: str, group: str, name: str) -> None:
|
def remove_user(flake_name: FlakeName, group: str, name: str) -> None:
|
||||||
remove_member(flake_name, users_folder(flake_name, group), name)
|
remove_member(flake_name, users_folder(flake_name, group), name)
|
||||||
|
|
||||||
|
|
||||||
@@ -153,7 +154,7 @@ def remove_user_command(args: argparse.Namespace) -> None:
|
|||||||
remove_user(args.flake, args.group, args.user)
|
remove_user(args.flake, args.group, args.user)
|
||||||
|
|
||||||
|
|
||||||
def add_machine(flake_name: str, group: str, name: str) -> None:
|
def add_machine(flake_name: FlakeName, group: str, name: str) -> None:
|
||||||
add_member(
|
add_member(
|
||||||
flake_name,
|
flake_name,
|
||||||
machines_folder(flake_name, group),
|
machines_folder(flake_name, group),
|
||||||
@@ -166,7 +167,7 @@ def add_machine_command(args: argparse.Namespace) -> None:
|
|||||||
add_machine(args.flake, args.group, args.machine)
|
add_machine(args.flake, args.group, args.machine)
|
||||||
|
|
||||||
|
|
||||||
def remove_machine(flake_name: str, group: str, name: str) -> None:
|
def remove_machine(flake_name: FlakeName, group: str, name: str) -> None:
|
||||||
remove_member(flake_name, machines_folder(flake_name, group), name)
|
remove_member(flake_name, machines_folder(flake_name, group), name)
|
||||||
|
|
||||||
|
|
||||||
@@ -178,7 +179,7 @@ def add_group_argument(parser: argparse.ArgumentParser) -> None:
|
|||||||
parser.add_argument("group", help="the name of the secret", type=group_name_type)
|
parser.add_argument("group", help="the name of the secret", type=group_name_type)
|
||||||
|
|
||||||
|
|
||||||
def add_secret(flake_name: str, group: str, name: str) -> None:
|
def add_secret(flake_name: FlakeName, group: str, name: str) -> None:
|
||||||
secrets.allow_member(
|
secrets.allow_member(
|
||||||
secrets.groups_folder(flake_name, name), sops_groups_folder(flake_name), group
|
secrets.groups_folder(flake_name, name), sops_groups_folder(flake_name), group
|
||||||
)
|
)
|
||||||
@@ -188,7 +189,7 @@ def add_secret_command(args: argparse.Namespace) -> None:
|
|||||||
add_secret(args.flake, args.group, args.secret)
|
add_secret(args.flake, args.group, args.secret)
|
||||||
|
|
||||||
|
|
||||||
def remove_secret(flake_name: str, group: str, name: str) -> None:
|
def remove_secret(flake_name: FlakeName, group: str, name: str) -> None:
|
||||||
secrets.disallow_member(secrets.groups_folder(flake_name, name), group)
|
secrets.disallow_member(secrets.groups_folder(flake_name, name), group)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
from ..flakes.types import FlakeName
|
||||||
from ..machines.types import machine_name_type, validate_hostname
|
from ..machines.types import machine_name_type, validate_hostname
|
||||||
from . import secrets
|
from . import secrets
|
||||||
from .folders import list_objects, remove_object, sops_machines_folder
|
from .folders import list_objects, remove_object, sops_machines_folder
|
||||||
@@ -7,23 +8,23 @@ from .sops import read_key, write_key
|
|||||||
from .types import public_or_private_age_key_type, secret_name_type
|
from .types import public_or_private_age_key_type, secret_name_type
|
||||||
|
|
||||||
|
|
||||||
def add_machine(flake_name: str, name: str, key: str, force: bool) -> None:
|
def add_machine(flake_name: FlakeName, name: str, key: str, force: bool) -> None:
|
||||||
write_key(sops_machines_folder(flake_name) / name, key, force)
|
write_key(sops_machines_folder(flake_name) / name, key, force)
|
||||||
|
|
||||||
|
|
||||||
def remove_machine(flake_name: str, name: str) -> None:
|
def remove_machine(flake_name: FlakeName, name: str) -> None:
|
||||||
remove_object(sops_machines_folder(flake_name), name)
|
remove_object(sops_machines_folder(flake_name), name)
|
||||||
|
|
||||||
|
|
||||||
def get_machine(flake_name: str, name: str) -> str:
|
def get_machine(flake_name: FlakeName, name: str) -> str:
|
||||||
return read_key(sops_machines_folder(flake_name) / name)
|
return read_key(sops_machines_folder(flake_name) / name)
|
||||||
|
|
||||||
|
|
||||||
def has_machine(flake_name: str, name: str) -> bool:
|
def has_machine(flake_name: FlakeName, name: str) -> bool:
|
||||||
return (sops_machines_folder(flake_name) / name / "key.json").exists()
|
return (sops_machines_folder(flake_name) / name / "key.json").exists()
|
||||||
|
|
||||||
|
|
||||||
def list_machines(flake_name: str) -> list[str]:
|
def list_machines(flake_name: FlakeName) -> list[str]:
|
||||||
path = sops_machines_folder(flake_name)
|
path = sops_machines_folder(flake_name)
|
||||||
|
|
||||||
def validate(name: str) -> bool:
|
def validate(name: str) -> bool:
|
||||||
@@ -32,7 +33,7 @@ def list_machines(flake_name: str) -> list[str]:
|
|||||||
return list_objects(path, validate)
|
return list_objects(path, validate)
|
||||||
|
|
||||||
|
|
||||||
def add_secret(flake_name: str, machine: str, secret: str) -> None:
|
def add_secret(flake_name: FlakeName, machine: str, secret: str) -> None:
|
||||||
secrets.allow_member(
|
secrets.allow_member(
|
||||||
secrets.machines_folder(flake_name, secret),
|
secrets.machines_folder(flake_name, secret),
|
||||||
sops_machines_folder(flake_name),
|
sops_machines_folder(flake_name),
|
||||||
@@ -40,7 +41,7 @@ def add_secret(flake_name: str, machine: str, secret: str) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def remove_secret(flake_name: str, machine: str, secret: str) -> None:
|
def remove_secret(flake_name: FlakeName, machine: str, secret: str) -> None:
|
||||||
secrets.disallow_member(secrets.machines_folder(flake_name, secret), machine)
|
secrets.disallow_member(secrets.machines_folder(flake_name, secret), machine)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from typing import IO
|
|||||||
|
|
||||||
from .. import tty
|
from .. import tty
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
|
from ..flakes.types import FlakeName
|
||||||
from .folders import (
|
from .folders import (
|
||||||
list_objects,
|
list_objects,
|
||||||
sops_groups_folder,
|
sops_groups_folder,
|
||||||
@@ -53,7 +54,7 @@ def collect_keys_for_path(path: Path) -> set[str]:
|
|||||||
|
|
||||||
|
|
||||||
def encrypt_secret(
|
def encrypt_secret(
|
||||||
flake_name: str,
|
flake_name: FlakeName,
|
||||||
secret: Path,
|
secret: Path,
|
||||||
value: IO[str] | str | None,
|
value: IO[str] | str | None,
|
||||||
add_users: list[str] = [],
|
add_users: list[str] = [],
|
||||||
@@ -101,7 +102,7 @@ def encrypt_secret(
|
|||||||
encrypt_file(secret / "secret", value, list(sorted(keys)))
|
encrypt_file(secret / "secret", value, list(sorted(keys)))
|
||||||
|
|
||||||
|
|
||||||
def remove_secret(flake_name: str, secret: str) -> None:
|
def remove_secret(flake_name: FlakeName, secret: str) -> None:
|
||||||
path = sops_secrets_folder(flake_name) / secret
|
path = sops_secrets_folder(flake_name) / secret
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
raise ClanError(f"Secret '{secret}' does not exist")
|
raise ClanError(f"Secret '{secret}' does not exist")
|
||||||
@@ -116,15 +117,15 @@ def add_secret_argument(parser: argparse.ArgumentParser) -> None:
|
|||||||
parser.add_argument("secret", help="the name of the secret", type=secret_name_type)
|
parser.add_argument("secret", help="the name of the secret", type=secret_name_type)
|
||||||
|
|
||||||
|
|
||||||
def machines_folder(flake_name: str, group: str) -> Path:
|
def machines_folder(flake_name: FlakeName, group: str) -> Path:
|
||||||
return sops_secrets_folder(flake_name) / group / "machines"
|
return sops_secrets_folder(flake_name) / group / "machines"
|
||||||
|
|
||||||
|
|
||||||
def users_folder(flake_name: str, group: str) -> Path:
|
def users_folder(flake_name: FlakeName, group: str) -> Path:
|
||||||
return sops_secrets_folder(flake_name) / group / "users"
|
return sops_secrets_folder(flake_name) / group / "users"
|
||||||
|
|
||||||
|
|
||||||
def groups_folder(flake_name: str, group: str) -> Path:
|
def groups_folder(flake_name: FlakeName, group: str) -> Path:
|
||||||
return sops_secrets_folder(flake_name) / group / "groups"
|
return sops_secrets_folder(flake_name) / group / "groups"
|
||||||
|
|
||||||
|
|
||||||
@@ -188,11 +189,11 @@ def disallow_member(group_folder: Path, name: str) -> None:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def has_secret(flake_name: str, secret: str) -> bool:
|
def has_secret(flake_name: FlakeName, secret: str) -> bool:
|
||||||
return (sops_secrets_folder(flake_name) / secret / "secret").exists()
|
return (sops_secrets_folder(flake_name) / secret / "secret").exists()
|
||||||
|
|
||||||
|
|
||||||
def list_secrets(flake_name: str) -> list[str]:
|
def list_secrets(flake_name: FlakeName) -> list[str]:
|
||||||
path = sops_secrets_folder(flake_name)
|
path = sops_secrets_folder(flake_name)
|
||||||
|
|
||||||
def validate(name: str) -> bool:
|
def validate(name: str) -> bool:
|
||||||
@@ -209,7 +210,7 @@ def list_command(args: argparse.Namespace) -> None:
|
|||||||
print("\n".join(lst))
|
print("\n".join(lst))
|
||||||
|
|
||||||
|
|
||||||
def decrypt_secret(flake_name: str, secret: str) -> str:
|
def decrypt_secret(flake_name: FlakeName, secret: str) -> str:
|
||||||
ensure_sops_key(flake_name)
|
ensure_sops_key(flake_name)
|
||||||
secret_path = sops_secrets_folder(flake_name) / secret / "secret"
|
secret_path = sops_secrets_folder(flake_name) / secret / "secret"
|
||||||
if not secret_path.exists():
|
if not secret_path.exists():
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ from typing import IO, Iterator
|
|||||||
|
|
||||||
from ..dirs import user_config_dir
|
from ..dirs import user_config_dir
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
|
from ..flakes.types import FlakeName
|
||||||
from ..nix import nix_shell
|
from ..nix import nix_shell
|
||||||
from .folders import sops_machines_folder, sops_users_folder
|
from .folders import sops_machines_folder, sops_users_folder
|
||||||
|
|
||||||
@@ -51,7 +52,7 @@ def generate_private_key() -> tuple[str, str]:
|
|||||||
raise ClanError("Failed to generate private sops key") from e
|
raise ClanError("Failed to generate private sops key") from e
|
||||||
|
|
||||||
|
|
||||||
def get_user_name(flake_name: str, user: str) -> str:
|
def get_user_name(flake_name: FlakeName, user: str) -> str:
|
||||||
"""Ask the user for their name until a unique one is provided."""
|
"""Ask the user for their name until a unique one is provided."""
|
||||||
while True:
|
while True:
|
||||||
name = input(
|
name = input(
|
||||||
@@ -64,7 +65,7 @@ def get_user_name(flake_name: str, user: str) -> str:
|
|||||||
print(f"{sops_users_folder(flake_name) / user} already exists")
|
print(f"{sops_users_folder(flake_name) / user} already exists")
|
||||||
|
|
||||||
|
|
||||||
def ensure_user_or_machine(flake_name: str, pub_key: str) -> SopsKey:
|
def ensure_user_or_machine(flake_name: FlakeName, pub_key: str) -> SopsKey:
|
||||||
key = SopsKey(pub_key, username="")
|
key = SopsKey(pub_key, username="")
|
||||||
folders = [sops_users_folder(flake_name), sops_machines_folder(flake_name)]
|
folders = [sops_users_folder(flake_name), sops_machines_folder(flake_name)]
|
||||||
for folder in folders:
|
for folder in folders:
|
||||||
@@ -90,7 +91,7 @@ def default_sops_key_path() -> Path:
|
|||||||
return user_config_dir() / "sops" / "age" / "keys.txt"
|
return user_config_dir() / "sops" / "age" / "keys.txt"
|
||||||
|
|
||||||
|
|
||||||
def ensure_sops_key(flake_name: str) -> SopsKey:
|
def ensure_sops_key(flake_name: FlakeName) -> SopsKey:
|
||||||
key = os.environ.get("SOPS_AGE_KEY")
|
key = os.environ.get("SOPS_AGE_KEY")
|
||||||
if key:
|
if key:
|
||||||
return ensure_user_or_machine(flake_name, get_public_key(key))
|
return ensure_user_or_machine(flake_name, get_public_key(key))
|
||||||
|
|||||||
@@ -11,13 +11,14 @@ from clan_cli.nix import nix_shell
|
|||||||
|
|
||||||
from ..dirs import specific_flake_dir
|
from ..dirs import specific_flake_dir
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
|
from ..flakes.types import FlakeName
|
||||||
from .folders import sops_secrets_folder
|
from .folders import sops_secrets_folder
|
||||||
from .machines import add_machine, has_machine
|
from .machines import add_machine, has_machine
|
||||||
from .secrets import decrypt_secret, encrypt_secret, has_secret
|
from .secrets import decrypt_secret, encrypt_secret, has_secret
|
||||||
from .sops import generate_private_key
|
from .sops import generate_private_key
|
||||||
|
|
||||||
|
|
||||||
def generate_host_key(flake_name: str, machine_name: str) -> None:
|
def generate_host_key(flake_name: FlakeName, machine_name: str) -> None:
|
||||||
if has_machine(flake_name, machine_name):
|
if has_machine(flake_name, machine_name):
|
||||||
return
|
return
|
||||||
priv_key, pub_key = generate_private_key()
|
priv_key, pub_key = generate_private_key()
|
||||||
@@ -30,7 +31,7 @@ def generate_host_key(flake_name: str, machine_name: str) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def generate_secrets_group(
|
def generate_secrets_group(
|
||||||
flake_name: str,
|
flake_name: FlakeName,
|
||||||
secret_group: str,
|
secret_group: str,
|
||||||
machine_name: str,
|
machine_name: str,
|
||||||
tempdir: Path,
|
tempdir: Path,
|
||||||
@@ -88,7 +89,7 @@ export secrets={shlex.quote(str(secrets_dir))}
|
|||||||
|
|
||||||
# this is called by the sops.nix clan core module
|
# this is called by the sops.nix clan core module
|
||||||
def generate_secrets_from_nix(
|
def generate_secrets_from_nix(
|
||||||
flake_name: str,
|
flake_name: FlakeName,
|
||||||
machine_name: str,
|
machine_name: str,
|
||||||
secret_submodules: dict[str, Any],
|
secret_submodules: dict[str, Any],
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -112,7 +113,7 @@ def generate_secrets_from_nix(
|
|||||||
|
|
||||||
# this is called by the sops.nix clan core module
|
# this is called by the sops.nix clan core module
|
||||||
def upload_age_key_from_nix(
|
def upload_age_key_from_nix(
|
||||||
flake_name: str,
|
flake_name: FlakeName,
|
||||||
machine_name: str,
|
machine_name: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
secret_name = f"{machine_name}-age.key"
|
secret_name = f"{machine_name}-age.key"
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
|
from ..flakes.types import FlakeName
|
||||||
from . import secrets
|
from . import secrets
|
||||||
from .folders import list_objects, remove_object, sops_users_folder
|
from .folders import list_objects, remove_object, sops_users_folder
|
||||||
from .sops import read_key, write_key
|
from .sops import read_key, write_key
|
||||||
@@ -11,19 +12,19 @@ from .types import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def add_user(flake_name: str, name: str, key: str, force: bool) -> None:
|
def add_user(flake_name: FlakeName, name: str, key: str, force: bool) -> None:
|
||||||
write_key(sops_users_folder(flake_name) / name, key, force)
|
write_key(sops_users_folder(flake_name) / name, key, force)
|
||||||
|
|
||||||
|
|
||||||
def remove_user(flake_name: str, name: str) -> None:
|
def remove_user(flake_name: FlakeName, name: str) -> None:
|
||||||
remove_object(sops_users_folder(flake_name), name)
|
remove_object(sops_users_folder(flake_name), name)
|
||||||
|
|
||||||
|
|
||||||
def get_user(flake_name: str, name: str) -> str:
|
def get_user(flake_name: FlakeName, name: str) -> str:
|
||||||
return read_key(sops_users_folder(flake_name) / name)
|
return read_key(sops_users_folder(flake_name) / name)
|
||||||
|
|
||||||
|
|
||||||
def list_users(flake_name: str) -> list[str]:
|
def list_users(flake_name: FlakeName) -> list[str]:
|
||||||
path = sops_users_folder(flake_name)
|
path = sops_users_folder(flake_name)
|
||||||
|
|
||||||
def validate(name: str) -> bool:
|
def validate(name: str) -> bool:
|
||||||
@@ -35,13 +36,13 @@ def list_users(flake_name: str) -> list[str]:
|
|||||||
return list_objects(path, validate)
|
return list_objects(path, validate)
|
||||||
|
|
||||||
|
|
||||||
def add_secret(flake_name: str, user: str, secret: str) -> None:
|
def add_secret(flake_name: FlakeName, user: str, secret: str) -> None:
|
||||||
secrets.allow_member(
|
secrets.allow_member(
|
||||||
secrets.users_folder(flake_name, secret), sops_users_folder(flake_name), user
|
secrets.users_folder(flake_name, secret), sops_users_folder(flake_name), user
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def remove_secret(flake_name: str, user: str, secret: str) -> None:
|
def remove_secret(flake_name: FlakeName, user: str, secret: str) -> None:
|
||||||
secrets.disallow_member(secrets.users_folder(flake_name, secret), user)
|
secrets.disallow_member(secrets.users_folder(flake_name, secret), user)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from ...config.machine import (
|
|||||||
set_config_for_machine,
|
set_config_for_machine,
|
||||||
verify_machine_config,
|
verify_machine_config,
|
||||||
)
|
)
|
||||||
|
from ...flakes.types import FlakeName
|
||||||
from ...machines.create import create_machine as _create_machine
|
from ...machines.create import create_machine as _create_machine
|
||||||
from ...machines.list import list_machines as _list_machines
|
from ...machines.list import list_machines as _list_machines
|
||||||
from ..api_outputs import (
|
from ..api_outputs import (
|
||||||
@@ -28,7 +29,7 @@ router = APIRouter()
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/api/{flake_name}/machines")
|
@router.get("/api/{flake_name}/machines")
|
||||||
async def list_machines(flake_name: str) -> MachinesResponse:
|
async def list_machines(flake_name: FlakeName) -> MachinesResponse:
|
||||||
machines = []
|
machines = []
|
||||||
for m in _list_machines(flake_name):
|
for m in _list_machines(flake_name):
|
||||||
machines.append(Machine(name=m, status=Status.UNKNOWN))
|
machines.append(Machine(name=m, status=Status.UNKNOWN))
|
||||||
@@ -37,7 +38,7 @@ async def list_machines(flake_name: str) -> MachinesResponse:
|
|||||||
|
|
||||||
@router.post("/api/{flake_name}/machines", status_code=201)
|
@router.post("/api/{flake_name}/machines", status_code=201)
|
||||||
async def create_machine(
|
async def create_machine(
|
||||||
flake_name: str, machine: Annotated[MachineCreate, Body()]
|
flake_name: FlakeName, machine: Annotated[MachineCreate, Body()]
|
||||||
) -> MachineResponse:
|
) -> MachineResponse:
|
||||||
out = await _create_machine(flake_name, machine.name)
|
out = await _create_machine(flake_name, machine.name)
|
||||||
log.debug(out)
|
log.debug(out)
|
||||||
@@ -51,21 +52,21 @@ async def get_machine(name: str) -> MachineResponse:
|
|||||||
|
|
||||||
|
|
||||||
@router.get("/api/{flake_name}/machines/{name}/config")
|
@router.get("/api/{flake_name}/machines/{name}/config")
|
||||||
async def get_machine_config(flake_name: str, name: str) -> ConfigResponse:
|
async def get_machine_config(flake_name: FlakeName, name: str) -> ConfigResponse:
|
||||||
config = config_for_machine(flake_name, name)
|
config = config_for_machine(flake_name, name)
|
||||||
return ConfigResponse(config=config)
|
return ConfigResponse(config=config)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/api/{flake_name}/machines/{name}/config")
|
@router.put("/api/{flake_name}/machines/{name}/config")
|
||||||
async def set_machine_config(
|
async def set_machine_config(
|
||||||
flake_name: str, name: str, config: Annotated[dict, Body()]
|
flake_name: FlakeName, name: str, config: Annotated[dict, Body()]
|
||||||
) -> ConfigResponse:
|
) -> ConfigResponse:
|
||||||
set_config_for_machine(flake_name, name, config)
|
set_config_for_machine(flake_name, name, config)
|
||||||
return ConfigResponse(config=config)
|
return ConfigResponse(config=config)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/{flake_name}/machines/{name}/schema")
|
@router.get("/api/{flake_name}/machines/{name}/schema")
|
||||||
async def get_machine_schema(flake_name: str, name: str) -> SchemaResponse:
|
async def get_machine_schema(flake_name: FlakeName, name: str) -> SchemaResponse:
|
||||||
schema = schema_for_machine(flake_name, name)
|
schema = schema_for_machine(flake_name, name)
|
||||||
return SchemaResponse(schema=schema)
|
return SchemaResponse(schema=schema)
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import pytest
|
|||||||
from root import CLAN_CORE
|
from root import CLAN_CORE
|
||||||
|
|
||||||
from clan_cli.dirs import nixpkgs_source
|
from clan_cli.dirs import nixpkgs_source
|
||||||
|
from clan_cli.flakes.types import FlakeName
|
||||||
|
|
||||||
|
|
||||||
# substitutes string sin a file.
|
# substitutes string sin a file.
|
||||||
@@ -28,13 +29,13 @@ def substitute(
|
|||||||
|
|
||||||
|
|
||||||
class TestFlake(NamedTuple):
|
class TestFlake(NamedTuple):
|
||||||
name: str
|
name: FlakeName
|
||||||
path: Path
|
path: Path
|
||||||
|
|
||||||
|
|
||||||
def create_flake(
|
def create_flake(
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
flake_name: str,
|
flake_name: FlakeName,
|
||||||
clan_core_flake: Path | None = None,
|
clan_core_flake: Path | None = None,
|
||||||
machines: list[str] = [],
|
machines: list[str] = [],
|
||||||
remote: bool = False,
|
remote: bool = False,
|
||||||
@@ -74,7 +75,7 @@ def create_flake(
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def test_flake(monkeypatch: pytest.MonkeyPatch) -> Iterator[TestFlake]:
|
def test_flake(monkeypatch: pytest.MonkeyPatch) -> Iterator[TestFlake]:
|
||||||
yield from create_flake(monkeypatch, "test_flake")
|
yield from create_flake(monkeypatch, FlakeName("test_flake"))
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -83,7 +84,7 @@ def test_flake_with_core(monkeypatch: pytest.MonkeyPatch) -> Iterator[TestFlake]
|
|||||||
raise Exception(
|
raise Exception(
|
||||||
"clan-core flake not found. This test requires the clan-core flake to be present"
|
"clan-core flake not found. This test requires the clan-core flake to be present"
|
||||||
)
|
)
|
||||||
yield from create_flake(monkeypatch, "test_flake_with_core", CLAN_CORE)
|
yield from create_flake(monkeypatch, FlakeName("test_flake_with_core"), CLAN_CORE)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -94,4 +95,6 @@ def test_flake_with_core_and_pass(
|
|||||||
raise Exception(
|
raise Exception(
|
||||||
"clan-core flake not found. This test requires the clan-core flake to be present"
|
"clan-core flake not found. This test requires the clan-core flake to be present"
|
||||||
)
|
)
|
||||||
yield from create_flake(monkeypatch, "test_flake_with_core_and_pass", CLAN_CORE)
|
yield from create_flake(
|
||||||
|
monkeypatch, FlakeName("test_flake_with_core_and_pass"), CLAN_CORE
|
||||||
|
)
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ from fixtures_flakes import TestFlake, create_flake
|
|||||||
from httpx import SyncByteStream
|
from httpx import SyncByteStream
|
||||||
from root import CLAN_CORE
|
from root import CLAN_CORE
|
||||||
|
|
||||||
|
from clan_cli.flakes.types import FlakeName
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from age_keys import KeyPair
|
from age_keys import KeyPair
|
||||||
|
|
||||||
@@ -17,7 +19,7 @@ if TYPE_CHECKING:
|
|||||||
def flake_with_vm_with_secrets(monkeypatch: pytest.MonkeyPatch) -> Iterator[TestFlake]:
|
def flake_with_vm_with_secrets(monkeypatch: pytest.MonkeyPatch) -> Iterator[TestFlake]:
|
||||||
yield from create_flake(
|
yield from create_flake(
|
||||||
monkeypatch,
|
monkeypatch,
|
||||||
"test_flake_with_core_dynamic_machines",
|
FlakeName("test_flake_with_core_dynamic_machines"),
|
||||||
CLAN_CORE,
|
CLAN_CORE,
|
||||||
machines=["vm_with_secrets"],
|
machines=["vm_with_secrets"],
|
||||||
)
|
)
|
||||||
@@ -29,7 +31,7 @@ def remote_flake_with_vm_without_secrets(
|
|||||||
) -> Iterator[TestFlake]:
|
) -> Iterator[TestFlake]:
|
||||||
yield from create_flake(
|
yield from create_flake(
|
||||||
monkeypatch,
|
monkeypatch,
|
||||||
"test_flake_with_core_dynamic_machines",
|
FlakeName("test_flake_with_core_dynamic_machines"),
|
||||||
CLAN_CORE,
|
CLAN_CORE,
|
||||||
machines=["vm_without_secrets"],
|
machines=["vm_without_secrets"],
|
||||||
remote=True,
|
remote=True,
|
||||||
|
|||||||
Reference in New Issue
Block a user