diff --git a/pkgs/clan-cli/clan_cli/dirs.py b/pkgs/clan-cli/clan_cli/dirs.py index 77d32990d..e0a000dd6 100644 --- a/pkgs/clan-cli/clan_cli/dirs.py +++ b/pkgs/clan-cli/clan_cli/dirs.py @@ -15,6 +15,11 @@ def find_git_repo_root() -> Path | None: return find_toplevel([".git"]) +def clan_key_safe(clan_name: str, flake_url: str) -> str: + quoted_url = urllib.parse.quote_plus(flake_url) + return f"{clan_name}-{quoted_url}" + + def find_toplevel(top_level_files: list[str]) -> Path | None: """Returns the path to the toplevel of the clan flake""" for project_file in top_level_files: @@ -45,9 +50,7 @@ def user_gcroot_dir() -> Path: def specific_groot_dir(*, clan_name: str, flake_url: str) -> Path: # Always build icon so that we can symlink it to the gcroot gcroot_dir = user_gcroot_dir() - burl = urllib.parse.quote_plus(flake_url) - clan_gcroot = gcroot_dir / f"{clan_name}-{burl}" - + clan_gcroot = gcroot_dir / clan_key_safe(clan_name, flake_url) clan_gcroot.mkdir(parents=True, exist_ok=True) return clan_gcroot @@ -56,8 +59,9 @@ def user_history_file() -> Path: return user_config_dir() / "clan" / "history" -def vm_state_dir(clan_name: str, vm_name: str) -> Path: - return user_config_dir() / "clan" / "vmstate" / clan_name / vm_name +def vm_state_dir(clan_name: str, flake_url: str, vm_name: str) -> Path: + clan_key = clan_key_safe(clan_name, flake_url) + return user_config_dir() / "clan" / "vmstate" / clan_key / vm_name def machines_dir(flake_dir: Path) -> Path: diff --git a/pkgs/clan-cli/clan_cli/vms/run.py b/pkgs/clan-cli/clan_cli/vms/run.py index c8f326399..5d0717737 100644 --- a/pkgs/clan-cli/clan_cli/vms/run.py +++ b/pkgs/clan-cli/clan_cli/vms/run.py @@ -255,7 +255,7 @@ def run_vm( secrets_dir = generate_secrets(vm, nixos_config, tmpdir, log_fd) disk_img = prepare_disk(tmpdir, log_fd) - state_dir = vm_state_dir(vm.clan_name, machine) + state_dir = vm_state_dir(vm.clan_name, str(vm.flake_url), machine) state_dir.mkdir(parents=True, exist_ok=True) qemu_cmd = qemu_command( diff --git a/pkgs/clan-cli/tests/test_dirs.py b/pkgs/clan-cli/tests/test_dirs.py index 25191b419..75b01d6a3 100644 --- a/pkgs/clan-cli/tests/test_dirs.py +++ b/pkgs/clan-cli/tests/test_dirs.py @@ -15,3 +15,21 @@ # monkeypatch.chdir(subdir) # (subdir / ".clan-flake").touch() # assert _get_clan_flake_toplevel() == subdir + +from clan_cli.dirs import clan_key_safe, vm_state_dir + + +def test_clan_key_safe() -> None: + assert clan_key_safe("clan1", "/foo/bar") == "clan1-%2Ffoo%2Fbar" + + +def test_vm_state_dir_identity() -> None: + dir1 = vm_state_dir("clan1", "https://some.clan", "vm1") + dir2 = vm_state_dir("clan1", "https://some.clan", "vm1") + assert str(dir1) == str(dir2) + + +def test_vm_state_dir_no_collision() -> None: + dir1 = vm_state_dir("clan1", "/foo/bar", "vm1") + dir2 = vm_state_dir("clan1", "https://some.clan", "vm1") + assert str(dir1) != str(dir2) diff --git a/pkgs/clan-cli/tests/test_vms_cli.py b/pkgs/clan-cli/tests/test_vms_cli.py index af72be46b..61d86aeb0 100644 --- a/pkgs/clan-cli/tests/test_vms_cli.py +++ b/pkgs/clan-cli/tests/test_vms_cli.py @@ -7,6 +7,8 @@ from cli import Cli from fixtures_flakes import FlakeForTest, generate_flake from root import CLAN_CORE +from clan_cli.dirs import vm_state_dir + if TYPE_CHECKING: from age_keys import KeyPair @@ -89,14 +91,8 @@ def test_vm_persistence( ) monkeypatch.chdir(flake.path) Cli().run(["vms", "run", "my_machine"]) - test_file = ( - temporary_home - / ".config" - / "clan" - / "vmstate" - / "_test_vm_persistence" - / "my_machine" + vm_state_dir("_test_vm_persistence", str(flake.path), "my_machine") / "var" / "my-state" / "test"