Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow modules to access global variables #5480

Closed
joshuaspence opened this issue Mar 6, 2016 · 27 comments
Closed

Allow modules to access global variables #5480

joshuaspence opened this issue Mar 6, 2016 · 27 comments

Comments

@joshuaspence
Copy link
Contributor

I have a module that creates an EC2 instance with a bunch of common properties. Whenever I use this function, I end up passing a bunch of common variables such as ${var.region} (which is used by our bootstrap script injected into user data).

module "instance" {
  source = "..."
  region = "${var.region}"
}

It would be useful if this variable could be set once rather than every time that the module is used... I think that this could be implemented in one of two ways:

  1. Allow default values to be set. In puppet, default values for a file resource can be set with a File meta-resource. Perhaps a similar thing could happen in Terraform.
Module {
  source = "..."
  region = "${var.region}"
}
  1. Allow the module to access variables globally with an alternative syntax. Perhaps ${global.region} instead of ${var.region}.
@phinze
Copy link
Contributor

phinze commented Mar 7, 2016

Hi @joshuaspence - thanks for the feedback!

We've discussed global variables in the past:

In general we're against the particular solution of Global Variables, since it makes the input -> resources -> output flow of Modules less explicit, and explicitness is a core design goal.

That being said, we're definitely aware of the heavy repetition required in Terraform configs today, and it's something we'd like to address without sacrificing clarity (which is not a simple combination to achieve!).

The puppet-style default attribute syntax is one interesting idea. A related discussion can be found here: #1478

It'd be great to get these efforts consolidated in a canonical issue - if you feel like #1478 fits the bill, let's merge this down with that conversation. If not, let's figure out how to get this issue expressing the need clearly. 👍

@cahlbin
Copy link

cahlbin commented Apr 13, 2016

I skimmed through the related issues and found most of them suggest, well truly global access which I agree should not be allowed. I would like to suggest a terraform function that would be available in the module scope, that would allow to propagate a specific set of variables in a more consise syntax.

Example:

# Currently one have to propagate each variable:
module "instance" {
  source = "..."
  var1 = "${var.var1}"
  var2 = "${var.var2}"
}

, could instead with propagate_vars look something like:

module "instance" {
  source = "..."
  propagate_vars("var1", "var2", ...)
}

That could possibly also be implemented so that one can easily propagate map-typed variables, as they are only references by var name so terraform.

@zanona
Copy link

zanona commented Aug 25, 2016

I believe we could have something like providers which would be really helpful to be applied to all instances of a module, for example:

provider "cool_module" {
  global_module_var = "foo"
  reference_module_var = "${aws_instance.web.arn}"
}

module "instance_1" {
  source = "./cool_module"
}
module "instance_2" {
  source = "./cool_module"
}

Perhaps there's already a way to achieve that currently?

@garo
Copy link

garo commented Sep 7, 2016

#1478 would solve this kind of problems partially, but I'm not sure it would be useful in a nested module dependency.

Consider an example:

  • A module which defines a single instance. Uses resource "aws_instance" along with a data "template_file" for the instance user_data.
  • A module which defines a single VPC. Includes the single instance module several times. For example variables like domain and key_name need to be passed to every module call.
  • A top level which calls the VPC module a few times. Uses a few variables to define high level data like domain and key name for the entire VPC.

In altogether I find that the HCL restricts the expressibility a bit too much as it's just a one-to-one conversion from HCL to json (and back if required). Consider adding preparser/template/iteration/control/loop features so that a simple HCL script could generate complex json , which would then be consumed with Terraform.

@reubenavery
Copy link

+1, and I would strongly argue for following the patterns from Puppet in variable scoping. The spaghetti code resulting from a highly modularized terraform project quickly becomes a nightmare.

@sheerun
Copy link
Contributor

sheerun commented Mar 7, 2017

Global variables is something kubernetes introduced for charts to simplify configuration:
https://github.com/kubernetes/helm/blob/master/docs/charts.md#global-values

