From de1a27903f3b52ad87e248ee10a0227d8b966219 Mon Sep 17 00:00:00 2001 From: Kiara Grouwstra Date: Wed, 23 Oct 2024 21:43:45 +0200 Subject: [PATCH] DRAFT: terraform: add `var.content`, see #413. This PR adds a Terraform input variable named `content`. This allows passing in a string from Terraform to expose to NixOS's run-time to make available as a file (default: `/etc/nixos-vars.json`) as suggested by @Mic92 at #414. This third iteration wraps the original `lib.nixosSystem` call to allow passing info without either use of `--impure` or having to stage to Git. Note the file is staged even if added to gitignore, making this less suited for development in case the file includes ephemeral content. As a result, I would consider this approach complementary rather than as superseding #414. Example usage: ```nix let servers = ...; variable = ...; data = ...; resource = ...; in { inherit variable data resource; module = lib.mapAttrs (server_name: _server_config: let in { # pin module version by nix flake inputs source = "github.com/numtide/nixos-anywhere?ref=${inputs.nixos-anywhere.sourceInfo.rev}/terraform/all-in-one"; ... content = lib.tfRef "jsonencode(${lib.strings.toJSON { # all variables # TF_VARS = lib.mapAttrs (k: _: lib.tfRef "jsonencode(var.${k})") variable; # non-sensitive variables TF_VARS = lib.mapAttrs (k: _: lib.tfRef "var.${k}") (lib.filterAttrs (_k: v: !(v ? sensitive && v.sensitive)) variable); TF_DATA = lib.mapAttrs (type: instances: lib.mapAttrs (k: _: tfRef "data.${type}.${k}") instances) data; TF_RESOURCES = lib.mapAttrs (type: instances: lib.mapAttrs (k: _: tfRef "resource.${type}.${k}") instances) resource; TF_SERVER = lib.tfRef "resource.hcloud_server.${server_name}"; SERVER_NAME = server_name; }})"; }) servers; } ``` You can then verify their contents from your `nixosConfigurations` like: `cat /etc/nixos-vars.json` However, so far I did not yet manage to reach my goal: - On this attempt I have so far just exposed the content to run-time, rather than to the intended NixOS build-time. To address this, I consider looking into injecting thru `specialArgs`, tho I am not sure yet this would work, and feel open to suggestions. - When putting the content file into `.gitignore`, the build currently errors on same NAR hash mismatch on the file. --- terraform/all-in-one.md | 3 ++ terraform/all-in-one/main.tf | 10 ++++++ terraform/all-in-one/variables.tf | 12 +++++++ terraform/nix-build/main.tf | 3 ++ terraform/nix-build/nix-build.sh | 53 +++++++++++++++++++++++----- terraform/nix-build/variables.tf | 12 +++++++ terraform/nix-build/wrapper.tmpl.nix | 7 ++++ terraform/nixos-vars.md | 34 ++++++++++++++++++ terraform/nixos-vars/main.tf | 10 ++++++ terraform/nixos-vars/nixos-vars.sh | 9 +++++ terraform/nixos-vars/variables.tf | 11 ++++++ 11 files changed, 156 insertions(+), 8 deletions(-) create mode 100644 terraform/nix-build/wrapper.tmpl.nix create mode 100644 terraform/nixos-vars.md create mode 100644 terraform/nixos-vars/main.tf create mode 100755 terraform/nixos-vars/nixos-vars.sh create mode 100644 terraform/nixos-vars/variables.tf diff --git a/terraform/all-in-one.md b/terraform/all-in-one.md index c30e41b7..5ded2151 100644 --- a/terraform/all-in-one.md +++ b/terraform/all-in-one.md @@ -96,6 +96,7 @@ No providers. | -------------------------------------------------------------------------------------- | ---------------- | ------- | | [install](#module_install) | ../install | n/a | | [nixos-rebuild](#module_nixos-rebuild) | ../nixos-rebuild | n/a | +| [nixos-vars](#module_nixos-vars) | ../nixos-vars | n/a | | [partitioner-build](#module_partitioner-build) | ../nix-build | n/a | | [system-build](#module_system-build) | ../nix-build | n/a | @@ -107,12 +108,14 @@ No resources. | Name | Description | Type | Default | Required | | --------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------------- | :------: | +| [content](#input_content) | Content to expose to the NixOS build as a file. | `string` | `"{}"` | no | | [debug\_logging](#input_debug_logging) | Enable debug logging | `bool` | `false` | no | | [deployment\_ssh\_key](#input_deployment_ssh_key) | Content of private key used to deploy to the target\_host after initial installation. To ensure maximum security, it is advisable to connect to your host using ssh-agent instead of relying on this variable | `string` | `null` | no | | [disk\_encryption\_key\_scripts](#input_disk_encryption_key_scripts) | Each script will be executed locally. Output of each will be created at the given path to disko during installation. The keys will be not copied to the final system |
list(object({
path = string
script = string
}))
| `[]` | no | | [extra\_environment](#input_extra_environment) | Extra environment variables to be set during installation. This can be useful to set extra variables for the extra\_files\_script or disk\_encryption\_key\_scripts | `map(string)` | `{}` | no | | [extra\_files\_script](#input_extra_files_script) | A script that should place files in the current directory that will be copied to the targets / directory | `string` | `null` | no | | [file](#input_file) | Nix file containing the nixos\_system\_attr and nixos\_partitioner\_attr. Use this if you are not using flake | `string` | `null` | no | +| [filename](#input_filename) | Name of the file to which to dump `content`. Defaults to `nixos-vars.json`. | `string` | `"./nixos-vars.json"` | no | | [install\_port](#input_install_port) | SSH port used to connect to the target\_host, before installing NixOS. If null than the value of `target_port` is used | `string` | `null` | no | | [install\_ssh\_key](#input_install_ssh_key) | Content of private key used to connect to the target\_host during initial installation | `string` | `null` | no | | [install\_user](#input_install_user) | SSH user used to connect to the target\_host, before installing NixOS. If null than the value of `target_host` is used | `string` | `null` | no | diff --git a/terraform/all-in-one/main.tf b/terraform/all-in-one/main.tf index 5689c64d..a30cda26 100644 --- a/terraform/all-in-one/main.tf +++ b/terraform/all-in-one/main.tf @@ -1,8 +1,16 @@ +module "nixos-vars" { + source = "../nixos-vars" + content = var.content + filename = var.filename +} + module "system-build" { source = "../nix-build" attribute = var.nixos_system_attr file = var.file nix_options = var.nix_options + content_file = var.filename + content_nar = module.nixos-vars.result.out } module "partitioner-build" { @@ -10,6 +18,8 @@ module "partitioner-build" { attribute = var.nixos_partitioner_attr file = var.file nix_options = var.nix_options + content_file = var.filename + content_nar = module.nixos-vars.result.out } locals { diff --git a/terraform/all-in-one/variables.tf b/terraform/all-in-one/variables.tf index 981c4f8c..a0677c72 100644 --- a/terraform/all-in-one/variables.tf +++ b/terraform/all-in-one/variables.tf @@ -131,3 +131,15 @@ variable "nixos_facter_path" { description = "Path to which to write a `facter.json` generated by `nixos-facter`." default = "" } + +variable "content" { + type = string + default = "{}" + description = "Content to expose to the NixOS build as a file." +} + +variable "filename" { + type = string + default = "./nixos-vars.json" + description = "Name of the file to which to dump `content`. Defaults to `nixos-vars.json`." +} diff --git a/terraform/nix-build/main.tf b/terraform/nix-build/main.tf index de73e5eb..1834b39d 100644 --- a/terraform/nix-build/main.tf +++ b/terraform/nix-build/main.tf @@ -6,9 +6,12 @@ locals { data "external" "nix-build" { program = [ "${path.module}/nix-build.sh" ] query = { + wrapper_path = "${path.module}/wrapper.tmpl.nix" attribute = var.attribute file = var.file nix_options = local.nix_options + content_file = var.content_file + content_nar = var.content_nar } } output "result" { diff --git a/terraform/nix-build/nix-build.sh b/terraform/nix-build/nix-build.sh index 8e5babca..e3068fce 100755 --- a/terraform/nix-build/nix-build.sh +++ b/terraform/nix-build/nix-build.sh @@ -1,15 +1,52 @@ #!/usr/bin/env bash -set -efu +set -xefu -declare file attribute nix_options -eval "$(jq -r '@sh "attribute=\(.attribute) file=\(.file) nix_options=\(.nix_options)"')" -options=$(echo "${nix_options}" | jq -r '.options | to_entries | map("--option \(.key) \(.value)") | join(" ")') +# example: +# content_file=/home/kiara/Downloads/tf-config/nixos-vars.json +# content_nar=sha256-GtULmQIY32Uv+tp9u9pFicoLIUuvMq9008BL/xxXhbw= +# nix_options={"options":{"allow-dirty":true}} +# attribute=.#nixosConfigurations.aarch64-linux.combined.config.system.build.toplevel + +declare file attribute nix_options content_file content_nar wrapper_path +eval "$(jaq -r '@sh "attribute=\(.attribute) file=\(.file) nix_options=\(.nix_options) content_file=\(.content_file) content_nar=\(.content_nar) wrapper_path=\(.wrapper_path)"')" +# echo "$wrapper_path" +if [ "${nix_options}" = '{"options":{}}' ]; then + options="" +else + options=$(echo "${nix_options}" | jaq -r '.options | to_entries | map("--option \(.key) \(.value)") | join(" ")') +fi +echo "$options" if [[ -n ${file-} ]] && [[ -e ${file-} ]]; then # shellcheck disable=SC2086 out=$(nix build --no-link --json $options -f "$file" "$attribute") - printf '%s' "$out" | jq -c '.[].outputs' else - # shellcheck disable=SC2086 - out=$(nix build --no-link --json $options "$attribute") - printf '%s' "$out" | jq -c '.[].outputs' + # flakes want files to be staged to git, which is annoying, so hack around that + if [[ -n ${content_file-} ]] && [[ -e ${content_file-} ]] && [[ -n ${content_nar-} ]]; then + # default to saving the content file under the same name + content_name="$(basename "$content_file")" + rest="$(echo "${attribute}" | cut -d "#" -f 2)" + # e.g. config_path=nixosConfigurations.aarch64-linux.combined + config_path="${rest%.config.*}" + # e.g. config_attribute=config.system.build.toplevel + config_attribute="config.${rest#*.config.}" + + # grab flake nar from error message + flake_rel="$(echo "${attribute}" | cut -d "#" -f 1)" + flake_dir="$(readlink -f "${flake_rel}")" + content_file="$(readlink -f "${content_file}")" + flake_nar="$(nix build --expr "builtins.getFlake ''git+file://${flake_dir}?narHash=sha256-0000000000000000000000000000000000000000000=''" 2>&1 | grep -Po "(?<=got ')sha256-[^']*(?=')")" + # substitute variables into the template + nix_expr="$(sed -e "s%\$flake_dir%${flake_dir}%g" -e "s%\$flake_nar%${flake_nar}%g" -e "s%\$content_name%${content_name}%g" -e "s%\$content_file%${content_file}%g" -e "s%\$content_nar%${content_nar}%g" -e "s%\$config_path%${config_path}%g" "${wrapper_path}")" + # nix_expr="$(eval "cat < +## Requirements + +No requirements. + +## Providers + +| Name | Version | +|------|---------| +| [external](#provider\_external) | n/a | + +## Modules + +No modules. + +## Resources + +| Name | Type | +|------|------| +| [external_external.nixos-vars](https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/external) | data source | + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [content](#input\_content) | Content to expose to the NixOS build as a file. | `string` | `"{}"` | no | +| [filename](#input\_filename) | Name of the file to which to dump `content`. Defaults to `nixos-vars.json`. | `string` | `"./nixos-vars.json"` | no | + +## Outputs + +| Name | Description | +|------|-------------| +| [result](#output\_result) | n/a | + \ No newline at end of file diff --git a/terraform/nixos-vars/main.tf b/terraform/nixos-vars/main.tf new file mode 100644 index 00000000..a576ed42 --- /dev/null +++ b/terraform/nixos-vars/main.tf @@ -0,0 +1,10 @@ +data "external" "nixos-vars" { + program = [ "${path.module}/nixos-vars.sh" ] + query = { + content = var.content + filename = var.filename + } +} +output "result" { + value = data.external.nixos-vars.result +} diff --git a/terraform/nixos-vars/nixos-vars.sh b/terraform/nixos-vars/nixos-vars.sh new file mode 100755 index 00000000..0f5e791c --- /dev/null +++ b/terraform/nixos-vars/nixos-vars.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash +set -efu + +declare content filename +eval "$(jq -r '@sh "content=\(.content) filename=\(.filename)"')" + +echo "${content}" > "${filename}" +nar=$(nix hash path "${filename}") +printf "{\"out\":\"%s\"}" "${nar}" diff --git a/terraform/nixos-vars/variables.tf b/terraform/nixos-vars/variables.tf new file mode 100644 index 00000000..9fb1038c --- /dev/null +++ b/terraform/nixos-vars/variables.tf @@ -0,0 +1,11 @@ +variable "content" { + type = string + default = "{}" + description = "Content to expose to the NixOS build as a file." +} + +variable "filename" { + type = string + default = "./nixos-vars.json" + description = "Name of the file to which to dump `content`. Defaults to `nixos-vars.json`." +}