nomad icon indicating copy to clipboard operation
nomad copied to clipboard

Variable indirection in `env {}` stanzas

Open wimax-grapl opened this issue 3 years ago • 4 comments

Proposal

Bash has a feature called variable indirection that lets you read the value of a value. The following example exemplifies it:

original_value="hey, im the original value"
original_value_env_var_name="original_value"
echo "${!original_value_env_var_name}"
>> hey, im the original value
echo "${original_value_env_var_name}"
>> original_value

I propose that this could potentially be useful in Nomad env{} stanzas (or perhaps elsewhere in its interpolation engine)!

Use-cases

I have an upstream with a name that changes based on vars. This makes it difficult to statically pass its NOMAD_UPSTREAM_ADDR into an env{}. Concretely, this looks like the following:

            upstreams {
              destination_name = "plugin-${var.plugin_id}"
              # port unique but arbitrary - https://github.com/hashicorp/nomad/issues/7135
              local_bind_port = 1001
            }

if I wanted to pass this upstream's NOMAD_UPSTREAM_ADDR in as an env, i'm out of luck! The following obviously fails:

env {
        GRAPH_QUERY_CLIENT_ADDRESS = "http://${NOMAD_UPSTREAM_ADDR_graph-query-sidecar-${var.plugin_id}}"
}

Attempted Solutions

My attempt, which led to this ticket, is the following, where I try to do the string interpolation as one step and then rely on the shell reading ${!VARIABLE_NAME} to do some variable indirection. (In this case, it means 'replace this {} with the value of GRAPH_QUERY_UPSTREAM_ADDR - so, the full NOMAD_UPSTREAM_ADDR.)

        GRAPH_QUERY_UPSTREAM_ADDR  = "NOMAD_UPSTREAM_ADDR_graph-query-sidecar-${var.plugin_id}"
        GRAPH_QUERY_CLIENT_ADDRESS = "http://${!GRAPH_QUERY_UPSTREAM_ADDR}"

resulting in

NonzeroExitStatus stderr=\"Error getting job struct: Error parsing job file from /tmp/.tmpRgkLfK:\\n.tmpRgkLfK
:294,49-74: Invalid operand; Unsuitable value for unary operand: a bool is required.\\n.tmpRgkLfK:294,39-46: Unsuitable value type; Unsuitable value: value must be known\\n\""

If I construct the name of the UPSTREAM_ADDR at runtime, and grab it from runtime env variables, it works just fine. However, it'd be nice and clean if I could pass it in as part of the env!

wimax-grapl avatar Oct 06 '22 00:10 wimax-grapl

Hi @wimax-grapl! You can get the shell-scripting approach you've described to work if you escape the $ in the script as a $$. Here's an simplified example jobspec that does the job. You'll need to adapt it to your Connect use case:

variable "plugin_id" {
  type    = string
  default = "www"
}

job "example" {
  datacenters = ["dc1"]

  group "group" {

    task "task" {

      driver = "docker"

      config {
        image   = "debian:buster"
        command = "bash"
        args    = ["local/run.sh"]
      }

      template {
        data        = <<EOT
export GRAPH_QUERY_CLIENT_ADDRESS="http://$${!MY_OWN_ADDR}"
echo GRAPH_QUERY_CLIENT_ADDRESS=$GRAPH_QUERY_CLIENT_ADDRESS
exec sleep 3600
EOT
        destination = "local/run.sh"
      }

      env {
        MY_OWN_ADDR = "NOMAD_ADDR_${var.plugin_id}"
      }

    }

    network {
      mode = "bridge"
      port "www" {
        to = 8001
      }
    }

    service {
      provider = "nomad"
      port     = "www"
    }

  }
}

$ nomad job run ./example.nomad
...
$ nomad alloc logs 9db
GRAPH_QUERY_CLIENT_ADDRESS=http://127.0.0.1:31581

Trying to do this natively in Nomad would be interesting though. One tricky bit is that interpolation happens multiple times in Nomad (the vars get interpolated on the CLI, whereas other interpolation happens on the client), and that might make it hard for users to reason about what's happening. But I'll mark this as a roadmap item for further discussion.

tgross avatar Oct 07 '22 17:10 tgross

Thank you for the guidance!!

wimax-grapl avatar Oct 10 '22 03:10 wimax-grapl

ah shoot, sorry for screwing up the automation

wimax-grapl avatar Oct 10 '22 03:10 wimax-grapl

I believe that you could obtain your desired output using a template with env=true. I'd be curious to hear how it works for you if you could give it a shot. I expect the ${var.plugin_id} to be interpolated by the CLI, leaving the remainder to be interpolated on the client and included in the task environment.

template {
  env = true
  destination = "env.txt"
  data = <<EOT
GRAPH_QUERY_CLIENT_ADDRESS="http://{{ env ( printf "NOMAD_UPSTREAM_ADDR_graph-query-sidecar-%s"  "${var.plugin_id}" ) }}
EOT
}

I realize that it's longer than your proposal, but using the template block and env = true is a routinely used pattern in jobspecs and allows for a lot more expressiveness at the expense of verbosity.

angrycub avatar Oct 28 '22 15:10 angrycub