Reputation: 1
We have a Hashistack infrastructure: Nomad, Vault, Consul... I want to deploy an app with a Terraform. The app has it's variables in Vault's kv store.
I have a problem understanding how to make key-value pairs from vault become environment variables in app container.
NOTE:
Currently, key-value pairs are a map under Vault path. E.g.
vault kv get kv/some/path/secrets
will output
FOO = 123
BAR = 456
BAZ = 789
Current idea is to make terraform create an env file during deployment, gathering all data from vault. So, I created a terraform configuration and a tftpl. The main module connects to vault and mounts kv, but I struggle how to collect kv's into current template.
The idea in to insert string to this template of a job:
job.tftpl
...
task "example" {
template = {
data = <<EOF
${local.env_vars}
EOF
destination = "secrets/.env"
env = true
}
}
What is "terraform way" to convert a map in datasource to a string? I've tried using a local variable with for_each = data.vault_kv_secret_v2.env
cycle, but can't quite figure it out, and can't find any relevant example to base on. Could anyone help out?
Upvotes: -2
Views: 792
Reputation: 74604
This particular use-case is awkward because it seems to require multiple levels of nesting different languages inside each other: an "env" file nested inside Nomad's template language nested inside Nomad's jobspec language nested inside Terraform's template language.
Situations like that are annoying to deal with because we must be careful to add all of the escaping required to ensure that the value arrives exactly as intended in the innermost system, without getting misinterpreted as some feature of one of the two containing languages.
To minimize that problem as far as possible, I'd suggest starting by building the final string you want to write into secrets/.env
alone as a Terraform string first, so you'll only need to worry about Terraform's own template language initially:
locals {
env_vars = tomap({
FOO = "123"
BAR = "456"
BAZ = "789"
})
env_vars_file_content = <<-EOT
%{ for name, value in local.env_vars ~}
${name}='${replace(value, "'", "'\\''"}'
%{ endfor ~}
EOT
}
In the above I used replace
to escape any quote marks so that the result will be valid shell syntax for typical Unix shells. If you aren't using a Unix-style shell to parse this file then you may need a different quoting and escaping convention.
With the above, env_vars_file_content
will be a Terraform string containing the following:
FOO='123'
BAR='456'
BAZ='789'
With that string constructed, you can now describe to Terraform all of the additional wrapping and escaping required to send this robustly to Nomad. You can make that easier by using Nomad's JSON jobspec format instead of the native syntax, because then you can use Terraform's jsonencode
function to automatically handle the JSON encoding part.
I'm assuming you render the jobspec by using Terraform's templatefile
function, something like this:
resource "nomad_job" "example" {
jobspec = templatefile("${path.module}/job.json.tftmpl", {
env_vars_file_content = local.env_vars_file_content
})
json = true
}
Inside job.json.tftmpl
:
${jsonencode({
"Job" = {
# ...
"TaskGroups" = [
{
# ...
"Tasks" = [
{
# ...
"Templates" = [
"DestPath" = "secrets/.env"
"EmbeddedTmpl" = replace(
env_vars_file_content,
"{{", "{{\"{{\"}}",
)
]
# ...
},
]
# ...
},
]
# ...
}
})}
The template content above uses jsonencode
to guarantee that the result will be valid JSON syntax, and then uses another call to replace
to make sure that Nomad's template engine won't try to interpret any {{
sequences in any of your variable values as the beginning of a template sequence.
I only showed the relevant parts of the JSON-based Nomad jobspec here. If you're not familiar with this variant of Nomad's language and want to start from Nomad's native syntax then you can write a separate .nomad
file containing example values for everything you want to configure and then ask Nomad to generate the JSON equivalent of it:
nomad job run -output example.nomad
The result will show you the equivalent JSON data structure to your given .nomad
file, and you can adapt that data structure into the Terraform language for use inside the jsonencode
call above.
Unfortunately using one language to generate another is always awkward like this if you want it to be robust and not misinterpret certain input containing special characters. If you always know that your input won't contain any characters that could potentially confuse Nomad or the program that will parse your "env" file then you could potentially skip some of these steps, but I've shown above a solution which I believe should be immune to misinterpreting values as long as your environment variable names are always valid identifiers.
Upvotes: 0