From dbafd014826a48963645fa38da074752ae4cc9c1 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 8 Jul 2025 16:31:51 +0200 Subject: [PATCH 1/3] api/disk_schema: make getter consistent --- pkgs/clan-cli/clan_cli/flash/list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/clan-cli/clan_cli/flash/list.py b/pkgs/clan-cli/clan_cli/flash/list.py index cbcfdace2..cbe327151 100644 --- a/pkgs/clan-cli/clan_cli/flash/list.py +++ b/pkgs/clan-cli/clan_cli/flash/list.py @@ -18,7 +18,7 @@ class FlashOptions(TypedDict): @API.register -def get_flash_options() -> FlashOptions: +def get_machine_flash_options() -> FlashOptions: """Retrieve available languages and keymaps for flash configuration. Returns: FlashOptions: A dictionary containing lists of available languages and keymaps. From 4beff2e02376224ce5bbb43541b41642ffc09bc2 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 8 Jul 2025 17:14:47 +0200 Subject: [PATCH 2/3] api: rename 'run_machine_deploy' into 'run_machine_update' --- pkgs/clan-cli/clan_cli/machines/update.py | 4 +- .../clan_lib/log_manager/example_usage.py | 4 +- .../clan_lib/log_manager/test_log_manager.py | 66 +++++++++---------- pkgs/clan-cli/clan_lib/machines/update.py | 2 +- 4 files changed, 38 insertions(+), 38 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/machines/update.py b/pkgs/clan-cli/clan_cli/machines/update.py index 5fdaa1f4d..d29e6a333 100644 --- a/pkgs/clan-cli/clan_cli/machines/update.py +++ b/pkgs/clan-cli/clan_cli/machines/update.py @@ -9,7 +9,7 @@ from clan_lib.machines.actions import list_machines from clan_lib.machines.list import instantiate_inventory_to_machines from clan_lib.machines.machines import Machine from clan_lib.machines.suggestions import validate_machine_names -from clan_lib.machines.update import run_machine_deploy +from clan_lib.machines.update import run_machine_update from clan_lib.nix import nix_config from clan_lib.ssh.remote import Remote @@ -144,7 +144,7 @@ def update_command(args: argparse.Namespace) -> None: tid=machine.name, async_ctx=AsyncContext(prefix=machine.name), ), - run_machine_deploy, + run_machine_update, machine=machine, target_host=target_host, build_host=machine.build_host(), diff --git a/pkgs/clan-cli/clan_lib/log_manager/example_usage.py b/pkgs/clan-cli/clan_lib/log_manager/example_usage.py index 03c85f727..c1aebda3c 100755 --- a/pkgs/clan-cli/clan_lib/log_manager/example_usage.py +++ b/pkgs/clan-cli/clan_lib/log_manager/example_usage.py @@ -17,7 +17,7 @@ def example_function() -> None: """Example function for creating logs.""" -def run_machine_deploy() -> None: +def run_machine_update() -> None: """Function for deploying machines.""" @@ -41,7 +41,7 @@ def main() -> None: for repo in repos: for machine in machines: log_manager.create_log_file( - run_machine_deploy, + run_machine_update, f"deploy_{machine}", ["clans", repo, "machines", machine], ) diff --git a/pkgs/clan-cli/clan_lib/log_manager/test_log_manager.py b/pkgs/clan-cli/clan_lib/log_manager/test_log_manager.py index 49f1c88f8..422e85083 100644 --- a/pkgs/clan-cli/clan_lib/log_manager/test_log_manager.py +++ b/pkgs/clan-cli/clan_lib/log_manager/test_log_manager.py @@ -17,7 +17,7 @@ from clan_lib.log_manager import ( # Test functions for log creation -def run_machine_deploy() -> None: +def run_machine_update() -> None: """Test function for deploying machines.""" @@ -194,13 +194,13 @@ class TestLogFileCreation: for repo in repos: for machine in machines: log_file = configured_log_manager.create_log_file( - run_machine_deploy, + run_machine_update, f"deploy_{machine}", ["clans", repo, "machines", machine], ) assert log_file.op_key == f"deploy_{machine}" - assert log_file.func_name == "run_machine_deploy" + assert log_file.func_name == "run_machine_update" assert log_file.get_file_path().exists() # Check the group structure includes URL encoding for dynamic parts @@ -241,7 +241,7 @@ class TestFilterFunction: """Test that empty filter returns top-level groups.""" # Create some log files first configured_log_manager.create_log_file( - run_machine_deploy, "test_op", ["clans", "repo1", "machines", "machine1"] + run_machine_update, "test_op", ["clans", "repo1", "machines", "machine1"] ) top_level = configured_log_manager.filter([]) @@ -258,7 +258,7 @@ class TestFilterFunction: for repo in repos: for machine in machines: configured_log_manager.create_log_file( - run_machine_deploy, + run_machine_update, f"deploy_{machine}", ["clans", repo, "machines", machine], ) @@ -281,7 +281,7 @@ class TestFilterFunction: """Test filtering with specific date.""" # Create log file log_file = configured_log_manager.create_log_file( - run_machine_deploy, "test_op", ["clans", "repo1", "machines", "machine1"] + run_machine_update, "test_op", ["clans", "repo1", "machines", "machine1"] ) # Filter with the specific date @@ -308,7 +308,7 @@ class TestGetLogFile: """Test getting log file by operation key.""" # Create log file configured_log_manager.create_log_file( - run_machine_deploy, + run_machine_update, "deploy_wintux", ["clans", "repo1", "machines", "wintux"], ) @@ -317,7 +317,7 @@ class TestGetLogFile: found_log_file = configured_log_manager.get_log_file("deploy_wintux") assert found_log_file is not None assert found_log_file.op_key == "deploy_wintux" - assert found_log_file.func_name == "run_machine_deploy" + assert found_log_file.func_name == "run_machine_update" def test_get_log_file_with_selector( self, configured_log_manager: LogManager @@ -325,12 +325,12 @@ class TestGetLogFile: """Test getting log file with specific selector like example_usage.py.""" # Create log files in different locations configured_log_manager.create_log_file( - run_machine_deploy, + run_machine_update, "deploy_wintux", ["clans", "repo1", "machines", "wintux"], ) configured_log_manager.create_log_file( - run_machine_deploy, + run_machine_update, "deploy_wintux", ["clans", "repo2", "machines", "wintux"], ) @@ -347,7 +347,7 @@ class TestGetLogFile: """Test getting log file with specific date.""" # Create log file log_file = configured_log_manager.create_log_file( - run_machine_deploy, "deploy_demo", ["clans", "repo1", "machines", "demo"] + run_machine_update, "deploy_demo", ["clans", "repo1", "machines", "demo"] ) # Find it by op_key and date @@ -384,10 +384,10 @@ class TestListLogDays: """Test listing log days when logs exist.""" # Create log files configured_log_manager.create_log_file( - run_machine_deploy, "op1", ["clans", "repo1", "machines", "machine1"] + run_machine_update, "op1", ["clans", "repo1", "machines", "machine1"] ) configured_log_manager.create_log_file( - run_machine_deploy, "op2", ["clans", "repo2", "machines", "machine2"] + run_machine_update, "op2", ["clans", "repo2", "machines", "machine2"] ) days = configured_log_manager.list_log_days() @@ -412,7 +412,7 @@ class TestApiCompatibility: for repo in repos: for machine in machines: configured_log_manager.create_log_file( - run_machine_deploy, + run_machine_update, f"deploy_{machine}", ["clans", repo, "machines", machine], ) @@ -723,21 +723,21 @@ class TestLogFileSorting: expected_order ): actual = sorted_files[i] - assert actual.op_key == exp_op, ( - f"Position {i}: expected op_key {exp_op}, got {actual.op_key}" - ) - assert actual.date_day == exp_date, ( - f"Position {i}: expected date {exp_date}, got {actual.date_day}" - ) - assert actual.group == exp_group, ( - f"Position {i}: expected group {exp_group}, got {actual.group}" - ) - assert actual.func_name == exp_func, ( - f"Position {i}: expected func {exp_func}, got {actual.func_name}" - ) - assert actual.date_second == exp_time, ( - f"Position {i}: expected time {exp_time}, got {actual.date_second}" - ) + assert ( + actual.op_key == exp_op + ), f"Position {i}: expected op_key {exp_op}, got {actual.op_key}" + assert ( + actual.date_day == exp_date + ), f"Position {i}: expected date {exp_date}, got {actual.date_day}" + assert ( + actual.group == exp_group + ), f"Position {i}: expected group {exp_group}, got {actual.group}" + assert ( + actual.func_name == exp_func + ), f"Position {i}: expected func {exp_func}, got {actual.func_name}" + assert ( + actual.date_second == exp_time + ), f"Position {i}: expected time {exp_time}, got {actual.date_second}" def test_get_log_file_returns_newest_when_multiple_exist( self, configured_log_manager: LogManager @@ -747,19 +747,19 @@ class TestLogFileSorting: # This simulates the realistic scenario where the same operation runs on different machines configured_log_manager.create_log_file( - run_machine_deploy, + run_machine_update, "deploy_operation", ["clans", "repo1", "machines", "machine1"], ) configured_log_manager.create_log_file( - run_machine_deploy, + run_machine_update, "deploy_operation", ["clans", "repo1", "machines", "machine2"], ) configured_log_manager.create_log_file( - run_machine_deploy, + run_machine_update, "deploy_operation", ["clans", "repo2", "machines", "machine1"], ) @@ -825,7 +825,7 @@ class TestURLEncoding: # Create log file with special characters log_file = configured_log_manager.create_log_file( - run_machine_deploy, + run_machine_update, "deploy_special", ["clans", special_repo, "machines", special_machine], ) diff --git a/pkgs/clan-cli/clan_lib/machines/update.py b/pkgs/clan-cli/clan_lib/machines/update.py index dfb725525..0ec0f8685 100644 --- a/pkgs/clan-cli/clan_lib/machines/update.py +++ b/pkgs/clan-cli/clan_lib/machines/update.py @@ -103,7 +103,7 @@ def upload_sources(machine: Machine, ssh: Remote) -> str: @API.register -def run_machine_deploy( +def run_machine_update( machine: Machine, target_host: Remote, build_host: Remote | None ) -> None: """Update an existing machine using nixos-rebuild or darwin-rebuild. From da04ab63b298b72efeb65de1f08d871f0923fe04 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Tue, 8 Jul 2025 17:20:31 +0200 Subject: [PATCH 3/3] api/docs: sort resources into tree order --- .../clan_lib/log_manager/test_log_manager.py | 30 +++++++++---------- pkgs/clan-cli/openapi.py | 28 +++++++++++++++++ 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/pkgs/clan-cli/clan_lib/log_manager/test_log_manager.py b/pkgs/clan-cli/clan_lib/log_manager/test_log_manager.py index 422e85083..e68e9385f 100644 --- a/pkgs/clan-cli/clan_lib/log_manager/test_log_manager.py +++ b/pkgs/clan-cli/clan_lib/log_manager/test_log_manager.py @@ -723,21 +723,21 @@ class TestLogFileSorting: expected_order ): actual = sorted_files[i] - assert ( - actual.op_key == exp_op - ), f"Position {i}: expected op_key {exp_op}, got {actual.op_key}" - assert ( - actual.date_day == exp_date - ), f"Position {i}: expected date {exp_date}, got {actual.date_day}" - assert ( - actual.group == exp_group - ), f"Position {i}: expected group {exp_group}, got {actual.group}" - assert ( - actual.func_name == exp_func - ), f"Position {i}: expected func {exp_func}, got {actual.func_name}" - assert ( - actual.date_second == exp_time - ), f"Position {i}: expected time {exp_time}, got {actual.date_second}" + assert actual.op_key == exp_op, ( + f"Position {i}: expected op_key {exp_op}, got {actual.op_key}" + ) + assert actual.date_day == exp_date, ( + f"Position {i}: expected date {exp_date}, got {actual.date_day}" + ) + assert actual.group == exp_group, ( + f"Position {i}: expected group {exp_group}, got {actual.group}" + ) + assert actual.func_name == exp_func, ( + f"Position {i}: expected func {exp_func}, got {actual.func_name}" + ) + assert actual.date_second == exp_time, ( + f"Position {i}: expected time {exp_time}, got {actual.date_second}" + ) def test_get_log_file_returns_newest_when_multiple_exist( self, configured_log_manager: LogManager diff --git a/pkgs/clan-cli/openapi.py b/pkgs/clan-cli/openapi.py index bd6c280de..ed4cfde04 100644 --- a/pkgs/clan-cli/openapi.py +++ b/pkgs/clan-cli/openapi.py @@ -118,6 +118,32 @@ def make_schema_name(func_name: str, part: str) -> str: return f"{func_name}_{part}" +def get_tag_key(tags: list[str]) -> tuple: + """Convert list of tags to a tuple key for sorting.""" + return tuple(tags) + + +def sort_openapi_paths_by_tag_tree(openapi: dict) -> None: + # Extract (tags, path, method, operation) tuples + operations = [] + + for path, methods in openapi["paths"].items(): + for method, operation in methods.items(): + tag_path = operation.get("tags", []) + operations.append((tag_path, path, method, operation)) + + # Sort by the tag hierarchy + operations.sort(key=lambda x: get_tag_key(x[0])) + + # Rebuild sorted openapi["paths"] + sorted_paths: dict = {} + for _tag_path, path, method, operation in operations: + sorted_paths[path] = sorted_paths.get(path, {}) + sorted_paths[path][method] = operation + + openapi["paths"] = dict(sorted_paths) # Ensure it's a plain dict + + def main() -> None: input_path = Path(os.environ["INPUT_PATH"]) @@ -203,6 +229,8 @@ def main() -> None: } } + sort_openapi_paths_by_tag_tree(openapi) + # === Add global definitions from $defs === for def_name, def_schema in defs.items(): fixed_schema = fix_nullables(deepcopy(def_schema))