hashicorp / terraform

Terraform enables you to safely and predictably create, change, and improve infrastructure. It is a source-available tool that codifies APIs into declarative configuration files that can be shared amongst team members, treated as code, edited, reviewed, and versioned.
https://www.terraform.io
Other
42.76k stars 9.56k forks source link

terraform get: can't use variable in module source parameter? #1439

Closed amaczuga closed 7 years ago

amaczuga commented 9 years ago

I'm trying to avoid hard-coding module sources; the simplest approach would be:

variable "foo_module_source" {
  default = "github.com/thisisme/terraform-foo-module"
}

module "foo" {
  source = "${var.foo_module_source}"
}

The result I get while attempting to run terraform get -update is

Error loading Terraform: Error downloading modules: error downloading module 'file:///home/thisisme/terraform-env/${var.foo_module_source}': source path error: stat /home/thisisme/terraform-env/${var.foo_module_source}: no such file or directory
dcharbonnier commented 7 years ago

really annoying to not be able to set the authentication to a private git source in the tfvars, we end up with pre-terraformn scripts with sed like @xsellier but it's not what we expect from terraform. We already have this as a workaround for the remote state... one more...

mfischer-zd commented 7 years ago

Has anyone considered using a $HOME/.netrc file for the GitHub HTTP authentication use case? (I'm showing my age here, but sometimes ancient solutions still have modern applicability.) The go-getter library used to fetch modules runs the git command, which uses libcurl under the hood, which in turn supports the .netrc file out of the box.

Ginja commented 7 years ago

We fetch our modules from an S3 bucket, and it works pretty well. It uses the exported IAM credentials in our CI pipeline.

leigh507 commented 7 years ago

@mitchellh Any chance of getting an update on your thinking on this one? Really desperate for this feature, we want to create a proper CI pipeline for our terraform code. We would like to be able to release a single var file with the github branches that are good to move to prod.

ghost commented 7 years ago

Admittedly, we don't have a problem with the private module use case, since we haven't had issues using them via public key auth in CI pipelines:

module "private_repo" {
  source = "git::ssh://git@bitbucket.org/myproject/somerepo.git?ref=tags/v1.0
  username = "AzureDiamond"
  password = "hunter2"
}

However, parameterizing the tags is something we would love to do, but we end up having our wrapper scripts that install the SSH keys do a sed replacement, similar to what another comment mentioned.

This would be a nice thing to have, but I'd be interested in exactly how to do it. It would mean variable interpolation would need to happen before terraform get pulls the repo. Moreover, nested module retrieval would require interpolating, then pulling modules, and then interpolating those module variables before pulling the next module... and so forth recursively. Seems challenging. Given the graph changes that make #953 more doable, maybe this has hope.

GarinKartes commented 7 years ago

Parameterizing the source for modules seems like a must at this point for us. I am debating writing a script that will fill this value in and write a tf file for me since we can't have it interpolated.

module "workspace" {
  source = "git::ssh://...//v2/workspace/${var.app_name}?ref=${var.branch}"
}

this would allow the project/branch to be looked up dynamically from a shared file module

hflamboauto1 commented 7 years ago

My use case:

my_lambda_0.0.1-beta.zip is lambda function and code is packed and published into a blob server (e.g artifactory, or any http(s) url) with a custom file deploy.tf that allows to plug this into a terraform module.

deploy.tf only contains a helper terraform output to help to find/reference the expanded directory on .terraform/modules

output "path" {
  value = "${path.module}"  
}

terraform deploy

# trigger the download / unzip of distribution as "terraform module":

module "lambda_blob_url" {
  source = "http://127.0.0.1/blobs/lambdas/my_lambda_0.0.1-beta.zip"
}

versus

variable "blob_server" {
     default = "http://127.0.0.1/"
}

variable "blob_context" {
     default = "blobs/lambdas/"
}

variable "lambda_name" {
     default = "my_lambda"
}

variable "artifact_version" {
     default = "0.0.1-beta"
}
# trigger the download of distribution as "terraform module":

module "lambda_blob_url" {
  source = "${blob_server}/${blob_context}/${lambda_name}_${var.version}.zip"
}

...

zip it again

data "archive_file" "lambda_deployment_zip" {
    type        = "zip"
    source_dir  = "${module.lambda_blob_url.path}"
    output_path = "${path.module}/${var.lambda_name}-${var.lambda_version}.zip"
}

deploy it

# AWS Lambda function
resource "aws_lambda_function" "my_lambda" {
...
}
ju2wheels commented 7 years ago

+1 for module source parameterization. I really need a way to do this so I can customize the selected ref for dev, staging, prod etc. Not much of the fan of the other workarounds so im just going to create a common dot directory with all my shared module files and symlink all files into env specific folders with different tfvars for each environment.

Either way all of these workarounds are more work than is really necessary, in the long run, I dont particularly care if this becomes a runtime issue, the added effort on the end user doesnt justify keeping it the way it is IMO, and alternative solutions like Terragrunt arent really a solution if I need a third party addon that does nothing more than move these hard coded parts into tfvars.

pll commented 7 years ago

I too have the need to use variables in source = lines, which I've explained here: #14745

The short story: I want to use gitlab API tokens to access my repos via https like this:

module "sample_app" { source = "https://gitlab-ci-token:${var.API_TOKEN}@gitlab.com:/sample-project/sample-module.git?ref=master//terraform_code" ... }

kevinpostlewaite commented 7 years ago

+1 for module source parameterization, or otherwise specifying the location for a particular location for modules in only one single location. My use case isn't as complicated as the others here but it would definitely make it easier when I need to work on our in-house modules if I can specify the source location in a single location.

jedi4ever commented 7 years ago

+1 on this , my use case is switching between different branches for different environments and developing local

WalkerGriggs commented 7 years ago

+1 It has been said many times before, but source parameterization is crucial for keeping things DRY -- doubly so larger scale enterprise infrastructure.

fmartingr commented 7 years ago

+1 on this, my use case is to have a module with different variables per environment.

cmacrae commented 7 years ago

Checking in to express my interest, also.
For using private GitHub repos in the module source, it sucks to have to put the credentials in plain text. Looking forward to movement on this šŸ™‚

ketzacoatl commented 7 years ago

I haven't commented on this in a while, but have been following along. I have two things to share:

1) For me personally, I have found a way to not care too much about this issue but using git sub-trees and otherwise including my upstream TF repos in a /vendor path within each project repo. Honestly, it works and scales pretty well, but I also work with a lot of different clients/projects/repos, so there's some overhead involved (not enough for me to care, as this use of git still lets different branches of the env to modify the upstream repos or point the subtrees at different branches of those upstreams).

