feature!: introduce --flake option for setting the flake to use
All checks were successful
/ ruff (push) Successful in 25s
/ black (push) Successful in 25s

Having this option available allows for using a flake which isn't in (or
upwards of) the directory the command gets executed in and allows for
using remote flakes.

Also archive the flake to use first and then operate on the archive.
This allows for easily getting the deployment_configuration.json from
the archive and also ensures that once the archiving suceeds, there
shouldn't be issues with the flakes source anymore.
Since now the deployment_configuration.json will always be taken from
the root of the flakes archive and therefore from the root of the flakes
repo, this is a breaking change, since previously it was taken from the
current working directory.
The idea of archiving the flake first and operating on the archive comes
from bij:
221052d846

Moreover introduce helper functions for facilitating recursive options
(i.e. options one can set on root and sub-commands).
This commit is contained in:
June 2024-06-10 02:08:23 +02:00
parent d164f502a1
commit a7772aa154
Signed by: june
SSH key fingerprint: SHA256:o9EAq4Y9N9K0pBQeBTqhSDrND5E7oB+60ZNx0U1yPe0
3 changed files with 100 additions and 26 deletions

View file

@ -20,7 +20,7 @@ However to override aspects of the target host for specific or all hosts, infra-
## Configuration
infra-rebuild accepts optional configuration in a `deployment_configuration.json`. \
infra-rebuild accepts optional configuration in a `deployment_configuration.json` present in the flake repos root. \
The following keys are available to be set for configuring various aspects of deployment for specific or all hosts:
- `default.targetPort`: A default port to use for connecting to all host.

View file

@ -3,54 +3,108 @@ import click
from infra_rebuild import operations
reboot_option_help_text = "Reboot the target hosts after running the operation."
flake_option_help_text = "URI of the flake to use. Defaults to the current direcory."
def initialize_recursive_option(ctx, option_name, default_value):
"""Helper function for initializing a recursive option.
Arguments:
`ctx` -- The click context passed to the calling command.
`option_name` -- The name of the option in question.
`default_value` -- A default value for the option.
Simply sets the value of the recursive option (`ctx.obj[option_name]`) to `default_value`.
"""
ctx.obj[option_name] = default_value
def update_recursive_option(ctx, option_name, given_value):
"""Helper function for updating a recursive option.
Arguments:
ctx -- The click context passed to the calling command.
option_name -- The name of the option in question.
given_value -- The value the calling command received for the option.
It overrides the current value for the recursive option (`ctx.obj[option_name]`) with `given_value`, if `given_value` isn't `None`.
This ensure precedence of the innermost command (since the innermost command would call this function last).
""" # noqa: E501
if given_value is not None:
ctx.obj[option_name] = given_value
@click.group(context_settings={"help_option_names": ["-h", "--help"]}, invoke_without_command=True)
@click.version_option(prog_name="infra-rebuild")
def infra_rebuild():
pass
@click.pass_context
@click.option("--flake", help=flake_option_help_text)
def infra_rebuild(ctx, flake):
ctx.ensure_object(dict)
initialize_recursive_option(ctx, "flake", ".")
update_recursive_option(ctx, "flake", flake)
@infra_rebuild.command()
@click.pass_context
@click.option("--flake", help=flake_option_help_text)
@click.argument("hosts", nargs=-1)
def build(hosts):
operations.run("build", hosts)
def build(ctx, flake, hosts):
update_recursive_option(ctx, "flake", flake)
operations.run("build", hosts, ctx.obj["flake"])
@infra_rebuild.command()
@click.pass_context
@click.option("--flake", help=flake_option_help_text)
@click.argument("hosts", nargs=-1)
def build_vm(hosts):
operations.run("build-vm", hosts)
def build_vm(ctx, flake, hosts):
update_recursive_option(ctx, "flake", flake)
operations.run("build-vm", hosts, ctx.obj["flake"])
@infra_rebuild.command()
@click.pass_context
@click.option("--flake", help=flake_option_help_text)
@click.argument("hosts", nargs=-1)
def build_vm_with_bootloader(hosts):
operations.run("build-vm-with-bootloader", hosts)
def build_vm_with_bootloader(ctx, flake, hosts):
update_recursive_option(ctx, "flake", flake)
operations.run("build-vm-with-bootloader", hosts, ctx.obj["flake"])
@infra_rebuild.command()
@click.pass_context
@click.option("--flake", help=flake_option_help_text)
@click.option("--reboot", is_flag=True, help=reboot_option_help_text)
@click.argument("hosts", nargs=-1)
def switch(hosts, reboot):
operations.run("switch", hosts, reboot)
def switch(ctx, flake, reboot, hosts):
update_recursive_option(ctx, "flake", flake)
operations.run("switch", hosts, ctx.obj["flake"], reboot)
@infra_rebuild.command()
@click.pass_context
@click.option("--flake", help=flake_option_help_text)
@click.option("--reboot", is_flag=True, help=reboot_option_help_text)
@click.argument("hosts", nargs=-1)
def boot(hosts, reboot):
operations.run("boot", hosts, reboot)
def boot(ctx, flake, reboot, hosts):
update_recursive_option(ctx, "flake", flake)
operations.run("boot", hosts, ctx.obj["flake"], reboot)
@infra_rebuild.command()
@click.pass_context
@click.option("--flake", help=flake_option_help_text)
@click.option("--reboot", is_flag=True, help=reboot_option_help_text)
@click.argument("hosts", nargs=-1)
def test(hosts, reboot):
operations.run("test", hosts, reboot)
def test(ctx, flake, reboot, hosts):
update_recursive_option(ctx, "flake", flake)
operations.run("test", hosts, ctx.obj["flake"], reboot)
@infra_rebuild.command(short_help=" ", help="This operation is a convenience alias for boot --reboot.")
@click.pass_context
@click.option("--flake", help=flake_option_help_text)
@click.argument("hosts", nargs=-1)
def reboot(hosts):
operations.run("boot", hosts, True)
def reboot(ctx, flake, hosts):
update_recursive_option(ctx, "flake", flake)
operations.run("boot", hosts, ctx.obj["flake"], True)

