Added state directory.
This commit is contained in:
@@ -2,7 +2,7 @@ import asyncio
|
|||||||
import logging
|
import logging
|
||||||
import shlex
|
import shlex
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import NamedTuple, Optional
|
from typing import Any, Callable, Coroutine, Dict, NamedTuple, Optional
|
||||||
|
|
||||||
from .errors import ClanError
|
from .errors import ClanError
|
||||||
|
|
||||||
@@ -36,10 +36,28 @@ async def run(cmd: list[str], cwd: Optional[Path] = None) -> CmdOut:
|
|||||||
raise ClanError(
|
raise ClanError(
|
||||||
f"""
|
f"""
|
||||||
command: {shlex.join(cmd)}
|
command: {shlex.join(cmd)}
|
||||||
|
working directory: {cwd_res}
|
||||||
exit code: {proc.returncode}
|
exit code: {proc.returncode}
|
||||||
command output:
|
stderr:
|
||||||
{stderr.decode("utf-8")}
|
{stderr.decode("utf-8")}
|
||||||
|
stdout:
|
||||||
|
{stdout.decode("utf-8")}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
|
||||||
return CmdOut(stdout.decode("utf-8"), stderr.decode("utf-8"), cwd=cwd)
|
return CmdOut(stdout.decode("utf-8"), stderr.decode("utf-8"), cwd=cwd)
|
||||||
|
|
||||||
|
|
||||||
|
def runforcli(func: Callable[..., Coroutine[Any, Any, Dict[str, CmdOut]]], *args: Any) -> None:
|
||||||
|
try:
|
||||||
|
res = asyncio.run(func(*args))
|
||||||
|
|
||||||
|
for i in res.items():
|
||||||
|
name, out = i
|
||||||
|
if out.stderr:
|
||||||
|
print(f"{name}: {out.stderr}", end="")
|
||||||
|
if out.stdout:
|
||||||
|
print(f"{name}: {out.stdout}", end="")
|
||||||
|
except ClanError as e:
|
||||||
|
print(e)
|
||||||
|
exit(1)
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
# !/usr/bin/env python3
|
# !/usr/bin/env python3
|
||||||
import argparse
|
import argparse
|
||||||
import asyncio
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
from pydantic import AnyUrl
|
from pydantic import AnyUrl
|
||||||
from pydantic.tools import parse_obj_as
|
from pydantic.tools import parse_obj_as
|
||||||
|
|
||||||
from ..async_cmd import CmdOut, run
|
from ..async_cmd import CmdOut, run, runforcli
|
||||||
from ..errors import ClanError
|
|
||||||
from ..nix import nix_command, nix_shell
|
from ..nix import nix_command, nix_shell
|
||||||
|
|
||||||
DEFAULT_URL: AnyUrl = parse_obj_as(AnyUrl, "git+https://git.clan.lol/clan/clan-core#new-clan")
|
DEFAULT_URL: AnyUrl = parse_obj_as(AnyUrl, "git+https://git.clan.lol/clan/clan-core#new-clan")
|
||||||
@@ -33,6 +31,10 @@ async def create_flake(directory: Path, url: AnyUrl) -> Dict[str, CmdOut]:
|
|||||||
out = await run(command, directory)
|
out = await run(command, directory)
|
||||||
response["git init"] = out
|
response["git init"] = out
|
||||||
|
|
||||||
|
command = nix_shell(["git"], ["git", "add", "."])
|
||||||
|
out = await run(command, directory)
|
||||||
|
response["git add"] = out
|
||||||
|
|
||||||
command = nix_shell(["git"], ["git", "config", "user.name", "clan-tool"])
|
command = nix_shell(["git"], ["git", "config", "user.name", "clan-tool"])
|
||||||
out = await run(command, directory)
|
out = await run(command, directory)
|
||||||
response["git config"] = out
|
response["git config"] = out
|
||||||
@@ -49,18 +51,8 @@ async def create_flake(directory: Path, url: AnyUrl) -> Dict[str, CmdOut]:
|
|||||||
|
|
||||||
|
|
||||||
def create_flake_command(args: argparse.Namespace) -> None:
|
def create_flake_command(args: argparse.Namespace) -> None:
|
||||||
try:
|
runforcli(create_flake, args.directory, DEFAULT_URL)
|
||||||
res = asyncio.run(create_flake(args.directory, DEFAULT_URL))
|
|
||||||
|
|
||||||
for i in res.items():
|
|
||||||
name, out = i
|
|
||||||
if out.stderr:
|
|
||||||
print(f"{name}: {out.stderr}", end="")
|
|
||||||
if out.stdout:
|
|
||||||
print(f"{name}: {out.stdout}", end="")
|
|
||||||
except ClanError as e:
|
|
||||||
print(e)
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
|
|
||||||
# takes a (sub)parser and configures it
|
# takes a (sub)parser and configures it
|
||||||
|
|||||||
@@ -1,18 +1,31 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
import logging
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from ..async_cmd import CmdOut, run, runforcli
|
||||||
|
from ..nix import nix_shell
|
||||||
from .folders import machine_folder
|
from .folders import machine_folder
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
def create_machine(name: str) -> None:
|
async def create_machine(name: str) -> Dict[str, CmdOut]:
|
||||||
folder = machine_folder(name)
|
folder = machine_folder(name)
|
||||||
folder.mkdir(parents=True, exist_ok=True)
|
folder.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
# create empty settings.json file inside the folder
|
# create empty settings.json file inside the folder
|
||||||
with open(folder / "settings.json", "w") as f:
|
with open(folder / "settings.json", "w") as f:
|
||||||
f.write("{}")
|
f.write("{}")
|
||||||
|
response = {}
|
||||||
|
out = await run(nix_shell(["git"], ["git", "add", str(folder)]))
|
||||||
|
response["git add"] = out
|
||||||
|
|
||||||
|
out = await run(nix_shell(["git"], ["git", "commit", "-m", f"Added machine {name}", str(folder)]))
|
||||||
|
response["git commit"] = out
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
def create_command(args: argparse.Namespace) -> None:
|
def create_command(args: argparse.Namespace) -> None:
|
||||||
create_machine(args.host)
|
runforcli(create_machine, args.host)
|
||||||
|
|
||||||
|
|
||||||
def register_create_parser(parser: argparse.ArgumentParser) -> None:
|
def register_create_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
|
|||||||
@@ -27,8 +27,8 @@ async def inspect_vm(flake_url: AnyUrl | Path, flake_attr: str) -> VmConfig:
|
|||||||
f'{flake_url}#clanInternals.machines."{system}"."{flake_attr}".config.system.clan.vm.config'
|
f'{flake_url}#clanInternals.machines."{system}"."{flake_attr}".config.system.clan.vm.config'
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
stdout, stderr = await run(cmd)
|
out = await run(cmd)
|
||||||
data = json.loads(stdout)
|
data = json.loads(out.stdout)
|
||||||
return VmConfig(flake_url=flake_url, flake_attr=flake_attr, **data)
|
return VmConfig(flake_url=flake_url, flake_attr=flake_attr, **data)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
# mypy: ignore-errors
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from pydantic import AnyUrl, BaseModel, validator
|
from pydantic import AnyUrl, BaseModel, validator
|
||||||
|
|
||||||
@@ -28,7 +30,7 @@ class ClanDataPath(BaseModel):
|
|||||||
dest: Path
|
dest: Path
|
||||||
|
|
||||||
@validator("dest")
|
@validator("dest")
|
||||||
def check_data_path(cls, v: Path) -> Path:
|
def check_data_path(cls: Any, v: Path) -> Path: # type: ignore
|
||||||
return validate_path(clan_data_dir(), v)
|
return validate_path(clan_data_dir(), v)
|
||||||
|
|
||||||
|
|
||||||
@@ -36,7 +38,7 @@ class ClanFlakePath(BaseModel):
|
|||||||
dest: Path
|
dest: Path
|
||||||
|
|
||||||
@validator("dest")
|
@validator("dest")
|
||||||
def check_dest(cls, v: Path) -> Path:
|
def check_dest(cls: Any, v: Path) -> Path: # type: ignore
|
||||||
return validate_path(clan_flake_dir(), v)
|
return validate_path(clan_flake_dir(), v)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,15 +6,15 @@ from typing import Annotated
|
|||||||
from fastapi import APIRouter, Body, HTTPException, status
|
from fastapi import APIRouter, Body, HTTPException, status
|
||||||
from pydantic import AnyUrl
|
from pydantic import AnyUrl
|
||||||
|
|
||||||
|
from clan_cli.webui.api_inputs import (
|
||||||
|
FlakeCreateInput,
|
||||||
|
)
|
||||||
from clan_cli.webui.api_outputs import (
|
from clan_cli.webui.api_outputs import (
|
||||||
FlakeAction,
|
FlakeAction,
|
||||||
FlakeAttrResponse,
|
FlakeAttrResponse,
|
||||||
FlakeCreateResponse,
|
FlakeCreateResponse,
|
||||||
FlakeResponse,
|
FlakeResponse,
|
||||||
)
|
)
|
||||||
from clan_cli.webui.api_inputs import (
|
|
||||||
FlakeCreateInput,
|
|
||||||
)
|
|
||||||
|
|
||||||
from ...async_cmd import run
|
from ...async_cmd import run
|
||||||
from ...flake import create
|
from ...flake import create
|
||||||
@@ -25,11 +25,11 @@ router = APIRouter()
|
|||||||
# TODO: Check for directory traversal
|
# TODO: Check for directory traversal
|
||||||
async def get_attrs(url: AnyUrl | Path) -> list[str]:
|
async def get_attrs(url: AnyUrl | Path) -> list[str]:
|
||||||
cmd = nix_flake_show(url)
|
cmd = nix_flake_show(url)
|
||||||
stdout, stderr = await run(cmd)
|
out = await run(cmd)
|
||||||
|
|
||||||
data: dict[str, dict] = {}
|
data: dict[str, dict] = {}
|
||||||
try:
|
try:
|
||||||
data = json.loads(stdout)
|
data = json.loads(out.stdout)
|
||||||
except JSONDecodeError:
|
except JSONDecodeError:
|
||||||
raise HTTPException(status_code=422, detail="Could not load flake.")
|
raise HTTPException(status_code=422, detail="Could not load flake.")
|
||||||
|
|
||||||
@@ -57,8 +57,8 @@ async def inspect_flake(
|
|||||||
# Extract the flake from the given URL
|
# Extract the flake from the given URL
|
||||||
# We do this by running 'nix flake prefetch {url} --json'
|
# We do this by running 'nix flake prefetch {url} --json'
|
||||||
cmd = nix_command(["flake", "prefetch", str(url), "--json", "--refresh"])
|
cmd = nix_command(["flake", "prefetch", str(url), "--json", "--refresh"])
|
||||||
stdout, stderr = await run(cmd)
|
out = await run(cmd)
|
||||||
data: dict[str, str] = json.loads(stdout)
|
data: dict[str, str] = json.loads(out.stdout)
|
||||||
|
|
||||||
if data.get("storePath") is None:
|
if data.get("storePath") is None:
|
||||||
raise HTTPException(status_code=500, detail="Could not load flake")
|
raise HTTPException(status_code=500, detail="Could not load flake")
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ async def list_machines() -> MachinesResponse:
|
|||||||
|
|
||||||
@router.post("/api/machines", status_code=201)
|
@router.post("/api/machines", status_code=201)
|
||||||
async def create_machine(machine: Annotated[MachineCreate, Body()]) -> MachineResponse:
|
async def create_machine(machine: Annotated[MachineCreate, Body()]) -> MachineResponse:
|
||||||
_create_machine(machine.name)
|
out = await _create_machine(machine.name)
|
||||||
|
log.debug(out)
|
||||||
return MachineResponse(machine=Machine(name=machine.name, status=Status.UNKNOWN))
|
return MachineResponse(machine=Machine(name=machine.name, status=Status.UNKNOWN))
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
from typing import Annotated, Iterator
|
from typing import Annotated, Iterator
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
@@ -6,13 +7,17 @@ from fastapi import APIRouter, Body, status
|
|||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
from fastapi.responses import StreamingResponse
|
from fastapi.responses import StreamingResponse
|
||||||
from pydantic import AnyUrl
|
from pydantic import AnyUrl
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from clan_cli.webui.routers.flake import get_attrs
|
from clan_cli.webui.routers.flake import get_attrs
|
||||||
|
|
||||||
from ...task_manager import get_task
|
from ...task_manager import get_task
|
||||||
from ...vms import create, inspect
|
from ...vms import create, inspect
|
||||||
from ..api_outputs import VmConfig, VmCreateResponse, VmInspectResponse, VmStatusResponse
|
from ..api_outputs import (
|
||||||
|
VmConfig,
|
||||||
|
VmCreateResponse,
|
||||||
|
VmInspectResponse,
|
||||||
|
VmStatusResponse,
|
||||||
|
)
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import os
|
||||||
import tempfile
|
import tempfile
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
@@ -7,5 +8,11 @@ import pytest
|
|||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def temporary_dir() -> Iterator[Path]:
|
def temporary_dir() -> Iterator[Path]:
|
||||||
with tempfile.TemporaryDirectory(prefix="pytest-") as dirpath:
|
if os.getenv("TEST_KEEP_TEMPORARY_DIR"):
|
||||||
yield Path(dirpath)
|
temp_dir = tempfile.mkdtemp(prefix="pytest-")
|
||||||
|
path = Path(temp_dir)
|
||||||
|
yield path
|
||||||
|
print("=========> Keeping temporary directory: ", path)
|
||||||
|
else:
|
||||||
|
with tempfile.TemporaryDirectory(prefix="pytest-") as dirpath:
|
||||||
|
yield Path(dirpath)
|
||||||
|
|||||||
@@ -74,8 +74,9 @@ def generic_create_vm_test(api: TestClient, flake: Path, vm: str) -> None:
|
|||||||
print(line.decode("utf-8"))
|
print(line.decode("utf-8"))
|
||||||
print("=========END LOGS==========")
|
print("=========END LOGS==========")
|
||||||
assert response.status_code == 200, "Failed to get vm logs"
|
assert response.status_code == 200, "Failed to get vm logs"
|
||||||
|
print("Get /api/vms/{uuid}/status")
|
||||||
response = api.get(f"/api/vms/{uuid}/status")
|
response = api.get(f"/api/vms/{uuid}/status")
|
||||||
|
print("Finished Get /api/vms/{uuid}/status")
|
||||||
assert response.status_code == 200, "Failed to get vm status"
|
assert response.status_code == 200, "Failed to get vm status"
|
||||||
data = response.json()
|
data = response.json()
|
||||||
assert (
|
assert (
|
||||||
|
|||||||
Reference in New Issue
Block a user