2) This issue is less about "allowing variables and interpolation in a module's source parameter", and it is more about having better support for versioning/branches with modules. One sub-group/use-case there is developer friendliness, and the other is about CI pipelines. It seems TF could address the "allow interpolation in source" by giving modules some of these capabilities - for example, if source is a git URL, there could be an optional module parameter git_revision, or similar. Perhaps that is a better direction to take this problem?

hflamboauto1 commented 7 years ago

I completely agree with @ketzacoatl and I had some similar thoughts as he explain on point 2 At least some "simple" interpolation features on module source level would help a bit at least.

apparentlymart commented 7 years ago

Hi all! Thanks for the great discussion here. I noticed there's been a lot of new discussion recently but it's been a while since we checked in.

As noted previously, there is a chicken-and-egg problem with allowing interpolations in module sources, since the interpolation language assumes a certain amount of work has already been done, which includes parsing and inspecting modules, etc. So with this in mind, I think the solution here must be something different than allowing interpolations into source, but I would like to find a good solution.

The two most common use-cases I see discussed in this issue are:

To me these feel like distinct use-cases that are worth solving separately.


The first of these could be addressed by what I'd proposed earlier (note: my previous comments were from before I was a Hashicorp employee) of providing a mechanism to override module source, either via some extra arguments to init or via a separate command like terraform get module.foo git://...?ref=test that can be run after init to override the default source for module.foo.

Overriding this via CLI actions rather than variables opens the door to third-party tools to help manage more complex/custom cases. As I'd noted before, I see this as similar to running go get github.com/hashicorp/terraform and then going into that package directory and switching branches using regular git commands. Similar approaches are used in other programming languages, either by exposing directly the git repository or providing a special command to install a named package/library from a non-default source.


For the second of these, we usually encourage passing credentials via non-configuration and non-variable means, such as environment variables, since that avoids them getting included in things like the plan files, console output, etc. Unfortunately for those not using git over protocols other than SSH, the story for authentication with environment variables is rather lacking. It is possible to do something with the GIT_ASKPASS environment variable to temporarily set a Credentials Helper, but indeed that's rather more work than ideal.

It seems like some more thinking is required on this problem, since we need to find a compromise that weighs keeping the credentials "out of band" while allowing different credentials per module, but in the mean time that non-ideal GIT_ASKPASS solution may pass as a workaround in simple cases where the same credentials are used for all modules hosted in private repositories.

