steve
steve

Reputation: 3356

How to reference a variable from an earlier line in the same block in terraform?

Suppose I had a module (or resource, or locals block, or whatever)

module "example" {
  foo = "foo"
  bar = "${foo}" # why wont' this work??
}

It's frustrating to me that the terraform "language" doesn't support this. How am I supposed to write simple DRY code without being able to reference variables?

Edit: I think that example is too contrived. Here's a better one:

module "example" {
  domain = "example.com"
  uri = "https://${domain}" # why wont' this work??
}

Upvotes: 1

Views: 1662

Answers (2)

Martin Atkins
Martin Atkins

Reputation: 74574

Your question seems to be two different questions: how can you achieve the goal you described, and also why didn't the thing you tried first work.

Others already answered how you can factor out values to use in multiple locations, and so I wanted to just try to address the second question about why this didn't work, in case it's useful for your onward journey with Terraform.

The Terraform language is designed to appear as a heirarchical data structure rather than as linear code to be executed, which of course has some significant tradeoffs. In return for hopefully making a well-written Terraform configuration read like a manifest of what should exist rather than a program for creating it, the Terraform language does somewhat obscure the real control flow and symbol scoping that you might be accustomed to in other languages.

For your question in particular, I think it's important to recognize that the arguments inside a module block are not like declarations of variables, but are instead more like arguments passed to a function.

For your second example then, it might help to see it reworked into a function-call-like syntax:

module "example" {
  source = "./mymodule1"

  domain = "example.com"
  uri    = "https://${domain}"
}
# NOTE: pseudocode
example(domain: "example.com", uri: "https://#{domain}")

In most imperative-style languages, function arguments bind to symbols inside the module rather than outside of it, and so defining domain as "example.com" here doesn't make that symbol visible to other subsequent arguments in the same call.

Now taking Marcin's final example, adapted to resemble your second example:

locals {
  domain = "example.com"
}

module "example" {
  source = "./mymodule1"

  domain = local.domain
  url    = "https://${local.domain}"
}
# NOTE: pseudocode
domain = "example.com"
example(domain: domain, uri: "https://#{domain}")

Of course this is also exposing another difference with Terraform compared to general-purpose languages, which is that it doesn't have a shared variable scope with everything assigned into it and instead places local variables inside a namespace local. But despite the difference in syntax, they are local values scoped to the module they are defined with, and so you can refer to local.domain anywhere else in that same module. (Note: this is a whole-module scope, and not a lexical scope as you might be accustomed to in other languages.)

A similar principle applies to resource, data, and provider blocks; those two are more like arguments to a function than they are variable declarations, but just shaped in a different syntax with the goal of it appearing as a series of declarations rather than as sequential code.

Upvotes: 1

Marcin
Marcin

Reputation: 238687

It is supported, but it depends on your module. Your module must output foo first. Then you can do:

module "exmaple" {
  source = "./mymodule1"
  foo = "dfsaf"
  bar = module.exmaple.foo 
}

The simplest example of mymodule1 would be:

variable "foo" {
    default = 1
}

variable "bar" {
   default = 2
}

output "foo" {
  value = var.foo
}

But in your example it really does not make sense doing that, as bar is always same as foo, thus it shouldn't even be exposed.

The better and more natrual way would be:


locals {
  foo = "foo"
}

module "exmaple" {
  source = "./mymodule1"
  foo = local.foo
  bar = local.foo
}

Upvotes: 3

Related Questions