Merge pull request 'import nixos-facter by default' (#2178) from nixos-facter into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/2178
This commit is contained in:
@@ -10,7 +10,6 @@
|
|||||||
{
|
{
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
config,
|
|
||||||
...
|
...
|
||||||
}:
|
}:
|
||||||
let
|
let
|
||||||
@@ -30,6 +29,7 @@
|
|||||||
clan.core.networking.targetHost = "machine";
|
clan.core.networking.targetHost = "machine";
|
||||||
networking.hostName = "machine";
|
networking.hostName = "machine";
|
||||||
services.openssh.settings.UseDns = false;
|
services.openssh.settings.UseDns = false;
|
||||||
|
nixpkgs.hostPlatform = "x86_64-linux";
|
||||||
|
|
||||||
programs.ssh.knownHosts = {
|
programs.ssh.knownHosts = {
|
||||||
machine.hostNames = [ "machine" ];
|
machine.hostNames = [ "machine" ];
|
||||||
|
|||||||
@@ -20,13 +20,13 @@
|
|||||||
|
|
||||||
environment.etc."install-successful".text = "ok";
|
environment.etc."install-successful".text = "ok";
|
||||||
|
|
||||||
|
nixpkgs.hostPlatform = "x86_64-linux";
|
||||||
boot.consoleLogLevel = lib.mkForce 100;
|
boot.consoleLogLevel = lib.mkForce 100;
|
||||||
boot.kernelParams = [ "boot.shell_on_fail" ];
|
boot.kernelParams = [ "boot.shell_on_fail" ];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
perSystem =
|
perSystem =
|
||||||
{
|
{
|
||||||
nodes,
|
|
||||||
pkgs,
|
pkgs,
|
||||||
lib,
|
lib,
|
||||||
...
|
...
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ in
|
|||||||
# Dummy file system
|
# Dummy file system
|
||||||
fileSystems."/".device = "/dev/null";
|
fileSystems."/".device = "/dev/null";
|
||||||
boot.loader.grub.device = "/dev/null";
|
boot.loader.grub.device = "/dev/null";
|
||||||
|
nixpkgs.hostPlatform = "x86_64-linux";
|
||||||
imports = [
|
imports = [
|
||||||
documentationModule
|
documentationModule
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -12,8 +12,7 @@
|
|||||||
},
|
},
|
||||||
"description": "A nice thing",
|
"description": "A nice thing",
|
||||||
"icon": "./path/to/icon.png",
|
"icon": "./path/to/icon.png",
|
||||||
"tags": ["1", "2", "3"],
|
"tags": ["1", "2", "3"]
|
||||||
"system": "x86_64-linux"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"services": {
|
"services": {
|
||||||
|
|||||||
@@ -26,36 +26,10 @@ let
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
machineSettings =
|
|
||||||
machineName:
|
|
||||||
let
|
|
||||||
warn = lib.warn ''
|
|
||||||
The use of ./machines/<machine>/settings.json is deprecated.
|
|
||||||
If your settings.json is empty, you can safely remove it.
|
|
||||||
!!! Consider using the inventory system. !!!
|
|
||||||
|
|
||||||
File: ${directory + /machines/${machineName}/settings.json}
|
|
||||||
|
|
||||||
If there are still features missing in the inventory system, please open an issue on the clan-core repository.
|
|
||||||
'';
|
|
||||||
in
|
|
||||||
# CLAN_MACHINE_SETTINGS_FILE allows to override the settings file temporarily
|
|
||||||
# This is useful for doing a dry-run before writing changes into the settings.json
|
|
||||||
# Using CLAN_MACHINE_SETTINGS_FILE requires passing --impure to nix eval
|
|
||||||
if builtins.getEnv "CLAN_MACHINE_SETTINGS_FILE" != "" then
|
|
||||||
warn (builtins.fromJSON (builtins.readFile (builtins.getEnv "CLAN_MACHINE_SETTINGS_FILE")))
|
|
||||||
else
|
|
||||||
lib.optionalAttrs (builtins.pathExists "${directory}/machines/${machineName}/settings.json") (
|
|
||||||
warn (builtins.fromJSON (builtins.readFile (directory + /machines/${machineName}/settings.json)))
|
|
||||||
);
|
|
||||||
|
|
||||||
machineImports =
|
|
||||||
machineSettings: map (module: clan-core.clanModules.${module}) (machineSettings.clanImports or [ ]);
|
|
||||||
|
|
||||||
# TODO: remove default system once we have a hardware-config mechanism
|
# TODO: remove default system once we have a hardware-config mechanism
|
||||||
nixosConfiguration =
|
nixosConfiguration =
|
||||||
{
|
{
|
||||||
system ? "x86_64-linux",
|
system ? null,
|
||||||
name,
|
name,
|
||||||
pkgs ? null,
|
pkgs ? null,
|
||||||
extraConfig ? { },
|
extraConfig ? { },
|
||||||
@@ -63,40 +37,16 @@ let
|
|||||||
nixpkgs.lib.nixosSystem {
|
nixpkgs.lib.nixosSystem {
|
||||||
modules =
|
modules =
|
||||||
let
|
let
|
||||||
settings = machineSettings name;
|
|
||||||
facterJson = "${directory}/machines/${name}/facter.json";
|
|
||||||
hwConfig = "${directory}/machines/${name}/hardware-configuration.nix";
|
hwConfig = "${directory}/machines/${name}/hardware-configuration.nix";
|
||||||
|
|
||||||
facterModules = lib.optionals (builtins.pathExists facterJson) [
|
|
||||||
clan-core.inputs.nixos-facter-modules.nixosModules.facter
|
|
||||||
{ config.facter.reportPath = facterJson; }
|
|
||||||
];
|
|
||||||
in
|
in
|
||||||
(machineImports settings)
|
[
|
||||||
++ facterModules
|
|
||||||
++ [
|
|
||||||
{
|
{
|
||||||
# Autoinclude configuration.nix and hardware-configuration.nix
|
# Autoinclude configuration.nix and hardware-configuration.nix
|
||||||
imports = builtins.filter builtins.pathExists [
|
imports = builtins.filter builtins.pathExists [
|
||||||
"${directory}/machines/${name}/configuration.nix"
|
"${directory}/machines/${name}/configuration.nix"
|
||||||
hwConfig
|
hwConfig
|
||||||
];
|
];
|
||||||
config.warnings =
|
|
||||||
lib.optionals
|
|
||||||
(builtins.all builtins.pathExists [
|
|
||||||
hwConfig
|
|
||||||
facterJson
|
|
||||||
])
|
|
||||||
[
|
|
||||||
''
|
|
||||||
Duplicate hardware facts: '${hwConfig}' and '${facterJson}' exist.
|
|
||||||
Using both is not recommended.
|
|
||||||
|
|
||||||
It is recommended to use the hardware facts from '${facterJson}', please remove '${hwConfig}'.
|
|
||||||
''
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
settings
|
|
||||||
clan-core.nixosModules.clanCore
|
clan-core.nixosModules.clanCore
|
||||||
extraConfig
|
extraConfig
|
||||||
(machines.${name} or { })
|
(machines.${name} or { })
|
||||||
@@ -115,7 +65,7 @@ let
|
|||||||
# Machine specific settings
|
# Machine specific settings
|
||||||
clan.core.machineName = name;
|
clan.core.machineName = name;
|
||||||
networking.hostName = lib.mkDefault name;
|
networking.hostName = lib.mkDefault name;
|
||||||
nixpkgs.hostPlatform = lib.mkDefault system;
|
nixpkgs.hostPlatform = lib.mkIf (system != null) (lib.mkDefault system);
|
||||||
|
|
||||||
# speeds up nix commands by using the nixpkgs from the host system (especially useful in VMs)
|
# speeds up nix commands by using the nixpkgs from the host system (especially useful in VMs)
|
||||||
nix.registry.nixpkgs.to = {
|
nix.registry.nixpkgs.to = {
|
||||||
@@ -132,38 +82,7 @@ let
|
|||||||
} // specialArgs;
|
} // specialArgs;
|
||||||
};
|
};
|
||||||
|
|
||||||
# TODO: Will be deprecated
|
allMachines = inventory.machines or { } // machines;
|
||||||
# We must migrate the tests, that create a settings.json to add a machine.
|
|
||||||
##################################################
|
|
||||||
testMachines =
|
|
||||||
lib.mapAttrs
|
|
||||||
(name: _: {
|
|
||||||
inherit name;
|
|
||||||
system = (machineSettings name).nixpkgs.hostSystem or null;
|
|
||||||
})
|
|
||||||
(
|
|
||||||
lib.filterAttrs (
|
|
||||||
machineName: _:
|
|
||||||
if builtins.pathExists "${directory}/machines/${machineName}/settings.json" then
|
|
||||||
lib.warn ''
|
|
||||||
The use of ./machines/<machine>/settings.json is deprecated.
|
|
||||||
If your settings.json is empty, you can safely remove it.
|
|
||||||
!!! Consider using the inventory system. !!!
|
|
||||||
|
|
||||||
File: ${directory + /machines/${machineName}/settings.json}
|
|
||||||
|
|
||||||
If there are still features missing in the inventory system, please open an issue on the clan-core repository.
|
|
||||||
'' true
|
|
||||||
else
|
|
||||||
false
|
|
||||||
) machinesDirs
|
|
||||||
);
|
|
||||||
machinesDirs = lib.optionalAttrs (builtins.pathExists "${directory}/machines") (
|
|
||||||
builtins.readDir (directory + /machines)
|
|
||||||
);
|
|
||||||
##################################################
|
|
||||||
|
|
||||||
allMachines = inventory.machines or { } // machines // testMachines;
|
|
||||||
|
|
||||||
supportedSystems = [
|
supportedSystems = [
|
||||||
"x86_64-linux"
|
"x86_64-linux"
|
||||||
|
|||||||
@@ -146,6 +146,7 @@ in
|
|||||||
{ foo, ... }:
|
{ foo, ... }:
|
||||||
{
|
{
|
||||||
networking.hostName = foo;
|
networking.hostName = foo;
|
||||||
|
nixpkgs.hostPlatform = "x86_64-linux";
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
in
|
in
|
||||||
|
|||||||
@@ -154,9 +154,6 @@ let
|
|||||||
) [ ] inventory.services
|
) [ ] inventory.services
|
||||||
# Append each machine config
|
# Append each machine config
|
||||||
++ [
|
++ [
|
||||||
(lib.optionalAttrs (machineConfig.system or null != null) {
|
|
||||||
config.nixpkgs.hostPlatform = machineConfig.system;
|
|
||||||
})
|
|
||||||
(lib.optionalAttrs (machineConfig.deploy.targetHost or null != null) {
|
(lib.optionalAttrs (machineConfig.deploy.targetHost or null != null) {
|
||||||
config.clan.core.networking.targetHost = machineConfig.deploy.targetHost;
|
config.clan.core.networking.targetHost = machineConfig.deploy.targetHost;
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -142,10 +142,6 @@ in
|
|||||||
apply = lib.unique;
|
apply = lib.unique;
|
||||||
type = types.listOf types.str;
|
type = types.listOf types.str;
|
||||||
};
|
};
|
||||||
system = lib.mkOption {
|
|
||||||
default = null;
|
|
||||||
type = types.nullOr types.str;
|
|
||||||
};
|
|
||||||
deploy.targetHost = lib.mkOption {
|
deploy.targetHost = lib.mkOption {
|
||||||
description = "Configuration for the deployment of the machine";
|
description = "Configuration for the deployment of the machine";
|
||||||
default = null;
|
default = null;
|
||||||
|
|||||||
@@ -92,9 +92,9 @@ in
|
|||||||
not_used_machine = builtins.length configs.not_used_machine;
|
not_used_machine = builtins.length configs.not_used_machine;
|
||||||
};
|
};
|
||||||
expected = {
|
expected = {
|
||||||
client_1_machine = 6;
|
client_1_machine = 5;
|
||||||
client_2_machine = 6;
|
client_2_machine = 5;
|
||||||
not_used_machine = 3;
|
not_used_machine = 2;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
4
machines/test-inventory-machine/facter.json
Normal file
4
machines/test-inventory-machine/facter.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"system": "x86_64-linux"
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
./meta/interface.nix
|
./meta/interface.nix
|
||||||
./metadata.nix
|
./metadata.nix
|
||||||
./networking.nix
|
./networking.nix
|
||||||
|
./nixos-facter.nix
|
||||||
./nix-settings.nix
|
./nix-settings.nix
|
||||||
./options.nix
|
./options.nix
|
||||||
./outputs.nix
|
./outputs.nix
|
||||||
|
|||||||
24
nixosModules/clanCore/nixos-facter.nix
Normal file
24
nixosModules/clanCore/nixos-facter.nix
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{ lib, config, ... }:
|
||||||
|
let
|
||||||
|
directory = config.clan.core.clanDir;
|
||||||
|
inherit (config.clan.core) machineName;
|
||||||
|
facterJson = "${directory}/machines/${machineName}/facter.json";
|
||||||
|
hwConfig = "${directory}/machines/${machineName}/hardware-configuration.nix";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
facter.reportPath = lib.mkIf (builtins.pathExists facterJson) facterJson;
|
||||||
|
warnings =
|
||||||
|
lib.optionals
|
||||||
|
(builtins.all builtins.pathExists [
|
||||||
|
hwConfig
|
||||||
|
facterJson
|
||||||
|
])
|
||||||
|
[
|
||||||
|
''
|
||||||
|
Duplicate hardware facts: '${hwConfig}' and '${facterJson}' exist.
|
||||||
|
Using both is not recommended.
|
||||||
|
|
||||||
|
It is recommended to use the hardware facts from '${facterJson}', please remove '${hwConfig}'.
|
||||||
|
''
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
];
|
];
|
||||||
clanCore.imports = [
|
clanCore.imports = [
|
||||||
inputs.sops-nix.nixosModules.sops
|
inputs.sops-nix.nixosModules.sops
|
||||||
|
inputs.nixos-facter-modules.nixosModules.facter
|
||||||
inputs.disko.nixosModules.default
|
inputs.disko.nixosModules.default
|
||||||
./clanCore
|
./clanCore
|
||||||
(
|
(
|
||||||
|
|||||||
@@ -1,306 +0,0 @@
|
|||||||
# !/usr/bin/env python3
|
|
||||||
import argparse
|
|
||||||
import json
|
|
||||||
import logging
|
|
||||||
import re
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any, get_origin
|
|
||||||
|
|
||||||
from clan_cli.cmd import run
|
|
||||||
from clan_cli.dirs import machine_settings_file
|
|
||||||
from clan_cli.errors import ClanError
|
|
||||||
from clan_cli.git import commit_file
|
|
||||||
from clan_cli.nix import nix_eval
|
|
||||||
|
|
||||||
script_dir = Path(__file__).parent
|
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
# nixos option type description to python type
|
|
||||||
def map_type(nix_type: str) -> Any:
|
|
||||||
if nix_type == "boolean":
|
|
||||||
return bool
|
|
||||||
if nix_type in [
|
|
||||||
"integer",
|
|
||||||
"signed integer",
|
|
||||||
"16 bit unsigned integer; between 0 and 65535 (both inclusive)",
|
|
||||||
]:
|
|
||||||
return int
|
|
||||||
if nix_type.startswith("string"):
|
|
||||||
return str
|
|
||||||
if nix_type.startswith("null or "):
|
|
||||||
subtype = nix_type.removeprefix("null or ")
|
|
||||||
return map_type(subtype) | None
|
|
||||||
if nix_type.startswith("attribute set of"):
|
|
||||||
subtype = nix_type.removeprefix("attribute set of ")
|
|
||||||
return dict[str, map_type(subtype)] # type: ignore
|
|
||||||
if nix_type.startswith("list of"):
|
|
||||||
subtype = nix_type.removeprefix("list of ")
|
|
||||||
return list[map_type(subtype)] # type: ignore
|
|
||||||
msg = f"Unknown type {nix_type}"
|
|
||||||
raise ClanError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
# merge two dicts recursively
|
|
||||||
def merge(a: dict, b: dict, path: list[str] | None = None) -> dict:
|
|
||||||
a = a.copy()
|
|
||||||
if path is None:
|
|
||||||
path = []
|
|
||||||
for key in b:
|
|
||||||
if key in a:
|
|
||||||
if isinstance(a[key], dict) and isinstance(b[key], dict):
|
|
||||||
merge(a[key], b[key], [*path, str(key)])
|
|
||||||
elif isinstance(a[key], list) and isinstance(b[key], list):
|
|
||||||
a[key].extend(b[key])
|
|
||||||
elif a[key] != b[key]:
|
|
||||||
a[key] = b[key]
|
|
||||||
else:
|
|
||||||
a[key] = b[key]
|
|
||||||
return a
|
|
||||||
|
|
||||||
|
|
||||||
# A container inheriting from list, but overriding __contains__ to return True
|
|
||||||
# for all values.
|
|
||||||
# This is used to allow any value for the "choices" field of argparse
|
|
||||||
class AllContainer(list):
|
|
||||||
def __contains__(self, item: Any) -> bool:
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
# value is always a list, as the arg parser cannot know the type upfront
|
|
||||||
# and therefore always allows multiple arguments.
|
|
||||||
def cast(value: Any, input_type: Any, opt_description: str) -> Any:
|
|
||||||
try:
|
|
||||||
# handle bools
|
|
||||||
if isinstance(input_type, bool):
|
|
||||||
if value[0] in ["true", "True", "yes", "y", "1"]:
|
|
||||||
return True
|
|
||||||
if value[0] in ["false", "False", "no", "n", "0"]:
|
|
||||||
return False
|
|
||||||
msg = f"Invalid value {value} for boolean"
|
|
||||||
raise ClanError(msg)
|
|
||||||
# handle lists
|
|
||||||
if get_origin(input_type) is list:
|
|
||||||
subtype = input_type.__args__[0]
|
|
||||||
return [cast([x], subtype, opt_description) for x in value]
|
|
||||||
# handle dicts
|
|
||||||
if get_origin(input_type) is dict:
|
|
||||||
if not isinstance(value, dict):
|
|
||||||
msg = f"Cannot set {opt_description} directly. Specify a suboption like {opt_description}.<name>"
|
|
||||||
raise ClanError(msg)
|
|
||||||
subtype = input_type.__args__[1]
|
|
||||||
return {k: cast(v, subtype, opt_description) for k, v in value.items()}
|
|
||||||
if str(input_type) == "str | None":
|
|
||||||
if value[0] in ["null", "None"]:
|
|
||||||
return None
|
|
||||||
return value[0]
|
|
||||||
if len(value) > 1:
|
|
||||||
msg = f"Too many values for {opt_description}"
|
|
||||||
raise ClanError(msg)
|
|
||||||
return input_type(value[0])
|
|
||||||
except ValueError as e:
|
|
||||||
msg = f"Invalid type for option {opt_description} (expected {input_type.__name__})"
|
|
||||||
raise ClanError(msg) from e
|
|
||||||
|
|
||||||
|
|
||||||
def options_for_machine(
|
|
||||||
flake_dir: Path, machine_name: str, show_trace: bool = False
|
|
||||||
) -> dict:
|
|
||||||
clan_dir = flake_dir
|
|
||||||
flags = []
|
|
||||||
if show_trace:
|
|
||||||
flags.append("--show-trace")
|
|
||||||
flags.append(
|
|
||||||
f"{clan_dir}#nixosConfigurations.{machine_name}.config.clan.core.optionsNix"
|
|
||||||
)
|
|
||||||
cmd = nix_eval(flags=flags)
|
|
||||||
proc = run(
|
|
||||||
cmd,
|
|
||||||
error_msg=f"Failed to read options for machine {machine_name}",
|
|
||||||
)
|
|
||||||
|
|
||||||
return json.loads(proc.stdout)
|
|
||||||
|
|
||||||
|
|
||||||
def read_machine_option_value(
|
|
||||||
flake_dir: Path, machine_name: str, option: str, show_trace: bool = False
|
|
||||||
) -> str:
|
|
||||||
clan_dir = flake_dir
|
|
||||||
# use nix eval to read from .#nixosConfigurations.default.config.{option}
|
|
||||||
# this will give us the evaluated config with the options attribute
|
|
||||||
cmd = nix_eval(
|
|
||||||
flags=[
|
|
||||||
"--show-trace",
|
|
||||||
f"{clan_dir}#nixosConfigurations.{machine_name}.config.{option}",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
proc = run(cmd, error_msg=f"Failed to read option {option}")
|
|
||||||
|
|
||||||
value = json.loads(proc.stdout)
|
|
||||||
# print the value so that the output can be copied and fed as an input.
|
|
||||||
# for example a list should be displayed as space separated values surrounded by quotes.
|
|
||||||
if isinstance(value, list):
|
|
||||||
out = " ".join([json.dumps(x) for x in value])
|
|
||||||
elif isinstance(value, dict):
|
|
||||||
out = json.dumps(value, indent=2)
|
|
||||||
else:
|
|
||||||
out = json.dumps(value, indent=2)
|
|
||||||
return out
|
|
||||||
|
|
||||||
|
|
||||||
def get_option(args: argparse.Namespace) -> None:
|
|
||||||
print(
|
|
||||||
read_machine_option_value(
|
|
||||||
args.flake, args.machine, args.option, args.show_trace
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Currently writing is disabled
|
|
||||||
def get_or_set_option(args: argparse.Namespace) -> None:
|
|
||||||
if args.value == []:
|
|
||||||
print(
|
|
||||||
read_machine_option_value(
|
|
||||||
args.flake, args.machine, args.option, args.show_trace
|
|
||||||
)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# load options
|
|
||||||
if args.options_file is None:
|
|
||||||
options = options_for_machine(
|
|
||||||
args.flake, machine_name=args.machine, show_trace=args.show_trace
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
with args.options_file.open() as f:
|
|
||||||
options = json.load(f)
|
|
||||||
# compute settings json file location
|
|
||||||
if args.settings_file is None:
|
|
||||||
settings_file = machine_settings_file(args.flake.path, args.machine)
|
|
||||||
else:
|
|
||||||
settings_file = args.settings_file
|
|
||||||
# set the option with the given value
|
|
||||||
set_option(
|
|
||||||
flake_dir=args.flake.path,
|
|
||||||
option=args.option,
|
|
||||||
value=args.value,
|
|
||||||
options=options,
|
|
||||||
settings_file=settings_file,
|
|
||||||
option_description=args.option,
|
|
||||||
show_trace=args.show_trace,
|
|
||||||
)
|
|
||||||
if not args.quiet:
|
|
||||||
new_value = read_machine_option_value(args.flake, args.machine, args.option)
|
|
||||||
print(f"New Value for {args.option}:")
|
|
||||||
print(new_value)
|
|
||||||
|
|
||||||
|
|
||||||
def find_option(
|
|
||||||
option: str, value: Any, options: dict, option_description: str | None = None
|
|
||||||
) -> tuple[str, Any]:
|
|
||||||
"""
|
|
||||||
The option path specified by the user doesn't have to match exactly to an
|
|
||||||
entry in the options.json file. Examples
|
|
||||||
|
|
||||||
Example 1:
|
|
||||||
$ clan config services.openssh.settings.SomeSetting 42
|
|
||||||
This is a freeform option that does not appear in the options.json
|
|
||||||
The actual option is `services.openssh.settings`
|
|
||||||
And the value must be wrapped: {"SomeSettings": 42}
|
|
||||||
|
|
||||||
Example 2:
|
|
||||||
$ clan config users.users.my-user.name my-name
|
|
||||||
The actual option is `users.users.<name>.name`
|
|
||||||
"""
|
|
||||||
|
|
||||||
# option description is used for error messages
|
|
||||||
if option_description is None:
|
|
||||||
option_description = option
|
|
||||||
|
|
||||||
option_path = option.split(".")
|
|
||||||
|
|
||||||
# fuzzy search the option paths, so when
|
|
||||||
# specified option path: "foo.bar.baz.bum"
|
|
||||||
# available option path: "foo.<name>.baz.<name>"
|
|
||||||
# we can still find the option
|
|
||||||
first = option_path[0]
|
|
||||||
regex = rf"({first}|<name>)"
|
|
||||||
for elem in option_path[1:]:
|
|
||||||
regex += rf"\.({elem}|<name>)"
|
|
||||||
for opt in options:
|
|
||||||
if re.match(regex, opt):
|
|
||||||
return opt, value
|
|
||||||
|
|
||||||
# if the regex search did not find the option, start stripping the last
|
|
||||||
# element of the option path and find matching parent option
|
|
||||||
# (see examples above for why this is needed)
|
|
||||||
if len(option_path) == 1:
|
|
||||||
msg = f"Option {option_description} not found"
|
|
||||||
raise ClanError(msg)
|
|
||||||
option_path_parent = option_path[:-1]
|
|
||||||
attr_prefix = option_path[-1]
|
|
||||||
return find_option(
|
|
||||||
option=".".join(option_path_parent),
|
|
||||||
value={attr_prefix: value},
|
|
||||||
options=options,
|
|
||||||
option_description=option_description,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def set_option(
|
|
||||||
flake_dir: Path,
|
|
||||||
option: str,
|
|
||||||
value: Any,
|
|
||||||
options: dict,
|
|
||||||
settings_file: Path,
|
|
||||||
option_description: str = "",
|
|
||||||
show_trace: bool = False,
|
|
||||||
) -> None:
|
|
||||||
option_path_orig = option.split(".")
|
|
||||||
|
|
||||||
# returns for example:
|
|
||||||
# option: "users.users.<name>.name"
|
|
||||||
# value: "my-name"
|
|
||||||
option, value = find_option(
|
|
||||||
option=option,
|
|
||||||
value=value,
|
|
||||||
options=options,
|
|
||||||
option_description=option_description,
|
|
||||||
)
|
|
||||||
option_path = option.split(".")
|
|
||||||
|
|
||||||
option_path_store = option_path_orig[: len(option_path)]
|
|
||||||
|
|
||||||
target_type = map_type(options[option]["type"])
|
|
||||||
casted = cast(value, target_type, option)
|
|
||||||
|
|
||||||
# construct a nested dict from the option path and set the value
|
|
||||||
result: dict[str, Any] = {}
|
|
||||||
current = result
|
|
||||||
for part in option_path_store[:-1]:
|
|
||||||
current[part] = {}
|
|
||||||
current = current[part]
|
|
||||||
current[option_path_store[-1]] = value
|
|
||||||
|
|
||||||
current[option_path_store[-1]] = casted
|
|
||||||
|
|
||||||
# check if there is an existing config file
|
|
||||||
if settings_file.exists():
|
|
||||||
with settings_file.open() as f:
|
|
||||||
current_config = json.load(f)
|
|
||||||
else:
|
|
||||||
current_config = {}
|
|
||||||
|
|
||||||
# merge and save the new config file
|
|
||||||
new_config = merge(current_config, result)
|
|
||||||
settings_file.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with settings_file.open("w") as f:
|
|
||||||
json.dump(new_config, f, indent=2)
|
|
||||||
print(file=f) # add newline at the end of the file to make git happy
|
|
||||||
|
|
||||||
if settings_file.resolve().is_relative_to(flake_dir):
|
|
||||||
commit_file(
|
|
||||||
settings_file,
|
|
||||||
repo_dir=flake_dir,
|
|
||||||
commit_message=f"Set option {option_description}",
|
|
||||||
)
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
import json
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
from pathlib import Path
|
|
||||||
from tempfile import NamedTemporaryFile
|
|
||||||
|
|
||||||
from clan_cli.cmd import Log, run
|
|
||||||
from clan_cli.dirs import machine_settings_file, nixpkgs_source, specific_machine_dir
|
|
||||||
from clan_cli.errors import ClanError, ClanHttpError
|
|
||||||
from clan_cli.git import commit_file
|
|
||||||
from clan_cli.nix import nix_eval
|
|
||||||
|
|
||||||
|
|
||||||
def verify_machine_config(
|
|
||||||
flake_dir: Path,
|
|
||||||
machine_name: str,
|
|
||||||
config: dict | None = None,
|
|
||||||
) -> str | None:
|
|
||||||
"""
|
|
||||||
Verify that the machine evaluates successfully
|
|
||||||
Returns None, in case of success, or a String containing the error_message
|
|
||||||
"""
|
|
||||||
if config is None:
|
|
||||||
config = config_for_machine(flake_dir, machine_name)
|
|
||||||
flake = flake_dir
|
|
||||||
with NamedTemporaryFile(mode="w", dir=flake) as clan_machine_settings_file:
|
|
||||||
json.dump(config, clan_machine_settings_file, indent=2)
|
|
||||||
clan_machine_settings_file.seek(0)
|
|
||||||
env = os.environ.copy()
|
|
||||||
env["CLAN_MACHINE_SETTINGS_FILE"] = clan_machine_settings_file.name
|
|
||||||
cmd = nix_eval(
|
|
||||||
flags=[
|
|
||||||
"--show-trace",
|
|
||||||
"--impure", # needed to access CLAN_MACHINE_SETTINGS_FILE
|
|
||||||
"--expr",
|
|
||||||
f"""
|
|
||||||
let
|
|
||||||
# hardcoding system for now, not sure where to get it from
|
|
||||||
system = "x86_64-linux";
|
|
||||||
flake = builtins.getFlake (toString {flake});
|
|
||||||
clan-core = flake.inputs.clan-core;
|
|
||||||
nixpkgsSrc = flake.inputs.nixpkgs or {nixpkgs_source()};
|
|
||||||
lib = import (nixpkgsSrc + /lib);
|
|
||||||
pkgs = import nixpkgsSrc {{ inherit system; }};
|
|
||||||
config = lib.importJSON (builtins.getEnv "CLAN_MACHINE_SETTINGS_FILE");
|
|
||||||
fakeMachine = pkgs.nixos {{
|
|
||||||
imports =
|
|
||||||
[
|
|
||||||
clan-core.nixosModules.clanCore
|
|
||||||
# potentially the config might affect submodule options,
|
|
||||||
# therefore we need to import it
|
|
||||||
config
|
|
||||||
{{clan.core.clanDir = {flake};}}
|
|
||||||
]
|
|
||||||
# add all clan modules specified via clanImports
|
|
||||||
++ (map (name: clan-core.clanModules.${{name}}) config.clanImports or []);
|
|
||||||
}};
|
|
||||||
in
|
|
||||||
fakeMachine.config.system.build.vm.outPath
|
|
||||||
""",
|
|
||||||
],
|
|
||||||
)
|
|
||||||
|
|
||||||
proc = run(
|
|
||||||
cmd,
|
|
||||||
cwd=flake,
|
|
||||||
env=env,
|
|
||||||
log=Log.BOTH,
|
|
||||||
)
|
|
||||||
if proc.returncode != 0:
|
|
||||||
return proc.stderr
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
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
|
|
||||||
if not specific_machine_dir(flake_dir, machine_name).exists():
|
|
||||||
raise ClanHttpError(
|
|
||||||
msg=f"Machine {machine_name} not found. Create the machine first`",
|
|
||||||
status_code=404,
|
|
||||||
)
|
|
||||||
settings_path = machine_settings_file(flake_dir, machine_name)
|
|
||||||
if not settings_path.exists():
|
|
||||||
return {}
|
|
||||||
with settings_path.open() as f:
|
|
||||||
return json.load(f)
|
|
||||||
|
|
||||||
|
|
||||||
def set_config_for_machine(flake_dir: Path, machine_name: str, config: dict) -> None:
|
|
||||||
hostname_regex = r"^(?!-)[A-Za-z0-9-]{1,63}(?<!-)$"
|
|
||||||
if not re.match(hostname_regex, machine_name):
|
|
||||||
msg = "Machine name must be a valid hostname"
|
|
||||||
raise ClanError(msg)
|
|
||||||
if "networking" in config and "hostName" in config["networking"]:
|
|
||||||
if machine_name != config["networking"]["hostName"]:
|
|
||||||
raise ClanHttpError(
|
|
||||||
msg="Machine name does not match the 'networking.hostName' setting in the config",
|
|
||||||
status_code=400,
|
|
||||||
)
|
|
||||||
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
|
|
||||||
settings_path = machine_settings_file(flake_dir, machine_name)
|
|
||||||
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
|
||||||
with settings_path.open("w") as f:
|
|
||||||
json.dump(config, f)
|
|
||||||
|
|
||||||
if flake_dir is not None:
|
|
||||||
commit_file(settings_path, flake_dir)
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
import json
|
|
||||||
from pathlib import Path
|
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
from clan_cli.cmd import run
|
|
||||||
from clan_cli.errors import ClanError
|
|
||||||
from clan_cli.nix import nix_eval
|
|
||||||
|
|
||||||
script_dir = Path(__file__).parent
|
|
||||||
|
|
||||||
|
|
||||||
type_map: dict[str, type] = {
|
|
||||||
"array": list,
|
|
||||||
"boolean": bool,
|
|
||||||
"integer": int,
|
|
||||||
"number": float,
|
|
||||||
"string": str,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def schema_from_module_file(
|
|
||||||
file: str | Path = f"{script_dir}/jsonschema/example-schema.json",
|
|
||||||
) -> dict[str, Any]:
|
|
||||||
absolute_path = Path(file).absolute()
|
|
||||||
# define a nix expression that loads the given module file using lib.evalModules
|
|
||||||
nix_expr = f"""
|
|
||||||
let
|
|
||||||
lib = import <nixpkgs/lib>;
|
|
||||||
slib = import {script_dir}/jsonschema {{inherit lib;}};
|
|
||||||
in
|
|
||||||
slib.parseModule {absolute_path}
|
|
||||||
"""
|
|
||||||
# run the nix expression and parse the output as json
|
|
||||||
cmd = nix_eval(["--expr", nix_expr])
|
|
||||||
proc = run(cmd)
|
|
||||||
return json.loads(proc.stdout)
|
|
||||||
|
|
||||||
|
|
||||||
def subtype_from_schema(schema: dict[str, Any]) -> type:
|
|
||||||
if schema["type"] == "object":
|
|
||||||
if "additionalProperties" in schema:
|
|
||||||
sub_type = subtype_from_schema(schema["additionalProperties"])
|
|
||||||
return dict[str, sub_type] # type: ignore
|
|
||||||
if "properties" in schema:
|
|
||||||
msg = "Nested dicts are not supported"
|
|
||||||
raise ClanError(msg)
|
|
||||||
msg = "Unknown object type"
|
|
||||||
raise ClanError(msg)
|
|
||||||
if schema["type"] == "array":
|
|
||||||
if "items" not in schema:
|
|
||||||
msg = "Untyped arrays are not supported"
|
|
||||||
raise ClanError(msg)
|
|
||||||
sub_type = subtype_from_schema(schema["items"])
|
|
||||||
return list[sub_type] # type: ignore
|
|
||||||
return type_map[schema["type"]]
|
|
||||||
|
|
||||||
|
|
||||||
def type_from_schema_path(
|
|
||||||
schema: dict[str, Any],
|
|
||||||
path: list[str],
|
|
||||||
full_path: list[str] | None = None,
|
|
||||||
) -> type:
|
|
||||||
if full_path is None:
|
|
||||||
full_path = path
|
|
||||||
if len(path) == 0:
|
|
||||||
return subtype_from_schema(schema)
|
|
||||||
if schema["type"] == "object":
|
|
||||||
if "properties" in schema:
|
|
||||||
subtype = type_from_schema_path(schema["properties"][path[0]], path[1:])
|
|
||||||
return subtype
|
|
||||||
if "additionalProperties" in schema:
|
|
||||||
subtype = type_from_schema_path(schema["additionalProperties"], path[1:])
|
|
||||||
return subtype
|
|
||||||
msg = f"Unknown type for path {path}"
|
|
||||||
raise ClanError(msg)
|
|
||||||
msg = f"Unknown type for path {path}"
|
|
||||||
raise ClanError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
def options_types_from_schema(schema: dict[str, Any]) -> dict[str, type]:
|
|
||||||
result: dict[str, type] = {}
|
|
||||||
for name, value in schema.get("properties", {}).items():
|
|
||||||
assert isinstance(value, dict)
|
|
||||||
type_ = value["type"]
|
|
||||||
if type_ == "object":
|
|
||||||
# handle additionalProperties
|
|
||||||
if "additionalProperties" in value:
|
|
||||||
sub_type = value["additionalProperties"].get("type")
|
|
||||||
if sub_type not in type_map:
|
|
||||||
msg = f"Unsupported object type {sub_type} (field {name})"
|
|
||||||
raise ClanError(msg)
|
|
||||||
result[f"{name}.<name>"] = type_map[sub_type]
|
|
||||||
continue
|
|
||||||
# handle properties
|
|
||||||
sub_result = options_types_from_schema(value)
|
|
||||||
for sub_name, sub_type in sub_result.items():
|
|
||||||
result[f"{name}.{sub_name}"] = sub_type
|
|
||||||
continue
|
|
||||||
if type_ == "array":
|
|
||||||
if "items" not in value:
|
|
||||||
msg = f"Untyped arrays are not supported (field: {name})"
|
|
||||||
raise ClanError(msg)
|
|
||||||
sub_type = value["items"].get("type")
|
|
||||||
if sub_type not in type_map:
|
|
||||||
msg = f"Unsupported list type {sub_type} (field {name})"
|
|
||||||
raise ClanError(msg)
|
|
||||||
sub_type_: type = type_map[sub_type]
|
|
||||||
result[name] = list[sub_type_] # type: ignore
|
|
||||||
continue
|
|
||||||
result[name] = type_map[type_]
|
|
||||||
return result
|
|
||||||
@@ -115,10 +115,6 @@ def specific_machine_dir(flake_dir: Path, machine: str) -> Path:
|
|||||||
return machines_dir(flake_dir) / machine
|
return machines_dir(flake_dir) / machine
|
||||||
|
|
||||||
|
|
||||||
def machine_settings_file(flake_dir: Path, machine: str) -> Path:
|
|
||||||
return specific_machine_dir(flake_dir, machine) / "settings.json"
|
|
||||||
|
|
||||||
|
|
||||||
def module_root() -> Path:
|
def module_root() -> Path:
|
||||||
return Path(__file__).parent
|
return Path(__file__).parent
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ class Machine:
|
|||||||
name: str
|
name: str
|
||||||
description: None | str = field(default = None)
|
description: None | str = field(default = None)
|
||||||
icon: None | str = field(default = None)
|
icon: None | str = field(default = None)
|
||||||
system: None | str = field(default = None)
|
|
||||||
tags: list[str] = field(default_factory = list)
|
tags: list[str] = field(default_factory = list)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -33,13 +33,11 @@
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
flakeLockFile = builtins.toFile "clan-core-flake.lock" (builtins.toJSON flakeLockVendoredDeps);
|
flakeLockFile = builtins.toFile "clan-core-flake.lock" (builtins.toJSON flakeLockVendoredDeps);
|
||||||
clanCoreWithVendoredDeps =
|
clanCoreWithVendoredDeps = pkgs.runCommand "clan-core-with-vendored-deps" { } ''
|
||||||
lib.trace flakeLockFile pkgs.runCommand "clan-core-with-vendored-deps" { }
|
cp -r ${self} $out
|
||||||
''
|
chmod +w -R $out
|
||||||
cp -r ${self} $out
|
cp ${flakeLockFile} $out/flake.lock
|
||||||
chmod +w -R $out
|
'';
|
||||||
cp ${flakeLockFile} $out/flake.lock
|
|
||||||
'';
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
devShells.clan-cli = pkgs.callPackage ./shell.nix {
|
devShells.clan-cli = pkgs.callPackage ./shell.nix {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ pytest_plugins = [
|
|||||||
"host_group",
|
"host_group",
|
||||||
"fixtures_flakes",
|
"fixtures_flakes",
|
||||||
"stdout",
|
"stdout",
|
||||||
|
"nix_config",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
This is a revocation certificate for the OpenPGP key:
|
||||||
|
|
||||||
|
pub rsa1024 2024-09-29 [SCEAR]
|
||||||
|
9A9B2741C8062D3D3DF1302D8B049E262A5CA255
|
||||||
|
uid Root Superuser <test@local>
|
||||||
|
|
||||||
|
A revocation certificate is a kind of "kill switch" to publicly
|
||||||
|
declare that a key shall not anymore be used. It is not possible
|
||||||
|
to retract such a revocation certificate once it has been published.
|
||||||
|
|
||||||
|
Use it to revoke this key in case of a compromise or loss of
|
||||||
|
the secret key. However, if the secret key is still accessible,
|
||||||
|
it is better to generate a new revocation certificate and give
|
||||||
|
a reason for the revocation. For details see the description of
|
||||||
|
of the gpg command "--generate-revocation" in the GnuPG manual.
|
||||||
|
|
||||||
|
To avoid an accidental use of this file, a colon has been inserted
|
||||||
|
before the 5 dashes below. Remove this colon with a text editor
|
||||||
|
before importing and publishing this revocation certificate.
|
||||||
|
|
||||||
|
:-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
|
Comment: This is a revocation certificate
|
||||||
|
|
||||||
|
iLYEIAEIACAWIQSamydByAYtPT3xMC2LBJ4mKlyiVQUCZvl/cAIdAAAKCRCLBJ4m
|
||||||
|
KlyiVUWOA/9rDw6tSSw7Gh3vlaLZXSQvkftO3x9cJwePn6JPmM2nWLDcaOj+/Cd0
|
||||||
|
guyakYt7Fsxa6fqcv5sYV50bPRqAnfOWbR7jRl4DF6pSYNCHPlkWuLghdYsBOBo2
|
||||||
|
1MG/J+155aclsB8JQez1eGMe8KcpcJBcrYuZTAMekMGPrfyr9SwDUg==
|
||||||
|
=V2Jo
|
||||||
|
-----END PGP PUBLIC KEY BLOCK-----
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
Created: 20240929T162520
|
||||||
|
Key: (private-key (rsa (n #00B1BF3E8A8CEA6A68439F67CDCAF5616B50D99A9F88
|
||||||
|
6D9E879D3FE990854E9ADFC35D7D26DBC5BC1800B3FF7B814F4623C1DFC34CAB4D326C
|
||||||
|
3E269C6059D567B5144659B3C895B52B428BA7B74CC2FA130D06C689C45B8FF8DA1D7C
|
||||||
|
7A578C99C0F221189D6BE045AE2EC8D2389423994BA0D650A2EDD2B7664642BFBF9691
|
||||||
|
495F#)(e #010001#)(d #57605C65AE94F39EF293136BB23842DE06DE19A90FDF573D
|
||||||
|
723B3F5D5872C626767AE831687B0116498E326AABABE51E61C9564FC3ABCCBC322737
|
||||||
|
DB137E191EB3B012B9C142290050EBD8ADD40BC68CCB577521E3A76DFD668BC6E584C7
|
||||||
|
0DD3B6CE545CC392B1D893EFB959BE3BD0EB7DF73A1F7AFBD9693353BA4FD3C05AED#)
|
||||||
|
(p #00C169E9E1DF8F39E7B2140FD52723FC5D10CCFC62D8A0876D39641AB00441345C
|
||||||
|
FC239EF8551B5F39CE850EF2DD79B98D70D57AD933648C86B7DD536B1B3AD6CB#)(q
|
||||||
|
#00EB43872BDDA397AC02A32E7CB0061ACB26A30497031D24FA793DE9EE4EFBACB1A4
|
||||||
|
6BF1444DE47CB63A6E254F2E4928BB0BB1F5C51C5247EEA8FF2D84BE25F13D#)(u
|
||||||
|
#00CEBE9717B5F7B59393065F884ACCA692F64545F492E50DF9070ACA9FBDA8A1EC03
|
||||||
|
906FDB9C112A97FADBB273E69548C6B17E6BE3BB664B9D02FB2100EF19AF7D#)))
|
||||||
BIN
pkgs/clan-cli/tests/data/gnupg-home/pubring.kbx
Normal file
BIN
pkgs/clan-cli/tests/data/gnupg-home/pubring.kbx
Normal file
Binary file not shown.
BIN
pkgs/clan-cli/tests/data/gnupg-home/random_seed
Normal file
BIN
pkgs/clan-cli/tests/data/gnupg-home/random_seed
Normal file
Binary file not shown.
BIN
pkgs/clan-cli/tests/data/gnupg-home/trustdb.gpg
Normal file
BIN
pkgs/clan-cli/tests/data/gnupg-home/trustdb.gpg
Normal file
Binary file not shown.
6
pkgs/clan-cli/tests/data/gnupg.conf
Normal file
6
pkgs/clan-cli/tests/data/gnupg.conf
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
Key-Type: 1
|
||||||
|
Key-Length: 1024
|
||||||
|
Name-Real: Root Superuser
|
||||||
|
Name-Email: test@local
|
||||||
|
Expire-Date: 0
|
||||||
|
%no-protection
|
||||||
1
pkgs/clan-cli/tests/data/password-store/.gpg-id
Normal file
1
pkgs/clan-cli/tests/data/password-store/.gpg-id
Normal file
@@ -0,0 +1 @@
|
|||||||
|
test@local
|
||||||
@@ -55,14 +55,33 @@ def set_machine_settings(
|
|||||||
machine_name: str,
|
machine_name: str,
|
||||||
machine_settings: dict,
|
machine_settings: dict,
|
||||||
) -> None:
|
) -> None:
|
||||||
settings_path = flake / "machines" / machine_name / "settings.json"
|
config_path = flake / "machines" / machine_name / "configuration.json"
|
||||||
settings_path.parent.mkdir(parents=True, exist_ok=True)
|
config_path.write_text(json.dumps(machine_settings, indent=2))
|
||||||
settings_path.write_text(json.dumps(machine_settings, indent=2))
|
|
||||||
|
|
||||||
|
def init_git(monkeypatch: pytest.MonkeyPatch, flake: Path) -> None:
|
||||||
|
monkeypatch.setenv("GIT_AUTHOR_NAME", "clan-tool")
|
||||||
|
monkeypatch.setenv("GIT_AUTHOR_EMAIL", "clan@example.com")
|
||||||
|
monkeypatch.setenv("GIT_COMMITTER_NAME", "clan-tool")
|
||||||
|
monkeypatch.setenv("GIT_COMMITTER_EMAIL", "clan@example.com")
|
||||||
|
|
||||||
|
# TODO: Find out why test_vms_api.py fails in nix build
|
||||||
|
# but works in pytest when this bottom line is commented out
|
||||||
|
sp.run(
|
||||||
|
["git", "config", "--global", "init.defaultBranch", "main"],
|
||||||
|
cwd=flake,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
sp.run(["git", "init"], cwd=flake, check=True)
|
||||||
|
sp.run(["git", "add", "."], cwd=flake, check=True)
|
||||||
|
sp.run(["git", "commit", "-a", "-m", "Initial commit"], cwd=flake, check=True)
|
||||||
|
|
||||||
|
|
||||||
def generate_flake(
|
def generate_flake(
|
||||||
temporary_home: Path,
|
temporary_home: Path,
|
||||||
flake_template: Path,
|
flake_template: Path,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
substitutions: dict[str, str] | None = None,
|
substitutions: dict[str, str] | None = None,
|
||||||
# define the machines directly including their config
|
# define the machines directly including their config
|
||||||
machine_configs: dict[str, dict] | None = None,
|
machine_configs: dict[str, dict] | None = None,
|
||||||
@@ -90,13 +109,12 @@ def generate_flake(
|
|||||||
inventory = {}
|
inventory = {}
|
||||||
if machine_configs is None:
|
if machine_configs is None:
|
||||||
machine_configs = {}
|
machine_configs = {}
|
||||||
if substitutions is None:
|
substitutions = {
|
||||||
substitutions = {
|
"__CHANGE_ME__": "_test_vm_persistence",
|
||||||
"__CHANGE_ME__": "_test_vm_persistence",
|
"git+https://git.clan.lol/clan/clan-core": "path://" + str(CLAN_CORE),
|
||||||
"git+https://git.clan.lol/clan/clan-core": "path://" + str(CLAN_CORE),
|
"https://git.clan.lol/clan/clan-core/archive/main.tar.gz": "path://"
|
||||||
"https://git.clan.lol/clan/clan-core/archive/main.tar.gz": "path://"
|
+ str(CLAN_CORE),
|
||||||
+ str(CLAN_CORE),
|
}
|
||||||
}
|
|
||||||
flake = temporary_home / "flake"
|
flake = temporary_home / "flake"
|
||||||
shutil.copytree(flake_template, flake)
|
shutil.copytree(flake_template, flake)
|
||||||
sp.run(["chmod", "+w", "-R", str(flake)], check=True)
|
sp.run(["chmod", "+w", "-R", str(flake)], check=True)
|
||||||
@@ -121,6 +139,11 @@ def generate_flake(
|
|||||||
|
|
||||||
# generate machines from machineConfigs
|
# generate machines from machineConfigs
|
||||||
for machine_name, machine_config in machine_configs.items():
|
for machine_name, machine_config in machine_configs.items():
|
||||||
|
configuration_nix = flake / "machines" / machine_name / "configuration.nix"
|
||||||
|
configuration_nix.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
configuration_nix.write_text("""
|
||||||
|
{ imports = [ (builtins.fromJSON (builtins.readFile ./configuration.json)) ]; }
|
||||||
|
""")
|
||||||
set_machine_settings(flake, machine_name, machine_config)
|
set_machine_settings(flake, machine_name, machine_config)
|
||||||
|
|
||||||
if "/tmp" not in str(os.environ.get("HOME")):
|
if "/tmp" not in str(os.environ.get("HOME")):
|
||||||
@@ -135,17 +158,15 @@ def generate_flake(
|
|||||||
cwd=flake,
|
cwd=flake,
|
||||||
check=True,
|
check=True,
|
||||||
)
|
)
|
||||||
sp.run(["git", "init"], cwd=flake, check=True)
|
init_git(monkeypatch, flake)
|
||||||
sp.run(["git", "add", "."], cwd=flake, check=True)
|
|
||||||
sp.run(["git", "config", "user.name", "clan-tool"], cwd=flake, check=True)
|
|
||||||
sp.run(["git", "config", "user.email", "clan@example.com"], cwd=flake, check=True)
|
|
||||||
sp.run(["git", "commit", "-a", "-m", "Initial commit"], cwd=flake, check=True)
|
|
||||||
return FlakeForTest(flake)
|
return FlakeForTest(flake)
|
||||||
|
|
||||||
|
|
||||||
def create_flake(
|
def create_flake(
|
||||||
temporary_home: Path,
|
temporary_home: Path,
|
||||||
flake_template: str | Path,
|
flake_template: str | Path,
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
clan_core_flake: Path | None = None,
|
clan_core_flake: Path | None = None,
|
||||||
# names referring to pre-defined machines from ../machines
|
# names referring to pre-defined machines from ../machines
|
||||||
machines: list[str] | None = None,
|
machines: list[str] | None = None,
|
||||||
@@ -198,18 +219,7 @@ def create_flake(
|
|||||||
f"!! $HOME does not point to a temp directory!! HOME={os.environ['HOME']}"
|
f"!! $HOME does not point to a temp directory!! HOME={os.environ['HOME']}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Find out why test_vms_api.py fails in nix build
|
init_git(monkeypatch, flake)
|
||||||
# but works in pytest when this bottom line is commented out
|
|
||||||
sp.run(
|
|
||||||
["git", "config", "--global", "init.defaultBranch", "main"],
|
|
||||||
cwd=flake,
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
sp.run(["git", "init"], cwd=flake, check=True)
|
|
||||||
sp.run(["git", "add", "."], cwd=flake, check=True)
|
|
||||||
sp.run(["git", "config", "user.name", "clan-tool"], cwd=flake, check=True)
|
|
||||||
sp.run(["git", "config", "user.email", "clan@example.com"], cwd=flake, check=True)
|
|
||||||
sp.run(["git", "commit", "-a", "-m", "Initial commit"], cwd=flake, check=True)
|
|
||||||
|
|
||||||
if remote:
|
if remote:
|
||||||
with tempfile.TemporaryDirectory(prefix="flake-"):
|
with tempfile.TemporaryDirectory(prefix="flake-"):
|
||||||
@@ -222,7 +232,11 @@ def create_flake(
|
|||||||
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(temporary_home, "test_flake")
|
yield from create_flake(
|
||||||
|
temporary_home=temporary_home,
|
||||||
|
flake_template="test_flake",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
|
)
|
||||||
# check that git diff on ./sops is empty
|
# check that git diff on ./sops is empty
|
||||||
if (temporary_home / "test_flake" / "sops").exists():
|
if (temporary_home / "test_flake" / "sops").exists():
|
||||||
git_proc = sp.run(
|
git_proc = sp.run(
|
||||||
@@ -244,9 +258,10 @@ def test_flake_with_core(
|
|||||||
msg = "clan-core flake not found. This test requires the clan-core flake to be present"
|
msg = "clan-core flake not found. This test requires the clan-core flake to be present"
|
||||||
raise FixtureError(msg)
|
raise FixtureError(msg)
|
||||||
yield from create_flake(
|
yield from create_flake(
|
||||||
temporary_home,
|
temporary_home=temporary_home,
|
||||||
"test_flake_with_core",
|
flake_template="test_flake_with_core",
|
||||||
CLAN_CORE,
|
clan_core_flake=CLAN_CORE,
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -276,9 +291,10 @@ def test_flake_with_core_and_pass(
|
|||||||
msg = "clan-core flake not found. This test requires the clan-core flake to be present"
|
msg = "clan-core flake not found. This test requires the clan-core flake to be present"
|
||||||
raise FixtureError(msg)
|
raise FixtureError(msg)
|
||||||
yield from create_flake(
|
yield from create_flake(
|
||||||
temporary_home,
|
temporary_home=temporary_home,
|
||||||
"test_flake_with_core_and_pass",
|
flake_template="test_flake_with_core_and_pass",
|
||||||
CLAN_CORE,
|
clan_core_flake=CLAN_CORE,
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -290,7 +306,8 @@ def test_flake_minimal(
|
|||||||
msg = "clan-core flake not found. This test requires the clan-core flake to be present"
|
msg = "clan-core flake not found. This test requires the clan-core flake to be present"
|
||||||
raise FixtureError(msg)
|
raise FixtureError(msg)
|
||||||
yield from create_flake(
|
yield from create_flake(
|
||||||
temporary_home,
|
temporary_home=temporary_home,
|
||||||
CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
CLAN_CORE,
|
monkeypatch=monkeypatch,
|
||||||
|
clan_core_flake=CLAN_CORE,
|
||||||
)
|
)
|
||||||
|
|||||||
24
pkgs/clan-cli/tests/nix_config.py
Normal file
24
pkgs/clan-cli/tests/nix_config.py
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ConfigItem:
|
||||||
|
aliases: list[str]
|
||||||
|
defaultValue: bool # noqa: N815
|
||||||
|
description: str
|
||||||
|
documentDefault: bool # noqa: N815
|
||||||
|
experimentalFeature: str # noqa: N815
|
||||||
|
value: str | bool | list[str] | dict[str, str]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="session")
|
||||||
|
def nix_config() -> dict[str, ConfigItem]:
|
||||||
|
proc = subprocess.run(
|
||||||
|
["nix", "show-config", "--json"], check=True, stdout=subprocess.PIPE
|
||||||
|
)
|
||||||
|
data = json.loads(proc.stdout)
|
||||||
|
return {name: ConfigItem(**c) for name, c in data.items()}
|
||||||
@@ -1,171 +0,0 @@
|
|||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
from clan_cli import config
|
|
||||||
from clan_cli.config import parsing
|
|
||||||
from clan_cli.errors import ClanError
|
|
||||||
|
|
||||||
example_options = f"{Path(config.__file__).parent}/jsonschema/options.json"
|
|
||||||
|
|
||||||
|
|
||||||
def test_walk_jsonschema_all_types() -> None:
|
|
||||||
schema = {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"array": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "string",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"boolean": {"type": "boolean"},
|
|
||||||
"integer": {"type": "integer"},
|
|
||||||
"number": {"type": "number"},
|
|
||||||
"string": {"type": "string"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
expected = {
|
|
||||||
"array": list[str],
|
|
||||||
"boolean": bool,
|
|
||||||
"integer": int,
|
|
||||||
"number": float,
|
|
||||||
"string": str,
|
|
||||||
}
|
|
||||||
assert config.parsing.options_types_from_schema(schema) == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_walk_jsonschema_nested() -> None:
|
|
||||||
schema = {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"first": {"type": "string"},
|
|
||||||
"last": {"type": "string"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"age": {"type": "integer"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
expected = {
|
|
||||||
"age": int,
|
|
||||||
"name.first": str,
|
|
||||||
"name.last": str,
|
|
||||||
}
|
|
||||||
assert config.parsing.options_types_from_schema(schema) == expected
|
|
||||||
|
|
||||||
|
|
||||||
# test walk_jsonschema with dynamic attributes (e.g. "additionalProperties")
|
|
||||||
def test_walk_jsonschema_dynamic_attrs() -> None:
|
|
||||||
schema = {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"age": {"type": "integer"},
|
|
||||||
"users": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {"type": "string"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
expected = {
|
|
||||||
"age": int,
|
|
||||||
"users.<name>": str, # <name> is a placeholder for any string
|
|
||||||
}
|
|
||||||
assert config.parsing.options_types_from_schema(schema) == expected
|
|
||||||
|
|
||||||
|
|
||||||
def test_type_from_schema_path_simple() -> None:
|
|
||||||
schema = {
|
|
||||||
"type": "boolean",
|
|
||||||
}
|
|
||||||
assert parsing.type_from_schema_path(schema, []) is bool
|
|
||||||
|
|
||||||
|
|
||||||
def test_type_from_schema_path_nested() -> None:
|
|
||||||
schema = {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"name": {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"first": {"type": "string"},
|
|
||||||
"last": {"type": "string"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"age": {"type": "integer"},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert parsing.type_from_schema_path(schema, ["age"]) is int
|
|
||||||
assert parsing.type_from_schema_path(schema, ["name", "first"]) is str
|
|
||||||
|
|
||||||
|
|
||||||
def test_type_from_schema_path_dynamic_attrs() -> None:
|
|
||||||
schema = {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"age": {"type": "integer"},
|
|
||||||
"users": {
|
|
||||||
"type": "object",
|
|
||||||
"additionalProperties": {"type": "string"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
assert parsing.type_from_schema_path(schema, ["age"]) is int
|
|
||||||
assert parsing.type_from_schema_path(schema, ["users", "foo"]) is str
|
|
||||||
|
|
||||||
|
|
||||||
def test_map_type() -> None:
|
|
||||||
with pytest.raises(ClanError):
|
|
||||||
config.map_type("foo")
|
|
||||||
assert config.map_type("string") is str
|
|
||||||
assert config.map_type("integer") is int
|
|
||||||
assert config.map_type("boolean") is bool
|
|
||||||
assert config.map_type("attribute set of string") == dict[str, str]
|
|
||||||
assert config.map_type("attribute set of integer") == dict[str, int]
|
|
||||||
assert config.map_type("null or string") == str | None
|
|
||||||
|
|
||||||
|
|
||||||
# test the cast function with simple types
|
|
||||||
def test_cast() -> None:
|
|
||||||
assert (
|
|
||||||
config.cast(value=["true"], input_type=bool, opt_description="foo-option")
|
|
||||||
is True
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
config.cast(value=["null"], input_type=str | None, opt_description="foo-option")
|
|
||||||
is None
|
|
||||||
)
|
|
||||||
assert (
|
|
||||||
config.cast(value=["bar"], input_type=str | None, opt_description="foo-option")
|
|
||||||
== "bar"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
|
||||||
("option", "value", "options", "expected"),
|
|
||||||
[
|
|
||||||
("foo.bar", ["baz"], {"foo.bar": {"type": "str"}}, ("foo.bar", ["baz"])),
|
|
||||||
("foo.bar", ["baz"], {"foo": {"type": "attrs"}}, ("foo", {"bar": ["baz"]})),
|
|
||||||
(
|
|
||||||
"users.users.my-user.name",
|
|
||||||
["my-name"],
|
|
||||||
{"users.users.<name>.name": {"type": "str"}},
|
|
||||||
("users.users.<name>.name", ["my-name"]),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"foo.bar.baz.bum",
|
|
||||||
["val"],
|
|
||||||
{"foo.<name>.baz": {"type": "attrs"}},
|
|
||||||
("foo.<name>.baz", {"bum": ["val"]}),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"userIds.DavHau",
|
|
||||||
["42"],
|
|
||||||
{"userIds": {"type": "attrs"}},
|
|
||||||
("userIds", {"DavHau": ["42"]}),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
def test_find_option(option: str, value: list, options: dict, expected: tuple) -> None:
|
|
||||||
assert config.find_option(option, value, options) == expected
|
|
||||||
@@ -12,23 +12,11 @@
|
|||||||
inputs = inputs' // {
|
inputs = inputs' // {
|
||||||
clan-core = fake-clan-core;
|
clan-core = fake-clan-core;
|
||||||
};
|
};
|
||||||
machineSettings = (
|
|
||||||
if builtins.getEnv "CLAN_MACHINE_SETTINGS_FILE" != "" then
|
|
||||||
builtins.fromJSON (builtins.readFile (builtins.getEnv "CLAN_MACHINE_SETTINGS_FILE"))
|
|
||||||
else if builtins.pathExists ./machines/machine1/settings.json then
|
|
||||||
builtins.fromJSON (builtins.readFile ./machines/machine1/settings.json)
|
|
||||||
else
|
|
||||||
{ }
|
|
||||||
);
|
|
||||||
machineImports = map (module: fake-clan-core.clanModules.${module}) (
|
|
||||||
machineSettings.clanImports or [ ]
|
|
||||||
);
|
|
||||||
in
|
in
|
||||||
{
|
{
|
||||||
nixosConfigurations.machine1 = inputs.nixpkgs.lib.nixosSystem {
|
nixosConfigurations.machine1 = inputs.nixpkgs.lib.nixosSystem {
|
||||||
modules = machineImports ++ [
|
modules = [
|
||||||
./nixosModules/machine1.nix
|
./nixosModules/machine1.nix
|
||||||
machineSettings
|
|
||||||
(
|
(
|
||||||
{
|
{
|
||||||
lib,
|
lib,
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
|
import subprocess
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
@@ -55,12 +56,19 @@ def test_add_module_to_inventory(
|
|||||||
)
|
)
|
||||||
opts = CreateOptions(
|
opts = CreateOptions(
|
||||||
clan_dir=FlakeId(str(base_path)),
|
clan_dir=FlakeId(str(base_path)),
|
||||||
machine=Machine(
|
machine=Machine(name="machine1", tags=[], deploy=MachineDeploy()),
|
||||||
name="machine1", tags=[], system="x86_64-linux", deploy=MachineDeploy()
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
create_machine(opts)
|
create_machine(opts)
|
||||||
|
(test_flake_with_core.path / "machines" / "machine1" / "facter.json").write_text(
|
||||||
|
json.dumps(
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"system": "x86_64-linux",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
subprocess.run(["git", "add", "."], cwd=test_flake_with_core.path)
|
||||||
|
|
||||||
inventory = load_inventory_json(base_path)
|
inventory = load_inventory_json(base_path)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import json
|
import json
|
||||||
import subprocess
|
import shutil
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from io import StringIO
|
from io import StringIO
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -9,7 +9,7 @@ from age_keys import SopsSetup
|
|||||||
from clan_cli.clan_uri import FlakeId
|
from clan_cli.clan_uri import FlakeId
|
||||||
from clan_cli.errors import ClanError
|
from clan_cli.errors import ClanError
|
||||||
from clan_cli.machines.machines import Machine
|
from clan_cli.machines.machines import Machine
|
||||||
from clan_cli.nix import nix_eval, nix_shell, run
|
from clan_cli.nix import nix_eval, run
|
||||||
from clan_cli.vars.check import check_vars
|
from clan_cli.vars.check import check_vars
|
||||||
from clan_cli.vars.generate import generate_vars_for_machine
|
from clan_cli.vars.generate import generate_vars_for_machine
|
||||||
from clan_cli.vars.list import stringify_all_vars
|
from clan_cli.vars.list import stringify_all_vars
|
||||||
@@ -83,12 +83,14 @@ def test_generate_public_var(
|
|||||||
temporary_home: Path,
|
temporary_home: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
config = nested_dict()
|
config = nested_dict()
|
||||||
|
config["nixpkgs"]["hostPlatform"] = "x86_64-linux"
|
||||||
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
||||||
my_generator["files"]["my_value"]["secret"] = False
|
my_generator["files"]["my_value"]["secret"] = False
|
||||||
my_generator["script"] = "echo hello > $out/my_value"
|
my_generator["script"] = "echo hello > $out/my_value"
|
||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -122,12 +124,14 @@ def test_generate_secret_var_sops(
|
|||||||
sops_setup: SopsSetup,
|
sops_setup: SopsSetup,
|
||||||
) -> None:
|
) -> None:
|
||||||
config = nested_dict()
|
config = nested_dict()
|
||||||
|
config["nixpkgs"]["hostPlatform"] = "x86_64-linux"
|
||||||
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
||||||
my_generator["files"]["my_secret"]["secret"] = True
|
my_generator["files"]["my_secret"]["secret"] = True
|
||||||
my_generator["script"] = "echo hello > $out/my_secret"
|
my_generator["script"] = "echo hello > $out/my_secret"
|
||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -162,6 +166,7 @@ def test_generate_secret_var_sops_with_default_group(
|
|||||||
sops_setup: SopsSetup,
|
sops_setup: SopsSetup,
|
||||||
) -> None:
|
) -> None:
|
||||||
config = nested_dict()
|
config = nested_dict()
|
||||||
|
config["nixpkgs"]["hostPlatform"] = "x86_64-linux"
|
||||||
config["clan"]["core"]["sops"]["defaultGroups"] = ["my_group"]
|
config["clan"]["core"]["sops"]["defaultGroups"] = ["my_group"]
|
||||||
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
||||||
my_generator["files"]["my_secret"]["secret"] = True
|
my_generator["files"]["my_secret"]["secret"] = True
|
||||||
@@ -169,6 +174,7 @@ def test_generate_secret_var_sops_with_default_group(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -193,6 +199,7 @@ def test_generated_shared_secret_sops(
|
|||||||
sops_setup: SopsSetup,
|
sops_setup: SopsSetup,
|
||||||
) -> None:
|
) -> None:
|
||||||
m1_config = nested_dict()
|
m1_config = nested_dict()
|
||||||
|
m1_config["nixpkgs"]["hostPlatform"] = "x86_64-linux"
|
||||||
shared_generator = m1_config["clan"]["core"]["vars"]["generators"][
|
shared_generator = m1_config["clan"]["core"]["vars"]["generators"][
|
||||||
"my_shared_generator"
|
"my_shared_generator"
|
||||||
]
|
]
|
||||||
@@ -200,12 +207,14 @@ def test_generated_shared_secret_sops(
|
|||||||
shared_generator["files"]["my_shared_secret"]["secret"] = True
|
shared_generator["files"]["my_shared_secret"]["secret"] = True
|
||||||
shared_generator["script"] = "echo hello > $out/my_shared_secret"
|
shared_generator["script"] = "echo hello > $out/my_shared_secret"
|
||||||
m2_config = nested_dict()
|
m2_config = nested_dict()
|
||||||
|
m2_config["nixpkgs"]["hostPlatform"] = "x86_64-linux"
|
||||||
m2_config["clan"]["core"]["vars"]["generators"]["my_shared_generator"] = (
|
m2_config["clan"]["core"]["vars"]["generators"]["my_shared_generator"] = (
|
||||||
shared_generator.copy()
|
shared_generator.copy()
|
||||||
)
|
)
|
||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"machine1": m1_config, "machine2": m2_config},
|
machine_configs={"machine1": m1_config, "machine2": m2_config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -233,8 +242,10 @@ def test_generated_shared_secret_sops(
|
|||||||
def test_generate_secret_var_password_store(
|
def test_generate_secret_var_password_store(
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
temporary_home: Path,
|
temporary_home: Path,
|
||||||
|
test_root: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
config = nested_dict()
|
config = nested_dict()
|
||||||
|
config["nixpkgs"]["hostPlatform"] = "x86_64-linux"
|
||||||
config["clan"]["core"]["vars"]["settings"]["secretStore"] = "password-store"
|
config["clan"]["core"]["vars"]["settings"]["secretStore"] = "password-store"
|
||||||
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
||||||
my_generator["files"]["my_secret"]["secret"] = True
|
my_generator["files"]["my_secret"]["secret"] = True
|
||||||
@@ -248,33 +259,18 @@ def test_generate_secret_var_password_store(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
gnupghome = temporary_home / "gpg"
|
gnupghome = temporary_home / "gpg"
|
||||||
gnupghome.mkdir(mode=0o700)
|
shutil.copytree(test_root / "data" / "gnupg-home", gnupghome)
|
||||||
monkeypatch.setenv("GNUPGHOME", str(gnupghome))
|
monkeypatch.setenv("GNUPGHOME", str(gnupghome))
|
||||||
|
|
||||||
|
password_store_dir = temporary_home / "pass"
|
||||||
|
shutil.copytree(test_root / "data" / "password-store", password_store_dir)
|
||||||
monkeypatch.setenv("PASSWORD_STORE_DIR", str(temporary_home / "pass"))
|
monkeypatch.setenv("PASSWORD_STORE_DIR", str(temporary_home / "pass"))
|
||||||
gpg_key_spec = temporary_home / "gpg_key_spec"
|
|
||||||
gpg_key_spec.write_text(
|
|
||||||
"""
|
|
||||||
Key-Type: 1
|
|
||||||
Key-Length: 1024
|
|
||||||
Name-Real: Root Superuser
|
|
||||||
Name-Email: test@local
|
|
||||||
Expire-Date: 0
|
|
||||||
%no-protection
|
|
||||||
"""
|
|
||||||
)
|
|
||||||
subprocess.run(
|
|
||||||
nix_shell(
|
|
||||||
["nixpkgs#gnupg"], ["gpg", "--batch", "--gen-key", str(gpg_key_spec)]
|
|
||||||
),
|
|
||||||
check=True,
|
|
||||||
)
|
|
||||||
subprocess.run(
|
|
||||||
nix_shell(["nixpkgs#pass"], ["pass", "init", "test@local"]), check=True
|
|
||||||
)
|
|
||||||
machine = Machine(name="my_machine", flake=FlakeId(str(flake.path)))
|
machine = Machine(name="my_machine", flake=FlakeId(str(flake.path)))
|
||||||
assert not check_vars(machine)
|
assert not check_vars(machine)
|
||||||
cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"])
|
cli.run(["vars", "generate", "--flake", str(flake.path), "my_machine"])
|
||||||
@@ -318,6 +314,7 @@ def test_generate_secret_for_multiple_machines(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"machine1": machine1_config, "machine2": machine2_config},
|
machine_configs={"machine1": machine1_config, "machine2": machine2_config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -363,6 +360,7 @@ def test_dependant_generators(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -402,6 +400,7 @@ def test_prompt(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -421,6 +420,7 @@ def test_share_flag(
|
|||||||
sops_setup: SopsSetup,
|
sops_setup: SopsSetup,
|
||||||
) -> None:
|
) -> None:
|
||||||
config = nested_dict()
|
config = nested_dict()
|
||||||
|
config["nixpkgs"]["hostPlatform"] = "x86_64-linux"
|
||||||
shared_generator = config["clan"]["core"]["vars"]["generators"]["shared_generator"]
|
shared_generator = config["clan"]["core"]["vars"]["generators"]["shared_generator"]
|
||||||
shared_generator["share"] = True
|
shared_generator["share"] = True
|
||||||
shared_generator["files"]["my_secret"]["secret"] = True
|
shared_generator["files"]["my_secret"]["secret"] = True
|
||||||
@@ -440,6 +440,7 @@ def test_share_flag(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -490,6 +491,7 @@ def test_prompt_create_file(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -518,6 +520,7 @@ def test_api_get_prompts(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -546,6 +549,7 @@ def test_api_set_prompts(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -592,6 +596,7 @@ def test_commit_message(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -641,6 +646,7 @@ def test_default_value(
|
|||||||
temporary_home: Path,
|
temporary_home: Path,
|
||||||
) -> None:
|
) -> None:
|
||||||
config = nested_dict()
|
config = nested_dict()
|
||||||
|
config["nixpkgs"]["hostPlatform"] = "x86_64-linux"
|
||||||
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
||||||
my_generator["files"]["my_value"]["secret"] = False
|
my_generator["files"]["my_value"]["secret"] = False
|
||||||
my_generator["files"]["my_value"]["value"]["_type"] = "override"
|
my_generator["files"]["my_value"]["value"]["_type"] = "override"
|
||||||
@@ -650,6 +656,7 @@ def test_default_value(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -683,6 +690,7 @@ def test_stdout_of_generate(
|
|||||||
sops_setup: SopsSetup,
|
sops_setup: SopsSetup,
|
||||||
) -> None:
|
) -> None:
|
||||||
config = nested_dict()
|
config = nested_dict()
|
||||||
|
config["nixpkgs"]["hostPlatform"] = "x86_64-linux"
|
||||||
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
my_generator = config["clan"]["core"]["vars"]["generators"]["my_generator"]
|
||||||
my_generator["files"]["my_value"]["secret"] = False
|
my_generator["files"]["my_value"]["secret"] = False
|
||||||
my_generator["script"] = "echo -n hello > $out/my_value"
|
my_generator["script"] = "echo -n hello > $out/my_value"
|
||||||
@@ -694,6 +702,7 @@ def test_stdout_of_generate(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -761,6 +770,7 @@ def test_migration_skip(
|
|||||||
sops_setup: SopsSetup,
|
sops_setup: SopsSetup,
|
||||||
) -> None:
|
) -> None:
|
||||||
config = nested_dict()
|
config = nested_dict()
|
||||||
|
config["nixpkgs"]["hostPlatform"] = "x86_64-linux"
|
||||||
my_service = config["clan"]["core"]["facts"]["services"]["my_service"]
|
my_service = config["clan"]["core"]["facts"]["services"]["my_service"]
|
||||||
my_service["secret"]["my_value"] = {}
|
my_service["secret"]["my_value"] = {}
|
||||||
my_service["generator"]["script"] = "echo -n hello > $secrets/my_value"
|
my_service["generator"]["script"] = "echo -n hello > $secrets/my_value"
|
||||||
@@ -772,6 +782,7 @@ def test_migration_skip(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -792,6 +803,7 @@ def test_migration(
|
|||||||
sops_setup: SopsSetup,
|
sops_setup: SopsSetup,
|
||||||
) -> None:
|
) -> None:
|
||||||
config = nested_dict()
|
config = nested_dict()
|
||||||
|
config["nixpkgs"]["hostPlatform"] = "x86_64-linux"
|
||||||
my_service = config["clan"]["core"]["facts"]["services"]["my_service"]
|
my_service = config["clan"]["core"]["facts"]["services"]["my_service"]
|
||||||
my_service["public"]["my_value"] = {}
|
my_service["public"]["my_value"] = {}
|
||||||
my_service["generator"]["script"] = "echo -n hello > $facts/my_value"
|
my_service["generator"]["script"] = "echo -n hello > $facts/my_value"
|
||||||
@@ -802,6 +814,7 @@ def test_migration(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
@@ -822,6 +835,7 @@ def test_fails_when_files_are_left_from_other_backend(
|
|||||||
sops_setup: SopsSetup,
|
sops_setup: SopsSetup,
|
||||||
) -> None:
|
) -> None:
|
||||||
config = nested_dict()
|
config = nested_dict()
|
||||||
|
config["nixpkgs"]["hostPlatform"] = "x86_64-linux"
|
||||||
my_secret_generator = config["clan"]["core"]["vars"]["generators"][
|
my_secret_generator = config["clan"]["core"]["vars"]["generators"][
|
||||||
"my_secret_generator"
|
"my_secret_generator"
|
||||||
]
|
]
|
||||||
@@ -835,6 +849,7 @@ def test_fails_when_files_are_left_from_other_backend(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"my_machine": config},
|
machine_configs={"my_machine": config},
|
||||||
)
|
)
|
||||||
monkeypatch.chdir(flake.path)
|
monkeypatch.chdir(flake.path)
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from clan_cli.vms.run import inspect_vm, spawn_vm
|
|||||||
from fixtures_flakes import generate_flake
|
from fixtures_flakes import generate_flake
|
||||||
from helpers import cli
|
from helpers import cli
|
||||||
from helpers.nixos_config import nested_dict
|
from helpers.nixos_config import nested_dict
|
||||||
|
from nix_config import ConfigItem
|
||||||
from root import CLAN_CORE
|
from root import CLAN_CORE
|
||||||
|
|
||||||
|
|
||||||
@@ -19,10 +20,12 @@ from root import CLAN_CORE
|
|||||||
def test_vm_deployment(
|
def test_vm_deployment(
|
||||||
monkeypatch: pytest.MonkeyPatch,
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
temporary_home: Path,
|
temporary_home: Path,
|
||||||
|
nix_config: dict[str, ConfigItem],
|
||||||
sops_setup: SopsSetup,
|
sops_setup: SopsSetup,
|
||||||
) -> None:
|
) -> None:
|
||||||
# machine 1
|
# machine 1
|
||||||
machine1_config = nested_dict()
|
machine1_config = nested_dict()
|
||||||
|
machine1_config["nixpkgs"]["hostPlatform"] = nix_config["system"].value
|
||||||
machine1_config["clan"]["virtualisation"]["graphics"] = False
|
machine1_config["clan"]["virtualisation"]["graphics"] = False
|
||||||
machine1_config["services"]["getty"]["autologinUser"] = "root"
|
machine1_config["services"]["getty"]["autologinUser"] = "root"
|
||||||
machine1_config["services"]["openssh"]["enable"] = True
|
machine1_config["services"]["openssh"]["enable"] = True
|
||||||
@@ -48,6 +51,7 @@ def test_vm_deployment(
|
|||||||
"""
|
"""
|
||||||
# machine 2
|
# machine 2
|
||||||
machine2_config = nested_dict()
|
machine2_config = nested_dict()
|
||||||
|
machine2_config["nixpkgs"]["hostPlatform"] = nix_config["system"].value
|
||||||
machine2_config["clan"]["virtualisation"]["graphics"] = False
|
machine2_config["clan"]["virtualisation"]["graphics"] = False
|
||||||
machine2_config["services"]["getty"]["autologinUser"] = "root"
|
machine2_config["services"]["getty"]["autologinUser"] = "root"
|
||||||
machine2_config["services"]["openssh"]["enable"] = True
|
machine2_config["services"]["openssh"]["enable"] = True
|
||||||
@@ -62,6 +66,7 @@ def test_vm_deployment(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs={"m1_machine": machine1_config, "m2_machine": machine2_config},
|
machine_configs={"m1_machine": machine1_config, "m2_machine": machine2_config},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ def test_vm_persistence(
|
|||||||
flake = generate_flake(
|
flake = generate_flake(
|
||||||
temporary_home,
|
temporary_home,
|
||||||
flake_template=CLAN_CORE / "templates" / "minimal",
|
flake_template=CLAN_CORE / "templates" / "minimal",
|
||||||
|
monkeypatch=monkeypatch,
|
||||||
machine_configs=config,
|
machine_configs=config,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
modulename: _: jsonLib.parseOptions (optionsFromModule modulename) { }
|
modulename: _: jsonLib.parseOptions (optionsFromModule modulename) { }
|
||||||
) clanModules;
|
) clanModules;
|
||||||
|
|
||||||
clanModuleFunctionSchemas = lib.mapAttrsFlatten (
|
clanModuleFunctionSchemas = lib.attrsets.mapAttrsToList (
|
||||||
modulename: _:
|
modulename: _:
|
||||||
(self.lib.modules.getFrontmatter modulename)
|
(self.lib.modules.getFrontmatter modulename)
|
||||||
// {
|
// {
|
||||||
|
|||||||
@@ -7,10 +7,19 @@
|
|||||||
initialized = inputs.nixpkgs.legacyPackages.x86_64-linux.runCommand "minimal-clan-flake" { } ''
|
initialized = inputs.nixpkgs.legacyPackages.x86_64-linux.runCommand "minimal-clan-flake" { } ''
|
||||||
mkdir $out
|
mkdir $out
|
||||||
cp -r ${path}/* $out
|
cp -r ${path}/* $out
|
||||||
mkdir -p $out/machines/foo
|
mkdir -p $out/machines/testmachine
|
||||||
|
|
||||||
# TODO: Instead create a machine by calling the API, this wont break in future tests and is much closer to what the user performs
|
# TODO: Instead create a machine by calling the API, this wont break in future tests and is much closer to what the user performs
|
||||||
echo '{ "nixpkgs": { "hostPlatform": "x86_64-linux" } }' > $out/machines/foo/settings.json
|
cat > $out/machines/testmachine/hardware-configuration.nix << EOF
|
||||||
|
{ lib, ... }: {
|
||||||
|
nixpkgs.hostPlatform = "x86_64-linux";
|
||||||
|
system.stateVersion = lib.version;
|
||||||
|
documentation.enable = false;
|
||||||
|
users.users.root.initialPassword = "fnord23";
|
||||||
|
boot.loader.grub.devices = lib.mkForce [ "/dev/sda" ];
|
||||||
|
fileSystems."/".device = lib.mkDefault "/dev/sda";
|
||||||
|
}
|
||||||
|
EOF
|
||||||
'';
|
'';
|
||||||
evaled = (import "${initialized}/flake.nix").outputs {
|
evaled = (import "${initialized}/flake.nix").outputs {
|
||||||
self = evaled // {
|
self = evaled // {
|
||||||
@@ -22,7 +31,7 @@
|
|||||||
{
|
{
|
||||||
type = "derivation";
|
type = "derivation";
|
||||||
name = "minimal-clan-flake-check";
|
name = "minimal-clan-flake-check";
|
||||||
inherit (evaled.nixosConfigurations.foo.config.system.build.vm) drvPath outPath;
|
inherit (evaled.nixosConfigurations.testmachine.config.system.build.toplevel) drvPath outPath;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user