mtougeron commented 7 years ago

The first use-case for different branches is the primary driver for my needs. Having an approach similar to the init and backend config would be good for us.

MichaelDeCorte commented 7 years ago

There's also the use case for local sources. Relative path's aren't the best in some cases. It would be better for absolute paths relative to TF-Home

pll commented 7 years ago

@apparentlymart,

Thanks for thinking about this a little more deeply here. I do not pretend to understand the internals of Terraform, or even Go for that matter, therefore, I don't understand the chicken-and-egg problem you're discussing (naively, it seems to me that tf should be able to eval it's variables files, recognize that there's a variable on the RHS of an equal sign, and appropriately fill in the blank :)

As for the credentials issue, this is a big on when using private Github and Gitlab installations and trying to checkout multiple repositories. As I explained above, in order for terraform get to be able to checkout my module when running in an automated pipeline container, the user must have credentials to access the git repo. The acceptable way to do this is to set an API_TOKEN environment variable which is inherited by the pipeline runner. As such, I need terrform to be able to evaluate something l like this:

module "sample_app" {
  source = "https://gitlab-ci-token:${var.API_TOKEN}@gitlab.com:/sample-project/sample-module.git?ref=master//terraform_code"
... 
}

such that API_TOKEN is pulled from the environment, or, even pulled from a variables file which is built on the fly by my automation code. Using something like GIT_ASKPASS is a non-started, since in an automated CI/CD pipeline scenario, there is no one to ask for said credentials. I suppose GIT_ASKPASS could be set to some external script to automatically pull the environment variable and pass it to git, but that seems rather hackish to me.

I guess I don't understand what's so special about the source = line. When I create a module, I'm passing all sorts of variable assignments to my module. Before tf can pass those variables to the module, it has to know the values on the RHS to assign. If it can evaluate variables on the RHS of parameters being passed into the module, why can't it evaluate the variables on the RHS of the source = line?

I am not asking this to be a jerk, I honestly don't know how TF does things behind the scenes, and I don't know Go either. It (again, naively) seems like it's a simple templating issue that I'm currently considering by using python and jinja2 to solve because I need this capability pretty badly.

Thanks.

Paul

apparentlymart commented 7 years ago

Indeed setting GIT_ASKPASS to a non-interactive script that reads credentials from some source was what I was proposing as a workaround. This is the solution Git provides for customizing its authentication mechanism, but I agree it's not very ergonomic for this use-case and would like to find something more user-friendly in the long run. Was just offering that as a path forward for those who need something now.


As for why interpolations can't work here, there are lots of different reasons why this is more complex than it first appears, but the primary one to consider is that variables aren't the only kind of interpolation expression, and so the interpolator expects to have enough context to be able to resolve things like aws_instance.foo.id and module.baz.bar.

You're right that in principle there could be an entirely different interpolation mode that only supports variables, but that's a pretty significant shift and lots of duplicated code, and doesn't really solve the problem anyway since the goal (as I've defined it, anyway) is for the credentials to not be in the URL where they would then appear in the console output, disclosing the secret to anyone who can see the console output.

Believe me that if doing this with interpolations were straightforward and did address the problem completely then this is definitely the solution I'd favor!

ju2wheels commented 7 years ago

The first of these could be addressed by what I'd proposed earlier (note: my previous comments were from before I was a Hashicorp employee) of providing a mechanism to override module source, either via some extra arguments to init or via a separate command like terraform get module.foo git://...?ref=test that can be run after init to override the default source for module.foo.

@apparentlymart please no. My teammates already want to rip my head off for the number of commands they have to run per module (init, env select, plan, apply). I would not want this as a separate command to add to this chain. I would rather have something like terraform init -backend-config <file> -module-config <file> and be able to do it all via init (even if internally it does use the get mechanics). This would allow me to get the same effect of being able to specify a ref for each module as if I had done it via a variable.

suneeta-mall commented 7 years ago

+1. This is currently blocking us too and using sed to workaround this! Would be nice to have this feature.

RobertFr0st commented 7 years ago
mukund1989 commented 7 years ago

pretty please!

mcoulombe636 commented 7 years ago

Not being able to interpolate module's source location is a huge deal breaker for me. Any decent sized project that wants reusable parts would need something more flexible than hardcoding the files' locations.

apparentlymart commented 7 years ago

Hi everyone! Thanks again for the great discussion here, and thanks to everyone for confirming and clarifying their different use-cases.

I've captured the two big ones we discussed before in their own issues, so we can have a more focused discussion about each of them separately:

