Skip to content

Commit 1a0b60a

Browse files
KiaraGrouwstramergify[bot]
authored andcommitted
This PR adds a Terraform input variable named special_args.
This allows passing in a map from Terraform to expose to NixOS's `specialArgs` at build-time. Example usage, in this case presuming deployment to a Hetzner Cloud server (`resource.hcloud_server`): ```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"; ... special_args = { tf = { inherit server_name; # all variables # var = lib.mapAttrs (k: _: lib.tfRef "var.${k}") variable; # non-sensitive variables var = lib.mapAttrs (k: _: lib.tfRef "var.${k}") (lib.filterAttrs (_k: v: !(v ? sensitive && v.sensitive)) variable); data = lib.mapAttrs (type: instances: lib.mapAttrs (k: _: tfRef "data.${type}.${k}") instances) data; resource = lib.mapAttrs (type: instances: lib.mapAttrs (k: _: tfRef "resource.${type}.${k}") instances) resource; server = lib.tfRef "resource.hcloud_server.${server_name}"; }; }; }) servers; } ``` You can then use these in your `nixosConfigurations`, in this example thru the `tf` argument. As a note on security, information passed this way _will_ hit `/nix/store/`. As such, the above usage example has defaulted to omitting TF variables marked as sensitive. This PR incorporates ideas from: - @aanderse, who implemented a similar feature in [teraflops](https://github.com/aanderse/teraflops) that inspired this PR. - @Mic92, who suggested (see #414) to extend the original `lib.nixosSystem` call to pass in info without `--impure` or staging to Git. - @getchoo, who suggested getting the NAR hash by `nix flake prefetch` over `getFlake`, fixing an 'unlocked flake reference' error on (non-Lix) Nix. - @threddast, who suggested to use TF's `any` type to automate serializing. An [alternative design](main...KiaraGrouwstra:nixos-anywhere:tf-info-to-wrapper#diff-2e2429dde4812f0b50c784e8d4c8b93cc9faeb52cce4747733200f65ea5c2bbb) suggested by @Mic92 involved passing the information not directly, but rather thru a file. The idea would be that this might help reduce the risk of stack overflows, tho I have imagined (perhaps naively) that TF info has tended not to get too big, whereas I also had a bit more trouble getting that approach to work properly so far (involving both NARs that would suddenly mismatch again, while I'd also yet to test if one could put such files in `.gitignore`).
1 parent 548b4a9 commit 1a0b60a

File tree

7 files changed

+54
-13
lines changed

7 files changed

+54
-13
lines changed

terraform/all-in-one.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ No resources.
125125
| <a name="input_nixos_system_attr"></a> [nixos\_system\_attr](#input_nixos_system_attr) | The nixos system to deploy i.e. your-flake#nixosConfigurations.your-evaluated-nixos.config.system.build.toplevel or just your-evaluated-nixos.config.system.build.toplevel if you are not using flakes | `string` | n/a | yes |
126126
| <a name="input_no_reboot"></a> [no\_reboot](#input_no_reboot) | DEPRECATED: Use `phases` instead. Do not reboot after installation | `bool` | `false` | no |
127127
| <a name="input_phases"></a> [phases](#input_phases) | Phases to run. See `nixos-anywhere --help` for more information | `set(string)` | <pre>[<br> "kexec",<br> "disko",<br> "install",<br> "reboot"<br>]</pre> | no |
128+
| <a name="input_special_args"></a> [special\_args](#input_special_args) | A map exposed as NixOS's `specialArgs` thru a file. | `any` | `{}` | no |
128129
| <a name="input_stop_after_disko"></a> [stop\_after\_disko](#input_stop_after_disko) | DEPRECATED: Use `phases` instead. Exit after disko formatting | `bool` | `false` | no |
129130
| <a name="input_target_host"></a> [target\_host](#input_target_host) | DNS host to deploy to | `string` | n/a | yes |
130131
| <a name="input_target_port"></a> [target\_port](#input_target_port) | SSH port used to connect to the target\_host after installing NixOS. If install\_port is not set than this port is also used before installing. | `number` | `22` | no |

terraform/all-in-one/main.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,15 @@ module "system-build" {
33
attribute = var.nixos_system_attr
44
file = var.file
55
nix_options = var.nix_options
6+
special_args = var.special_args
67
}
78

89
module "partitioner-build" {
910
source = "../nix-build"
1011
attribute = var.nixos_partitioner_attr
1112
file = var.file
1213
nix_options = var.nix_options
14+
special_args = var.special_args
1315
}
1416

1517
locals {

terraform/all-in-one/variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,9 @@ variable "nixos_facter_path" {
131131
description = "Path to which to write a `facter.json` generated by `nixos-facter`."
132132
default = ""
133133
}
134+
135+
variable "special_args" {
136+
type = any
137+
default = {}
138+
description = "A map exposed as NixOS's `specialArgs` thru a file."
139+
}

terraform/nix-build.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,12 @@ No modules.
3131

3232
## Inputs
3333

34-
| Name | Description | Type | Default | Required |
35-
| ------------------------------------------------------------------- | -------------------------------------------------- | ------------- | ------- | :------: |
36-
| <a name="input_attribute"></a> [attribute](#input_attribute) | the attribute to build, can also be a flake | `string` | n/a | yes |
37-
| <a name="input_file"></a> [file](#input_file) | the nix file to evaluate, if not run in flake mode | `string` | `null` | no |
38-
| <a name="input_nix_options"></a> [nix\_options](#input_nix_options) | the options of nix | `map(string)` | `{}` | no |
34+
| Name | Description | Type | Default | Required |
35+
| ---------------------------------------------------------------------- | --------------------------------------------------- | ------------- | ------- | :------: |
36+
| <a name="input_attribute"></a> [attribute](#input_attribute) | the attribute to build, can also be a flake | `string` | n/a | yes |
37+
| <a name="input_file"></a> [file](#input_file) | the nix file to evaluate, if not run in flake mode | `string` | `null` | no |
38+
| <a name="input_nix_options"></a> [nix\_options](#input_nix_options) | the options of nix | `map(string)` | `{}` | no |
39+
| <a name="input_special_args"></a> [special\_args](#input_special_args) | A map exposed as NixOS's `specialArgs` thru a file. | `any` | `{}` | no |
3940

4041
## Outputs
4142

terraform/nix-build/main.tf

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ data "external" "nix-build" {
99
attribute = var.attribute
1010
file = var.file
1111
nix_options = local.nix_options
12+
special_args = jsonencode(var.special_args)
1213
}
1314
}
1415
output "result" {

terraform/nix-build/nix-build.sh

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,43 @@
11
#!/usr/bin/env bash
22
set -efu
33

4-
declare file attribute nix_options
5-
eval "$(jq -r '@sh "attribute=\(.attribute) file=\(.file) nix_options=\(.nix_options)"')"
4+
declare file attribute nix_options special_args
5+
eval "$(jq -r '@sh "attribute=\(.attribute) file=\(.file) nix_options=\(.nix_options) special_args=\(.special_args)"')"
66
if [ "${nix_options}" != '{"options":{}}' ]; then
77
options=$(echo "${nix_options}" | jq -r '.options | to_entries | map("--option \(.key) \(.value)") | join(" ")')
88
else
99
options=""
1010
fi
11-
if [[ -n ${file-} ]] && [[ -e ${file-} ]]; then
12-
# shellcheck disable=SC2086
13-
out=$(nix build --no-link --json $options -f "$file" "$attribute")
14-
printf '%s' "$out" | jq -c '.[].outputs'
11+
if [[ ${special_args-} == "{}" ]]; then
12+
# no special arguments, proceed as normal
13+
if [[ -n ${file-} ]] && [[ -e ${file-} ]]; then
14+
# shellcheck disable=SC2086
15+
out=$(nix build --no-link --json $options -f "$file" "$attribute")
16+
else
17+
# shellcheck disable=SC2086
18+
out=$(nix build --no-link --json ${options} "$attribute")
19+
fi
1520
else
21+
if [[ ${file-} != 'null' ]]; then
22+
echo "special_args are currently only supported when using flakes!" >&2
23+
exit 1
24+
fi
25+
# pass the args in a pure fashion by extending the original config
26+
rest="$(echo "${attribute}" | cut -d "#" -f 2)"
27+
# e.g. config_path=nixosConfigurations.aarch64-linux.myconfig
28+
config_path="${rest%.config.*}"
29+
# e.g. config_attribute=config.system.build.toplevel
30+
config_attribute="config.${rest#*.config.}"
31+
32+
# grab flake nar from error message
33+
flake_rel="$(echo "${attribute}" | cut -d "#" -f 1)"
34+
# e.g. flake_rel="."
35+
flake_dir="$(readlink -f "${flake_rel}")"
36+
flake_nar="$(nix flake prefetch "${flake_dir}" --json | jq -r '.hash')"
37+
# substitute variables into the template
38+
nix_expr="(builtins.getFlake ''file://${flake_dir}/flake.nix?narHash=${flake_nar}'').${config_path}.extendModules { specialArgs = builtins.fromJSON ''${special_args}''; }"
39+
# inject `special_args` into nixos config's `specialArgs`
1640
# shellcheck disable=SC2086
17-
out=$(nix build --no-link --json $options "$attribute")
18-
printf '%s' "$out" | jq -c '.[].outputs'
41+
out=$(nix build --no-link --json ${options} --expr "${nix_expr}" "${config_attribute}")
1942
fi
43+
printf '%s' "$out" | jq -c '.[].outputs'

terraform/nix-build/variables.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,9 @@ variable "nix_options" {
1414
description = "the options of nix"
1515
default = {}
1616
}
17+
18+
variable "special_args" {
19+
type = any
20+
default = {}
21+
description = "A map exposed as NixOS's `specialArgs` thru a file."
22+
}

0 commit comments

Comments
 (0)