View file

@ -6,7 +6,27 @@ import sys
from infra_rebuild import msg
def run(operation, hosts, reboot=False): # noqa: FBT002 - having reboot as a Boolean positional argument is fine I think # fmt: skip
def run(operation, hosts, flake_uri, reboot=False): # noqa: FBT002 - having reboot as a Boolean positional argument is fine I think # fmt: skip
try:
flake_archive_json = subprocess.run(
["nix", "flake", "archive", "--json", f"{flake_uri}"], # noqa: S607, S603 - can't know what the users system looks like, maybe do some kind of validation for the flake_uri in the future?
capture_output=True,
text=True,
check=True
) # fmt: skip
try:
flake_archive = json.loads(flake_archive_json.stdout)
except json.JSONDecodeError:
msg.error("Internal Error: Couldn't parse the output of \"nix flake archive\".")
sys.exit(1)
if not flake_archive["path"]:
msg.error("Internal Error: The \"nix flake archive\" output doesn't include a path.")
sys.exit(1)
except subprocess.CalledProcessError:
msg.error("Error: Couldn't archive the provided flake.")
sys.exit(1)
match operation:
case "build" | "build-vm" | "build-vm-with-bootloader":
act_remotely = False
@ -22,23 +42,23 @@ def run(operation, hosts, reboot=False): # noqa: FBT002 - having reboot as a Bo
continue
if act_remotely:
remote(operation, host, reboot)
remote(operation, host, flake_archive, reboot)
else:
local(operation, host)
local(operation, host, flake_archive)
def local(operation, host):
def local(operation, host, flake_archive):
msg.info(f"Running nixos-rebuild {operation} for {host}...")
subprocess.run(
["nixos-rebuild", operation, "--flake", f".#{host}"], # noqa: S607, S603 - can't know what the users system looks like, maybe do some kind of validation for the given host in the future?
["nixos-rebuild", operation, "--flake", f"{flake_archive['path']}#{host}"], # noqa: S607, S603 - can't know what the users system looks like, maybe do some kind of validation for the given host in the future?
check=False
) # fmt: skip
def remote(operation, host, reboot=False): # noqa: FBT002 - having reboot as a Boolean positional argument is fine I think # fmt: skip
def remote(operation, host, flake_archive, reboot=False): # noqa: FBT002 - having reboot as a Boolean positional argument is fine I think # fmt: skip
config = None
try:
with open("deployment_configuration.json") as config_file:
with open(f"{flake_archive['path']}/deployment_configuration.json") as config_file:
try:
config = json.load(config_file)
except json.JSONDecodeError:
@ -62,7 +82,7 @@ def remote(operation, host, reboot=False): # noqa: FBT002 - having reboot as a
pass
if not target_hostname:
nix_config_fqdn = subprocess.run(
["nix", "eval", "--raw", f".#nixosConfigurations.{host}.config.networking.fqdn"], # noqa: S607, S603 - can't know what the users system looks like, maybe do some kind of validation for host in the future?
["nix", "eval", "--raw", f"{flake_archive['path']}#nixosConfigurations.{host}.config.networking.fqdn"], # noqa: S607, S603 - can't know what the users system looks like, maybe do some kind of validation for host in the future?
capture_output=True,
text=True,
check=False,
@ -119,7 +139,7 @@ def remote(operation, host, reboot=False): # noqa: FBT002 - having reboot as a
msg.info(f"Running nixos-rebuild {operation} for {host} on {target_host}...")
nixos_rebuild = subprocess.run(
["nixos-rebuild", operation, "--flake", f".#{host}", "--target-host", target_host, "--use-substitutes", "--use-remote-sudo"], # noqa: S607, S603, E501 - can't know what the users system looks like, maybe do some kind of validation for the given host in the future?, not breaking up the command makes it more readable
["nixos-rebuild", operation, "--flake", f"{flake_archive['path']}#{host}", "--target-host", target_host, "--use-substitutes", "--use-remote-sudo"], # noqa: S607, S603, E501 - can't know what the users system looks like, maybe do some kind of validation for the given host in the future?, not breaking up the command makes it more readable
env=nixos_rebuild_env,
check=False,
) # fmt: skip