@keirhbadger
Copy link

Just figured out a way of doing this

If you create just an output.tf file in a directory and have value="global string" rather than value="${variable name}" you can run terraform apply and it will populate the terraform.tfstate file with your global string

Now you can access this in your std terraform tf files using remote states

@rolandkool
Copy link

@keirhbadger can you share some code example how you did this? I've tried to accomplish the same using remote state in consul, but for some reason having a terraform module that just outputs some variables doesn't result in a remote state containing those variables. So when I want to read them in a module, they are empty.
Thanks!

@keirbadger
Copy link

So this is an output.tf file in a global_vars directory

output "environment"        { value = "prod" }										
output "region"             { value = "us-east-2" }

Run terraform apply in that dir and the terraform.tfstate file will contain these lines

            "outputs": {
                "environment": {
                    "sensitive": false,
                    "type": "string",
                    "value": "prod"
                },
                "region": {
                    "sensitive": false,
                    "type": "string",
                    "value": "us-east-2"
                }
            },

You can now access this is a diff Terraform playbook with something like

data "terraform_remote_state" "global" {
  backend = "local"
  config {
      path = "../global_vars/terraform.tfstate"
  }
}

And
Name = "priv-b-${data.terraform_remote_state.global.environment}"

@apparentlymart
Copy link
Contributor

Hi all,

Thanks for the great discussion here and sorry we've been quiet for a while.

Recently we've started some early planning for a set of improvements to the configuration language for an upcoming Terraform release. One of the features we're looking at is the ability to pass complex objects into modules, which (amongst other uses) can be a compromise to reduce the boilerplate of "global settings" without hurting the explicitness goal discussed earlier.

In combination with the feature added in #15449 (which is paused for the moment while we get 0.10.0 released) this may allow configuration like the following:

# This is a DRAFT and not yet implemented; final syntax/behavior may differ

locals {
  config = {
    region = "${var.region}"
    # ...and any other settings you want to make available to modules...
  }
}

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

  # pass the whole config object in a single variable
  config = "${local.config}"
}

Passing around this sort of composite structure will inevitably increase coupling between caller and callee, so we'd still recommend that users do this sort of thing with care, but this at least opens up more options for concisely passing data between modules.

The other pattern we hope to allow with this feature is to invert dependencies by making it easier to pass around resources created elsewhere. Although this doesn't apply to the region use-case that motivated this issue, it could make it more convenient to write small, self-contained modules that compose together, which is tricky today:

# This is a DRAFT and not yet implemented; final syntax/behavior may differ

resource "aws_vpc" "example" {
  # ... vpc settings ...
}

module "security_groups" {
  source = "./security_groups"

  # pass the entire VPC object to the module, giving it access to the id, CIDR block, etc
  vpc = "${aws_vpc.example}"
}

As noted above, this is all just early design sketch right now and the details are likely to shift as we prototype and learn more, but the general hope is to foster new patterns that can reduce the amount of boilerplate while remaining consistent with the goals of keeping modules loosely coupled with each other and keeping module interactions explicit.

@rolandkool
Copy link

@keirbadger thanks for your example. I was doing the same and I now think I'm hitting some kind of bug with the kubernetes namespace resource. The labels don't seem to like the remote_state variables I pull in. Will raise a separate issue for that.

@ghost
Copy link

ghost commented Oct 3, 2017

Really looking forward to this feature and sad to see it still here. Can you kindly point me in the direction of what needs to be done to define some sort of include or module functionality like this? I thought of using templates but realize that they can only be assigned per parameter values, and not for the entire block. An include functionality would be nice for situations like these as well.

@petewilcock
Copy link

petewilcock commented Apr 5, 2018

I have a somewhat hacky workaround for this, but it's the best I've got for now:

I specify the variable I want to be global in my root module. In this example, I'm specifying the security group for an aws instance. I then set that variable to be an output of the root module:

output "aws_security_group" {
  value = "${var.aws_security_group}"
}

I then specify a data source for the root module's tfstate file:

