add toplevel machines-json that can deploy all hosts
This commit is contained in:
parent
293e7f8ae6
commit
16b33eb0a8
|
@ -11,6 +11,7 @@ let
|
|||
(builtins.fromJSON
|
||||
(builtins.readFile (directory + /machines/${machineName}/settings.json)));
|
||||
|
||||
# TODO: remove default system once we have a hardware-config mechanism
|
||||
nixosConfiguration = { system ? "x86_64-linux", name }: nixpkgs.lib.nixosSystem {
|
||||
modules = [
|
||||
self.nixosModules.clanCore
|
||||
|
@ -19,8 +20,7 @@ let
|
|||
{
|
||||
clanCore.machineName = name;
|
||||
clanCore.clanDir = directory;
|
||||
# TODO: remove this once we have a hardware-config mechanism
|
||||
nixpkgs.hostPlatform = lib.mkDefault system;
|
||||
nixpkgs.hostPlatform = lib.mkForce system;
|
||||
}
|
||||
];
|
||||
inherit specialArgs;
|
||||
|
@ -41,27 +41,32 @@ let
|
|||
# This instantiates nixos for each system that we support:
|
||||
# configPerSystem = <system>.<machine>.nixosConfiguration
|
||||
# We need this to build nixos secret generators for each system
|
||||
configPerSystem = builtins.listToAttrs
|
||||
configsPerSystem = builtins.listToAttrs
|
||||
(builtins.map
|
||||
(system: lib.nameValuePair system
|
||||
(lib.mapAttrs (name: _: nixosConfiguration { inherit name system; }) allMachines))
|
||||
supportedSystems);
|
||||
|
||||
machinesPerSystem = lib.mapAttrs (_: machine:
|
||||
getMachine = machine: {
|
||||
inherit (machine.config.system.clan) uploadSecrets generateSecrets;
|
||||
inherit (machine.config.clan.networking) deploymentAddress;
|
||||
};
|
||||
|
||||
machinesPerSystem = lib.mapAttrs (_: machine: getMachine machine);
|
||||
|
||||
machinesPerSystemWithJson = lib.mapAttrs (_: machine:
|
||||
let
|
||||
config = {
|
||||
inherit (machine.config.system.clan) uploadSecrets generateSecrets;
|
||||
inherit (machine.config.clan.networking) deploymentAddress;
|
||||
};
|
||||
m = getMachine machine;
|
||||
in
|
||||
config // {
|
||||
json = machine.pkgs.writeText "config.json" (builtins.toJSON config);
|
||||
m // {
|
||||
json = machine.pkgs.writers.writeJSON "machine.json" m;
|
||||
});
|
||||
in
|
||||
{
|
||||
inherit nixosConfigurations;
|
||||
|
||||
clanInternals = {
|
||||
machines = lib.mapAttrs (_: machinesPerSystem) configPerSystem;
|
||||
machines = lib.mapAttrs (_: configs: machinesPerSystemWithJson configs) configsPerSystem;
|
||||
machines-json = lib.mapAttrs (system: configs: nixpkgs.legacyPackages.${system}.writers.writeJSON "machines.json" (machinesPerSystem configs)) configsPerSystem;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -2,15 +2,17 @@ import argparse
|
|||
import json
|
||||
import os
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from ..dirs import get_clan_flake_toplevel
|
||||
from ..nix import nix_command, nix_config, nix_eval
|
||||
from ..secrets.generate import generate_secrets
|
||||
from ..secrets.upload import upload_secrets
|
||||
from ..nix import nix_build, nix_command, nix_config
|
||||
from ..secrets.generate import run_generate_secrets
|
||||
from ..secrets.upload import run_upload_secrets
|
||||
from ..ssh import Host, HostGroup, HostKeyCheck, parse_deployment_address
|
||||
|
||||
|
||||
def deploy_nixos(hosts: HostGroup) -> None:
|
||||
def deploy_nixos(hosts: HostGroup, clan_dir: Path) -> None:
|
||||
"""
|
||||
Deploy to all hosts in parallel
|
||||
"""
|
||||
|
@ -38,8 +40,11 @@ def deploy_nixos(hosts: HostGroup) -> None:
|
|||
|
||||
flake_attr = h.meta.get("flake_attr", "")
|
||||
|
||||
generate_secrets(flake_attr)
|
||||
upload_secrets(flake_attr)
|
||||
if generate_secrets_script := h.meta.get("generate_secrets"):
|
||||
run_generate_secrets(generate_secrets_script, clan_dir)
|
||||
|
||||
if upload_secrets_script := h.meta.get("upload_secrets"):
|
||||
run_upload_secrets(upload_secrets_script, clan_dir)
|
||||
|
||||
target_host = h.meta.get("target_host")
|
||||
if target_host:
|
||||
|
@ -74,31 +79,65 @@ def deploy_nixos(hosts: HostGroup) -> None:
|
|||
hosts.run_function(deploy)
|
||||
|
||||
|
||||
# FIXME: we want some kind of inventory here.
|
||||
def update(args: argparse.Namespace) -> None:
|
||||
clan_dir = get_clan_flake_toplevel().as_posix()
|
||||
machine = args.machine
|
||||
def build_json(targets: list[str]) -> list[dict[str, Any]]:
|
||||
outpaths = subprocess.run(
|
||||
nix_build(targets),
|
||||
stdout=subprocess.PIPE,
|
||||
check=True,
|
||||
text=True,
|
||||
).stdout
|
||||
parsed = []
|
||||
for outpath in outpaths.splitlines():
|
||||
parsed.append(json.loads(Path(outpath).read_text()))
|
||||
return parsed
|
||||
|
||||
|
||||
def get_all_machines(clan_dir: Path) -> HostGroup:
|
||||
config = nix_config()
|
||||
system = config["system"]
|
||||
what = f'{clan_dir}#clanInternals.machines-json."{system}"'
|
||||
machines = build_json([what])[0]
|
||||
|
||||
address = json.loads(
|
||||
subprocess.run(
|
||||
nix_eval(
|
||||
[
|
||||
f'{clan_dir}#clanInternals.machines."{system}"."{machine}".deploymentAddress'
|
||||
]
|
||||
),
|
||||
stdout=subprocess.PIPE,
|
||||
check=True,
|
||||
text=True,
|
||||
).stdout
|
||||
)
|
||||
host = parse_deployment_address(machine, address)
|
||||
print(f"deploying {machine}")
|
||||
deploy_nixos(HostGroup([host]))
|
||||
hosts = []
|
||||
for name, machine in machines.items():
|
||||
host = parse_deployment_address(
|
||||
name, machine["deploymentAddress"], meta=machine
|
||||
)
|
||||
hosts.append(host)
|
||||
return HostGroup(hosts)
|
||||
|
||||
|
||||
def get_selected_machines(machine_names: list[str], clan_dir: Path) -> HostGroup:
|
||||
config = nix_config()
|
||||
system = config["system"]
|
||||
what = []
|
||||
for name in machine_names:
|
||||
what.append(f'{clan_dir}#clanInternals.machines."{system}"."{name}".json')
|
||||
machines = build_json(what)
|
||||
hosts = []
|
||||
for i, machine in enumerate(machines):
|
||||
host = parse_deployment_address(machine_names[i], machine["deploymentAddress"])
|
||||
hosts.append(host)
|
||||
return HostGroup(hosts)
|
||||
|
||||
|
||||
# FIXME: we want some kind of inventory here.
|
||||
def update(args: argparse.Namespace) -> None:
|
||||
clan_dir = get_clan_flake_toplevel()
|
||||
if len(args.machines) == 0:
|
||||
machines = get_all_machines(clan_dir)
|
||||
else:
|
||||
machines = get_selected_machines(args.machines, clan_dir)
|
||||
|
||||
deploy_nixos(machines, clan_dir)
|
||||
|
||||
|
||||
def register_update_parser(parser: argparse.ArgumentParser) -> None:
|
||||
parser.add_argument("machine", type=str)
|
||||
parser.add_argument(
|
||||
"machines",
|
||||
type=str,
|
||||
help="machine to update. if empty, update all machines",
|
||||
nargs="*",
|
||||
default=[],
|
||||
)
|
||||
parser.set_defaults(func=update)
|
||||
|
|
|
@ -2,6 +2,7 @@ import argparse
|
|||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from clan_cli.errors import ClanError
|
||||
|
||||
|
@ -9,11 +10,7 @@ from ..dirs import get_clan_flake_toplevel, module_root
|
|||
from ..nix import nix_build, nix_config
|
||||
|
||||
|
||||
def generate_secrets(machine: str) -> None:
|
||||
clan_dir = get_clan_flake_toplevel().as_posix().strip()
|
||||
env = os.environ.copy()
|
||||
env["CLAN_DIR"] = clan_dir
|
||||
env["PYTHONPATH"] = str(module_root().parent) # TODO do this in the clanCore module
|
||||
def build_generate_script(machine: str, clan_dir: Path) -> str:
|
||||
config = nix_config()
|
||||
system = config["system"]
|
||||
|
||||
|
@ -28,21 +25,32 @@ def generate_secrets(machine: str) -> None:
|
|||
f"failed to generate secrets:\n{shlex.join(cmd)}\nexited with {proc.returncode}"
|
||||
)
|
||||
|
||||
secret_generator_script = proc.stdout.strip()
|
||||
print(secret_generator_script)
|
||||
secret_generator = subprocess.run(
|
||||
return proc.stdout.strip()
|
||||
|
||||
|
||||
def run_generate_secrets(secret_generator_script: str, clan_dir: Path) -> None:
|
||||
env = os.environ.copy()
|
||||
env["CLAN_DIR"] = str(clan_dir)
|
||||
env["PYTHONPATH"] = str(module_root().parent) # TODO do this in the clanCore module
|
||||
print(f"generating secrets... {secret_generator_script}")
|
||||
proc = subprocess.run(
|
||||
[secret_generator_script],
|
||||
env=env,
|
||||
)
|
||||
|
||||
if secret_generator.returncode != 0:
|
||||
if proc.returncode != 0:
|
||||
raise ClanError("failed to generate secrets")
|
||||
else:
|
||||
print("successfully generated secrets")
|
||||
|
||||
|
||||
def generate(machine: str) -> None:
|
||||
clan_dir = get_clan_flake_toplevel()
|
||||
run_generate_secrets(build_generate_script(machine, clan_dir), clan_dir)
|
||||
|
||||
|
||||
def generate_command(args: argparse.Namespace) -> None:
|
||||
generate_secrets(args.machine)
|
||||
generate(args.machine)
|
||||
|
||||
|
||||
def register_generate_parser(parser: argparse.ArgumentParser) -> None:
|
||||
|
|
|
@ -44,8 +44,8 @@ def generate_secrets_group(
|
|||
|
||||
text = f"""\
|
||||
set -euo pipefail
|
||||
facts={shlex.quote(str(facts_dir))}
|
||||
secrets={shlex.quote(str(secrets_dir))}
|
||||
export facts={shlex.quote(str(facts_dir))}
|
||||
export secrets={shlex.quote(str(secrets_dir))}
|
||||
{generator}
|
||||
"""
|
||||
try:
|
||||
|
|
|
@ -1,57 +1,51 @@
|
|||
import argparse
|
||||
import json
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
from ..dirs import get_clan_flake_toplevel, module_root
|
||||
from ..errors import ClanError
|
||||
from ..nix import nix_build, nix_config, nix_eval
|
||||
from ..nix import nix_build, nix_config
|
||||
|
||||
|
||||
def upload_secrets(machine: str) -> None:
|
||||
clan_dir = get_clan_flake_toplevel().as_posix()
|
||||
def build_upload_script(machine: str, clan_dir: Path) -> str:
|
||||
config = nix_config()
|
||||
system = config["system"]
|
||||
|
||||
proc = subprocess.run(
|
||||
nix_build(
|
||||
[f'{clan_dir}#clanInternals.machines."{system}"."{machine}".uploadSecrets']
|
||||
),
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
check=True,
|
||||
cmd = nix_build(
|
||||
[f'{clan_dir}#clanInternals.machines."{system}"."{machine}".uploadSecrets']
|
||||
)
|
||||
proc = subprocess.run(cmd, stdout=subprocess.PIPE, text=True)
|
||||
if proc.returncode != 0:
|
||||
raise ClanError(
|
||||
f"failed to upload secrets:\n{shlex.join(cmd)}\nexited with {proc.returncode}"
|
||||
)
|
||||
|
||||
return proc.stdout.strip()
|
||||
|
||||
|
||||
def run_upload_secrets(flake_attr: str, clan_dir: Path) -> None:
|
||||
env = os.environ.copy()
|
||||
env["CLAN_DIR"] = str(clan_dir)
|
||||
env["PYTHONPATH"] = str(module_root().parent) # TODO do this in the clanCore module
|
||||
host = json.loads(
|
||||
subprocess.run(
|
||||
nix_eval(
|
||||
[
|
||||
f'{clan_dir}#clanInternals.machines."{system}"."{machine}".deploymentAddress'
|
||||
]
|
||||
),
|
||||
stdout=subprocess.PIPE,
|
||||
text=True,
|
||||
check=True,
|
||||
).stdout
|
||||
)
|
||||
|
||||
secret_upload_script = proc.stdout.strip()
|
||||
secret_upload = subprocess.run(
|
||||
[
|
||||
secret_upload_script,
|
||||
host,
|
||||
],
|
||||
print(f"uploading secrets... {flake_attr}")
|
||||
proc = subprocess.run(
|
||||
[flake_attr],
|
||||
env=env,
|
||||
)
|
||||
|
||||
if secret_upload.returncode != 0:
|
||||
if proc.returncode != 0:
|
||||
raise ClanError("failed to upload secrets")
|
||||
else:
|
||||
print("successfully uploaded secrets")
|
||||
|
||||
|
||||
def upload_secrets(machine: str) -> None:
|
||||
clan_dir = get_clan_flake_toplevel()
|
||||
run_upload_secrets(build_upload_script(machine, clan_dir), clan_dir)
|
||||
|
||||
|
||||
def upload_command(args: argparse.Namespace) -> None:
|
||||
upload_secrets(args.machine)
|
||||
|
||||
|
|
|
@ -756,7 +756,9 @@ class HostGroup:
|
|||
return HostGroup(list(filter(pred, self.hosts)))
|
||||
|
||||
|
||||
def parse_deployment_address(machine_name: str, host: str) -> Host:
|
||||
def parse_deployment_address(
|
||||
machine_name: str, host: str, meta: dict[str, str] = {}
|
||||
) -> Host:
|
||||
parts = host.split("@")
|
||||
user: Optional[str] = None
|
||||
if len(parts) > 1:
|
||||
|
@ -776,12 +778,14 @@ def parse_deployment_address(machine_name: str, host: str) -> Host:
|
|||
if len(maybe_port) > 1:
|
||||
hostname = maybe_port[0]
|
||||
port = int(maybe_port[1])
|
||||
meta = meta.copy()
|
||||
meta["flake_attr"] = machine_name
|
||||
return Host(
|
||||
hostname,
|
||||
user=user,
|
||||
port=port,
|
||||
command_prefix=machine_name,
|
||||
meta=dict(flake_attr=machine_name),
|
||||
meta=meta,
|
||||
ssh_options=options,
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user