Nomad 1.4 variable and iterate over `nomadVar` elements

I am trying out the Nomad 1.4-beta.1 and was experimenting with the variables section.

Using this as reference:

I created the variable named sample:

Is the following the most optimal way of consuming the key,value into the env?

As my Go-template fu is quite sub-par, I was wondering if there is a better alternative that the two step with and range.


      template {
        data = <<EOF
{{- with nomadVar "sample" }}
{{- range $kk, $vv := . }}
{{ $kk }}={{ $vv }}
{{- end }}
{{- end }}
    EOF

        destination = "local/env"
        env         = true
      }

@shantanugadgil :wave:

That template looks great. You need the wrapping with to guard the range function until the variable data is available, since consul-template uses multiple rendering passes over the template—so this is spot on. As Nomad 1.4 moves to GA, Nomad Variable support will make its way into consul-template mainline releases, which will expand access to Nomad Variables for templating outside of Nomad and also facilitate template development and troubleshooting without having to necessarily make a lot of round trip job submissions.

I did golf it down a little, and there is an interesting dance because these dependencies have real types.

{{ with nomadVar "sample" }}{{ .Parent.Items | sprig_toJson | parseJSON | toTOML }}{{end}}

The sprig_toJson | parseJSON shenanigans is a dirty cheat to remove the type information from the .Items object. I added GH issue with a proposed enhancement—Add a helper function to NomadVariableItems to get the Items as `map[string]interface{}` · Issue #14603 · hashicorp/nomad · GitHub

I’m excited that you’re already experimenting with Nomad Variables.

Best,
-cv


Charlie Voiselle (@angrycub)
Engineer - Nomad, HashiCorp

3 Likes

Necrobumping for a related issue I am trying (noob alert, started using nomad this week)…

I want to use a single SQL template to initialize multiple databases with the same template.
For this, I defined a task variable within the job spec (later get this from Vault or so)

variable "dbases" {
    type = list(object({
        dbname = string
        dbuser = string
        dbpass = string
    }))
    default = [
        {
            dbname = "db1"
            dbuser = "user1"
            dbpass = "pass1"
        },
        {
            dbname = "db2"
            dbuser = "user2"
            dbpass = "pass2"
        }
    ]
}
....
        task "task-postgres" {
            driver = "podman"

            config {
                image = "docker.io/postgres:16-alpine"
                ports = ["postgrestcp"]
            }

            env {
                POSTGRES_PASSWORD = var.pgpass
            }

            template {
                data = <<EOTPL
{{ range var.dbases }}
DO $$
    BEGIN
        CREATE ROLE {{ .dbuser }} WITH LOGIN PASSWORD '{{ .dbpass }}' CREATEDB;
        EXCEPTION WHEN duplicate_object THEN RAISE NOTICE '%, skipping', SQLERRM USING ERRCODE = SQLSTATE;
    END
$$;
SELECT 'CREATE DATABASE {{ .dbname }}'
    WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '{{ .dbname }}')\gexec
GRANT ALL PRIVILEGES ON DATABASE {{ .dbname }} TO {{ .dbuser }};
\c {{ .dbname }}
GRANT ALL PRIVILEGES ON SCHEMA public TO {{ .dbuser }};
{{ end }}
EOTPL
                destination = "/docker-entrypoint-initdb.d/dbinit.sql"
                error_on_missing_key = true
                change_mode = "noop" # this is just a DB mutation, so no restarts needed
            }

but I can’t find the right reference for var.dbases, nomad complains it can’t find the variable.

Incidentally, another issue: is there a way to use an external template (e.g. with template { source = "..." }) where the template file resides with the job specification (i.e. not on the nomad client) without having to “pull” the file from an external address? (artifact would also be acceptable, as long as I can have a path local to the job spec for source).

According to the job specification template documentation, this should also work which some might find less verbose:

      template {
        destination = "local/env"
        env         = true
        data = <<EOF
{{- with nomadVar "sample" }}
{{- range .Tuples }}
{{ .K }}={{ .V }}
{{- end }}
{{- end }}
EOF
      }