The first of these has some notes about a possible design building on some internal discussions we'd had on this subject. The second doesn't currently have a design proposal attached, but we can discuss some more the requirements and see if we can get to one.


Regarding @MichaelDeCorte's use-case of module sources that aren't relative to the source module, it is intentional that modules are specified relative to one another when they appear in the local filesystem, since we believe that reduces coupling by avoiding the modules needing to "know" where their root is. At this time I don't expect we will make changes to support absolute paths here, since that would complicate the model and add more different situations to document and test.

However, if you believe you have a compelling use-case that would warrant the additional complexity here, please feel free to open a fresh issue for this with some more details on the use-case. It's easier to have discussions about these things with concrete examples to work from, since that way we can potentially find alternative implementations that solve some or all of the same problems with fewer drawbacks.


Given that there are now some more specific issues covering the main use-cases here, and this is progressing from general "thinking" to more specific design work, I'm going to close this issue and encourage further discussion in those issues. If anyone has an additional use-case not covered already in this comment then please do open a new issue referencing this one -- ideally following a similar format to the ones I opened above, focusing on a problem statement rather than a candidate solution -- and we'll then be able to have a more detailed discussion about each separate use-case and find the best approach for each.

Thanks again for all the discussion here! People sharing their use-cases is always very helpful to feel out the shape of a problem and explore solutions for it.

jjshoe commented 7 years ago

@apparentlymart how about when I want to use one method to find a module, and if not found, fall back to another?

Think local development, before I push my changes up to git.

edit: https://github.com/hashicorp/terraform/issues/1439#issuecomment-101150068

boostrack commented 7 years ago
If you need Terraform to be able to fetch modules from private GitHub repos on a remote machine (like Terraform Enterprise or a CI server), you'll need to provide Terraform with credentials that can be used to authenticate as a user with read access to the private repo.
First, create aĀ machine userĀ on GitHub with read access to the private repo in question, then embed this user's credentials into theĀ sourceĀ parameter:
module "private-infra" {
  source = "git::https://MACHINE-USER:MACHINE-PASS@github.com/org/privatemodules//modules/foo"
}

Note: Terraform does not yet support interpolations in the source field, so the machine username and password will have to be embedded directly into the source string. You can track GH-1439 to learn when this limitation is addressed.

SOURCE GH-1439

https://www.terraform.io/docs/modules/sources.html

@apparentlymart maybe the docs should not reference this "closed" issue

simplytunde commented 7 years ago

Yes, the issue is already closed but the Doc says to keep tracking the same issue. Any suggestion on what to put instead?

boostrack commented 7 years ago

maybe just remove ?

Note: Terraform does not yet support interpolations in the source field, so the machine username and password will have to be embedded directly into the source string. You can track GH-1439 to learn when this limitation is addressed.

SOURCE GH-1439

The worst than not docs is no accurate docs. maybe just that interpolations in the source field are not supported

apparentlymart commented 7 years ago

Hmm thanks for pointing that out, @boostrack. We don't normally make direct references to github from the website so I didn't even think to check for that. For the moment I'm just going to remove that link altogether since we don't have any good means to ensure that this sort of problem won't happen again, and github issues are too transient to be referenced in the long-lived documentation.

alevinetx commented 7 years ago

The note for lack of source interpolation is buried under the Private GitHub Repos section. Because this section doesn't apply to me, I missed this note the many times I was looking at the page.

May I suggest you call this restriction out at the top of the page in the generic summary?

TinajaLabs commented 7 years ago

Perhaps this is the wrong place to bring this up, but how is one to build a system where one can create abstract modules that call to either an AWS deployment or an Openstack deployment.

I need to be able to deploy the same infrastructure into either environment and I'm not sure how to structure it. This is the beauty of "providers", right?

I should be able to build custom modules that have references to instance_image (ami for aws, image_id for openstack) and instance_type (instance_type for aws, flavor_id for openstack).

It seems one should be able to build a set of custom modules where the source is either a set of aws modules, or openstack modules. Hence the ability to turn this on a root level variable for source definition.

Is everyone else using a completely separate code base for each type of infrastructure deployment? How ultimately tedious and error prone that would be.

ju2wheels commented 7 years ago

@TinajaLabs Thats a discussion for the Gitter channel: gitter.im/hashicorp-terraform/Lobby

dmikalova commented 7 years ago

Can a warning be added that interpolations aren't allowed since there should be a validation? Error loading modules: module name: invalid source string: ${path.root}/modules/name isn't very obvious at first.

luisdavim commented 6 years ago

I would like to at least be able to use variables to specify module versions

nik-shornikov commented 6 years ago