data "terraform_remote_state" "root_module" {
  backend = "local"
  config {
    path = "../../../../terraform.tfstate"
  }
}

and then reference this in my modules like so:

aws_instance "instance" {
  region = "us-east-1"
  security_groups = ["${data.terraform_remote_state.root_module.aws_security_group}"]
}

Aside from feeling a bit janky, this relies on the root module being updated with variable changes (an apply that does nothing will achieve this), and that updated file being available immediately to the modules you want the change to ripple across.

+1 for a better way.

@ghost
Copy link

ghost commented Apr 5, 2018

@petewilcock Best workaround I've found is to use Ansible instead.

@accolver
Copy link

accolver commented Apr 5, 2018

I got around it by defining my global variables in my root directory and them symlinking them in my module.

ln -s ../../variables.tf ./global_variables.tf

This symlink lives along side my scoped variables.tf file in my module(s).

@ghost
Copy link

ghost commented Apr 5, 2018

@accolver How does that work for Windows users editing source files in Git?

@Jtaylorapps
Copy link

@accolver Your solution worked, but with absolute paths. Wish I didn't have to add a Makefile to the project in order to symlink all of my modules

@Alexhha
Copy link

Alexhha commented Jun 20, 2018

@apparentlymart

Have I correctly understood that your example from #5480 (comment) still haven't been implemented?

@o6uoq
Copy link

o6uoq commented Oct 3, 2018

+1

1 similar comment
@Stainless5792
Copy link

+1

@tdmalone
Copy link

tdmalone commented Nov 1, 2018

@o6uoq @humanchair Please use GitHub reactions rather than commenting with a +1. Comments add noise and additional notifications for those who are watching this issue, and they don’t actually contribute to Hashicorp’s internal issue trackers which do look at the reactions. Thanks!

@hammadzz
Copy link

@apparentlymart any update on when this can be expected? I see locals are in but am dying to be able to pass them through to a module soon.

@ispulkit
Copy link

ispulkit commented Dec 6, 2018

Waiting for updates. Dying to see this happening.

@ansoni
Copy link

ansoni commented Jan 9, 2019

Just wanted to post an idea. I would solve global variables issue using environment variables. Terraform would need accessors for this to work (We only have the TF_VAR_... pattern today). IMHO this keeps things pretty clean (environment variables are global to the runtime by nature).

env.PROJECT_NAME  # (could be referenced throughout the stack)
#or
"${getenv("PROJECT_NAME")}" # using a function

even cooler, could be a setter function that allows for promotion within the current run:

locals {
   export_project_name = "${setenv("PROJECT_NAME", var.project_name)}"
}

# ... in module code
variable "project_name" {
  default = "${getenv("PROJECT_NAME")}"
}

or 

locals {
  project_name = "${getenv("PROJECT_NAME")}"
}

@c33s
Copy link

c33s commented Jan 10, 2019

having global variables is really important to keep the code DRY, even if i dislike global variables because i prefer dependency injection with autowiring (https://symfony.com/doc/current/service_container/autowiring.html) having to pipe my global config, like my ssh key or my puppetmaster fqdn, ... to each module which needs it is unhandy.

the idea with environment variables is nice but is in fact the same as global variables only on a different level which also requires some kind of dotenv file to easily handle them accross multiple projects and environments on the developers machine (https://symfony.com/blog/new-in-symfony-4-2-define-env-vars-per-environment)

@mildwonkey
Copy link
Contributor

Hi all, thanks for the great discussion on this topic!

Terraform 0.12 supports complex objects for variables, and entire resources (and modules) can be used as inputs or outputs. The ability to pass objects between modules is meant to solve, or at least reduce, the need for global variables.
You can find more information in the module composition documentation.

Since we do not plan on adding global variables I am going to close this issue. Thanks again!

@ghost
Copy link

ghost commented Aug 13, 2019

I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues.

If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further.

@ghost ghost locked and limited conversation to collaborators Aug 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests