diff --git a/pkgs/clan-cli/clan_cli/api/admin.py b/pkgs/clan-cli/clan_cli/api/admin.py index 5e0deed58..2f9ec3586 100644 --- a/pkgs/clan-cli/clan_cli/api/admin.py +++ b/pkgs/clan-cli/clan_cli/api/admin.py @@ -1,62 +1,37 @@ -from clan_cli.errors import ClanError -from clan_cli.inventory import ( - AdminConfig, - ServiceAdmin, - ServiceAdminRole, - ServiceAdminRoleDefault, - ServiceMeta, - load_inventory_eval, - save_inventory, -) +# @API.register +# def set_admin_service( +# base_url: str, +# allowed_keys: dict[str, str], +# instance_name: str = "admin", +# extra_machines: list[str] | None = None, +# ) -> None: +# """ +# Set the admin service of a clan +# Every machine is by default part of the admin service via the 'all' tag +# """ +# if extra_machines is None: +# extra_machines = [] +# inventory = load_inventory_eval(base_url) -from . import API +# if not allowed_keys: +# msg = "At least one key must be provided to ensure access" +# raise ClanError(msg) +# instance = ServiceAdmin( +# meta=ServiceMeta(name=instance_name), +# roles=ServiceAdminRole( +# default=ServiceAdminRoleDefault( +# machines=extra_machines, +# tags=["all"], +# ) +# ), +# config=AdminConfig(allowedKeys=allowed_keys), +# ) -@API.register -def get_admin_service(base_url: str) -> ServiceAdmin | None: - """ - Return the admin service of a clan. +# inventory.services.admin[instance_name] = instance - There is only one admin service. This might be changed in the future - """ - inventory = load_inventory_eval(base_url) - return inventory.services.admin.get("admin") - - -@API.register -def set_admin_service( - base_url: str, - allowed_keys: dict[str, str], - instance_name: str = "admin", - extra_machines: list[str] | None = None, -) -> None: - """ - Set the admin service of a clan - Every machine is by default part of the admin service via the 'all' tag - """ - if extra_machines is None: - extra_machines = [] - inventory = load_inventory_eval(base_url) - - if not allowed_keys: - msg = "At least one key must be provided to ensure access" - raise ClanError(msg) - - instance = ServiceAdmin( - meta=ServiceMeta(name=instance_name), - roles=ServiceAdminRole( - default=ServiceAdminRoleDefault( - machines=extra_machines, - tags=["all"], - ) - ), - config=AdminConfig(allowedKeys=allowed_keys), - ) - - inventory.services.admin[instance_name] = instance - - save_inventory( - inventory, - base_url, - f"Set admin service: '{instance_name}'", - ) +# save_inventory( +# inventory, +# base_url, +# f"Set admin service: '{instance_name}'", +# ) diff --git a/pkgs/clan-cli/clan_cli/api/disk.py b/pkgs/clan-cli/clan_cli/api/disk.py index 10d6d8ea0..a71a96f01 100644 --- a/pkgs/clan-cli/clan_cli/api/disk.py +++ b/pkgs/clan-cli/clan_cli/api/disk.py @@ -1,67 +1,34 @@ -from clan_cli.inventory import ( - ServiceMeta, - ServiceSingleDisk, - ServiceSingleDiskRole, - ServiceSingleDiskRoleDefault, - SingleDiskConfig, - load_inventory_eval, - load_inventory_json, - save_inventory, -) - -from . import API - - def get_instance_name(machine_name: str) -> str: return f"{machine_name}-single-disk" -@API.register -def set_single_disk_uuid( - base_path: str, - machine_name: str, - disk_uuid: str, -) -> None: - """ - Set the disk UUID of single disk machine - """ - inventory = load_inventory_json(base_path) +# @API.register +# def set_single_disk_uuid( +# base_path: str, +# machine_name: str, +# disk_uuid: str, +# ) -> None: +# """ +# Set the disk UUID of single disk machine +# """ +# inventory = load_inventory_json(base_path) - instance_name = get_instance_name(machine_name) +# instance_name = get_instance_name(machine_name) - single_disk_config: ServiceSingleDisk = ServiceSingleDisk( - meta=ServiceMeta(name=instance_name), - roles=ServiceSingleDiskRole( - default=ServiceSingleDiskRoleDefault( - config=SingleDiskConfig(device=f"/dev/disk/by-id/{disk_uuid}"), - machines=[machine_name], - ) - ), - ) +# single_disk_config: ServiceSingleDisk = ServiceSingleDisk( +# meta=ServiceMeta(name=instance_name), +# roles=ServiceSingleDiskRole( +# default=ServiceSingleDiskRoleDefault( +# config=SingleDiskConfig(device=f"/dev/disk/by-id/{disk_uuid}"), +# machines=[machine_name], +# ) +# ), +# ) - inventory.services.single_disk[instance_name] = single_disk_config +# inventory.services.single_disk[instance_name] = single_disk_config - save_inventory( - inventory, - base_path, - f"Set disk UUID: '{disk_uuid}' on machine: '{machine_name}'", - ) - - -@API.register -def get_single_disk_uuid( - base_path: str, - machine_name: str, -) -> str | None: - """ - Get the disk UUID of single disk machine - """ - inventory = load_inventory_eval(base_path) - - instance_name = get_instance_name(machine_name) - - single_disk_config: ServiceSingleDisk = inventory.services.single_disk[ - instance_name - ] - - return single_disk_config.roles.default.config.device +# save_inventory( +# inventory, +# base_path, +# f"Set disk UUID: '{disk_uuid}' on machine: '{machine_name}'", +# ) diff --git a/pkgs/clan-cli/clan_cli/api/iwd.py b/pkgs/clan-cli/clan_cli/api/iwd.py index 387a337f2..8d13be6df 100644 --- a/pkgs/clan-cli/clan_cli/api/iwd.py +++ b/pkgs/clan-cli/clan_cli/api/iwd.py @@ -1,109 +1,67 @@ from dataclasses import dataclass -from pathlib import Path - -from clan_cli.clan_uri import FlakeId -from clan_cli.errors import ClanError -from clan_cli.facts.generate import generate_facts -from clan_cli.inventory import ( - IwdConfig, - IwdConfigNetwork, - ServiceIwd, - ServiceIwdRole, - ServiceIwdRoleDefault, - ServiceMeta, - load_inventory_eval, - save_inventory, -) -from clan_cli.machines.machines import Machine -from clan_cli.secrets.sops import ( - maybe_get_public_key, - maybe_get_user_or_machine, -) - -from . import API def instance_name(machine_name: str) -> str: return f"{machine_name}_wifi_0_" -@API.register -def get_iwd_service(base_url: str, machine_name: str) -> ServiceIwd: - """ - Return the admin service of a clan. - - There is only one admin service. This might be changed in the future - """ - inventory = load_inventory_eval(base_url) - service_config = inventory.services.iwd.get(instance_name(machine_name)) - if service_config: - return service_config - - # Empty service - return ServiceIwd( - meta=ServiceMeta(name="wifi_0"), - roles=ServiceIwdRole(default=ServiceIwdRoleDefault(machines=[machine_name])), - config=IwdConfig(networks={}), - ) - - @dataclass class NetworkConfig: ssid: str password: str -@API.register -def set_iwd_service_for_machine( - base_url: str, machine_name: str, networks: dict[str, NetworkConfig] -) -> None: - """ - Set the admin service of a clan - Every machine is by default part of the admin service via the 'all' tag - """ - _instance_name = instance_name(machine_name) +# @API.register +# def set_iwd_service_for_machine( +# base_url: str, machine_name: str, networks: dict[str, NetworkConfig] +# ) -> None: +# """ +# Set the admin service of a clan +# Every machine is by default part of the admin service via the 'all' tag +# """ +# _instance_name = instance_name(machine_name) - inventory = load_inventory_eval(base_url) +# inventory = load_inventory_eval(base_url) - instance = ServiceIwd( - meta=ServiceMeta(name="wifi_0"), - roles=ServiceIwdRole( - default=ServiceIwdRoleDefault( - machines=[machine_name], - ) - ), - config=IwdConfig( - networks={k: IwdConfigNetwork(v.ssid) for k, v in networks.items()} - ), - ) +# instance = ServiceIwd( +# meta=ServiceMeta(name="wifi_0"), +# roles=ServiceIwdRole( +# default=ServiceIwdRoleDefault( +# machines=[machine_name], +# ) +# ), +# config=IwdConfig( +# networks={k: IwdConfigNetwork(v.ssid) for k, v in networks.items()} +# ), +# ) - inventory.services.iwd[_instance_name] = instance +# inventory.services.iwd[_instance_name] = instance - save_inventory( - inventory, - base_url, - f"Set iwd service: '{_instance_name}'", - ) +# save_inventory( +# inventory, +# base_url, +# f"Set iwd service: '{_instance_name}'", +# ) - pubkey = maybe_get_public_key() - if not pubkey: - # TODO: do this automatically - # pubkey = generate_key() - raise ClanError(msg="No public key found. Please initialize your key.") +# pubkey = maybe_get_public_key() +# if not pubkey: +# # TODO: do this automatically +# # pubkey = generate_key() +# raise ClanError(msg="No public key found. Please initialize your key.") - registered_key = maybe_get_user_or_machine(Path(base_url), pubkey) - if not registered_key: - # TODO: do this automatically - # username = os.getlogin() - # add_user(Path(base_url), username, pubkey, force=False) - raise ClanError(msg="Your public key is not registered for use with this clan.") +# registered_key = maybe_get_user_or_machine(Path(base_url), pubkey) +# if not registered_key: +# # TODO: do this automatically +# # username = os.getlogin() +# # add_user(Path(base_url), username, pubkey, force=False) +# raise ClanError(msg="Your public key is not registered for use with this clan.") - password_dict = {f"iwd.{net.ssid}": net.password for net in networks.values()} - for net in networks.values(): - generate_facts( - service=f"iwd.{net.ssid}", - machines=[Machine(machine_name, FlakeId(base_url))], - regenerate=True, - # Just returns the password - prompt=lambda service, _msg: password_dict[service], - ) +# password_dict = {f"iwd.{net.ssid}": net.password for net in networks.values()} +# for net in networks.values(): +# generate_facts( +# service=f"iwd.{net.ssid}", +# machines=[Machine(machine_name, FlakeId(base_url))], +# regenerate=True, +# # Just returns the password +# prompt=lambda service, _msg: password_dict[service], +# ) diff --git a/pkgs/clan-cli/clan_cli/inventory/__init__.py b/pkgs/clan-cli/clan_cli/inventory/__init__.py index 9c535beb4..019be0713 100644 --- a/pkgs/clan-cli/clan_cli/inventory/__init__.py +++ b/pkgs/clan-cli/clan_cli/inventory/__init__.py @@ -23,35 +23,13 @@ from clan_cli.git import commit_file from clan_cli.nix import nix_eval from .classes import ( - AdminConfig, Inventory, - IwdConfig, - IwdConfigNetwork, # Machine classes Machine, MachineDeploy, # General classes Meta, Service, - # Admin service - ServiceAdmin, - ServiceAdminRole, - ServiceAdminRoleDefault, - # Borgbackup service - ServiceBorgbackup, - ServiceBorgbackupRole, - ServiceBorgbackupRoleClient, - ServiceBorgbackupRoleServer, - # IWD - ServiceIwd, - ServiceIwdRole, - ServiceIwdRoleDefault, - ServiceMeta, - # Single Disk service - ServiceSingleDisk, - ServiceSingleDiskRole, - ServiceSingleDiskRoleDefault, - SingleDiskConfig, ) # Re export classes here @@ -64,27 +42,6 @@ __all__ = [ "Meta", "Inventory", "MachineDeploy", - "ServiceBorgbackup", - "ServiceMeta", - "ServiceBorgbackupRole", - "ServiceBorgbackupRoleClient", - "ServiceBorgbackupRoleServer", - # Single Disk service - "ServiceSingleDisk", - "ServiceSingleDiskRole", - "ServiceSingleDiskRoleDefault", - "SingleDiskConfig", - # Admin service - "ServiceAdmin", - "ServiceAdminRole", - "ServiceAdminRoleDefault", - "AdminConfig", - # IWD service, - "ServiceIwd", - "ServiceIwdRole", - "ServiceIwdRoleDefault", - "IwdConfig", - "IwdConfigNetwork", ] diff --git a/pkgs/clan-cli/clan_cli/inventory/classes.py b/pkgs/clan-cli/clan_cli/inventory/classes.py index bfb9433dc..c0e5310ec 100644 --- a/pkgs/clan-cli/clan_cli/inventory/classes.py +++ b/pkgs/clan-cli/clan_cli/inventory/classes.py @@ -30,234 +30,7 @@ class Meta: icon: None | str = field(default = None) -@dataclass -class AdminConfig: - allowedKeys: dict[str, str] = field(default_factory = dict) - - -@dataclass -class ServiceAdminMachine: - config: AdminConfig = field(default_factory = AdminConfig) - imports: list[str] = field(default_factory = list) - - -@dataclass -class ServiceMeta: - name: str - description: None | str = field(default = None) - icon: None | str = field(default = None) - - -@dataclass -class ServiceAdminRoleDefault: - config: AdminConfig = field(default_factory = AdminConfig) - imports: list[str] = field(default_factory = list) - machines: list[str] = field(default_factory = list) - tags: list[str] = field(default_factory = list) - - -@dataclass -class ServiceAdminRole: - default: ServiceAdminRoleDefault - - -@dataclass -class ServiceAdmin: - meta: ServiceMeta - roles: ServiceAdminRole - config: AdminConfig = field(default_factory = AdminConfig) - machines: dict[str, ServiceAdminMachine] = field(default_factory = dict) - - -@dataclass -class BorgbackupConfigDestination: - name: str - repo: str - - -@dataclass -class BorgbackupConfig: - destinations: dict[str, BorgbackupConfigDestination] = field(default_factory = dict) - exclude: list[str] = field(default_factory = list) - - -@dataclass -class ServiceBorgbackupMachine: - config: BorgbackupConfig = field(default_factory = BorgbackupConfig) - imports: list[str] = field(default_factory = list) - - -@dataclass -class ServiceBorgbackupRoleClient: - config: BorgbackupConfig = field(default_factory = BorgbackupConfig) - imports: list[str] = field(default_factory = list) - machines: list[str] = field(default_factory = list) - tags: list[str] = field(default_factory = list) - - -@dataclass -class ServiceBorgbackupRoleServer: - config: BorgbackupConfig = field(default_factory = BorgbackupConfig) - imports: list[str] = field(default_factory = list) - machines: list[str] = field(default_factory = list) - tags: list[str] = field(default_factory = list) - - -@dataclass -class ServiceBorgbackupRole: - client: ServiceBorgbackupRoleClient - server: ServiceBorgbackupRoleServer - - -@dataclass -class ServiceBorgbackup: - meta: ServiceMeta - roles: ServiceBorgbackupRole - config: BorgbackupConfig = field(default_factory = BorgbackupConfig) - machines: dict[str, ServiceBorgbackupMachine] = field(default_factory = dict) - - -@dataclass -class IwdConfigNetwork: - ssid: str - - -@dataclass -class IwdConfig: - networks: dict[str, IwdConfigNetwork] = field(default_factory = dict) - - -@dataclass -class ServiceIwdMachine: - config: IwdConfig = field(default_factory = IwdConfig) - imports: list[str] = field(default_factory = list) - - -@dataclass -class ServiceIwdRoleDefault: - config: IwdConfig = field(default_factory = IwdConfig) - imports: list[str] = field(default_factory = list) - machines: list[str] = field(default_factory = list) - tags: list[str] = field(default_factory = list) - - -@dataclass -class ServiceIwdRole: - default: ServiceIwdRoleDefault - - -@dataclass -class ServiceIwd: - meta: ServiceMeta - roles: ServiceIwdRole - config: IwdConfig = field(default_factory = IwdConfig) - machines: dict[str, ServiceIwdMachine] = field(default_factory = dict) - - -@dataclass -class PackagesConfig: - packages: list[str] = field(default_factory = list) - - -@dataclass -class ServicePackageMachine: - config: PackagesConfig = field(default_factory = PackagesConfig) - imports: list[str] = field(default_factory = list) - - -@dataclass -class ServicePackageRoleDefault: - config: PackagesConfig = field(default_factory = PackagesConfig) - imports: list[str] = field(default_factory = list) - machines: list[str] = field(default_factory = list) - tags: list[str] = field(default_factory = list) - - -@dataclass -class ServicePackageRole: - default: ServicePackageRoleDefault - - -@dataclass -class ServicePackage: - meta: ServiceMeta - roles: ServicePackageRole - config: PackagesConfig = field(default_factory = PackagesConfig) - machines: dict[str, ServicePackageMachine] = field(default_factory = dict) - - -@dataclass -class SingleDiskConfig: - device: None | str = field(default = None) - - -@dataclass -class ServiceSingleDiskMachine: - config: SingleDiskConfig = field(default_factory = SingleDiskConfig) - imports: list[str] = field(default_factory = list) - - -@dataclass -class ServiceSingleDiskRoleDefault: - config: SingleDiskConfig = field(default_factory = SingleDiskConfig) - imports: list[str] = field(default_factory = list) - machines: list[str] = field(default_factory = list) - tags: list[str] = field(default_factory = list) - - -@dataclass -class ServiceSingleDiskRole: - default: ServiceSingleDiskRoleDefault - - -@dataclass -class ServiceSingleDisk: - meta: ServiceMeta - roles: ServiceSingleDiskRole - config: SingleDiskConfig = field(default_factory = SingleDiskConfig) - machines: dict[str, ServiceSingleDiskMachine] = field(default_factory = dict) - - -@dataclass -class StateVersionConfig: - pass - -@dataclass -class ServiceStateVersionMachine: - config: StateVersionConfig = field(default_factory = StateVersionConfig) - imports: list[str] = field(default_factory = list) - - -@dataclass -class ServiceStateVersionRoleDefault: - config: StateVersionConfig = field(default_factory = StateVersionConfig) - imports: list[str] = field(default_factory = list) - machines: list[str] = field(default_factory = list) - tags: list[str] = field(default_factory = list) - - -@dataclass -class ServiceStateVersionRole: - default: ServiceStateVersionRoleDefault - - -@dataclass -class ServiceStateVersion: - meta: ServiceMeta - roles: ServiceStateVersionRole - config: StateVersionConfig = field(default_factory = StateVersionConfig) - machines: dict[str, ServiceStateVersionMachine] = field(default_factory = dict) - - -@dataclass -class Service: - admin: dict[str, ServiceAdmin] = field(default_factory = dict) - borgbackup: dict[str, ServiceBorgbackup] = field(default_factory = dict) - iwd: dict[str, ServiceIwd] = field(default_factory = dict) - packages: dict[str, ServicePackage] = field(default_factory = dict) - single_disk: dict[str, ServiceSingleDisk] = field(default_factory = dict, metadata = {"alias": "single-disk"}) - state_version: dict[str, ServiceStateVersion] = field(default_factory = dict, metadata = {"alias": "state-version"}) - +Service = dict[str, Any] @dataclass class Inventory: diff --git a/pkgs/clan-cli/default.nix b/pkgs/clan-cli/default.nix index 3deb86fc9..333c5da85 100644 --- a/pkgs/clan-cli/default.nix +++ b/pkgs/clan-cli/default.nix @@ -63,7 +63,7 @@ let ln -sf ${nixpkgs'} $out/clan_cli/nixpkgs cp -r ${../../templates} $out/clan_cli/templates - ${classgen}/bin/classgen ${inventory-schema}/schema.json $out/clan_cli/inventory/classes.py + ${classgen}/bin/classgen ${inventory-schema}/schema.json $out/clan_cli/inventory/classes.py --stop-at "Service" ''; # Create a custom nixpkgs for use within the project diff --git a/pkgs/clan-cli/flake-module.nix b/pkgs/clan-cli/flake-module.nix index 4b63e681a..28fcab023 100644 --- a/pkgs/clan-cli/flake-module.nix +++ b/pkgs/clan-cli/flake-module.nix @@ -73,7 +73,7 @@ ]; installPhase = '' - ${self'.packages.classgen}/bin/classgen ${self'.packages.inventory-schema}/schema.json ./clan_cli/inventory/classes.py + ${self'.packages.classgen}/bin/classgen ${self'.packages.inventory-schema}/schema.json ./clan_cli/inventory/classes.py --stop-at "Service" python docs.py reference mkdir -p $out @@ -93,10 +93,12 @@ ]; installPhase = '' - ${self'.packages.classgen}/bin/classgen ${self'.packages.inventory-schema}/schema.json ./clan_cli/inventory/classes.py + ${self'.packages.classgen}/bin/classgen ${self'.packages.inventory-schema}/schema.json ./clan_cli/inventory/classes.py --stop-at "Service" mkdir -p $out python api.py > $out/API.json ${self'.packages.json2ts}/bin/json2ts --input $out/API.json > $out/API.ts + ${self'.packages.json2ts}/bin/json2ts --input ${self'.packages.inventory-schema}/schema.json > $out/Inventory.ts + cp ${self'.packages.inventory-schema}/schema.json $out/inventory-schema.json ''; }; json2ts = pkgs.buildNpmPackage { @@ -122,7 +124,7 @@ classFile = "classes.py"; }; installPhase = '' - ${self'.packages.classgen}/bin/classgen ${self'.packages.inventory-schema}/schema.json b_classes.py + ${self'.packages.classgen}/bin/classgen ${self'.packages.inventory-schema}/schema.json b_classes.py --stop-at "Service" file1=$classFile file2=b_classes.py diff --git a/pkgs/clan-cli/shell.nix b/pkgs/clan-cli/shell.nix index e52bd8a5b..0d4d656e4 100644 --- a/pkgs/clan-cli/shell.nix +++ b/pkgs/clan-cli/shell.nix @@ -45,6 +45,6 @@ mkShell { # Generate classes.py from inventory schema # This file is in .gitignore - ${self'.packages.classgen}/bin/classgen ${self'.packages.inventory-schema}/schema.json $PKG_ROOT/clan_cli/inventory/classes.py + ${self'.packages.classgen}/bin/classgen ${self'.packages.inventory-schema}/schema.json $PKG_ROOT/clan_cli/inventory/classes.py --stop-at "Service" ''; } diff --git a/pkgs/clan-cli/tests/test_deserializers.py b/pkgs/clan-cli/tests/test_deserializers.py index 49f964635..7b0e64233 100644 --- a/pkgs/clan-cli/tests/test_deserializers.py +++ b/pkgs/clan-cli/tests/test_deserializers.py @@ -7,18 +7,6 @@ import pytest # Functions to test from clan_cli.api import dataclass_to_dict, from_dict from clan_cli.errors import ClanError -from clan_cli.inventory import ( - Inventory, - Machine, - MachineDeploy, - Meta, - Service, - ServiceBorgbackup, - ServiceBorgbackupRole, - ServiceBorgbackupRoleClient, - ServiceBorgbackupRoleServer, - ServiceMeta, -) from clan_cli.machines import machines @@ -172,43 +160,6 @@ def test_list() -> None: assert result == [Name("John"), Name("Sarah")] -def test_deserialize_extensive_inventory() -> None: - # TODO: Make this an abstract test, so it doesn't break the test if the inventory changes - data = { - "meta": {"name": "superclan", "description": "nice clan"}, - "services": { - "borgbackup": { - "instance1": { - "meta": { - "name": "borg1", - }, - "roles": { - "client": {}, - "server": {}, - }, - } - }, - }, - "machines": {"foo": {"name": "foo", "deploy": {}}}, - } - expected = Inventory( - meta=Meta(name="superclan", description="nice clan"), - services=Service( - borgbackup={ - "instance1": ServiceBorgbackup( - meta=ServiceMeta(name="borg1"), - roles=ServiceBorgbackupRole( - client=ServiceBorgbackupRoleClient(), - server=ServiceBorgbackupRoleServer(), - ), - ) - } - ), - machines={"foo": Machine(deploy=MachineDeploy(), name="foo")}, - ) - assert from_dict(Inventory, data) == expected - - def test_alias_field() -> None: @dataclass class Person: diff --git a/pkgs/clan-cli/tests/test_modules.py b/pkgs/clan-cli/tests/test_modules.py index e46db3a4c..45c507d8b 100644 --- a/pkgs/clan-cli/tests/test_modules.py +++ b/pkgs/clan-cli/tests/test_modules.py @@ -7,11 +7,6 @@ from clan_cli.clan_uri import FlakeId from clan_cli.inventory import ( Machine, MachineDeploy, - ServiceBorgbackup, - ServiceBorgbackupRole, - ServiceBorgbackupRoleClient, - ServiceBorgbackupRoleServer, - ServiceMeta, load_inventory_json, save_inventory, ) @@ -67,18 +62,16 @@ def test_add_module_to_inventory( inventory = load_inventory_json(base_path) - inventory.services.borgbackup = { - "borg1": ServiceBorgbackup( - meta=ServiceMeta(name="borg1"), - roles=ServiceBorgbackupRole( - client=ServiceBorgbackupRoleClient( - machines=["machine1"], - ), - server=ServiceBorgbackupRoleServer( - machines=["machine1"], - ), - ), - ) + inventory.services = { + "borgbackup": { + "borg1": { + "meta": {"name": "borg1"}, + "roles": { + "client": {"machines": ["machine1"]}, + "server": {"machines": ["machine1"]}, + }, + } + } } save_inventory(inventory, base_path, "Add borgbackup service") diff --git a/pkgs/classgen/main.py b/pkgs/classgen/main.py index 4b9626b7c..92a21835c 100644 --- a/pkgs/classgen/main.py +++ b/pkgs/classgen/main.py @@ -45,6 +45,7 @@ def map_json_type( known_classes = set() root_class = "Inventory" +stop_at = None def field_def_from_default_type( @@ -198,6 +199,9 @@ def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) -> required_fields = [] fields_with_default = [] nested_classes: list[str] = [] + if stop_at and class_name == stop_at: + # Skip generating classes below the stop_at property + return f"{class_name} = dict[str, Any]" for prop, prop_info in properties.items(): field_name = prop.replace("-", "_") @@ -272,9 +276,6 @@ def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) -> field_meta = f"""{{"alias": "{prop}"}}""" finalize_field = partial(get_field_def, field_name, field_meta) - # if class_name == "DyndnsConfig": - # if class_name == "ServiceDyndnMachine": - # breakpoint() if "default" in prop_info or field_name not in prop_info.get("required", []): if "default" in prop_info: @@ -334,6 +335,10 @@ def generate_dataclass(schema: dict[str, Any], class_name: str = root_class) -> def run_gen(args: argparse.Namespace) -> None: print(f"Converting {args.input} to {args.output}") + if args.stop_at: + global stop_at + stop_at = args.stop_at + dataclass_code = "" with args.input.open() as f: schema = json.load(f) @@ -358,6 +363,12 @@ def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("input", help="Input JSON schema file", type=Path) parser.add_argument("output", help="Output Python file", type=Path) + parser.add_argument( + "--stop-at", + type=str, + help="Property name to stop generating classes for. Other classes below that property will be generated", + default=None, + ) parser.set_defaults(func=run_gen) args = parser.parse_args() diff --git a/pkgs/webview-ui/.gitignore b/pkgs/webview-ui/.gitignore index 590601772..9a800f70d 100644 --- a/pkgs/webview-ui/.gitignore +++ b/pkgs/webview-ui/.gitignore @@ -1,3 +1,3 @@ -api +app/api .vite \ No newline at end of file diff --git a/pkgs/webview-ui/app/src/api/disk.ts b/pkgs/webview-ui/app/src/api/disk.ts new file mode 100644 index 000000000..592451ff1 --- /dev/null +++ b/pkgs/webview-ui/app/src/api/disk.ts @@ -0,0 +1,31 @@ +import { get_inventory } from "./inventory"; + +export const instance_name = (machine_name: string) => + `${machine_name}-single-disk` as const; + +export async function set_single_disk_id( + base_path: string, + machine_name: string, + disk_id: string, +) { + const inventory = await get_inventory(base_path); + if (!inventory.services) { + return new Error("No services found in inventory"); + } + if (!inventory.services["single-disk"]) { + inventory.services["single-disk"] = {}; + } + inventory.services["single-disk"][instance_name(machine_name)] = { + meta: { + name: instance_name(machine_name), + }, + roles: { + default: { + machines: [machine_name], + config: { + device: `/dev/disk/by-id/${disk_id}`, + }, + }, + }, + }; +} diff --git a/pkgs/webview-ui/app/src/api.ts b/pkgs/webview-ui/app/src/api/index.ts similarity index 93% rename from pkgs/webview-ui/app/src/api.ts rename to pkgs/webview-ui/app/src/api/index.ts index d3ae83c27..9bf104c68 100644 --- a/pkgs/webview-ui/app/src/api.ts +++ b/pkgs/webview-ui/app/src/api/index.ts @@ -1,11 +1,19 @@ import schema from "@/api/API.json" assert { type: "json" }; import { API } from "@/api/API"; import { nanoid } from "nanoid"; +import { Schema as Inventory } from "@/api/Inventory"; export type OperationNames = keyof API; export type OperationArgs = API[T]["arguments"]; export type OperationResponse = API[T]["return"]; +export type Services = NonNullable; +export type ServiceNames = keyof Services; +export type ClanService = Services[T]; +export type ClanServiceInstance = NonNullable< + Services[T] +>[string]; + export type SuccessQuery = Extract< OperationResponse, { status: "success" } diff --git a/pkgs/webview-ui/app/src/api/inventory.ts b/pkgs/webview-ui/app/src/api/inventory.ts new file mode 100644 index 000000000..9ad4aa468 --- /dev/null +++ b/pkgs/webview-ui/app/src/api/inventory.ts @@ -0,0 +1,40 @@ +import { callApi, ClanService, ServiceNames, Services } from "."; +import { Schema as Inventory } from "@/api/Inventory"; + +export async function get_inventory(base_path: string) { + const r = await callApi("get_inventory", { + base_path, + }); + if (r.status == "error") { + throw new Error("Failed to get inventory"); + } + const inventory: Inventory = r.data; + return inventory; +} + +export const single_instance_name = ( + machine_name: string, + service_name: T, +) => `${machine_name}_${service_name}_0` as const; + +function get_service(base_path: string, service: T) { + return callApi("get_inventory", { base_path }).then((r) => { + if (r.status == "error") { + return null; + } + const inventory: Inventory = r.data; + + const serviceInstance = inventory.services?.[service]; + return serviceInstance; + }); +} + +export async function get_single_service( + base_path: string, + machine_name: string, + service_name: T, +) { + const instance_key = single_instance_name(machine_name, service_name); + const service = await get_service(base_path, "admin"); + return service?.[instance_key]; +} diff --git a/pkgs/webview-ui/app/src/api/wifi.ts b/pkgs/webview-ui/app/src/api/wifi.ts new file mode 100644 index 000000000..d6ae89785 --- /dev/null +++ b/pkgs/webview-ui/app/src/api/wifi.ts @@ -0,0 +1,18 @@ +import { callApi } from "."; +import { Schema as Inventory } from "@/api/Inventory"; + +export const instance_name = (machine_name: string) => + `${machine_name}_wifi_0` as const; + +export async function get_iwd_service(base_path: string, machine_name: string) { + const r = await callApi("get_inventory", { + base_path, + }); + if (r.status == "error") { + return null; + } + const inventory: Inventory = r.data; + + const instance_key = instance_name(machine_name); + return inventory.services?.iwd?.[instance_key] || null; +} diff --git a/pkgs/webview-ui/app/src/routes/clans/details.tsx b/pkgs/webview-ui/app/src/routes/clans/details.tsx index 1620763de..62e73726e 100644 --- a/pkgs/webview-ui/app/src/routes/clans/details.tsx +++ b/pkgs/webview-ui/app/src/routes/clans/details.tsx @@ -1,4 +1,9 @@ -import { callApi, SuccessQuery } from "@/src/api"; +import { + callApi, + ClanService, + ClanServiceInstance, + SuccessQuery, +} from "@/src/api"; import { BackButton } from "@/src/components/BackButton"; import { useParams } from "@solidjs/router"; import { @@ -19,6 +24,7 @@ import { } from "@modular-forms/solid"; import { TextInput } from "@/src/components/TextInput"; import toast from "solid-toast"; +import { get_single_service } from "@/src/api/inventory"; interface AdminModuleFormProps { admin: AdminData; @@ -184,19 +190,20 @@ const AdminModuleForm = (props: AdminModuleFormProps) => { const handleSubmit = async (values: AdminSettings) => { console.log("submitting", values, getValues(formStore)); - const r = await callApi("set_admin_service", { - base_url: props.base_url, - allowed_keys: values.allowedKeys.reduce( - (acc, curr) => ({ ...acc, [curr.name]: curr.value }), - {}, - ), - }); - if (r.status === "success") { - toast.success("Successfully updated admin settings"); - } - if (r.status === "error") { - toast.error(`Failed to update admin settings: ${r.errors[0].message}`); - } + // const r = await callApi("set_admin_service", { + // base_url: props.base_url, + // allowed_keys: values.allowedKeys.reduce( + // (acc, curr) => ({ ...acc, [curr.name]: curr.value }), + // {} + // ), + // }); + // if (r.status === "success") { + // toast.success("Successfully updated admin settings"); + // } + // if (r.status === "error") { + // toast.error(`Failed to update admin settings: ${r.errors[0].message}`); + toast.error(`Failed to update admin settings: feature disabled`); + // } queryClient.invalidateQueries({ queryKey: [props.base_url, "get_admin_service"], }); @@ -329,7 +336,7 @@ const AdminModuleForm = (props: AdminModuleFormProps) => { }; type GeneralData = SuccessQuery<"show_clan_meta">["data"]; -type AdminData = SuccessQuery<"get_admin_service">["data"]; +type AdminData = ClanServiceInstance<"admin">; export const ClanDetails = () => { const params = useParams(); @@ -347,11 +354,9 @@ export const ClanDetails = () => { const adminQuery = createQuery(() => ({ queryKey: [clan_dir, "get_admin_service"], queryFn: async () => { - const result = await callApi("get_admin_service", { - base_url: clan_dir, - }); - if (result.status === "error") throw new Error("Failed to fetch data"); - return result.data || null; + const result = await get_single_service(clan_dir, "", "admin"); + if (!result) throw new Error("Failed to fetch data"); + return result || null; }, })); diff --git a/pkgs/webview-ui/app/src/routes/machines/details.tsx b/pkgs/webview-ui/app/src/routes/machines/details.tsx index 821e9cea6..6e5499735 100644 --- a/pkgs/webview-ui/app/src/routes/machines/details.tsx +++ b/pkgs/webview-ui/app/src/routes/machines/details.tsx @@ -1,4 +1,12 @@ -import { callApi, SuccessData, SuccessQuery } from "@/src/api"; +import { + callApi, + ClanService, + Services, + SuccessData, + SuccessQuery, +} from "@/src/api"; +import { set_single_disk_id } from "@/src/api/disk"; +import { get_iwd_service } from "@/src/api/wifi"; import { activeURI } from "@/src/App"; import { BackButton } from "@/src/components/BackButton"; import { FileInput } from "@/src/components/FileInput"; @@ -117,17 +125,12 @@ const InstallMachine = (props: InstallMachineProps) => { return; } - const r = await callApi("set_single_disk_uuid", { - base_path: curr_uri, - machine_name: props.name, - disk_uuid: disk_id, - }); - if (r.status === "error") { - toast.error("Failed to set disk"); - } - if (r.status === "success") { + const r = await set_single_disk_id(curr_uri, props.name, disk_id); + if (!r) { toast.success("Disk set successfully"); setConfirmDisk(true); + } else { + toast.error("Failed to set disk"); } }; @@ -600,7 +603,7 @@ const MachineForm = (props: MachineDetailsProps) => { ); }; -type WifiData = SuccessData<"get_iwd_service">; +type WifiData = ClanService<"iwd">; export const MachineDetails = () => { const params = useParams(); @@ -629,12 +632,9 @@ export const MachineDetails = () => { queryFn: async () => { const curr = activeURI(); if (curr) { - const result = await callApi("get_iwd_service", { - base_url: curr, - machine_name: params.id, - }); - if (result.status === "error") throw new Error("Failed to fetch data"); - return Object.entries(result.data?.config?.networks || {}).map( + const result = await get_iwd_service(curr, params.id); + if (!result) throw new Error("Failed to fetch data"); + return Object.entries(result?.config?.networks || {}).map( ([name, value]) => ({ name, ssid: value.ssid }), ); } @@ -728,17 +728,17 @@ function WifiModule(props: MachineWifiProps) { ); console.log("submitting", values, networks); - const r = await callApi("set_iwd_service_for_machine", { - base_url: props.base_url, - machine_name: props.machine_name, - networks: networks, - }); - if (r.status === "error") { - toast.error("Failed to set wifi"); - } - if (r.status === "success") { - toast.success("Wifi set successfully"); - } + // const r = await callApi("set_iwd_service_for_machine", { + // base_url: props.base_url, + // machine_name: props.machine_name, + // networks: networks, + // }); + // if (r.status === "error") { + toast.error("Failed to set wifi. Feature disabled temporarily"); + // } + // if (r.status === "success") { + // toast.success("Wifi set successfully"); + // } }; return (