clan_cli: flake_name -> flake_dir
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Any, Optional, Sequence
|
from typing import Any, Optional, Sequence
|
||||||
|
|
||||||
@@ -57,7 +58,8 @@ def create_parser(prog: Optional[str] = None) -> argparse.ArgumentParser:
|
|||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--flake",
|
"--flake",
|
||||||
help="path to the flake where the clan resides in",
|
help="path to the flake where the clan resides in",
|
||||||
default=None,
|
default=get_clan_flake_toplevel(),
|
||||||
|
type=Path,
|
||||||
)
|
)
|
||||||
|
|
||||||
subparsers = parser.add_subparsers()
|
subparsers = parser.add_subparsers()
|
||||||
@@ -93,8 +95,6 @@ def create_parser(prog: Optional[str] = None) -> argparse.ArgumentParser:
|
|||||||
if argcomplete:
|
if argcomplete:
|
||||||
argcomplete.autocomplete(parser)
|
argcomplete.autocomplete(parser)
|
||||||
|
|
||||||
if len(sys.argv) == 1:
|
|
||||||
parser.print_help()
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@@ -103,13 +103,13 @@ def main() -> None:
|
|||||||
parser = create_parser()
|
parser = create_parser()
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if len(sys.argv) == 1:
|
||||||
|
parser.print_help()
|
||||||
|
|
||||||
if args.debug:
|
if args.debug:
|
||||||
setup_logging(logging.DEBUG)
|
setup_logging(logging.DEBUG)
|
||||||
log.debug("Debug log activated")
|
log.debug("Debug log activated")
|
||||||
|
|
||||||
if args.flake is None:
|
|
||||||
args.flake = get_clan_flake_toplevel()
|
|
||||||
|
|
||||||
if not hasattr(args, "func"):
|
if not hasattr(args, "func"):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -57,8 +57,7 @@ def runforcli(
|
|||||||
try:
|
try:
|
||||||
res = asyncio.run(func(*args))
|
res = asyncio.run(func(*args))
|
||||||
|
|
||||||
for i in res.items():
|
for name, out in res.items():
|
||||||
name, out = i
|
|
||||||
if out.stderr:
|
if out.stderr:
|
||||||
print(f"{name}: {out.stderr}", end="")
|
print(f"{name}: {out.stderr}", end="")
|
||||||
if out.stdout:
|
if out.stdout:
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
import json
|
import json
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from clan_cli.nix import nix_eval
|
from clan_cli.nix import nix_eval
|
||||||
|
|
||||||
from .dirs import specific_flake_dir
|
|
||||||
from .types import FlakeName
|
|
||||||
|
|
||||||
|
|
||||||
def get_clan_module_names(
|
def get_clan_module_names(
|
||||||
flake_name: FlakeName,
|
flake_dir: Path,
|
||||||
) -> tuple[list[str], Optional[str]]:
|
) -> tuple[list[str], Optional[str]]:
|
||||||
"""
|
"""
|
||||||
Get the list of clan modules from the clan-core flake input
|
Get the list of clan modules from the clan-core flake input
|
||||||
"""
|
"""
|
||||||
flake = specific_flake_dir(flake_name)
|
|
||||||
proc = subprocess.run(
|
proc = subprocess.run(
|
||||||
nix_eval(
|
nix_eval(
|
||||||
[
|
[
|
||||||
@@ -23,7 +20,7 @@ def get_clan_module_names(
|
|||||||
"--expr",
|
"--expr",
|
||||||
f"""
|
f"""
|
||||||
let
|
let
|
||||||
flake = builtins.getFlake (toString {flake});
|
flake = builtins.getFlake (toString {flake_dir});
|
||||||
in
|
in
|
||||||
builtins.attrNames flake.inputs.clan-core.clanModules
|
builtins.attrNames flake.inputs.clan-core.clanModules
|
||||||
""",
|
""",
|
||||||
@@ -31,7 +28,7 @@ def get_clan_module_names(
|
|||||||
),
|
),
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
cwd=flake,
|
cwd=flake_dir,
|
||||||
)
|
)
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
return [], proc.stderr
|
return [], proc.stderr
|
||||||
|
|||||||
@@ -10,11 +10,10 @@ import sys
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional, Tuple, get_origin
|
from typing import Any, Optional, Tuple, get_origin
|
||||||
|
|
||||||
from clan_cli.dirs import machine_settings_file, specific_flake_dir
|
from clan_cli.dirs import machine_settings_file
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
from clan_cli.git import commit_file
|
from clan_cli.git import commit_file
|
||||||
from clan_cli.nix import nix_eval
|
from clan_cli.nix import nix_eval
|
||||||
from clan_cli.types import FlakeName
|
|
||||||
|
|
||||||
script_dir = Path(__file__).parent
|
script_dir = Path(__file__).parent
|
||||||
|
|
||||||
@@ -108,9 +107,9 @@ def cast(value: Any, type: Any, opt_description: str) -> Any:
|
|||||||
|
|
||||||
|
|
||||||
def options_for_machine(
|
def options_for_machine(
|
||||||
flake_name: FlakeName, machine_name: str, show_trace: bool = False
|
flake_dir: Path, machine_name: str, show_trace: bool = False
|
||||||
) -> dict:
|
) -> dict:
|
||||||
clan_dir = specific_flake_dir(flake_name)
|
clan_dir = flake_dir
|
||||||
flags = []
|
flags = []
|
||||||
if show_trace:
|
if show_trace:
|
||||||
flags.append("--show-trace")
|
flags.append("--show-trace")
|
||||||
@@ -131,9 +130,9 @@ def options_for_machine(
|
|||||||
|
|
||||||
|
|
||||||
def read_machine_option_value(
|
def read_machine_option_value(
|
||||||
flake_name: FlakeName, machine_name: str, option: str, show_trace: bool = False
|
flake_dir: Path, machine_name: str, option: str, show_trace: bool = False
|
||||||
) -> str:
|
) -> str:
|
||||||
clan_dir = specific_flake_dir(flake_name)
|
clan_dir = flake_dir
|
||||||
# use nix eval to read from .#nixosConfigurations.default.config.{option}
|
# use nix eval to read from .#nixosConfigurations.default.config.{option}
|
||||||
# this will give us the evaluated config with the options attribute
|
# this will give us the evaluated config with the options attribute
|
||||||
cmd = nix_eval(
|
cmd = nix_eval(
|
||||||
@@ -177,12 +176,12 @@ def get_or_set_option(args: argparse.Namespace) -> None:
|
|||||||
options = json.load(f)
|
options = json.load(f)
|
||||||
# compute settings json file location
|
# compute settings json file location
|
||||||
if args.settings_file is None:
|
if args.settings_file is None:
|
||||||
settings_file = machine_settings_file(args.flake, args.machine)
|
settings_file = machine_settings_file(Path(args.flake), args.machine)
|
||||||
else:
|
else:
|
||||||
settings_file = args.settings_file
|
settings_file = args.settings_file
|
||||||
# set the option with the given value
|
# set the option with the given value
|
||||||
set_option(
|
set_option(
|
||||||
flake_name=args.flake,
|
flake_dir=Path(args.flake),
|
||||||
option=args.option,
|
option=args.option,
|
||||||
value=args.value,
|
value=args.value,
|
||||||
options=options,
|
options=options,
|
||||||
@@ -248,7 +247,7 @@ def find_option(
|
|||||||
|
|
||||||
|
|
||||||
def set_option(
|
def set_option(
|
||||||
flake_name: FlakeName,
|
flake_dir: Path,
|
||||||
option: str,
|
option: str,
|
||||||
value: Any,
|
value: Any,
|
||||||
options: dict,
|
options: dict,
|
||||||
@@ -298,10 +297,10 @@ def set_option(
|
|||||||
json.dump(new_config, f, indent=2)
|
json.dump(new_config, f, indent=2)
|
||||||
print(file=f) # add newline at the end of the file to make git happy
|
print(file=f) # add newline at the end of the file to make git happy
|
||||||
|
|
||||||
if settings_file.resolve().is_relative_to(specific_flake_dir(flake_name)):
|
if settings_file.resolve().is_relative_to(flake_dir):
|
||||||
commit_file(
|
commit_file(
|
||||||
settings_file,
|
settings_file,
|
||||||
repo_dir=specific_flake_dir(flake_name),
|
repo_dir=flake_dir,
|
||||||
commit_message=f"Set option {option_description}",
|
commit_message=f"Set option {option_description}",
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -360,11 +359,6 @@ def register_parser(
|
|||||||
nargs="*",
|
nargs="*",
|
||||||
help="option value to set (if omitted, the current value is printed)",
|
help="option value to set (if omitted, the current value is printed)",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"flake",
|
|
||||||
type=str,
|
|
||||||
help="name of the flake to set machine options for",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def main(argv: Optional[list[str]] = None) -> None:
|
def main(argv: Optional[list[str]] = None) -> None:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from pathlib import Path
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
@@ -10,18 +11,15 @@ from fastapi import HTTPException
|
|||||||
from clan_cli.dirs import (
|
from clan_cli.dirs import (
|
||||||
machine_settings_file,
|
machine_settings_file,
|
||||||
nixpkgs_source,
|
nixpkgs_source,
|
||||||
specific_flake_dir,
|
|
||||||
specific_machine_dir,
|
specific_machine_dir,
|
||||||
)
|
)
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
from clan_cli.git import commit_file
|
from clan_cli.git import commit_file
|
||||||
from clan_cli.nix import nix_eval
|
from clan_cli.nix import nix_eval
|
||||||
|
|
||||||
from ..types import FlakeName
|
|
||||||
|
|
||||||
|
|
||||||
def verify_machine_config(
|
def verify_machine_config(
|
||||||
flake_name: FlakeName,
|
flake_dir: Path,
|
||||||
machine_name: str,
|
machine_name: str,
|
||||||
config: Optional[dict] = None,
|
config: Optional[dict] = None,
|
||||||
) -> Optional[str]:
|
) -> Optional[str]:
|
||||||
@@ -30,8 +28,8 @@ def verify_machine_config(
|
|||||||
Returns a tuple of (success, error_message)
|
Returns a tuple of (success, error_message)
|
||||||
"""
|
"""
|
||||||
if config is None:
|
if config is None:
|
||||||
config = config_for_machine(flake_name, machine_name)
|
config = config_for_machine(flake_dir, machine_name)
|
||||||
flake = specific_flake_dir(flake_name)
|
flake = flake_dir
|
||||||
with NamedTemporaryFile(mode="w", dir=flake) as clan_machine_settings_file:
|
with NamedTemporaryFile(mode="w", dir=flake) as clan_machine_settings_file:
|
||||||
json.dump(config, clan_machine_settings_file, indent=2)
|
json.dump(config, clan_machine_settings_file, indent=2)
|
||||||
clan_machine_settings_file.seek(0)
|
clan_machine_settings_file.seek(0)
|
||||||
@@ -82,23 +80,21 @@ def verify_machine_config(
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def config_for_machine(flake_name: FlakeName, machine_name: str) -> dict:
|
def config_for_machine(flake_dir: Path, machine_name: str) -> dict:
|
||||||
# read the config from a json file located at {flake}/machines/{machine_name}/settings.json
|
# read the config from a json file located at {flake}/machines/{machine_name}/settings.json
|
||||||
if not specific_machine_dir(flake_name, machine_name).exists():
|
if not specific_machine_dir(flake_dir, machine_name).exists():
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=404,
|
status_code=404,
|
||||||
detail=f"Machine {machine_name} not found. Create the machine first`",
|
detail=f"Machine {machine_name} not found. Create the machine first`",
|
||||||
)
|
)
|
||||||
settings_path = machine_settings_file(flake_name, machine_name)
|
settings_path = machine_settings_file(flake_dir, machine_name)
|
||||||
if not settings_path.exists():
|
if not settings_path.exists():
|
||||||
return {}
|
return {}
|
||||||
with open(settings_path) as f:
|
with open(settings_path) as f:
|
||||||
return json.load(f)
|
return json.load(f)
|
||||||
|
|
||||||
|
|
||||||
def set_config_for_machine(
|
def set_config_for_machine(flake_dir: Path, machine_name: str, config: dict) -> None:
|
||||||
flake_name: FlakeName, machine_name: str, config: dict
|
|
||||||
) -> None:
|
|
||||||
hostname_regex = r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)$"
|
hostname_regex = r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)$"
|
||||||
if not re.match(hostname_regex, machine_name):
|
if not re.match(hostname_regex, machine_name):
|
||||||
raise ClanError("Machine name must be a valid hostname")
|
raise ClanError("Machine name must be a valid hostname")
|
||||||
@@ -111,11 +107,10 @@ def set_config_for_machine(
|
|||||||
config["networking"]["hostName"] = machine_name
|
config["networking"]["hostName"] = machine_name
|
||||||
# create machine folder if it doesn't exist
|
# create machine folder if it doesn't exist
|
||||||
# write the config to a json file located at {flake}/machines/{machine_name}/settings.json
|
# write the config to a json file located at {flake}/machines/{machine_name}/settings.json
|
||||||
settings_path = machine_settings_file(flake_name, machine_name)
|
settings_path = machine_settings_file(flake_dir, machine_name)
|
||||||
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
with open(settings_path, "w") as f:
|
with open(settings_path, "w") as f:
|
||||||
json.dump(config, f, indent=2)
|
json.dump(config, f)
|
||||||
repo_dir = specific_flake_dir(flake_name)
|
|
||||||
|
|
||||||
if repo_dir is not None:
|
if flake_dir is not None:
|
||||||
commit_file(settings_path, repo_dir)
|
commit_file(settings_path, flake_dir)
|
||||||
|
|||||||
@@ -10,22 +10,18 @@ from fastapi import HTTPException
|
|||||||
|
|
||||||
from clan_cli.dirs import (
|
from clan_cli.dirs import (
|
||||||
nixpkgs_source,
|
nixpkgs_source,
|
||||||
specific_flake_dir,
|
|
||||||
)
|
)
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
from clan_cli.nix import nix_eval
|
from clan_cli.nix import nix_eval
|
||||||
|
|
||||||
from ..types import FlakeName
|
|
||||||
|
|
||||||
|
|
||||||
def machine_schema(
|
def machine_schema(
|
||||||
flake_name: FlakeName,
|
flake_dir: Path,
|
||||||
config: dict,
|
config: dict,
|
||||||
clan_imports: Optional[list[str]] = None,
|
clan_imports: Optional[list[str]] = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
flake = specific_flake_dir(flake_name)
|
|
||||||
# use nix eval to lib.evalModules .#nixosConfigurations.<machine_name>.options.clan
|
# use nix eval to lib.evalModules .#nixosConfigurations.<machine_name>.options.clan
|
||||||
with NamedTemporaryFile(mode="w", dir=flake) as clan_machine_settings_file:
|
with NamedTemporaryFile(mode="w", dir=flake_dir) as clan_machine_settings_file:
|
||||||
env = os.environ.copy()
|
env = os.environ.copy()
|
||||||
if clan_imports is not None:
|
if clan_imports is not None:
|
||||||
config["clanImports"] = clan_imports
|
config["clanImports"] = clan_imports
|
||||||
@@ -43,9 +39,8 @@ def machine_schema(
|
|||||||
f"""
|
f"""
|
||||||
let
|
let
|
||||||
b = builtins;
|
b = builtins;
|
||||||
# hardcoding system for now, not sure where to get it from
|
system = b.currentSystem;
|
||||||
system = "x86_64-linux";
|
flake = b.getFlake (toString {flake_dir});
|
||||||
flake = b.getFlake (toString {flake});
|
|
||||||
clan-core = flake.inputs.clan-core;
|
clan-core = flake.inputs.clan-core;
|
||||||
config = b.fromJSON (b.readFile (b.getEnv "CLAN_MACHINE_SETTINGS_FILE"));
|
config = b.fromJSON (b.readFile (b.getEnv "CLAN_MACHINE_SETTINGS_FILE"));
|
||||||
modules_not_found =
|
modules_not_found =
|
||||||
@@ -59,7 +54,7 @@ def machine_schema(
|
|||||||
),
|
),
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
cwd=flake,
|
cwd=flake_dir,
|
||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
@@ -86,9 +81,8 @@ def machine_schema(
|
|||||||
"--expr",
|
"--expr",
|
||||||
f"""
|
f"""
|
||||||
let
|
let
|
||||||
# hardcoding system for now, not sure where to get it from
|
system = builtins.currentSystem;
|
||||||
system = "x86_64-linux";
|
flake = builtins.getFlake (toString {flake_dir});
|
||||||
flake = builtins.getFlake (toString {flake});
|
|
||||||
clan-core = flake.inputs.clan-core;
|
clan-core = flake.inputs.clan-core;
|
||||||
nixpkgsSrc = flake.inputs.nixpkgs or {nixpkgs_source()};
|
nixpkgsSrc = flake.inputs.nixpkgs or {nixpkgs_source()};
|
||||||
lib = import (nixpkgsSrc + /lib);
|
lib = import (nixpkgsSrc + /lib);
|
||||||
@@ -115,7 +109,7 @@ def machine_schema(
|
|||||||
),
|
),
|
||||||
capture_output=True,
|
capture_output=True,
|
||||||
text=True,
|
text=True,
|
||||||
cwd=flake,
|
cwd=flake_dir,
|
||||||
env=env,
|
env=env,
|
||||||
)
|
)
|
||||||
if proc.returncode != 0:
|
if proc.returncode != 0:
|
||||||
|
|||||||
@@ -4,9 +4,6 @@ import sys
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from .errors import ClanError
|
|
||||||
from .types import FlakeName
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@@ -15,10 +12,7 @@ def get_clan_flake_toplevel() -> Optional[Path]:
|
|||||||
|
|
||||||
|
|
||||||
def find_git_repo_root() -> Optional[Path]:
|
def find_git_repo_root() -> Optional[Path]:
|
||||||
try:
|
return find_toplevel([".git"])
|
||||||
return find_toplevel([".git"])
|
|
||||||
except ClanError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def find_toplevel(top_level_files: list[str]) -> Optional[Path]:
|
def find_toplevel(top_level_files: list[str]) -> Optional[Path]:
|
||||||
@@ -42,35 +36,16 @@ def user_config_dir() -> Path:
|
|||||||
return Path(os.getenv("XDG_CONFIG_HOME", os.path.expanduser("~/.config")))
|
return Path(os.getenv("XDG_CONFIG_HOME", os.path.expanduser("~/.config")))
|
||||||
|
|
||||||
|
|
||||||
def clan_config_dir() -> Path:
|
def machines_dir(flake_dir: Path) -> Path:
|
||||||
path = user_config_dir() / "clan"
|
return flake_dir / "machines"
|
||||||
path.mkdir(parents=True, exist_ok=True)
|
|
||||||
return path.resolve()
|
|
||||||
|
|
||||||
|
|
||||||
def clan_flakes_dir() -> Path:
|
def specific_machine_dir(flake_dir: Path, machine: str) -> Path:
|
||||||
path = clan_config_dir() / "flakes"
|
return machines_dir(flake_dir) / machine
|
||||||
path.mkdir(parents=True, exist_ok=True)
|
|
||||||
return path.resolve()
|
|
||||||
|
|
||||||
|
|
||||||
def specific_flake_dir(flake_name: FlakeName) -> Path:
|
def machine_settings_file(flake_dir: Path, machine: str) -> Path:
|
||||||
flake_dir = clan_flakes_dir() / flake_name
|
return specific_machine_dir(flake_dir, machine) / "settings.json"
|
||||||
if not flake_dir.exists():
|
|
||||||
raise ClanError(f"Flake '{flake_name}' does not exist in {clan_flakes_dir()}")
|
|
||||||
return flake_dir
|
|
||||||
|
|
||||||
|
|
||||||
def machines_dir(flake_name: FlakeName) -> Path:
|
|
||||||
return specific_flake_dir(flake_name) / "machines"
|
|
||||||
|
|
||||||
|
|
||||||
def specific_machine_dir(flake_name: FlakeName, machine: str) -> Path:
|
|
||||||
return machines_dir(flake_name) / machine
|
|
||||||
|
|
||||||
|
|
||||||
def machine_settings_file(flake_name: FlakeName, machine: str) -> Path:
|
|
||||||
return specific_machine_dir(flake_name, machine) / "settings.json"
|
|
||||||
|
|
||||||
|
|
||||||
def module_root() -> Path:
|
def module_root() -> Path:
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
import argparse
|
import argparse
|
||||||
|
|
||||||
from .create import register_create_parser
|
from .create import register_create_parser
|
||||||
from .list_flakes import register_list_parser
|
|
||||||
|
|
||||||
|
|
||||||
# takes a (sub)parser and configures it
|
# takes a (sub)parser and configures it
|
||||||
@@ -15,6 +14,3 @@ def register_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
)
|
)
|
||||||
create_parser = subparser.add_parser("create", help="Create a clan flake")
|
create_parser = subparser.add_parser("create", help="Create a clan flake")
|
||||||
register_create_parser(create_parser)
|
register_create_parser(create_parser)
|
||||||
|
|
||||||
list_parser = subparser.add_parser("list", help="List clan flakes")
|
|
||||||
register_list_parser(list_parser)
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ from pydantic import AnyUrl
|
|||||||
from pydantic.tools import parse_obj_as
|
from pydantic.tools import parse_obj_as
|
||||||
|
|
||||||
from ..async_cmd import CmdOut, run, runforcli
|
from ..async_cmd import CmdOut, run, runforcli
|
||||||
from ..dirs import clan_flakes_dir
|
|
||||||
from ..errors import ClanError
|
from ..errors import ClanError
|
||||||
from ..nix import nix_command, nix_shell
|
from ..nix import nix_command, nix_shell
|
||||||
|
|
||||||
@@ -63,22 +62,16 @@ 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:
|
||||||
flake_dir = clan_flakes_dir() / args.name
|
runforcli(create_flake, args.path, args.url)
|
||||||
runforcli(create_flake, flake_dir, args.url)
|
|
||||||
|
|
||||||
|
|
||||||
# takes a (sub)parser and configures it
|
# takes a (sub)parser and configures it
|
||||||
def register_create_parser(parser: argparse.ArgumentParser) -> None:
|
def register_create_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
parser.add_argument(
|
|
||||||
"name",
|
|
||||||
type=str,
|
|
||||||
help="name for the flake",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--url",
|
"--url",
|
||||||
type=str,
|
type=str,
|
||||||
help="url for the flake",
|
help="url for the flake",
|
||||||
default=DEFAULT_URL,
|
default=DEFAULT_URL,
|
||||||
)
|
)
|
||||||
# parser.add_argument("name", type=str, help="name of the flake")
|
parser.add_argument("path", type=Path, help="Path to the flake", default=Path("."))
|
||||||
parser.set_defaults(func=create_flake_command)
|
parser.set_defaults(func=create_flake_command)
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
import argparse
|
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
|
|
||||||
from ..dirs import clan_flakes_dir
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def list_flakes() -> list[str]:
|
|
||||||
path = clan_flakes_dir()
|
|
||||||
log.debug(f"Listing machines in {path}")
|
|
||||||
if not path.exists():
|
|
||||||
return []
|
|
||||||
objs: list[str] = []
|
|
||||||
for f in os.listdir(path):
|
|
||||||
objs.append(f)
|
|
||||||
return objs
|
|
||||||
|
|
||||||
|
|
||||||
def list_command(args: argparse.Namespace) -> None:
|
|
||||||
for flake in list_flakes():
|
|
||||||
print(flake)
|
|
||||||
|
|
||||||
|
|
||||||
def register_list_parser(parser: argparse.ArgumentParser) -> None:
|
|
||||||
parser.set_defaults(func=list_command)
|
|
||||||
@@ -12,9 +12,4 @@ def create_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
def register_create_parser(parser: argparse.ArgumentParser) -> None:
|
def register_create_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
parser.add_argument("machine", type=str)
|
parser.add_argument("machine", type=str)
|
||||||
parser.add_argument(
|
|
||||||
"flake",
|
|
||||||
type=str,
|
|
||||||
help="name of the flake to create machine for",
|
|
||||||
)
|
|
||||||
parser.set_defaults(func=create_command)
|
parser.set_defaults(func=create_command)
|
||||||
|
|||||||
@@ -15,9 +15,4 @@ def delete_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
def register_delete_parser(parser: argparse.ArgumentParser) -> None:
|
def register_delete_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
parser.add_argument("host", type=str)
|
parser.add_argument("host", type=str)
|
||||||
parser.add_argument(
|
|
||||||
"flake",
|
|
||||||
type=str,
|
|
||||||
help="name of the flake to create machine for",
|
|
||||||
)
|
|
||||||
parser.set_defaults(func=delete_command)
|
parser.set_defaults(func=delete_command)
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from ..dirs import specific_machine_dir
|
from ..dirs import specific_machine_dir
|
||||||
from ..types import FlakeName
|
|
||||||
|
|
||||||
|
|
||||||
def machine_has_fact(flake_name: FlakeName, machine: str, fact: str) -> bool:
|
def machine_has_fact(flake_dir: Path, machine: str, fact: str) -> bool:
|
||||||
return (specific_machine_dir(flake_name, machine) / "facts" / fact).exists()
|
return (specific_machine_dir(flake_dir, machine) / "facts" / fact).exists()
|
||||||
|
|
||||||
|
|
||||||
def machine_get_fact(flake_name: FlakeName, machine: str, fact: str) -> str:
|
def machine_get_fact(flake_dir: Path, machine: str, fact: str) -> str:
|
||||||
return (specific_machine_dir(flake_name, machine) / "facts" / fact).read_text()
|
return (specific_machine_dir(flake_dir, machine) / "facts" / fact).read_text()
|
||||||
|
|||||||
@@ -3,14 +3,12 @@ import subprocess
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import TemporaryDirectory
|
from tempfile import TemporaryDirectory
|
||||||
|
|
||||||
from ..dirs import specific_flake_dir
|
|
||||||
from ..machines.machines import Machine
|
from ..machines.machines import Machine
|
||||||
from ..nix import nix_shell
|
from ..nix import nix_shell
|
||||||
from ..secrets.generate import generate_secrets
|
from ..secrets.generate import generate_secrets
|
||||||
from ..types import FlakeName
|
|
||||||
|
|
||||||
|
|
||||||
def install_nixos(machine: Machine, flake_name: FlakeName) -> None:
|
def install_nixos(machine: Machine) -> None:
|
||||||
h = machine.host
|
h = machine.host
|
||||||
target_host = f"{h.user or 'root'}@{h.host}"
|
target_host = f"{h.user or 'root'}@{h.host}"
|
||||||
|
|
||||||
@@ -41,10 +39,10 @@ def install_nixos(machine: Machine, flake_name: FlakeName) -> None:
|
|||||||
|
|
||||||
|
|
||||||
def install_command(args: argparse.Namespace) -> None:
|
def install_command(args: argparse.Namespace) -> None:
|
||||||
machine = Machine(args.machine, flake_dir=specific_flake_dir(args.flake))
|
machine = Machine(args.machine, flake_dir=args.flake)
|
||||||
machine.deployment_address = args.target_host
|
machine.deployment_address = args.target_host
|
||||||
|
|
||||||
install_nixos(machine, args.flake)
|
install_nixos(machine)
|
||||||
|
|
||||||
|
|
||||||
def register_install_parser(parser: argparse.ArgumentParser) -> None:
|
def register_install_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
@@ -58,9 +56,4 @@ def register_install_parser(parser: argparse.ArgumentParser) -> None:
|
|||||||
type=str,
|
type=str,
|
||||||
help="ssh address to install to in the form of user@host:2222",
|
help="ssh address to install to in the form of user@host:2222",
|
||||||
)
|
)
|
||||||
parser.add_argument(
|
|
||||||
"flake",
|
|
||||||
type=str,
|
|
||||||
help="name of the flake to install machine from",
|
|
||||||
)
|
|
||||||
parser.set_defaults(func=install_command)
|
parser.set_defaults(func=install_command)
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from ..dirs import machines_dir
|
from ..dirs import machines_dir
|
||||||
from ..types import FlakeName
|
|
||||||
from .types import validate_hostname
|
from .types import validate_hostname
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def list_machines(flake_name: FlakeName) -> list[str]:
|
def list_machines(flake_dir: Path) -> list[str]:
|
||||||
path = machines_dir(flake_name)
|
path = machines_dir(flake_dir)
|
||||||
log.debug(f"Listing machines in {path}")
|
log.debug(f"Listing machines in {path}")
|
||||||
if not path.exists():
|
if not path.exists():
|
||||||
return []
|
return []
|
||||||
@@ -22,14 +22,9 @@ def list_machines(flake_name: FlakeName) -> list[str]:
|
|||||||
|
|
||||||
|
|
||||||
def list_command(args: argparse.Namespace) -> None:
|
def list_command(args: argparse.Namespace) -> None:
|
||||||
for machine in list_machines(args.flake):
|
for machine in list_machines(Path(args.flake)):
|
||||||
print(machine)
|
print(machine)
|
||||||
|
|
||||||
|
|
||||||
def register_list_parser(parser: argparse.ArgumentParser) -> None:
|
def register_list_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
parser.add_argument(
|
|
||||||
"flake",
|
|
||||||
type=str,
|
|
||||||
help="name of the flake to create machine for",
|
|
||||||
)
|
|
||||||
parser.set_defaults(func=list_command)
|
parser.set_defaults(func=list_command)
|
||||||
|
|||||||
@@ -1,27 +1,9 @@
|
|||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import NewType, Union
|
from typing import Union
|
||||||
|
|
||||||
from pydantic import AnyUrl
|
from pydantic import AnyUrl
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
FlakeName = NewType("FlakeName", str)
|
|
||||||
|
|
||||||
FlakeUrl = Union[AnyUrl, Path]
|
FlakeUrl = Union[AnyUrl, Path]
|
||||||
|
|
||||||
|
|
||||||
def validate_path(base_dir: Path, value: Path) -> Path:
|
|
||||||
user_path = (base_dir / value).resolve()
|
|
||||||
|
|
||||||
# Check if the path is within the data directory
|
|
||||||
if not str(user_path).startswith(str(base_dir)):
|
|
||||||
if not str(user_path).startswith("/tmp/pytest"):
|
|
||||||
raise ValueError(
|
|
||||||
f"Destination out of bounds. Expected {user_path} to start with {base_dir}"
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
log.warning(
|
|
||||||
f"Detected pytest tmpdir. Skipping path validation for {user_path}"
|
|
||||||
)
|
|
||||||
return user_path
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import argparse
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
|
||||||
import shlex
|
import shlex
|
||||||
import sys
|
import sys
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -10,19 +9,11 @@ from pathlib import Path
|
|||||||
from typing import Iterator
|
from typing import Iterator
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
from ..dirs import clan_flakes_dir, specific_flake_dir
|
|
||||||
from ..nix import nix_build, nix_config, nix_eval, nix_shell
|
from ..nix import nix_build, nix_config, nix_eval, nix_shell
|
||||||
from ..task_manager import BaseTask, Command, create_task
|
from ..task_manager import BaseTask, Command, create_task
|
||||||
from ..types import validate_path
|
|
||||||
from .inspect import VmConfig, inspect_vm
|
from .inspect import VmConfig, inspect_vm
|
||||||
|
|
||||||
|
|
||||||
def is_flake_url(s: str) -> bool:
|
|
||||||
if re.match(r"^http.?://[a-zA-Z0-9.-]+/[a-zA-Z0-9.-]+", s) is not None:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
class BuildVmTask(BaseTask):
|
class BuildVmTask(BaseTask):
|
||||||
def __init__(self, uuid: UUID, vm: VmConfig, nix_options: list[str] = []) -> None:
|
def __init__(self, uuid: UUID, vm: VmConfig, nix_options: list[str] = []) -> None:
|
||||||
super().__init__(uuid, num_cmds=7)
|
super().__init__(uuid, num_cmds=7)
|
||||||
@@ -72,8 +63,7 @@ class BuildVmTask(BaseTask):
|
|||||||
|
|
||||||
self.log.debug(f"Building VM for clan name: {clan_name}")
|
self.log.debug(f"Building VM for clan name: {clan_name}")
|
||||||
|
|
||||||
flake_dir = clan_flakes_dir() / clan_name
|
flake_dir = Path(self.vm.flake_url)
|
||||||
validate_path(clan_flakes_dir(), flake_dir)
|
|
||||||
flake_dir.mkdir(exist_ok=True)
|
flake_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
with tempfile.TemporaryDirectory() as tmpdir_:
|
with tempfile.TemporaryDirectory() as tmpdir_:
|
||||||
@@ -93,7 +83,7 @@ class BuildVmTask(BaseTask):
|
|||||||
env["SECRETS_DIR"] = str(secrets_dir)
|
env["SECRETS_DIR"] = str(secrets_dir)
|
||||||
|
|
||||||
# Only generate secrets for local clans
|
# Only generate secrets for local clans
|
||||||
if not is_flake_url(str(self.vm.flake_url)):
|
if isinstance(self.vm.flake_url, Path) and self.vm.flake_url.is_dir():
|
||||||
cmd = next(cmds)
|
cmd = next(cmds)
|
||||||
if Path(self.vm.flake_url).is_dir():
|
if Path(self.vm.flake_url).is_dir():
|
||||||
cmd.run(
|
cmd.run(
|
||||||
@@ -200,9 +190,7 @@ def create_vm(vm: VmConfig, nix_options: list[str] = []) -> BuildVmTask:
|
|||||||
|
|
||||||
|
|
||||||
def create_command(args: argparse.Namespace) -> None:
|
def create_command(args: argparse.Namespace) -> None:
|
||||||
flake_url = args.flake
|
flake_url = args.flake_url or args.flake
|
||||||
if not is_flake_url(str(args.flake)):
|
|
||||||
flake_url = specific_flake_dir(args.flake)
|
|
||||||
vm = asyncio.run(inspect_vm(flake_url=flake_url, flake_attr=args.machine))
|
vm = asyncio.run(inspect_vm(flake_url=flake_url, flake_attr=args.machine))
|
||||||
|
|
||||||
task = create_vm(vm, args.option)
|
task = create_vm(vm, args.option)
|
||||||
@@ -212,4 +200,5 @@ def create_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
def register_create_parser(parser: argparse.ArgumentParser) -> None:
|
def register_create_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
parser.add_argument("machine", type=str, help="machine in the flake to create")
|
parser.add_argument("machine", type=str, help="machine in the flake to create")
|
||||||
|
parser.add_argument("--flake_url", type=str, help="flake url")
|
||||||
parser.set_defaults(func=create_command)
|
parser.set_defaults(func=create_command)
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ from pathlib import Path
|
|||||||
from pydantic import AnyUrl, BaseModel
|
from pydantic import AnyUrl, BaseModel
|
||||||
|
|
||||||
from ..async_cmd import run
|
from ..async_cmd import run
|
||||||
from ..dirs import specific_flake_dir
|
|
||||||
from ..nix import nix_config, nix_eval
|
from ..nix import nix_config, nix_eval
|
||||||
|
|
||||||
|
|
||||||
@@ -34,7 +33,7 @@ async def inspect_vm(flake_url: AnyUrl | Path, flake_attr: str) -> VmConfig:
|
|||||||
|
|
||||||
|
|
||||||
def inspect_command(args: argparse.Namespace) -> None:
|
def inspect_command(args: argparse.Namespace) -> None:
|
||||||
clan_dir = specific_flake_dir(args.flake)
|
clan_dir = Path(args.flake)
|
||||||
res = asyncio.run(inspect_vm(flake_url=clan_dir, flake_attr=args.machine))
|
res = asyncio.run(inspect_vm(flake_url=clan_dir, flake_attr=args.machine))
|
||||||
print("Cores:", res.cores)
|
print("Cores:", res.cores)
|
||||||
print("Memory size:", res.memory_size)
|
print("Memory size:", res.memory_size)
|
||||||
@@ -43,9 +42,4 @@ def inspect_command(args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
def register_inspect_parser(parser: argparse.ArgumentParser) -> None:
|
def register_inspect_parser(parser: argparse.ArgumentParser) -> None:
|
||||||
parser.add_argument("machine", type=str)
|
parser.add_argument("machine", type=str)
|
||||||
parser.add_argument(
|
|
||||||
"flake",
|
|
||||||
type=str,
|
|
||||||
help="name of the flake to create machine for",
|
|
||||||
)
|
|
||||||
parser.set_defaults(func=inspect_command)
|
parser.set_defaults(func=inspect_command)
|
||||||
|
|||||||
@@ -1,25 +1,13 @@
|
|||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from pydantic import AnyUrl, BaseModel, Extra, validator
|
from pydantic import AnyUrl, BaseModel, Extra
|
||||||
|
|
||||||
from ..dirs import clan_flakes_dir
|
|
||||||
from ..flakes.create import DEFAULT_URL
|
from ..flakes.create import DEFAULT_URL
|
||||||
from ..types import validate_path
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class ClanFlakePath(BaseModel):
|
class FlakeCreateInput(BaseModel):
|
||||||
flake_name: Path
|
|
||||||
|
|
||||||
@validator("flake_name")
|
|
||||||
def check_flake_name(cls: Any, v: Path) -> Path: # noqa
|
|
||||||
return validate_path(clan_flakes_dir(), v)
|
|
||||||
|
|
||||||
|
|
||||||
class FlakeCreateInput(ClanFlakePath):
|
|
||||||
url: AnyUrl = DEFAULT_URL
|
url: AnyUrl = DEFAULT_URL
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# Logging setup
|
# Logging setup
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
from fastapi import APIRouter, HTTPException
|
from fastapi import APIRouter, HTTPException
|
||||||
|
|
||||||
from clan_cli.clan_modules import get_clan_module_names
|
from clan_cli.clan_modules import get_clan_module_names
|
||||||
from clan_cli.types import FlakeName
|
|
||||||
|
|
||||||
from ..api_outputs import (
|
from ..api_outputs import (
|
||||||
ClanModulesResponse,
|
ClanModulesResponse,
|
||||||
@@ -15,9 +15,9 @@ log = logging.getLogger(__name__)
|
|||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/{flake_name}/clan_modules", tags=[Tags.modules])
|
@router.get("/api/clan_modules", tags=[Tags.modules])
|
||||||
async def list_clan_modules(flake_name: FlakeName) -> ClanModulesResponse:
|
async def list_clan_modules(flake_dir: Path) -> ClanModulesResponse:
|
||||||
module_names, error = get_clan_module_names(flake_name)
|
module_names, error = get_clan_module_names(flake_dir)
|
||||||
if error is not None:
|
if error is not None:
|
||||||
raise HTTPException(status_code=400, detail=error)
|
raise HTTPException(status_code=400, detail=error)
|
||||||
return ClanModulesResponse(clan_modules=module_names)
|
return ClanModulesResponse(clan_modules=module_names)
|
||||||
|
|||||||
@@ -13,12 +13,11 @@ from clan_cli.webui.api_outputs import (
|
|||||||
FlakeAction,
|
FlakeAction,
|
||||||
FlakeAttrResponse,
|
FlakeAttrResponse,
|
||||||
FlakeCreateResponse,
|
FlakeCreateResponse,
|
||||||
FlakeListResponse,
|
|
||||||
FlakeResponse,
|
FlakeResponse,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ...async_cmd import run
|
from ...async_cmd import run
|
||||||
from ...flakes import create, list_flakes
|
from ...flakes import create
|
||||||
from ...nix import nix_command, nix_flake_show
|
from ...nix import nix_command, nix_flake_show
|
||||||
from ..tags import Tags
|
from ..tags import Tags
|
||||||
|
|
||||||
@@ -78,23 +77,17 @@ async def inspect_flake(
|
|||||||
return FlakeResponse(content=content, actions=actions)
|
return FlakeResponse(content=content, actions=actions)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/flake/list", tags=[Tags.flake])
|
|
||||||
async def list_all_flakes() -> FlakeListResponse:
|
|
||||||
flakes = list_flakes.list_flakes()
|
|
||||||
return FlakeListResponse(flakes=flakes)
|
|
||||||
|
|
||||||
|
|
||||||
@router.post(
|
@router.post(
|
||||||
"/api/flake/create", tags=[Tags.flake], status_code=status.HTTP_201_CREATED
|
"/api/flake/create", tags=[Tags.flake], status_code=status.HTTP_201_CREATED
|
||||||
)
|
)
|
||||||
async def create_flake(
|
async def create_flake(
|
||||||
args: Annotated[FlakeCreateInput, Body()],
|
flake_dir: Path, args: Annotated[FlakeCreateInput, Body()]
|
||||||
) -> FlakeCreateResponse:
|
) -> FlakeCreateResponse:
|
||||||
if args.flake_name.exists():
|
if flake_dir.exists():
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_409_CONFLICT,
|
status_code=status.HTTP_409_CONFLICT,
|
||||||
detail="Flake already exists",
|
detail="Flake already exists",
|
||||||
)
|
)
|
||||||
|
|
||||||
cmd_out = await create.create_flake(args.flake_name, args.url)
|
cmd_out = await create.create_flake(flake_dir, args.url)
|
||||||
return FlakeCreateResponse(cmd_out=cmd_out)
|
return FlakeCreateResponse(cmd_out=cmd_out)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# Logging setup
|
# Logging setup
|
||||||
import logging
|
import logging
|
||||||
|
from pathlib import Path
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from fastapi import APIRouter, Body
|
from fastapi import APIRouter, Body
|
||||||
@@ -15,7 +16,6 @@ from ...config.machine import (
|
|||||||
)
|
)
|
||||||
from ...config.schema import machine_schema
|
from ...config.schema import machine_schema
|
||||||
from ...machines.list import list_machines as _list_machines
|
from ...machines.list import list_machines as _list_machines
|
||||||
from ...types import FlakeName
|
|
||||||
from ..api_outputs import (
|
from ..api_outputs import (
|
||||||
ConfigResponse,
|
ConfigResponse,
|
||||||
Machine,
|
Machine,
|
||||||
@@ -31,66 +31,62 @@ log = logging.getLogger(__name__)
|
|||||||
router = APIRouter()
|
router = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/{flake_name}/machines", tags=[Tags.machine])
|
@router.get("/api/machines", tags=[Tags.machine])
|
||||||
async def list_machines(flake_name: FlakeName) -> MachinesResponse:
|
async def list_machines(flake_dir: Path) -> MachinesResponse:
|
||||||
machines = []
|
machines = []
|
||||||
for m in _list_machines(flake_name):
|
for m in _list_machines(flake_dir):
|
||||||
machines.append(Machine(name=m, status=Status.UNKNOWN))
|
machines.append(Machine(name=m, status=Status.UNKNOWN))
|
||||||
|
|
||||||
return MachinesResponse(machines=machines)
|
return MachinesResponse(machines=machines)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/{flake_name}/machines/{name}", tags=[Tags.machine])
|
@router.get("/api/machines/{name}", tags=[Tags.machine])
|
||||||
async def get_machine(flake_name: FlakeName, name: str) -> MachineResponse:
|
async def get_machine(flake_dir: Path, name: str) -> MachineResponse:
|
||||||
log.error("TODO")
|
log.error("TODO")
|
||||||
return MachineResponse(machine=Machine(name=name, status=Status.UNKNOWN))
|
return MachineResponse(machine=Machine(name=name, status=Status.UNKNOWN))
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/{flake_name}/machines/{name}/config", tags=[Tags.machine])
|
@router.get("/api/machines/{name}/config", tags=[Tags.machine])
|
||||||
async def get_machine_config(flake_name: FlakeName, name: str) -> ConfigResponse:
|
async def get_machine_config(flake_dir: Path, name: str) -> ConfigResponse:
|
||||||
config = config_for_machine(flake_name, name)
|
config = config_for_machine(flake_dir, name)
|
||||||
return ConfigResponse(**config)
|
return ConfigResponse(**config)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/api/{flake_name}/machines/{name}/config", tags=[Tags.machine])
|
@router.put("/api/machines/{name}/config", tags=[Tags.machine])
|
||||||
async def put_machine(
|
async def set_machine_config(
|
||||||
flake_name: FlakeName, name: str, config: Annotated[MachineConfig, Body()]
|
flake_dir: Path, name: str, config: Annotated[MachineConfig, Body()]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
|
||||||
Set the config for a machine.
|
|
||||||
Creates the machine if it doesn't yet exist.
|
|
||||||
"""
|
|
||||||
conf = jsonable_encoder(config)
|
conf = jsonable_encoder(config)
|
||||||
set_config_for_machine(flake_name, name, conf)
|
set_config_for_machine(flake_dir, name, conf)
|
||||||
|
|
||||||
|
|
||||||
@router.put(
|
@router.put(
|
||||||
"/api/{flake_name}/schema",
|
"/api/schema",
|
||||||
tags=[Tags.machine],
|
tags=[Tags.machine],
|
||||||
responses={400: {"model": MissingClanImports}},
|
responses={400: {"model": MissingClanImports}},
|
||||||
)
|
)
|
||||||
async def get_machine_schema(
|
async def get_machine_schema(
|
||||||
flake_name: FlakeName, config: Annotated[MachineConfig, Body()]
|
flake_dir: Path, config: Annotated[dict, Body()]
|
||||||
) -> SchemaResponse:
|
) -> SchemaResponse:
|
||||||
schema = machine_schema(flake_name, config=dict(config))
|
schema = machine_schema(flake_dir, config=config)
|
||||||
return SchemaResponse(schema=schema)
|
return SchemaResponse(schema=schema)
|
||||||
|
|
||||||
|
|
||||||
@router.get("/api/{flake_name}/machines/{name}/verify", tags=[Tags.machine])
|
@router.get("/api/machines/{name}/verify", tags=[Tags.machine])
|
||||||
async def get_verify_machine_config(
|
async def get_verify_machine_config(
|
||||||
flake_name: FlakeName, name: str
|
flake_dir: Path, name: str
|
||||||
) -> VerifyMachineResponse:
|
) -> VerifyMachineResponse:
|
||||||
error = verify_machine_config(flake_name, name)
|
error = verify_machine_config(flake_dir, name)
|
||||||
success = error is None
|
success = error is None
|
||||||
return VerifyMachineResponse(success=success, error=error)
|
return VerifyMachineResponse(success=success, error=error)
|
||||||
|
|
||||||
|
|
||||||
@router.put("/api/{flake_name}/machines/{name}/verify", tags=[Tags.machine])
|
@router.put("/api/machines/{name}/verify", tags=[Tags.machine])
|
||||||
async def put_verify_machine_config(
|
async def put_verify_machine_config(
|
||||||
flake_name: FlakeName,
|
flake_dir: Path,
|
||||||
name: str,
|
name: str,
|
||||||
config: Annotated[dict, Body()],
|
config: Annotated[dict, Body()],
|
||||||
) -> VerifyMachineResponse:
|
) -> VerifyMachineResponse:
|
||||||
error = verify_machine_config(flake_name, name, config)
|
error = verify_machine_config(flake_dir, name, config)
|
||||||
success = error is None
|
success = error is None
|
||||||
return VerifyMachineResponse(success=success, error=error)
|
return VerifyMachineResponse(success=success, error=error)
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ from pydantic.tools import parse_obj_as
|
|||||||
from root import CLAN_CORE
|
from root import CLAN_CORE
|
||||||
|
|
||||||
from clan_cli.dirs import nixpkgs_source
|
from clan_cli.dirs import nixpkgs_source
|
||||||
from clan_cli.types import FlakeName
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -36,14 +35,13 @@ def substitute(
|
|||||||
|
|
||||||
|
|
||||||
class FlakeForTest(NamedTuple):
|
class FlakeForTest(NamedTuple):
|
||||||
name: FlakeName
|
|
||||||
path: Path
|
path: Path
|
||||||
|
|
||||||
|
|
||||||
def create_flake(
|
def create_flake(
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
temporary_home: Path,
|
temporary_home: Path,
|
||||||
flake_name: FlakeName,
|
flake_name: str,
|
||||||
clan_core_flake: Path | None = None,
|
clan_core_flake: Path | None = None,
|
||||||
machines: list[str] = [],
|
machines: list[str] = [],
|
||||||
remote: bool = False,
|
remote: bool = False,
|
||||||
@@ -55,7 +53,7 @@ def create_flake(
|
|||||||
template = Path(__file__).parent / flake_name
|
template = Path(__file__).parent / flake_name
|
||||||
|
|
||||||
# copy the template to a new temporary location
|
# copy the template to a new temporary location
|
||||||
flake = temporary_home / ".config/clan/flakes" / flake_name
|
flake = temporary_home / flake_name
|
||||||
shutil.copytree(template, flake)
|
shutil.copytree(template, flake)
|
||||||
|
|
||||||
# lookup the requested machines in ./test_machines and include them
|
# lookup the requested machines in ./test_machines and include them
|
||||||
@@ -91,16 +89,16 @@ def create_flake(
|
|||||||
|
|
||||||
if remote:
|
if remote:
|
||||||
with tempfile.TemporaryDirectory():
|
with tempfile.TemporaryDirectory():
|
||||||
yield FlakeForTest(flake_name, flake)
|
yield FlakeForTest(flake)
|
||||||
else:
|
else:
|
||||||
yield FlakeForTest(flake_name, flake)
|
yield FlakeForTest(flake)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def test_flake(
|
def test_flake(
|
||||||
monkeypatch: pytest.MonkeyPatch, temporary_home: Path
|
monkeypatch: pytest.MonkeyPatch, temporary_home: Path
|
||||||
) -> Iterator[FlakeForTest]:
|
) -> Iterator[FlakeForTest]:
|
||||||
yield from create_flake(monkeypatch, temporary_home, FlakeName("test_flake"))
|
yield from create_flake(monkeypatch, temporary_home, "test_flake")
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
@@ -114,7 +112,7 @@ def test_flake_with_core(
|
|||||||
yield from create_flake(
|
yield from create_flake(
|
||||||
monkeypatch,
|
monkeypatch,
|
||||||
temporary_home,
|
temporary_home,
|
||||||
FlakeName("test_flake_with_core"),
|
"test_flake_with_core",
|
||||||
CLAN_CORE,
|
CLAN_CORE,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -140,6 +138,6 @@ def test_flake_with_core_and_pass(
|
|||||||
yield from create_flake(
|
yield from create_flake(
|
||||||
monkeypatch,
|
monkeypatch,
|
||||||
temporary_home,
|
temporary_home,
|
||||||
FlakeName("test_flake_with_core_and_pass"),
|
"test_flake_with_core_and_pass",
|
||||||
CLAN_CORE,
|
CLAN_CORE,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,22 +4,17 @@ import shlex
|
|||||||
|
|
||||||
from clan_cli import create_parser
|
from clan_cli import create_parser
|
||||||
from clan_cli.custom_logger import get_caller
|
from clan_cli.custom_logger import get_caller
|
||||||
from clan_cli.dirs import get_clan_flake_toplevel
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Cli:
|
class Cli:
|
||||||
def __init__(self) -> None:
|
|
||||||
self.parser = create_parser(prog="clan")
|
|
||||||
|
|
||||||
def run(self, args: list[str]) -> argparse.Namespace:
|
def run(self, args: list[str]) -> argparse.Namespace:
|
||||||
|
parser = create_parser(prog="clan")
|
||||||
cmd = shlex.join(["clan"] + args)
|
cmd = shlex.join(["clan"] + args)
|
||||||
log.debug(f"$ {cmd}")
|
log.debug(f"$ {cmd}")
|
||||||
log.debug(f"Caller {get_caller()}")
|
log.debug(f"Caller {get_caller()}")
|
||||||
parsed = self.parser.parse_args(args)
|
parsed = parser.parse_args(args)
|
||||||
if parsed.flake is None:
|
|
||||||
parsed.flake = get_clan_flake_toplevel()
|
|
||||||
if hasattr(parsed, "func"):
|
if hasattr(parsed, "func"):
|
||||||
parsed.func(parsed)
|
parsed.func(parsed)
|
||||||
return parsed
|
return parsed
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ from fixtures_flakes import FlakeForTest
|
|||||||
@pytest.mark.with_core
|
@pytest.mark.with_core
|
||||||
def test_configure_machine(api: TestClient, test_flake_with_core: FlakeForTest) -> None:
|
def test_configure_machine(api: TestClient, test_flake_with_core: FlakeForTest) -> None:
|
||||||
# retrieve the list of available clanModules
|
# retrieve the list of available clanModules
|
||||||
response = api.get(f"/api/{test_flake_with_core.name}/clan_modules")
|
response = api.get(f"/api/clan_modules?flake_dir={test_flake_with_core.path}")
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
response_json = response.json()
|
response_json = response.json()
|
||||||
assert response.status_code == 200
|
|
||||||
assert isinstance(response_json, dict)
|
assert isinstance(response_json, dict)
|
||||||
assert "clan_modules" in response_json
|
assert "clan_modules" in response_json
|
||||||
assert len(response_json["clan_modules"]) > 0
|
assert len(response_json["clan_modules"]) > 0
|
||||||
|
|||||||
@@ -39,6 +39,8 @@ def test_set_some_option(
|
|||||||
cli = Cli()
|
cli = Cli()
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
"config",
|
"config",
|
||||||
"--quiet",
|
"--quiet",
|
||||||
"--options-file",
|
"--options-file",
|
||||||
@@ -47,7 +49,6 @@ def test_set_some_option(
|
|||||||
out_file.name,
|
out_file.name,
|
||||||
]
|
]
|
||||||
+ args
|
+ args
|
||||||
+ [test_flake.name]
|
|
||||||
)
|
)
|
||||||
json_out = json.loads(open(out_file.name).read())
|
json_out = json.loads(open(out_file.name).read())
|
||||||
assert json_out == expected
|
assert json_out == expected
|
||||||
@@ -61,11 +62,30 @@ def test_configure_machine(
|
|||||||
) -> None:
|
) -> None:
|
||||||
cli = Cli()
|
cli = Cli()
|
||||||
|
|
||||||
cli.run(["config", "-m", "machine1", "clan.jitsi.enable", "true", test_flake.name])
|
cli.run(
|
||||||
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
|
"config",
|
||||||
|
"-m",
|
||||||
|
"machine1",
|
||||||
|
"clan.jitsi.enable",
|
||||||
|
"true",
|
||||||
|
]
|
||||||
|
)
|
||||||
# clear the output buffer
|
# clear the output buffer
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
# read a option value
|
# read a option value
|
||||||
cli.run(["config", "-m", "machine1", "clan.jitsi.enable", test_flake.name])
|
cli.run(
|
||||||
|
[
|
||||||
|
"--flake",
|
||||||
|
str(test_flake.path),
|
||||||
|
"config",
|
||||||
|
"-m",
|
||||||
|
"machine1",
|
||||||
|
"clan.jitsi.enable",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# read the output
|
# read the output
|
||||||
assert capsys.readouterr().out == "true\n"
|
assert capsys.readouterr().out == "true\n"
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import pytest
|
|||||||
from api import TestClient
|
from api import TestClient
|
||||||
from cli import Cli
|
from cli import Cli
|
||||||
|
|
||||||
from clan_cli.dirs import clan_flakes_dir
|
|
||||||
from clan_cli.flakes.create import DEFAULT_URL
|
from clan_cli.flakes.create import DEFAULT_URL
|
||||||
|
|
||||||
|
|
||||||
@@ -19,13 +18,11 @@ def cli() -> Cli:
|
|||||||
def test_create_flake_api(
|
def test_create_flake_api(
|
||||||
monkeypatch: pytest.MonkeyPatch, api: TestClient, temporary_home: Path
|
monkeypatch: pytest.MonkeyPatch, api: TestClient, temporary_home: Path
|
||||||
) -> None:
|
) -> None:
|
||||||
monkeypatch.chdir(clan_flakes_dir())
|
flake_dir = temporary_home / "test-flake"
|
||||||
flake_name = "flake_dir"
|
|
||||||
flake_dir = clan_flakes_dir() / flake_name
|
|
||||||
response = api.post(
|
response = api.post(
|
||||||
"/api/flake/create",
|
f"/api/flake/create?flake_dir={flake_dir}",
|
||||||
json=dict(
|
json=dict(
|
||||||
flake_name=str(flake_dir),
|
flake_dir=str(flake_dir),
|
||||||
url=str(DEFAULT_URL),
|
url=str(DEFAULT_URL),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -42,17 +39,15 @@ def test_create_flake(
|
|||||||
temporary_home: Path,
|
temporary_home: Path,
|
||||||
cli: Cli,
|
cli: Cli,
|
||||||
) -> None:
|
) -> None:
|
||||||
monkeypatch.chdir(clan_flakes_dir())
|
flake_dir = temporary_home / "test-flake"
|
||||||
flake_name = "flake_dir"
|
|
||||||
flake_dir = clan_flakes_dir() / flake_name
|
|
||||||
|
|
||||||
cli.run(["flakes", "create", flake_name])
|
cli.run(["flakes", "create", str(flake_dir)])
|
||||||
assert (flake_dir / ".clan-flake").exists()
|
assert (flake_dir / ".clan-flake").exists()
|
||||||
monkeypatch.chdir(flake_dir)
|
monkeypatch.chdir(flake_dir)
|
||||||
cli.run(["machines", "create", "machine1", flake_name])
|
cli.run(["machines", "create", "machine1"])
|
||||||
capsys.readouterr() # flush cache
|
capsys.readouterr() # flush cache
|
||||||
|
|
||||||
cli.run(["machines", "list", flake_name])
|
cli.run(["machines", "list"])
|
||||||
assert "machine1" in capsys.readouterr().out
|
assert "machine1" in capsys.readouterr().out
|
||||||
flake_show = subprocess.run(
|
flake_show = subprocess.run(
|
||||||
["nix", "flake", "show", "--json"],
|
["nix", "flake", "show", "--json"],
|
||||||
@@ -67,9 +62,7 @@ def test_create_flake(
|
|||||||
pytest.fail("nixosConfigurations.machine1 not found in flake outputs")
|
pytest.fail("nixosConfigurations.machine1 not found in flake outputs")
|
||||||
# configure machine1
|
# configure machine1
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(
|
cli.run(["config", "--machine", "machine1", "services.openssh.enable", ""])
|
||||||
["config", "--machine", "machine1", "services.openssh.enable", "", flake_name]
|
|
||||||
)
|
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(
|
cli.run(
|
||||||
[
|
[
|
||||||
@@ -78,6 +71,5 @@ def test_create_flake(
|
|||||||
"machine1",
|
"machine1",
|
||||||
"services.openssh.enable",
|
"services.openssh.enable",
|
||||||
"true",
|
"true",
|
||||||
flake_name,
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,15 +8,6 @@ from fixtures_flakes import FlakeForTest
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.impure
|
|
||||||
def test_list_flakes(api: TestClient, test_flake_with_core: FlakeForTest) -> None:
|
|
||||||
response = api.get("/api/flake/list")
|
|
||||||
assert response.status_code == 200, "Failed to list flakes"
|
|
||||||
data = response.json()
|
|
||||||
print("Data: ", data)
|
|
||||||
assert data.get("flakes") == ["test_flake_with_core"]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.impure
|
@pytest.mark.impure
|
||||||
def test_inspect_ok(api: TestClient, test_flake_with_core: FlakeForTest) -> None:
|
def test_inspect_ok(api: TestClient, test_flake_with_core: FlakeForTest) -> None:
|
||||||
params = {"url": str(test_flake_with_core.path)}
|
params = {"url": str(test_flake_with_core.path)}
|
||||||
|
|||||||
@@ -3,19 +3,20 @@ from api import TestClient
|
|||||||
from fixtures_flakes import FlakeForTest
|
from fixtures_flakes import FlakeForTest
|
||||||
|
|
||||||
|
|
||||||
def test_create_and_list(api: TestClient, test_flake: FlakeForTest) -> None:
|
def test_machines(api: TestClient, test_flake: FlakeForTest) -> None:
|
||||||
response = api.get(f"/api/{test_flake.name}/machines")
|
response = api.get(f"/api/machines?flake_dir={test_flake.path}")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {"machines": []}
|
assert response.json() == {"machines": []}
|
||||||
|
response = api.put(
|
||||||
response = api.put(f"/api/{test_flake.name}/machines/test/config", json=dict())
|
f"/api/machines/test/config?flake_dir={test_flake.path}", json={}
|
||||||
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
response = api.get(f"/api/{test_flake.name}/machines/test")
|
response = api.get(f"/api/machines/test?flake_dir={test_flake.path}")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {"machine": {"name": "test", "status": "unknown"}}
|
assert response.json() == {"machine": {"name": "test", "status": "unknown"}}
|
||||||
|
|
||||||
response = api.get(f"/api/{test_flake.name}/machines")
|
response = api.get(f"/api/machines?flake_dir={test_flake.path}")
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {"machines": [{"name": "test", "status": "unknown"}]}
|
assert response.json() == {"machines": [{"name": "test", "status": "unknown"}]}
|
||||||
|
|
||||||
@@ -24,7 +25,7 @@ def test_create_and_list(api: TestClient, test_flake: FlakeForTest) -> None:
|
|||||||
def test_schema_errors(api: TestClient, test_flake_with_core: FlakeForTest) -> None:
|
def test_schema_errors(api: TestClient, test_flake_with_core: FlakeForTest) -> None:
|
||||||
# make sure that eval errors do not raise an internal server error
|
# make sure that eval errors do not raise an internal server error
|
||||||
response = api.put(
|
response = api.put(
|
||||||
f"/api/{test_flake_with_core.name}/schema",
|
f"/api/schema?flake_dir={test_flake_with_core.path}",
|
||||||
json={"imports": ["some-invalid-import"]},
|
json={"imports": ["some-invalid-import"]},
|
||||||
)
|
)
|
||||||
assert response.status_code == 422
|
assert response.status_code == 422
|
||||||
@@ -39,7 +40,7 @@ def test_schema_invalid_clan_imports(
|
|||||||
api: TestClient, test_flake_with_core: FlakeForTest
|
api: TestClient, test_flake_with_core: FlakeForTest
|
||||||
) -> None:
|
) -> None:
|
||||||
response = api.put(
|
response = api.put(
|
||||||
f"/api/{test_flake_with_core.name}/schema",
|
f"/api/schema?flake_dir={test_flake_with_core.path}",
|
||||||
json={"clanImports": ["non-existing-clan-module"]},
|
json={"clanImports": ["non-existing-clan-module"]},
|
||||||
)
|
)
|
||||||
assert response.status_code == 400
|
assert response.status_code == 400
|
||||||
@@ -54,7 +55,8 @@ def test_create_machine_invalid_hostname(
|
|||||||
api: TestClient, test_flake: FlakeForTest
|
api: TestClient, test_flake: FlakeForTest
|
||||||
) -> None:
|
) -> None:
|
||||||
response = api.put(
|
response = api.put(
|
||||||
f"/api/{test_flake.name}/machines/-invalid-hostname/config", json=dict()
|
f"/api/machines/-invalid-hostname/config?flake_dir={test_flake.path}",
|
||||||
|
json=dict(),
|
||||||
)
|
)
|
||||||
assert response.status_code == 422
|
assert response.status_code == 422
|
||||||
assert (
|
assert (
|
||||||
@@ -67,7 +69,7 @@ def test_verify_config_without_machine(
|
|||||||
api: TestClient, test_flake_with_core: FlakeForTest
|
api: TestClient, test_flake_with_core: FlakeForTest
|
||||||
) -> None:
|
) -> None:
|
||||||
response = api.put(
|
response = api.put(
|
||||||
f"/api/{test_flake_with_core.name}/machines/test/verify",
|
f"/api/machines/test/verify?flake_dir={test_flake_with_core.path}",
|
||||||
json=dict(),
|
json=dict(),
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
@@ -79,12 +81,14 @@ def test_ensure_empty_config_is_valid(
|
|||||||
api: TestClient, test_flake_with_core: FlakeForTest
|
api: TestClient, test_flake_with_core: FlakeForTest
|
||||||
) -> None:
|
) -> None:
|
||||||
response = api.put(
|
response = api.put(
|
||||||
f"/api/{test_flake_with_core.name}/machines/test/config",
|
f"/api/machines/test/config?flake_dir={test_flake_with_core.path}",
|
||||||
json=dict(),
|
json=dict(),
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
response = api.get(f"/api/{test_flake_with_core.name}/machines/test/verify")
|
response = api.get(
|
||||||
|
f"/api/machines/test/verify?flake_dir={test_flake_with_core.path}"
|
||||||
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {"success": True, "error": None}
|
assert response.json() == {"success": True, "error": None}
|
||||||
|
|
||||||
@@ -92,17 +96,21 @@ def test_ensure_empty_config_is_valid(
|
|||||||
@pytest.mark.with_core
|
@pytest.mark.with_core
|
||||||
def test_configure_machine(api: TestClient, test_flake_with_core: FlakeForTest) -> None:
|
def test_configure_machine(api: TestClient, test_flake_with_core: FlakeForTest) -> None:
|
||||||
# ensure error 404 if machine does not exist when accessing the config
|
# ensure error 404 if machine does not exist when accessing the config
|
||||||
response = api.get(f"/api/{test_flake_with_core.name}/machines/machine1/config")
|
response = api.get(
|
||||||
|
f"/api/machines/machine1/config?flake_dir={test_flake_with_core.path}"
|
||||||
|
)
|
||||||
assert response.status_code == 404
|
assert response.status_code == 404
|
||||||
|
|
||||||
# create the machine
|
# create the machine
|
||||||
response = api.put(
|
response = api.put(
|
||||||
f"/api/{test_flake_with_core.name}/machines/machine1/config", json={}
|
f"/api/machines/machine1/config?flake_dir={test_flake_with_core.path}", json={}
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
# ensure an empty config is returned by default for a new machine
|
# 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")
|
response = api.get(
|
||||||
|
f"/api/machines/machine1/config?flake_dir={test_flake_with_core.path}"
|
||||||
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
"clanImports": [],
|
"clanImports": [],
|
||||||
@@ -111,7 +119,7 @@ def test_configure_machine(api: TestClient, test_flake_with_core: FlakeForTest)
|
|||||||
|
|
||||||
# get jsonschema for without imports
|
# get jsonschema for without imports
|
||||||
response = api.put(
|
response = api.put(
|
||||||
f"/api/{test_flake_with_core.name}/schema",
|
f"/api/schema?flake_dir={test_flake_with_core.path}",
|
||||||
json={"clanImports": []},
|
json={"clanImports": []},
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
@@ -133,7 +141,7 @@ def test_configure_machine(api: TestClient, test_flake_with_core: FlakeForTest)
|
|||||||
|
|
||||||
# verify an invalid config (foo option does not exist)
|
# verify an invalid config (foo option does not exist)
|
||||||
response = api.put(
|
response = api.put(
|
||||||
f"/api/{test_flake_with_core.name}/machines/machine1/verify",
|
f"/api/machines/machine1/verify?flake_dir={test_flake_with_core.path}",
|
||||||
json=invalid_config,
|
json=invalid_config,
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
@@ -141,13 +149,15 @@ def test_configure_machine(api: TestClient, test_flake_with_core: FlakeForTest)
|
|||||||
|
|
||||||
# set come invalid config (foo option does not exist)
|
# set come invalid config (foo option does not exist)
|
||||||
response = api.put(
|
response = api.put(
|
||||||
f"/api/{test_flake_with_core.name}/machines/machine1/config",
|
f"/api/machines/machine1/config?flake_dir={test_flake_with_core.path}",
|
||||||
json=invalid_config,
|
json=invalid_config,
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
# ensure the config has actually been updated
|
# ensure the config has actually been updated
|
||||||
response = api.get(f"/api/{test_flake_with_core.name}/machines/machine1/config")
|
response = api.get(
|
||||||
|
f"/api/machines/machine1/config?flake_dir={test_flake_with_core.path}"
|
||||||
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == dict(clanImports=[], **invalid_config)
|
assert response.json() == dict(clanImports=[], **invalid_config)
|
||||||
|
|
||||||
@@ -162,20 +172,22 @@ def test_configure_machine(api: TestClient, test_flake_with_core: FlakeForTest)
|
|||||||
)
|
)
|
||||||
|
|
||||||
response = api.put(
|
response = api.put(
|
||||||
f"/api/{test_flake_with_core.name}/machines/machine1/config",
|
f"/api/machines/machine1/config?flake_dir={test_flake_with_core.path}",
|
||||||
json=config2,
|
json=config2,
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
# ensure the config has been applied
|
# ensure the config has been applied
|
||||||
response = api.get(
|
response = api.get(
|
||||||
f"/api/{test_flake_with_core.name}/machines/machine1/config",
|
f"/api/machines/machine1/config?flake_dir={test_flake_with_core.path}",
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == dict(clanImports=[], **config2)
|
assert response.json() == dict(clanImports=[], **config2)
|
||||||
|
|
||||||
# get the config again
|
# get the config again
|
||||||
response = api.get(f"/api/{test_flake_with_core.name}/machines/machine1/config")
|
response = api.get(
|
||||||
|
f"/api/machines/machine1/config?flake_dir={test_flake_with_core.path}"
|
||||||
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {"clanImports": [], **config2}
|
assert response.json() == {"clanImports": [], **config2}
|
||||||
|
|
||||||
@@ -183,27 +195,29 @@ def test_configure_machine(api: TestClient, test_flake_with_core: FlakeForTest)
|
|||||||
# For example, this should not result in the boot.loader.grub.devices being
|
# For example, this should not result in the boot.loader.grub.devices being
|
||||||
# set twice (eg. merged)
|
# set twice (eg. merged)
|
||||||
response = api.put(
|
response = api.put(
|
||||||
f"/api/{test_flake_with_core.name}/machines/machine1/config",
|
f"/api/machines/machine1/config?flake_dir={test_flake_with_core.path}",
|
||||||
json=config2,
|
json=config2,
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
# ensure the config has been applied
|
# ensure the config has been applied
|
||||||
response = api.get(
|
response = api.get(
|
||||||
f"/api/{test_flake_with_core.name}/machines/machine1/config",
|
f"/api/machines/machine1/config?flake_dir={test_flake_with_core.path}",
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == dict(clanImports=[], **config2)
|
assert response.json() == dict(clanImports=[], **config2)
|
||||||
|
|
||||||
# verify the machine config evaluates
|
# verify the machine config evaluates
|
||||||
response = api.get(f"/api/{test_flake_with_core.name}/machines/machine1/verify")
|
response = api.get(
|
||||||
|
f"/api/machines/machine1/verify?flake_dir={test_flake_with_core.path}"
|
||||||
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
assert response.json() == {"success": True, "error": None}
|
assert response.json() == {"success": True, "error": None}
|
||||||
|
|
||||||
# get the schema with an extra module imported
|
# get the schema with an extra module imported
|
||||||
response = api.put(
|
response = api.put(
|
||||||
f"/api/{test_flake_with_core.name}/schema",
|
f"/api/schema?flake_dir={test_flake_with_core.path}",
|
||||||
json={"clanImports": ["diskLayouts"]},
|
json={"clanImports": ["diskLayouts"]},
|
||||||
)
|
)
|
||||||
# expect the result schema to contain the deltachat option
|
# expect the result schema to contain the deltachat option
|
||||||
@@ -227,14 +241,14 @@ def test_configure_machine(api: TestClient, test_flake_with_core: FlakeForTest)
|
|||||||
|
|
||||||
# set the fake-module.fake-flag option to true
|
# set the fake-module.fake-flag option to true
|
||||||
response = api.put(
|
response = api.put(
|
||||||
f"/api/{test_flake_with_core.name}/machines/machine1/config",
|
f"/api/machines/machine1/config?flake_dir={test_flake_with_core.path}",
|
||||||
json=config_with_imports,
|
json=config_with_imports,
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
|
|
||||||
# ensure the config has been applied
|
# ensure the config has been applied
|
||||||
response = api.get(
|
response = api.get(
|
||||||
f"/api/{test_flake_with_core.name}/machines/machine1/config",
|
f"/api/machines/machine1/config?flake_dir={test_flake_with_core.path}",
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
@@ -248,7 +262,7 @@ def test_configure_machine(api: TestClient, test_flake_with_core: FlakeForTest)
|
|||||||
|
|
||||||
# remove the import from the config
|
# remove the import from the config
|
||||||
response = api.put(
|
response = api.put(
|
||||||
f"/api/{test_flake_with_core.name}/machines/machine1/config",
|
f"/api/machines/machine1/config?flake_dir={test_flake_with_core.path}",
|
||||||
json=dict(
|
json=dict(
|
||||||
clanImports=[],
|
clanImports=[],
|
||||||
),
|
),
|
||||||
@@ -257,7 +271,7 @@ def test_configure_machine(api: TestClient, test_flake_with_core: FlakeForTest)
|
|||||||
|
|
||||||
# ensure the config has been applied
|
# ensure the config has been applied
|
||||||
response = api.get(
|
response = api.get(
|
||||||
f"/api/{test_flake_with_core.name}/machines/machine1/config",
|
f"/api/machines/machine1/config?flake_dir={test_flake_with_core.path}",
|
||||||
)
|
)
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.json() == {
|
assert response.json() == {
|
||||||
|
|||||||
@@ -7,16 +7,16 @@ def test_machine_subcommands(
|
|||||||
test_flake: FlakeForTest, capsys: pytest.CaptureFixture
|
test_flake: FlakeForTest, capsys: pytest.CaptureFixture
|
||||||
) -> None:
|
) -> None:
|
||||||
cli = Cli()
|
cli = Cli()
|
||||||
cli.run(["machines", "create", "machine1", test_flake.name])
|
cli.run(["--flake", str(test_flake.path), "machines", "create", "machine1"])
|
||||||
|
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(["machines", "list", test_flake.name])
|
cli.run(["--flake", str(test_flake.path), "machines", "list"])
|
||||||
out = capsys.readouterr()
|
out = capsys.readouterr()
|
||||||
assert "machine1\n" == out.out
|
assert "machine1\n" == out.out
|
||||||
|
|
||||||
cli.run(["machines", "delete", "machine1", test_flake.name])
|
cli.run(["--flake", str(test_flake.path), "machines", "delete", "machine1"])
|
||||||
|
|
||||||
capsys.readouterr()
|
capsys.readouterr()
|
||||||
cli.run(["machines", "list", test_flake.name])
|
cli.run(["--flake", str(test_flake.path), "machines", "list"])
|
||||||
out = capsys.readouterr()
|
out = capsys.readouterr()
|
||||||
assert "" == out.out
|
assert "" == out.out
|
||||||
|
|||||||
@@ -6,5 +6,5 @@ from clan_cli.config.schema import machine_schema
|
|||||||
|
|
||||||
@pytest.mark.with_core
|
@pytest.mark.with_core
|
||||||
def test_schema_for_machine(test_flake_with_core: FlakeForTest) -> None:
|
def test_schema_for_machine(test_flake_with_core: FlakeForTest) -> None:
|
||||||
schema = machine_schema(test_flake_with_core.name, config={})
|
schema = machine_schema(test_flake_with_core.path, config={})
|
||||||
assert "properties" in schema
|
assert "properties" in schema
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ def test_generate_secret(
|
|||||||
has_secret(test_flake_with_core.path, "vm1-zerotier-identity-secret")
|
has_secret(test_flake_with_core.path, "vm1-zerotier-identity-secret")
|
||||||
has_secret(test_flake_with_core.path, "vm1-zerotier-subnet")
|
has_secret(test_flake_with_core.path, "vm1-zerotier-subnet")
|
||||||
network_id = machine_get_fact(
|
network_id = machine_get_fact(
|
||||||
test_flake_with_core.name, "vm1", "zerotier-network-id"
|
test_flake_with_core.path, "vm1", "zerotier-network-id"
|
||||||
)
|
)
|
||||||
assert len(network_id) == 16
|
assert len(network_id) == 16
|
||||||
secrets_folder = sops_secrets_folder(test_flake_with_core.path)
|
secrets_folder = sops_secrets_folder(test_flake_with_core.path)
|
||||||
@@ -59,7 +59,7 @@ def test_generate_secret(
|
|||||||
cli.run(["secrets", "generate", "vm2"])
|
cli.run(["secrets", "generate", "vm2"])
|
||||||
assert has_secret(test_flake_with_core.path, "vm2-age.key")
|
assert has_secret(test_flake_with_core.path, "vm2-age.key")
|
||||||
assert has_secret(test_flake_with_core.path, "vm2-zerotier-identity-secret")
|
assert has_secret(test_flake_with_core.path, "vm2-zerotier-identity-secret")
|
||||||
ip = machine_get_fact(test_flake_with_core.name, "vm1", "zerotier-ip")
|
ip = machine_get_fact(test_flake_with_core.path, "vm1", "zerotier-ip")
|
||||||
assert ipaddress.IPv6Address(ip).is_private
|
assert ipaddress.IPv6Address(ip).is_private
|
||||||
meshname = machine_get_fact(test_flake_with_core.name, "vm1", "zerotier-meshname")
|
meshname = machine_get_fact(test_flake_with_core.path, "vm1", "zerotier-meshname")
|
||||||
assert len(meshname) == 26
|
assert len(meshname) == 26
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ def test_upload_secret(
|
|||||||
subprocess.run(nix_shell(["pass"], ["pass", "init", "test@local"]), check=True)
|
subprocess.run(nix_shell(["pass"], ["pass", "init", "test@local"]), check=True)
|
||||||
cli.run(["secrets", "generate", "vm1"])
|
cli.run(["secrets", "generate", "vm1"])
|
||||||
network_id = machine_get_fact(
|
network_id = machine_get_fact(
|
||||||
test_flake_with_core_and_pass.name, "vm1", "zerotier-network-id"
|
test_flake_with_core_and_pass.path, "vm1", "zerotier-network-id"
|
||||||
)
|
)
|
||||||
assert len(network_id) == 16
|
assert len(network_id) == 16
|
||||||
identity_secret = (
|
identity_secret = (
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ from httpx import SyncByteStream
|
|||||||
from pydantic import AnyUrl
|
from pydantic import AnyUrl
|
||||||
from root import CLAN_CORE
|
from root import CLAN_CORE
|
||||||
|
|
||||||
from clan_cli.types import FlakeName
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from age_keys import KeyPair
|
from age_keys import KeyPair
|
||||||
|
|
||||||
@@ -23,7 +21,7 @@ def flake_with_vm_with_secrets(
|
|||||||
yield from create_flake(
|
yield from create_flake(
|
||||||
monkeypatch,
|
monkeypatch,
|
||||||
temporary_home,
|
temporary_home,
|
||||||
FlakeName("test_flake_with_core_dynamic_machines"),
|
"test_flake_with_core_dynamic_machines",
|
||||||
CLAN_CORE,
|
CLAN_CORE,
|
||||||
machines=["vm_with_secrets"],
|
machines=["vm_with_secrets"],
|
||||||
)
|
)
|
||||||
@@ -36,7 +34,7 @@ def remote_flake_with_vm_without_secrets(
|
|||||||
yield from create_flake(
|
yield from create_flake(
|
||||||
monkeypatch,
|
monkeypatch,
|
||||||
temporary_home,
|
temporary_home,
|
||||||
FlakeName("test_flake_with_core_dynamic_machines"),
|
"test_flake_with_core_dynamic_machines",
|
||||||
CLAN_CORE,
|
CLAN_CORE,
|
||||||
machines=["vm_without_secrets"],
|
machines=["vm_without_secrets"],
|
||||||
remote=True,
|
remote=True,
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ def test_inspect(
|
|||||||
test_flake_with_core: FlakeForTest, capsys: pytest.CaptureFixture
|
test_flake_with_core: FlakeForTest, capsys: pytest.CaptureFixture
|
||||||
) -> None:
|
) -> None:
|
||||||
cli = Cli()
|
cli = Cli()
|
||||||
cli.run(["vms", "inspect", "vm1", test_flake_with_core.name])
|
cli.run(["--flake", str(test_flake_with_core.path), "vms", "inspect", "vm1"])
|
||||||
out = capsys.readouterr() # empty the buffer
|
out = capsys.readouterr() # empty the buffer
|
||||||
assert "Cores" in out.out
|
assert "Cores" in out.out
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user