lack of interpolation is pretty stealthed-out in the doc and the error you get for trying is still opaque

pixelicous commented 6 years ago

Please implement this!! no more running away to use cases and finding workarounds, interpolation should have much greater support >.<

ketzacoatl commented 6 years ago

There are new issues to capture further discussion on more specific nuances of this topic.

pll commented 6 years ago

@ketzacoatl Can you please link to those new issues so we can follow those discussions?

Thanks.

pixelicous commented 6 years ago

@ketzacoatl i would love a reference to where this specific issue is solved, i need to interpolate the source of a module, is it possible to reference a different directory name based on a passed variable.. The variables should be graphed in a way that this would be attainable

luisdavim commented 6 years ago

We're maintaining a terraform wrapper to overcome some of these issues. We implement the Terrafile pattern. It would be a great improvement if terraform would support the Terrafile approach natively.

coryjamesfisher commented 6 years ago

I come from the software development side of the house and abstraction to an interface seems like a good move.

My ideal is to create an interface for a module representing one logical component of our infrastructure. Then each module worries about the provider specific details.

So for example I would create 2 modules: module/aws/webserver module/openstack/webserver Finally a main.tf file with: variable "cloud_provider" { default = "aws", description = "Pass aws or openstack" } ... source = module/${vars.cloud_provider}/web

I'm looking for interpolation to work in the module source path. If there is another way to do this I'd be happy to hear it. I have one idea of wrapping the Terraform script with a templating/replacement engine of my own driven by a shell script. But it'd be nice to have a solution in Terraform itself.

pixelicous commented 6 years ago

@coryjamesfisher Same issue as you here.. couldn't find a workaround besides using third party tools to handle this problem, which is really ugly.

I seriously do not understand the problem, there should be an option to init with the variable or based upon folder structure.. most of the workarounds and issues are caused due to this.. funny thing now when i started using workspaces, you cant even use the workspace name as interpolation for a default of a variable.. very ugly.. need to go through a local..

eliblight commented 6 years ago

+1 Here is my use case (would love to hear about a work around)

I have multiple projects/deployments, each needs to be deployed on multiple regions/zones.

following github.com/kung-foo/multiregion-terraform/blob/master/main.tf, I adopted the module per region approch. For a single project this looks like this:

module "us-west1" { source = "per-region" region = "us-west1" zones = ["us-west1-a", "us-west1-b", "us-west1-c"] } ...

- per-region/main.tf

provider "google" { region = "${var.region}" project = "a-single-project" }

resource "google_compute_instance" "gci" { count = "${length(var.zones)}" zone = "${var.zones[count.index]}" ...

- per-region/variables.tf

variable "region" {}

variable "zones" { type = "list" }


**BUT** if I have multiple project, each and eveyone of them will need the main regions and zones file.

So I wanted to have the following workaround
- project-a/main.tf

module "deployment" { source = "../topology" dir = "../project-a/per-region" }

- project-a/per-region/main.tf (same as before)
- project-a/per-region/variables.tf (same as before)
- topology/main.tf

module "us-east4" { source = "${var.dir}" region = "us-east4" zones = ["us-east4-a", "us-east4-b"] }

module "us-west1" { source = "${var.dir}" region = "us-west1" zones = ["us-west1-a", "us-west1-b", "us-west1-c"] }

- topology/variables.tf

variable "dir" {}



each project follows the same pattern
- project-X/main.tf - side stepping to topology and back
- project-X/per-region - specific deployment of each project per region

Alas this does not work.

Any workarounds / suggestions?
Binternet commented 6 years ago

+1 for that, I am organizing my AWS infrastructure to modules and trying to define each module path under /services folder. instead of hard-coding /services path in each module source parameter, it should be set as a variable.

midacts commented 6 years ago

Has this been implemented yet or on the books to be implemented?

nbering commented 6 years ago

To recap, here's the closing comment from @apparentlymart.

https://github.com/hashicorp/terraform/issues/1439#issuecomment-317085508

When closed, he split this issue into #15613 and #15614.

Also in https://github.com/hashicorp/terraform/issues/1439#issuecomment-91254101 @mitchellh, indicated that this is one of the places where variables cannot be used (by design) because of the execution order of various steps. I can't find anything to indicate that this was considered for change.

lorengordon commented 6 years ago

Personally I'd love to see interpolation for the entire source parameter. The chosen direction to implement support for just the version is very limiting. I'd rather like to pull all my source definitions to the top of a configuration, in a locals definition, so I don't have to go hunting through every file to find/update the string. I expect it would make modules much more maintainable overall.