forked from clan/clan-core
api/machines: init put_machine replacing create_machine and set_machine_config
This allows creating and configuring a machine in one single step.
This commit is contained in:
parent
2395119d21
commit
1652b5c27b
@ -143,7 +143,7 @@
|
||||
"sops-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"sops-nix"
|
||||
"nixpkgs"
|
||||
],
|
||||
"nixpkgs-stable": []
|
||||
},
|
||||
|
@ -1,5 +1,6 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from tempfile import NamedTemporaryFile
|
||||
@ -12,6 +13,7 @@ from clan_cli.dirs import (
|
||||
specific_flake_dir,
|
||||
specific_machine_dir,
|
||||
)
|
||||
from clan_cli.errors import ClanError
|
||||
from clan_cli.git import commit_file
|
||||
from clan_cli.nix import nix_eval
|
||||
|
||||
@ -75,16 +77,22 @@ def config_for_machine(flake_name: FlakeName, machine_name: str) -> dict:
|
||||
def set_config_for_machine(
|
||||
flake_name: FlakeName, machine_name: str, config: dict
|
||||
) -> None:
|
||||
hostname_regex = r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)$"
|
||||
if not re.match(hostname_regex, machine_name):
|
||||
raise ClanError("Machine name must be a valid hostname")
|
||||
if "networking" in config and "hostName" in config["networking"]:
|
||||
if machine_name != config["networking"]["hostName"]:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Machine name does not match the 'networking.hostName' setting in the config",
|
||||
)
|
||||
config["networking"]["hostName"] = machine_name
|
||||
# create machine folder if it doesn't exist
|
||||
# write the config to a json file located at {flake}/machines/{machine_name}/settings.json
|
||||
if not specific_machine_dir(flake_name, machine_name).exists():
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Machine {machine_name} not found. Create the machine first`",
|
||||
)
|
||||
settings_path = machine_settings_file(flake_name, machine_name)
|
||||
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
with open(settings_path, "w") as f:
|
||||
json.dump(config, f)
|
||||
json.dump(config, f, indent=2)
|
||||
repo_dir = specific_flake_dir(flake_name)
|
||||
|
||||
if repo_dir is not None:
|
||||
|
@ -1,46 +1,13 @@
|
||||
import argparse
|
||||
import logging
|
||||
from typing import Dict
|
||||
|
||||
from ..async_cmd import CmdOut, run, runforcli
|
||||
from ..dirs import specific_flake_dir, specific_machine_dir
|
||||
from ..errors import ClanError
|
||||
from ..nix import nix_shell
|
||||
from ..types import FlakeName
|
||||
from clan_cli.config.machine import set_config_for_machine
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def create_machine(flake_name: FlakeName, machine_name: str) -> Dict[str, CmdOut]:
|
||||
folder = specific_machine_dir(flake_name, machine_name)
|
||||
if folder.exists():
|
||||
raise ClanError(f"Machine '{machine_name}' already exists")
|
||||
folder.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
# create empty settings.json file inside the folder
|
||||
with open(folder / "settings.json", "w") as f:
|
||||
f.write("{}")
|
||||
response = {}
|
||||
out = await run(nix_shell(["git"], ["git", "add", str(folder)]), cwd=folder)
|
||||
response["git add"] = out
|
||||
|
||||
out = await run(
|
||||
nix_shell(
|
||||
["git"],
|
||||
["git", "commit", "-m", f"Added machine {machine_name}", str(folder)],
|
||||
),
|
||||
cwd=folder,
|
||||
)
|
||||
response["git commit"] = out
|
||||
return response
|
||||
|
||||
|
||||
def create_command(args: argparse.Namespace) -> None:
|
||||
try:
|
||||
flake_dir = specific_flake_dir(args.flake)
|
||||
runforcli(create_machine, flake_dir, args.machine)
|
||||
except ClanError as e:
|
||||
print(e)
|
||||
set_config_for_machine(args.flake, args.machine, dict())
|
||||
|
||||
|
||||
def register_create_parser(parser: argparse.ArgumentParser) -> None:
|
||||
|
@ -1,5 +1,4 @@
|
||||
import logging
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
@ -31,15 +30,3 @@ class MachineConfig(BaseModel):
|
||||
# allow extra fields to cover the full spectrum of a nixos config
|
||||
class Config:
|
||||
extra = Extra.allow
|
||||
|
||||
|
||||
class MachineCreate(BaseModel):
|
||||
name: str
|
||||
|
||||
@classmethod
|
||||
@validator("name")
|
||||
def validate_hostname(cls, v: str) -> str:
|
||||
hostname_regex = r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)$"
|
||||
if not re.match(hostname_regex, v):
|
||||
raise ValueError("Machine name must be a valid hostname")
|
||||
return v
|
||||
|
@ -3,9 +3,10 @@ import logging
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Body
|
||||
from fastapi.encoders import jsonable_encoder
|
||||
|
||||
from clan_cli.webui.api_errors import MissingClanImports
|
||||
from clan_cli.webui.api_inputs import MachineConfig, MachineCreate
|
||||
from clan_cli.webui.api_inputs import MachineConfig
|
||||
|
||||
from ...config.machine import (
|
||||
config_for_machine,
|
||||
@ -13,7 +14,6 @@ from ...config.machine import (
|
||||
verify_machine_config,
|
||||
)
|
||||
from ...config.schema import machine_schema
|
||||
from ...machines.create import create_machine as _create_machine
|
||||
from ...machines.list import list_machines as _list_machines
|
||||
from ...types import FlakeName
|
||||
from ..api_outputs import (
|
||||
@ -40,14 +40,6 @@ async def list_machines(flake_name: FlakeName) -> MachinesResponse:
|
||||
return MachinesResponse(machines=machines)
|
||||
|
||||
|
||||
@router.post("/api/{flake_name}/machines", tags=[Tags.machine], status_code=201)
|
||||
async def create_machine(
|
||||
flake_name: FlakeName, machine: Annotated[MachineCreate, Body()]
|
||||
) -> MachineResponse:
|
||||
await _create_machine(flake_name, machine.name)
|
||||
return MachineResponse(machine=Machine(name=machine.name, status=Status.UNKNOWN))
|
||||
|
||||
|
||||
@router.get("/api/{flake_name}/machines/{name}", tags=[Tags.machine])
|
||||
async def get_machine(flake_name: FlakeName, name: str) -> MachineResponse:
|
||||
log.error("TODO")
|
||||
@ -61,10 +53,14 @@ async def get_machine_config(flake_name: FlakeName, name: str) -> ConfigResponse
|
||||
|
||||
|
||||
@router.put("/api/{flake_name}/machines/{name}/config", tags=[Tags.machine])
|
||||
async def set_machine_config(
|
||||
async def put_machine(
|
||||
flake_name: FlakeName, name: str, config: Annotated[MachineConfig, Body()]
|
||||
) -> None:
|
||||
conf = dict(config)
|
||||
"""
|
||||
Set the config for a machine.
|
||||
Creates the machine if it doesn't yet exist.
|
||||
"""
|
||||
conf = jsonable_encoder(config)
|
||||
set_config_for_machine(flake_name, name, conf)
|
||||
|
||||
|
||||
|
@ -57,6 +57,6 @@ mkShell {
|
||||
|
||||
|
||||
./bin/clan flakes create example_clan
|
||||
./bin/clan machines create example_machine example_clan
|
||||
./bin/clan machines create example-machine example_clan
|
||||
'';
|
||||
}
|
||||
|
@ -3,15 +3,13 @@ from api import TestClient
|
||||
from fixtures_flakes import FlakeForTest
|
||||
|
||||
|
||||
def test_machines(api: TestClient, test_flake: FlakeForTest) -> None:
|
||||
def test_create_and_list(api: TestClient, test_flake: FlakeForTest) -> None:
|
||||
response = api.get(f"/api/{test_flake.name}/machines")
|
||||
assert response.status_code == 200
|
||||
assert response.json() == {"machines": []}
|
||||
|
||||
response = api.post(f"/api/{test_flake.name}/machines", json={"name": "test"})
|
||||
assert response.status_code == 201
|
||||
|
||||
assert response.json() == {"machine": {"name": "test", "status": "unknown"}}
|
||||
response = api.put(f"/api/{test_flake.name}/machines/test/config", json=dict())
|
||||
assert response.status_code == 200
|
||||
|
||||
response = api.get(f"/api/{test_flake.name}/machines/test")
|
||||
assert response.status_code == 200
|
||||
@ -55,8 +53,8 @@ def test_schema_invalid_clan_imports(
|
||||
def test_create_machine_invalid_hostname(
|
||||
api: TestClient, test_flake: FlakeForTest
|
||||
) -> None:
|
||||
response = api.post(
|
||||
f"/api/{test_flake.name}/machines", json={"name": "-invalid-hostname"}
|
||||
response = api.put(
|
||||
f"/api/{test_flake.name}/machines/-invalid-hostname/config", json=dict()
|
||||
)
|
||||
assert response.status_code == 422
|
||||
assert (
|
||||
@ -70,17 +68,11 @@ def test_configure_machine(api: TestClient, test_flake_with_core: FlakeForTest)
|
||||
response = api.get(f"/api/{test_flake_with_core.name}/machines/machine1/config")
|
||||
assert response.status_code == 404
|
||||
|
||||
# ensure error 404 if machine does not exist when writing to the config
|
||||
# create the machine
|
||||
response = api.put(
|
||||
f"/api/{test_flake_with_core.name}/machines/machine1/config", json={}
|
||||
)
|
||||
assert response.status_code == 404
|
||||
|
||||
# create the machine
|
||||
response = api.post(
|
||||
f"/api/{test_flake_with_core.name}/machines", json={"name": "machine1"}
|
||||
)
|
||||
assert response.status_code == 201
|
||||
assert response.status_code == 200
|
||||
|
||||
# ensure an empty config is returned by default for a new machine
|
||||
response = api.get(f"/api/{test_flake_with_core.name}/machines/machine1/config")
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createMachine, setMachineConfig } from "@/api/machine/machine";
|
||||
import { putMachine } from "@/api/machine/machine";
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@ -79,10 +79,7 @@ export function CreateMachineForm() {
|
||||
toast.error("Machine name should not be empty");
|
||||
return;
|
||||
}
|
||||
await createMachine(clanName, {
|
||||
name: data.name,
|
||||
});
|
||||
await setMachineConfig(clanName, data.name, {
|
||||
await putMachine(clanName, data.name, {
|
||||
clan: data.config.formData,
|
||||
clanImports: data.modules,
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user