From 0e1b4ab96f24477f7771939091a4ad12b44b6450 Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Fri, 6 Oct 2023 09:41:37 +0200 Subject: [PATCH 1/4] clean up theme --- pkgs/theme/src/config.ts | 24 +++++++++---------- pkgs/ui/src/app/theme/themes.ts | 42 +++++++++++++++++++++++---------- 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/pkgs/theme/src/config.ts b/pkgs/theme/src/config.ts index 2d2bb39c..1a252aad 100644 --- a/pkgs/theme/src/config.ts +++ b/pkgs/theme/src/config.ts @@ -20,26 +20,26 @@ export const config: PaletteConfig = { * Steps are defined in 'tones' */ baseColors: { - neutral: { - keyColor: "#807788", - tones: [98], - }, - red: { - keyColor: "#e82439", - tones: [95], - }, green: { keyColor: "#7AC51B", tones: [98], }, - yellow: { - keyColor: "#E0E01F", - tones: [98], - }, purple: { keyColor: "#661bc5", tones: [], }, + neutral: { + keyColor: "#807788", + tones: [2, 5, 8, 98], + }, + red: { + keyColor: "#e82439", + tones: [95], + }, + yellow: { + keyColor: "#E0E01F", + tones: [98], + }, blue: { keyColor: "#1B7AC5", tones: [95], diff --git a/pkgs/ui/src/app/theme/themes.ts b/pkgs/ui/src/app/theme/themes.ts index 3da1b34d..ede9c8c9 100644 --- a/pkgs/ui/src/app/theme/themes.ts +++ b/pkgs/ui/src/app/theme/themes.ts @@ -1,8 +1,9 @@ -import { createTheme } from "@mui/material/styles"; +import { ThemeOptions, createTheme } from "@mui/material/styles"; import colors from "@clan/colors/colors.json"; +const { palette, common } = colors.ref; -export const darkTheme = createTheme({ +const commonOptions: Partial = { breakpoints: { values: { xs: 0, @@ -12,22 +13,39 @@ export const darkTheme = createTheme({ xl: 1536, }, }, +}; + +export const darkTheme = createTheme({ + ...commonOptions, palette: { mode: "dark", + background: { + default: palette.neutral5.value, + paper: palette.neutral20.value, + }, + primary: { + main: palette.green60.value, + }, + secondary: { + main: palette.green60.value, + }, + error: { + main: palette.red60.value, + }, + warning: { + main: palette.yellow60.value, + }, + success: { + main: palette.green60.value, + }, + info: { + main: palette.red60.value, + }, }, }); -const { palette, common } = colors.ref; export const lightTheme = createTheme({ - breakpoints: { - values: { - xs: 0, - sm: 400, - md: 900, - lg: 1200, - xl: 1536, - }, - }, + ...commonOptions, palette: { mode: "light", background: { From 5ab084093947975eef1b70948cd80490b8b19e65 Mon Sep 17 00:00:00 2001 From: ui-asset-bot Date: Fri, 6 Oct 2023 07:52:25 +0000 Subject: [PATCH 2/4] update ui-assets.nix --- pkgs/ui/nix/ui-assets.nix | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkgs/ui/nix/ui-assets.nix b/pkgs/ui/nix/ui-assets.nix index 455a4288..e69ae4cc 100644 --- a/pkgs/ui/nix/ui-assets.nix +++ b/pkgs/ui/nix/ui-assets.nix @@ -1,5 +1,5 @@ { fetchzip }: fetchzip { - url = "https://git.clan.lol/api/packages/clan/generic/ui/0l0hjjpvqyfiz5jk1yrqdhi50jc79v02fhdi7p7s39v1nfxzh3yn/assets.tar.gz"; - sha256 = "0l0hjjpvqyfiz5jk1yrqdhi50jc79v02fhdi7p7s39v1nfxzh3yn"; + url = "https://git.clan.lol/api/packages/clan/generic/ui/044pm5casi89nrbzp06l2akn797cdjcj49yyf495fspqfya3kxvz/assets.tar.gz"; + sha256 = "044pm5casi89nrbzp06l2akn797cdjcj49yyf495fspqfya3kxvz"; } From c5b2e9b5f3c3d60c8142ee8a30043374bfc4689a Mon Sep 17 00:00:00 2001 From: DavHau Date: Fri, 6 Oct 2023 18:34:49 +0200 Subject: [PATCH 3/4] secrets: fix: don't generate secrets if no secrets are defined --- nixosModules/clanCore/flake-module.nix | 2 + .../clanCore/secrets/password-store.nix | 64 ++++++++++--------- nixosModules/clanCore/secrets/sops.nix | 2 +- 3 files changed, 36 insertions(+), 32 deletions(-) diff --git a/nixosModules/clanCore/flake-module.nix b/nixosModules/clanCore/flake-module.nix index a45dda7b..d3b494cc 100644 --- a/nixosModules/clanCore/flake-module.nix +++ b/nixosModules/clanCore/flake-module.nix @@ -66,12 +66,14 @@ description = '' script to upload secrets to the deployment server ''; + default = "${pkgs.coreutils}/bin/true"; }; generateSecrets = lib.mkOption { type = lib.types.path; description = '' script to generate secrets ''; + default = "${pkgs.coreutils}/bin/true"; }; vm.config = lib.mkOption { type = lib.types.attrs; diff --git a/nixosModules/clanCore/secrets/password-store.nix b/nixosModules/clanCore/secrets/password-store.nix index d21eed50..cf23ccbf 100644 --- a/nixosModules/clanCore/secrets/password-store.nix +++ b/nixosModules/clanCore/secrets/password-store.nix @@ -13,44 +13,46 @@ in config = lib.mkIf (config.clanCore.secretStore == "password-store") { clanCore.secretsDirectory = config.clan.password-store.targetDirectory; clanCore.secretsUploadDirectory = config.clan.password-store.targetDirectory; - system.clan.generateSecrets = pkgs.writeScript "generate-secrets" '' - #!/bin/sh - set -efu + system.clan.generateSecrets = lib.mkIf (config.clanCore.secrets != { }) ( + pkgs.writeScript "generate-secrets" '' + #!/bin/sh + set -efu - test -d "$CLAN_DIR" - PATH=${lib.makeBinPath [ - pkgs.pass - ]}:$PATH + test -d "$CLAN_DIR" + PATH=${lib.makeBinPath [ + pkgs.pass + ]}:$PATH - # TODO maybe initialize password store if it doesn't exist yet + # TODO maybe initialize password store if it doesn't exist yet - ${lib.foldlAttrs (acc: n: v: '' - ${acc} - # ${n} - # if any of the secrets are missing, we regenerate all connected facts/secrets - (if ! (${lib.concatMapStringsSep " && " (x: "test -e ${passwordstoreDir}/machines/${config.clanCore.machineName}/${x.name}.gpg >/dev/null") (lib.attrValues v.secrets)}); then + ${lib.foldlAttrs (acc: n: v: '' + ${acc} + # ${n} + # if any of the secrets are missing, we regenerate all connected facts/secrets + (if ! (${lib.concatMapStringsSep " && " (x: "test -e ${passwordstoreDir}/machines/${config.clanCore.machineName}/${x.name}.gpg >/dev/null") (lib.attrValues v.secrets)}); then - tmpdir=$(mktemp -d) - trap "rm -rf $tmpdir" EXIT - cd $tmpdir + tmpdir=$(mktemp -d) + trap "rm -rf $tmpdir" EXIT + cd $tmpdir - facts=$(mktemp -d) - trap "rm -rf $facts" EXIT - secrets=$(mktemp -d) - trap "rm -rf $secrets" EXIT - ( ${v.generator} ) + facts=$(mktemp -d) + trap "rm -rf $facts" EXIT + secrets=$(mktemp -d) + trap "rm -rf $secrets" EXIT + ( ${v.generator} ) - ${lib.concatMapStrings (fact: '' - mkdir -p "$CLAN_DIR"/"$(dirname ${fact.path})" - cp "$facts"/${fact.name} "$CLAN_DIR"/${fact.path} - '') (lib.attrValues v.facts)} + ${lib.concatMapStrings (fact: '' + mkdir -p "$CLAN_DIR"/"$(dirname ${fact.path})" + cp "$facts"/${fact.name} "$CLAN_DIR"/${fact.path} + '') (lib.attrValues v.facts)} - ${lib.concatMapStrings (secret: '' - cat "$secrets"/${secret.name} | pass insert -m machines/${config.clanCore.machineName}/${secret.name} - '') (lib.attrValues v.secrets)} - fi) - '') "" config.clanCore.secrets} - ''; + ${lib.concatMapStrings (secret: '' + cat "$secrets"/${secret.name} | pass insert -m machines/${config.clanCore.machineName}/${secret.name} + '') (lib.attrValues v.secrets)} + fi) + '') "" config.clanCore.secrets} + '' + ); system.clan.uploadSecrets = pkgs.writeScript "upload-secrets" '' #!/bin/sh set -efu diff --git a/nixosModules/clanCore/secrets/sops.nix b/nixosModules/clanCore/secrets/sops.nix index fb792d46..99f6b84a 100644 --- a/nixosModules/clanCore/secrets/sops.nix +++ b/nixosModules/clanCore/secrets/sops.nix @@ -25,7 +25,7 @@ in config = lib.mkIf (config.clanCore.secretStore == "sops") { clanCore.secretsDirectory = "/run/secrets"; clanCore.secretsPrefix = config.clanCore.machineName + "-"; - system.clan = { + system.clan = lib.mkIf (config.clanCore.secrets != { }) { generateSecrets = pkgs.writeScript "generate-secrets" '' #!${pkgs.python3}/bin/python From ceb6bdd47453e5bfe0c4de56be51adc2e3bc950b Mon Sep 17 00:00:00 2001 From: DavHau Date: Fri, 6 Oct 2023 18:52:56 +0200 Subject: [PATCH 4/4] clan join: test create vm for clan join This adds a vm create test for the `clan join` scenario where: - there is no local clan to write changes to - a machine from a remote flake needs to be built and run - no users and no secrets need to be or can be managed (no flake to write files to) --- pkgs/clan-cli/tests/conftest.py | 2 +- pkgs/clan-cli/tests/fixtures_flakes.py | 90 +++++++++++++++ pkgs/clan-cli/tests/machines/vm1/default.nix | 20 ++++ .../machines/vm_with_secrets/default.nix | 20 ++++ .../machines/vm_without_secrets/default.nix | 17 +++ pkgs/clan-cli/tests/test_flake.py | 59 ---------- .../test_flake_with_core_and_pass/flake.nix | 2 +- .../.clan-flake | 0 .../flake.nix | 24 ++++ pkgs/clan-cli/tests/test_vms_api.py | 55 --------- pkgs/clan-cli/tests/test_vms_api_create.py | 106 ++++++++++++++++++ 11 files changed, 279 insertions(+), 116 deletions(-) create mode 100644 pkgs/clan-cli/tests/fixtures_flakes.py create mode 100644 pkgs/clan-cli/tests/machines/vm1/default.nix create mode 100644 pkgs/clan-cli/tests/machines/vm_with_secrets/default.nix create mode 100644 pkgs/clan-cli/tests/machines/vm_without_secrets/default.nix delete mode 100644 pkgs/clan-cli/tests/test_flake.py create mode 100644 pkgs/clan-cli/tests/test_flake_with_core_dynamic_machines/.clan-flake create mode 100644 pkgs/clan-cli/tests/test_flake_with_core_dynamic_machines/flake.nix create mode 100644 pkgs/clan-cli/tests/test_vms_api_create.py diff --git a/pkgs/clan-cli/tests/conftest.py b/pkgs/clan-cli/tests/conftest.py index 925c7cad..c3c11e75 100644 --- a/pkgs/clan-cli/tests/conftest.py +++ b/pkgs/clan-cli/tests/conftest.py @@ -18,7 +18,7 @@ pytest_plugins = [ "command", "ports", "host_group", - "test_flake", + "fixtures_flakes", ] diff --git a/pkgs/clan-cli/tests/fixtures_flakes.py b/pkgs/clan-cli/tests/fixtures_flakes.py new file mode 100644 index 00000000..0320270c --- /dev/null +++ b/pkgs/clan-cli/tests/fixtures_flakes.py @@ -0,0 +1,90 @@ +import fileinput +import shutil +import tempfile +from pathlib import Path +from typing import Iterator + +import pytest +from root import CLAN_CORE + +from clan_cli.dirs import nixpkgs_source + + +# substitutes string sin a file. +# This can be used on the flake.nix or default.nix of a machine +def substitute( + file: Path, + clan_core_flake: Path | None = None, + flake: Path = Path(__file__).parent, +) -> None: + sops_key = str(flake.joinpath("sops.key")) + for line in fileinput.input(file, inplace=True): + line = line.replace("__NIXPKGS__", str(nixpkgs_source())) + if clan_core_flake: + line = line.replace("__CLAN_CORE__", str(clan_core_flake)) + line = line.replace("__CLAN_SOPS_KEY_PATH__", sops_key) + line = line.replace("__CLAN_SOPS_KEY_DIR__", str(flake)) + print(line, end="") + + +def create_flake( + monkeypatch: pytest.MonkeyPatch, + name: str, + clan_core_flake: Path | None = None, + machines: list[str] = [], + remote: bool = False, +) -> Iterator[Path]: + """ + Creates a flake with the given name and machines. + The machine names map to the machines in ./test_machines + """ + template = Path(__file__).parent / name + # copy the template to a new temporary location + with tempfile.TemporaryDirectory() as tmpdir_: + home = Path(tmpdir_) + flake = home / name + shutil.copytree(template, flake) + # lookup the requested machines in ./test_machines and include them + if machines: + (flake / "machines").mkdir(parents=True, exist_ok=True) + for machine_name in machines: + machine_path = Path(__file__).parent / "machines" / machine_name + shutil.copytree(machine_path, flake / "machines" / machine_name) + substitute(flake / "machines" / machine_name / "default.nix", flake) + # in the flake.nix file replace the string __CLAN_URL__ with the the clan flake + # provided by get_test_flake_toplevel + flake_nix = flake / "flake.nix" + # this is where we would install the sops key to, when updating + substitute(flake_nix, clan_core_flake, flake) + if remote: + with tempfile.TemporaryDirectory() as workdir: + monkeypatch.chdir(workdir) + monkeypatch.setenv("HOME", str(home)) + yield flake + else: + monkeypatch.chdir(flake) + monkeypatch.setenv("HOME", str(home)) + yield flake + + +@pytest.fixture +def test_flake(monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]: + yield from create_flake(monkeypatch, "test_flake") + + +@pytest.fixture +def test_flake_with_core(monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]: + if not (CLAN_CORE / "flake.nix").exists(): + raise Exception( + "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) + + +@pytest.fixture +def test_flake_with_core_and_pass(monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]: + if not (CLAN_CORE / "flake.nix").exists(): + raise Exception( + "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) diff --git a/pkgs/clan-cli/tests/machines/vm1/default.nix b/pkgs/clan-cli/tests/machines/vm1/default.nix new file mode 100644 index 00000000..c6c1ee58 --- /dev/null +++ b/pkgs/clan-cli/tests/machines/vm1/default.nix @@ -0,0 +1,20 @@ +{ lib, ... }: { + clan.networking.deploymentAddress = "__CLAN_DEPLOYMENT_ADDRESS__"; + system.stateVersion = lib.version; + sops.age.keyFile = "__CLAN_SOPS_KEY_PATH__"; + clanCore.secretsUploadDirectory = "__CLAN_SOPS_KEY_DIR__"; + clan.virtualisation.graphics = false; + + clan.networking.zerotier.controller.enable = true; + networking.useDHCP = false; + + systemd.services.shutdown-after-boot = { + enable = true; + wantedBy = [ "multi-user.target" ]; + after = [ "multi-user.target" ]; + script = '' + #!/usr/bin/env bash + shutdown -h now + ''; + }; +} diff --git a/pkgs/clan-cli/tests/machines/vm_with_secrets/default.nix b/pkgs/clan-cli/tests/machines/vm_with_secrets/default.nix new file mode 100644 index 00000000..c6c1ee58 --- /dev/null +++ b/pkgs/clan-cli/tests/machines/vm_with_secrets/default.nix @@ -0,0 +1,20 @@ +{ lib, ... }: { + clan.networking.deploymentAddress = "__CLAN_DEPLOYMENT_ADDRESS__"; + system.stateVersion = lib.version; + sops.age.keyFile = "__CLAN_SOPS_KEY_PATH__"; + clanCore.secretsUploadDirectory = "__CLAN_SOPS_KEY_DIR__"; + clan.virtualisation.graphics = false; + + clan.networking.zerotier.controller.enable = true; + networking.useDHCP = false; + + systemd.services.shutdown-after-boot = { + enable = true; + wantedBy = [ "multi-user.target" ]; + after = [ "multi-user.target" ]; + script = '' + #!/usr/bin/env bash + shutdown -h now + ''; + }; +} diff --git a/pkgs/clan-cli/tests/machines/vm_without_secrets/default.nix b/pkgs/clan-cli/tests/machines/vm_without_secrets/default.nix new file mode 100644 index 00000000..96d980d3 --- /dev/null +++ b/pkgs/clan-cli/tests/machines/vm_without_secrets/default.nix @@ -0,0 +1,17 @@ +{ lib, ... }: { + clan.networking.deploymentAddress = "__CLAN_DEPLOYMENT_ADDRESS__"; + system.stateVersion = lib.version; + clan.virtualisation.graphics = false; + + networking.useDHCP = false; + + systemd.services.shutdown-after-boot = { + enable = true; + wantedBy = [ "multi-user.target" ]; + after = [ "multi-user.target" ]; + script = '' + #!/usr/bin/env bash + shutdown -h now + ''; + }; +} diff --git a/pkgs/clan-cli/tests/test_flake.py b/pkgs/clan-cli/tests/test_flake.py deleted file mode 100644 index b27c3bc2..00000000 --- a/pkgs/clan-cli/tests/test_flake.py +++ /dev/null @@ -1,59 +0,0 @@ -import fileinput -import shutil -import tempfile -from pathlib import Path -from typing import Iterator - -import pytest -from root import CLAN_CORE - -from clan_cli.dirs import nixpkgs_source - - -def create_flake( - monkeypatch: pytest.MonkeyPatch, name: str, clan_core_flake: Path | None = None -) -> Iterator[Path]: - template = Path(__file__).parent / name - # copy the template to a new temporary location - with tempfile.TemporaryDirectory() as tmpdir_: - home = Path(tmpdir_) - flake = home / name - shutil.copytree(template, flake) - # in the flake.nix file replace the string __CLAN_URL__ with the the clan flake - # provided by get_test_flake_toplevel - flake_nix = flake / "flake.nix" - # this is where we would install the sops key to, when updating - sops_key = str(flake.joinpath("sops.key")) - for line in fileinput.input(flake_nix, inplace=True): - line = line.replace("__NIXPKGS__", str(nixpkgs_source())) - if clan_core_flake: - line = line.replace("__CLAN_CORE__", str(clan_core_flake)) - line = line.replace("__CLAN_SOPS_KEY_PATH__", sops_key) - line = line.replace("__CLAN_SOPS_KEY_DIR__", str(flake)) - print(line, end="") - monkeypatch.chdir(flake) - monkeypatch.setenv("HOME", str(home)) - yield flake - - -@pytest.fixture -def test_flake(monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]: - yield from create_flake(monkeypatch, "test_flake") - - -@pytest.fixture -def test_flake_with_core(monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]: - if not (CLAN_CORE / "flake.nix").exists(): - raise Exception( - "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) - - -@pytest.fixture -def test_flake_with_core_and_pass(monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]: - if not (CLAN_CORE / "flake.nix").exists(): - raise Exception( - "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) diff --git a/pkgs/clan-cli/tests/test_flake_with_core_and_pass/flake.nix b/pkgs/clan-cli/tests/test_flake_with_core_and_pass/flake.nix index 8bd24afc..38346de6 100644 --- a/pkgs/clan-cli/tests/test_flake_with_core_and_pass/flake.nix +++ b/pkgs/clan-cli/tests/test_flake_with_core_and_pass/flake.nix @@ -2,7 +2,7 @@ # Use this path to our repo root e.g. for UI test # inputs.clan-core.url = "../../../../."; - # this placeholder is replaced by the path to nixpkgs + # this placeholder is replaced by the path to clan-core inputs.clan-core.url = "__CLAN_CORE__"; outputs = { self, clan-core }: diff --git a/pkgs/clan-cli/tests/test_flake_with_core_dynamic_machines/.clan-flake b/pkgs/clan-cli/tests/test_flake_with_core_dynamic_machines/.clan-flake new file mode 100644 index 00000000..e69de29b diff --git a/pkgs/clan-cli/tests/test_flake_with_core_dynamic_machines/flake.nix b/pkgs/clan-cli/tests/test_flake_with_core_dynamic_machines/flake.nix new file mode 100644 index 00000000..7c4558db --- /dev/null +++ b/pkgs/clan-cli/tests/test_flake_with_core_dynamic_machines/flake.nix @@ -0,0 +1,24 @@ +{ + # Use this path to our repo root e.g. for UI test + # inputs.clan-core.url = "../../../../."; + + # this placeholder is replaced by the path to nixpkgs + inputs.clan-core.url = "__CLAN_CORE__"; + + outputs = { self, clan-core }: + let + clan = clan-core.lib.buildClan { + directory = self; + machines = + let + machineModules = builtins.readDir (self + "/machines"); + in + builtins.mapAttrs + (name: _type: import (self + "/machines/${name}")) + machineModules; + }; + in + { + inherit (clan) nixosConfigurations clanInternals; + }; +} diff --git a/pkgs/clan-cli/tests/test_vms_api.py b/pkgs/clan-cli/tests/test_vms_api.py index 02bf655d..273e456e 100644 --- a/pkgs/clan-cli/tests/test_vms_api.py +++ b/pkgs/clan-cli/tests/test_vms_api.py @@ -1,14 +1,7 @@ -import os from pathlib import Path -from typing import TYPE_CHECKING import pytest from api import TestClient -from cli import Cli -from httpx import SyncByteStream - -if TYPE_CHECKING: - from age_keys import KeyPair @pytest.mark.impure @@ -34,51 +27,3 @@ def test_incorrect_uuid(api: TestClient) -> None: for endpoint in uuid_endpoints: response = api.get(endpoint.format("1234")) assert response.status_code == 422, "Failed to get vm status" - - -@pytest.mark.skipif(not os.path.exists("/dev/kvm"), reason="Requires KVM") -@pytest.mark.impure -def test_create( - api: TestClient, - monkeypatch: pytest.MonkeyPatch, - test_flake_with_core: Path, - age_keys: list["KeyPair"], -) -> None: - monkeypatch.chdir(test_flake_with_core) - monkeypatch.setenv("SOPS_AGE_KEY", age_keys[0].privkey) - cli = Cli() - cli.run(["secrets", "users", "add", "user1", age_keys[0].pubkey]) - print(f"flake_url: {test_flake_with_core} ") - response = api.post( - "/api/vms/create", - json=dict( - flake_url=str(test_flake_with_core), - flake_attr="vm1", - cores=1, - memory_size=1024, - graphics=False, - ), - ) - assert response.status_code == 200, "Failed to create vm" - - uuid = response.json()["uuid"] - assert len(uuid) == 36 - assert uuid.count("-") == 4 - - response = api.get(f"/api/vms/{uuid}/status") - assert response.status_code == 200, "Failed to get vm status" - - response = api.get(f"/api/vms/{uuid}/logs") - print("=========VM LOGS==========") - assert isinstance(response.stream, SyncByteStream) - for line in response.stream: - print(line.decode("utf-8")) - print("=========END LOGS==========") - assert response.status_code == 200, "Failed to get vm logs" - - response = api.get(f"/api/vms/{uuid}/status") - assert response.status_code == 200, "Failed to get vm status" - data = response.json() - assert ( - data["status"] == "FINISHED" - ), f"Expected to be finished, but got {data['status']} ({data})" diff --git a/pkgs/clan-cli/tests/test_vms_api_create.py b/pkgs/clan-cli/tests/test_vms_api_create.py new file mode 100644 index 00000000..5ff0fe0a --- /dev/null +++ b/pkgs/clan-cli/tests/test_vms_api_create.py @@ -0,0 +1,106 @@ +import os +from pathlib import Path +from typing import TYPE_CHECKING, Iterator + +import pytest +from api import TestClient +from cli import Cli +from fixtures_flakes import create_flake +from httpx import SyncByteStream +from root import CLAN_CORE + +if TYPE_CHECKING: + from age_keys import KeyPair + + +@pytest.fixture +def flake_with_vm_with_secrets(monkeypatch: pytest.MonkeyPatch) -> Iterator[Path]: + yield from create_flake( + monkeypatch, + "test_flake_with_core_dynamic_machines", + CLAN_CORE, + machines=["vm_with_secrets"], + ) + + +@pytest.fixture +def remote_flake_with_vm_without_secrets( + monkeypatch: pytest.MonkeyPatch, +) -> Iterator[Path]: + yield from create_flake( + monkeypatch, + "test_flake_with_core_dynamic_machines", + CLAN_CORE, + machines=["vm_without_secrets"], + remote=True, + ) + + +@pytest.fixture +def create_user_with_age_key( + monkeypatch: pytest.MonkeyPatch, + age_keys: list["KeyPair"], +) -> None: + monkeypatch.setenv("SOPS_AGE_KEY", age_keys[0].privkey) + cli = Cli() + cli.run(["secrets", "users", "add", "user1", age_keys[0].pubkey]) + + +def generic_create_vm_test(api: TestClient, flake: Path, vm: str) -> None: + print(f"flake_url: {flake} ") + response = api.post( + "/api/vms/create", + json=dict( + flake_url=str(flake), + flake_attr=vm, + cores=1, + memory_size=1024, + graphics=False, + ), + ) + assert response.status_code == 200, "Failed to create vm" + + uuid = response.json()["uuid"] + assert len(uuid) == 36 + assert uuid.count("-") == 4 + + response = api.get(f"/api/vms/{uuid}/status") + assert response.status_code == 200, "Failed to get vm status" + + response = api.get(f"/api/vms/{uuid}/logs") + print("=========VM LOGS==========") + assert isinstance(response.stream, SyncByteStream) + for line in response.stream: + print(line.decode("utf-8")) + print("=========END LOGS==========") + assert response.status_code == 200, "Failed to get vm logs" + + response = api.get(f"/api/vms/{uuid}/status") + assert response.status_code == 200, "Failed to get vm status" + data = response.json() + assert ( + data["status"] == "FINISHED" + ), f"Expected to be finished, but got {data['status']} ({data})" + + +@pytest.mark.skipif(not os.path.exists("/dev/kvm"), reason="Requires KVM") +@pytest.mark.impure +def test_create_local( + api: TestClient, + monkeypatch: pytest.MonkeyPatch, + flake_with_vm_with_secrets: Path, + create_user_with_age_key: None, +) -> None: + generic_create_vm_test(api, flake_with_vm_with_secrets, "vm_with_secrets") + + +@pytest.mark.skipif(not os.path.exists("/dev/kvm"), reason="Requires KVM") +@pytest.mark.impure +def test_create_remote( + api: TestClient, + monkeypatch: pytest.MonkeyPatch, + remote_flake_with_vm_without_secrets: Path, +) -> None: + generic_create_vm_test( + api, remote_flake_with_vm_without_secrets, "vm_without_secrets" + )