gruntwork-io / terragrunt

Terragrunt is a flexible orchestration tool that allows Infrastructure as Code written in OpenTofu/Terraform to scale.
https://terragrunt.gruntwork.io/
MIT License
8.02k stars 971 forks source link

Upgrading to Terraform 0.12: separate configuration file for Terragrunt? #466

Closed apparentlymart closed 5 years ago

apparentlymart commented 6 years ago

Hi! I'm one of the engineers at HashiCorp who works on Terraform Core.

As you might be aware, we've been working for some time now on various improvements to the Terraform configuration language, to address a number of usability problems that have been reported over the years. The new changes are broadly compatible with existing configurations, and particularly those using the common idioms reflected in the Terraform documentation.

Unfortunately resolving some of these problems required tightening up some things that were previously rather loose. Current and previous versions of Terraform ignore certain things that are considered invalid, often producing surprising results, and so to improve usability the new implementation has some more error checking in order to catch mistakes and produce error messages that are hopefully more actionable.

We're planning to include a migration helper tool in the next release in order to help detect and fix some of these rough edges. The details of what this tool does will be largely informed by real-world usage, and so we're planning to release an early preview of the new implementation as soon as it is in a usable state in the hope that we can gather as many examples as possible of patterns in the wild that the migration helper tool should be looking for.

With all of that context in mind, we know that there are lots of users wrapping Terraform with Terragrunt, and so we want to make sure there's a good upgrade path. I'm opening this issue to discuss a specific problem I've noticed, but I'd like to discuss any other potential hurdles too, in whatever forum makes sense.


Terragrunt currently uses an extension of the terraform.tfvars file as its own configuration file, relying on the fact that Terraform itself ignores definitions in that file that are not defined as variables, and then using a special variable name terragrunt whose value is not, by Terraform's rules, valid: it includes mixed-typed nested structures, has nested blocks, etc.

The new implementation has a few differences that will cause friction here if we proceed naively:

Looking at the "live infrastructure" example, a naive syntax-level pass of migration would produce the following rewritten version:

# Terragrunt is a thin wrapper for Terraform that provides extra tools for working with multiple Terraform modules,
# remote state, and locking: https://github.com/gruntwork-io/terragrunt
terragrunt = {
  # Configure Terragrunt to automatically store tfstate files in an S3 bucket
  remote_state = {
    backend = "s3"
    config = {
      encrypt        = true
      bucket         = "terragrunt-example-prod-terraform-state"
      key            = "$${path_relative_to_include()}/terraform.tfstate"
      region         = "us-west-2"
      dynamodb_table = "terraform-locks"
    }
  }
  # Configure root level variables that all resources can inherit
  terraform = {
    extra_arguments = {
      # (this part will need to do some heuristic processing to decide whether to use
      # direct objects or lists of objects, mimicking the behavior of the current hcl.Decode
      # into an interface{}, so the details may not exactly match this in practice.)
      bucket = {
        commands = ["$${get_terraform_commands_that_need_vars()}"]
        optional_var_files = [
          "$${get_tfvars_dir()}/$${find_in_parent_folders("account.tfvars", "ignore")}"
        ]
      }
    }
  }
}

Since Terragrunt is using the HCL parser directly to process its configuration file today, it has the freedom to interpret the tree of HCL AST objects inside here as desired, and so I expect there are expectations here that are different than how Terraform generally deals with HCL structures it has no specific schema for. Also, due to the fact that interpolation syntax is now accepted in all quoted strings (variables and functions are just not available in some contexts), the naive migration tool will escape the ${ sequences under the assumption that the user intended them to be treated literally, since that was Terraform's own interpretation of a .tfvars file before this change.

Therefore it feels to me like the most practical path to take here is for our migration tool to have a special case for a variable called terragrunt (possibly activated only if there is no variable "terragrunt" block in configuration) which can then have some light understanding of Terragrunt's configuration structure and make some allowances for it so that we can avoid unintentional breakage of the Terragrunt configuration.

Terragrunt is currently relying on syntax features that are considered invalid in the new implementation, so leaving the configuration untouched by the migration tool would inevitably lead to syntax errors during parsing.


In order to avoid conflicts between Terraform's and Terragrunt's interpretation of the same file, I'd like to propose having Terragrunt look for a Terragrunt-specific configuration filename (e.g. terragrunt.conf) in preference to terraform.tfvars, and then Terraform would entirely ignore this file and leave Terragrunt's own parser to deal with it. Terragrunt could continue to use the original implementation of HCL, avoiding the need to deal with any of the new processing rules from the new implementation.

If we were to go down this path, we could build the configuration migration tool so that its special case for the terragrunt variable name is to migrate the contents out into a terragrunt.conf file that retains the block structure and original-HCL syntax that Terragrunt is expecting, and then deletes this value from the terraform.tfvars file altogether so that Terraform itself won't try to parse it.

I understand that this is a pretty drastic shift in the design and usage of Terragrunt, so I wanted to start talking about this early so we have some time to think through the implications and any other alternative approaches. I hope that together we can find a reasonable path forward!

brikis98 commented 6 years ago

Hi @apparentlymart! First, a big thank you for checking in to make sure there's a migration path for Terragrunt! 🍺

Second, could you provide more details on what HCL and Terraform changes are coming? Terragrunt exists as a quick way to fill in gaps in Terraform, but as Terraform fills in those gaps itself, we're only too happy to reduce the need for Terragrunt, and hopefully have it go away entirely in the future.

Once that's clear, we can think about whether changing the config format or moving to a different config file makes more sense.

apparentlymart commented 6 years ago

Hi @brikis98!

We will have a comprehensive summary of all of the planned configuration changes coming soon. This is in progress but is waiting for the early preview release to be more complete before we finalize it so we can ensure we're describing its true scope/behavior. I wanted to get this conversation started early in case some adjustments need to be made either to Terraform itself (prior to the final release) or to Terragrunt.

However, I can say that the focus of this release is on configuration changes rather than workflow changes, and so I believe all of Terragrunt's use-cases continue to make sense for the moment. Once the configuration language is in good shape we are planning to divert our attention to workflow for a while, at which point we'll definitely be interested in discussing if some or all of Terragrunt's features make sense for incorporation into Terraform. There'll be at least one more major release cycle before any changes that might reduce the scope of Terragrunt, and so I think it's worthwhile to ensure there's a viable path forward .

If you'd rather pause this conversation until we have the more complete digest of changes ready, I'm happy to do so. I know it's hard to discuss a hypothetical situation. We're working on getting the new configuration language implementation integrated right now, and once we're at a point where we're back to feature-complete (bugs notwithstanding) I'll be able to run some real Terragrunt configurations through it and get a more concrete sense of how it behaves and whether some more surgical fixes might be possible.

My main motivation with this proposal was to try to minimize the need for tight co-ordination between the Terraform release and any Terragrunt changes, and that was why I lent towards a separate file as a way to leave Terragrunt in control of its own destiny with regards to its configuration parsing, rather than having a single file that needs to be understandable both to Terraform's new parser (based on HCL2) and Terragrunt's existing parser (based on the original HCL implementation).

brikis98 commented 6 years ago

However, I can say that the focus of this release is on configuration changes rather than workflow changes, and so I believe all of Terragrunt's use-cases continue to make sense for the moment. Once the configuration language is in good shape we are planning to divert our attention to workflow for a while, at which point we'll definitely be interested in discussing if some or all of Terragrunt's features make sense for incorporation into Terraform. There'll be at least one more major release cycle before any changes that might reduce the scope of Terragrunt, and so I think it's worthwhile to ensure there's a viable path forward .

Understood. Thx for providing the context.

My main motivation with this proposal was to try to minimize the need for tight co-ordination between the Terraform release and any Terragrunt changes, and that was why I lent towards a separate file as a way to leave Terragrunt in control of its own destiny with regards to its configuration parsing, rather than having a single file that needs to be understandable both to Terraform's new parser (based on HCL2) and Terragrunt's existing parser (based on the original HCL implementation).

The main concern I have with a separate file is that it will double the number of files necessary to use Terragrunt. One of the main complaints we have is that there are already too many files/folders to manage, so I'm a bit hesitant to make it worse.

I'm going to take a minute to think out loud and throw out some ideas:

  1. One of the primary use cases for Terragrunt "remote Terraform configurations", where you can have a single, versioned definition of your Terraform module in one place and deploy that module across environments by defining a separate terraform.tfvars file for each environment (see, Keep your Terraform code DRY). With this use case, Terragrunt checks out your Terraform code into a temp folder, copies the terraform.tfvars file into that temp folder, and then runs terraform. With this use case, it would be easy for Terragrunt to remove the terragrunt-specific code from terraform.tfvars before running terraform. This allows us to do whatever we want in terraform.tfvars and decouples the release.

  2. The approach above doesn't solve the use case for people who don't use the remote Terraform configurations feature. We could update Terragrunt to always copy code to a temp folder before running; might be a bit clunky and have side effects.

  3. If the HCL2 parser supports parsing of interpolation, that would actually be a great help, as currently, Terragrunt uses the HCL parser to read the general structure of the .tfvars file, but processes interpolations through hacky, sometimes buggy regular expressions 😁

  4. On the other hand, while the structural changes to the Terragrunt configuration you posted (mainly adding = in a few places?) all seem fairly minor, the double dollar sign for interpolation is a bit annoying and would not allow Terragrunt to use HCL2 to parse the interpolations.

  5. On the topic of interpolations, what will be allowed in terraform.tfvars?

lorengordon commented 6 years ago

The main concern I have with a separate file is that it will double the number of files necessary to use Terragrunt. One of the main complaints we have is that there are already too many files/folders to manage, so I'm a bit hesitant to make it worse.

fwiw, I know I'd have no problem using a separate config file. I rather like the clarity of separating the two more cleanly. Personally, I don't use terraform.tfvars with terragrunt to also define terraform variables anyway, since I like maintaining the separation of concerns. Instead, I use a separate file <foo>.auto.tfvars, which terraform reads automatically...

And technically, a separate config file wouldn't necessarily double the number of files, since terraform.tfvars would no longer be required...

How about a poll, maybe just using reactions on the issue, or perhaps publishing a poll through the newsletter?

brikis98 commented 6 years ago

And technically, a separate config file wouldn't necessarily double the number of files, since terraform.tfvars would no longer be required...

How so? You still need to configure Terragrunt, so that's one file. I suppose if your module needed no variables, then there wouldn't be a second, but that doesn't seem like a common use case.

lorengordon commented 6 years ago

Pass the vars inside the terragrunt config, using extra_arguments. :)

brikis98 commented 6 years ago

Ah, I gotcha. That's something worth considering, I suppose. Terragrunt as it's own, completely separate format for (a) downloading Terraform configurations from somewhere, (b) configuring remote state for them, and (c) running apply with a bunch of arguments passed using -var...

Then we're decoupled entirely from Terraform's .tfvars files. I'll have to think on this some more.

apparentlymart commented 6 years ago

Thanks for sharing that extra context, @brikis98! I didn't fully understand the "remote Terraform configurations" workflow on my first pass, but I think I have a better handle on what's going on there now, and can see why a Terragrunt-specific file would -- without other changes -- lead to needing two separate files, where only one is needed today.

The idea of including an extra map of variables inside the Terragrunt file sounds like a potential compromise, though we'd need to think through what the configuration migration tool should do here in order to leave the user with a valid Terragrunt configuration. That is, should it (when a terragrunt definition is present) also move all of the variables from the terraform.tfvars file into the Terragrunt-specific file? Or perhaps it must recognize when the "remote configurations" feature is in use and behave differently in that case?


Some additional background on the situation with interpolations in the .tfvars file:

The new "HCL2" implementation has interpolation built into the core language, rather than treating it as a separate processing pass (with the HIL library) on the strings produced by the HCL parser. A consequence of this is that interpolations are parsed during the initial parsing step, and so the AST for them (but not the value) is built much earlier than it would've been under the prior implementation.

An unintended consequence of this is that the .tfvars files, which are also read using the HCL2 parser, can now include expressions and interpolations, since that step is not separable from the structural parsing:

foo = "bar"
bar = "${ 1 + 2 }"
# HCL2 also allows "naked" expressions, without the interpolation markers
baz = 1 + 2

However, Terraform must still evaluate these at a stage where no variables or functions are available, so in practice the interpolations are limited to just basic computations on literal values. I think in practice there's little reason to use expressions in these files, and so users are unlikely to make use of this capability, but it's just a natural consequence of the design of the new parser.

# this will produce a "variables are not allowed here" error message
baz = "${any.variable}"

Because of this, the migration tool will need to conservatively escape any ${ sequences already present in existing files in order to ensure that they are interpreted the same way in the new parser as they would've been in the old parser. (unless we make some sort of exception for the terragrunt variable, of course.)

Another important consequence of the earlier handling of these is that Terraform will evaluate the given expression early in its initialization, when it produces the map of available input variables, since expression evaluation is how attribute values are read in HCL2. This means that a "function calls are not allowed here" message would be produced if given an unescaped Terragrunt configuration with the Terragrunt-specific function calls in it.

brikis98 commented 6 years ago

I've been thinking about this a bit more and moving to a separate file, along with some changes to Terragrunts "ergonomics", may make sense.

Here's a rough idea of what a typical "child" terragrunt.hcl may look like:

# Include all the settings from another Terragrunt config
include = "${find_in_parent_folders()}"

# The Terraform code to use. If not specified, it's assumed the code is in pwd.
source = "git::git@github.com:foo/modules.git//app?ref=v0.0.3"

# Variables to pass to the Terraform code
inputs = {
  instance_type  = "t2.micro"
  instance_count = 5

  # A new helper to get output variables from other Terragrunt / Terraform modules
  # This implicitly creates dependencies that will be respected by the apply-all command
  vpc_id = "${get_output("../vpc", "vpc_id")}"

  # A new helper to get input variables from other Terragrunt HCL files to help keep things DRY
  account_id = "${get_input("../account.hcl", "account_id")}" 
}

# Keep your CLI flags DRY
extra_arguments = {
  retry_lock = {
    commands  = ["${get_terraform_commands_that_need_locking()}"]
    arguments = ["-lock-timeout=20m"]
  }
}

# Keep your remote state config DRY
remote_state {
  backend = "s3"
  config {
    bucket = "my-terraform-bucket"
    region = "us-east-1"
    key    = "${path_relative_to_include()}/terraform.tfstate"
  }
}

The main changes are:

  1. The config lives in a separate .hcl file. The default name should be terragrunt.hcl.
  2. Most of the config settings are now flattened, top-level items to avoid unnecessary nesting.
  3. The variables that used to be listed directly in terraform.tfvars now go in a variables = { ... } block.
  4. There are new helpers to read variables from other Terragrunt .hcl files, which have their own variables = { ... } blocks, and new helpers to read outputs, by running terraform output or terragrunt output, from other Terraform/Terragrunt modules.

Note that this requires code changes in Terragrunt itself. We don't need all of these for it to be useful, but at the very least, what file it reads, and the structure of that file would need to be available immediately. The new helpers could come later.

I'd love to hear feedback from any Terragrunt users.

Also, @apparentlymart, would we be able to use the HCL2 parser to parse the new format above? Would it parse the "interpolations" and give us back a reasonable AST?

brikis98 commented 6 years ago

@apparentlymart What is the rough timeline for HCL2?

lorengordon commented 6 years ago

Seems reasonable to me!

I suppose the new "hooks" feature would be specified something like extra_arguments? But as a list of dictionaries, or an ordered dictionary, since execution order might matter... Not sure I have the syntax right for that in HCL2, but maybe:


after_hooks = [
  "foo" = {
    commands = [...]
    execute = [...]
  }

  "bar" = {
    commands = [...]
    execute = [...]
  }

before_hooks = [
  "abc" = {
    commands = [...]
    execute = [...]
  }

  "bcd" = {
    commands = [...]
    execute = [...]
  }
]
brikis98 commented 6 years ago

Ah, right, I left out hooks, and yes, they would look exactly like that.

apparentlymart commented 6 years ago

That looks great to me, @brikis98! The HCL2 parser should be able to accept that format, though I'd make some small adjustments to some details for "idiomatic" HCL2:

# No need to wrap a single expression in interpolation
include = find_in_parent_folders()

source = "git::git@github.com:foo/modules.git//app?ref=v0.0.3"

# Fixed elements (part of the structure of the format) tend to be defined as blocks rather than
# attributes, so no = sign here.
inputs {
  instance_type  = "t2.micro"
  instance_count = 5
  vpc_id         = get_output("../vpc", "vpc_id")
  account_id     = get_input("../account.hcl", "account_id")
}

extra_arguments {
  retry_lock {
    commands  = get_terraform_commands_that_need_locking()
    arguments = ["-lock-timeout=20m"]
  }
}

remote_state {
  backend = "s3"
  config {
    bucket = "my-terraform-bucket"
    region = "us-east-1"
    key    = "${path_relative_to_include()}/terraform.tfstate"
  }
}

# Not exactly sure about these, but their structure suggests something I'd model
# with repeated nested blocks rather than single attributes.
hook_after "foo" {
  commands = [ ... ]
  execute  = [ ... ]
}
hook_before "abc" {
  commands = [ ... ]
  execute  = [ ... ]
}

We're not promising compatibility for the HCL2 package APIs until some time after Terraform is released using it, in case we find design flaws during implementation in Terraform. With that said, we've already completed most of the work in Terraform that interacts with the HCL2 parsing APIs, and so in practice significant changes from here on out are not very likely.

The structure here seems rigid enough that it could probably be processed using the higher-level gohcl package, which uses Go's reflect package to drive decoding via structs. (Terraform's own processing is a little more involved and so it goes to the lower-level API so it can evaluate things gradually during the graph walk.) Since Terraform's needs are the main thing driving HCL2 development right now the gohcl package is not as well documented as it ought to be, but I think its complete enough to be able to process the format you described here, using DecodeBody with a set of structs that represent the intended structure, using functions defined in the EvalContext. Either way, the first step is to parse a file to get a "body" using the parser API.

We are currently in the process of updating Terraform Core to support HCL2's new type system, and so getting to the other end of that is the main blocker for us releasing the initial preview release, and there's still quite a lot of work to do here. How long it'll be until the final release after that depends on what sort of feedback we get from that preview release, but making time for the usual beta and release candidate process we do for any major release means that it'll be another couple months at least before we'll be final here. (The work is driving the schedule rather than the other way around, so unfortunately I can't be more precise at this stage.)

brikis98 commented 6 years ago

Thanks for the feedback @apparentlymart. The new syntax looks really nice, especially for simple expressions/interpolations 👍

I think I'm on board with moving to this format. We're a bit overloaded now, so probably won't be able to dig into this for at least a few more weeks, but it sounds like Terraform moving to HCL2 is still a little ways down the road, so I think we have time.

Two more questions for you:

  1. You mentioned some sort of migration tool. Are you building something to parse terraform.tfvars and transform it accordingly? If so, it would be very helpful if there was a way to use that tool to detect terraform.tfvars files with a terragrunt = { ... } block and transform them to the new terraform.hcl format automatically.

  2. Do the HCL2 libraries support parsing only, or also modifying the AST in memory, and writing it back out? The original HCL library could do pretty-printing of the AST, but not if you made any modifications to it.

apparentlymart commented 6 years ago

The planned migration tool is primarily for updating .tf files, but we were planning to also have it do some simple processing on .tfvars files just to deal with the fact that the HCL2 parser will now interpret ${ sequences in there. We had honestly not planned to do any super-complex processing of .tfvars files, but if there's a reasonably straightforward and well-defined way to map a terragrunt variable assignment onto a terraform.hcl file I'm certainly game to try it in order to smooth the transition here.

The original HCL parser did, as you noted, have a few issues with the generation portion. For HCL2, given the increased complexity of handling both structural and expression parsing in both HCL native and JSON syntaxes, we decided not to build writing support into the main API. Instead, we have a separate package hclwrite which offers an API more tailored for generation and for making surgical changes to existing files. However, this package is not yet fully-functional since again Terraform's needs are driving the priorities for now and the terraform fmt needs can be mostly met by the relatively-simpler Format function. The hclwrite package design and functionality will be finished up in due course, probably driven by some configuration-generation use-cases like hashicorp/terraform#15608.

For the sake of the migration helper tool, we are planning to generate HCL2 syntax output in a more template-ish way, rather than generating it with the hclwrite package, just because the needs there are pretty constrained and it reduces the size of the work required for what will hopefully be a pretty short-lived codepath. Our prototypes so far produced roughly-formatted HCL syntax using templates and then ran the result through Format to get back to idiomatic style.

Indigenuity commented 6 years ago

A question on the inputs block--will all inputs blocks be be merged from all included terragrunt.hcl files? Seems like that would make it so you could reference those values similarly to terraform's locals, like ${input.myvalue} rather than having to specify what file it comes from with ${get_input("my_terragrunt_source", "myvalue")}. Or was get_input intended to access more than just values in the inputs block?

brikis98 commented 6 years ago

A question on the inputs block--will all inputs blocks be be merged from all included terragrunt.hcl files?

Good question. Currently, Terragrunt only allows you to include a single file. I suppose inputs would be merged from that one file.

Seems like that would make it so you could reference those values similarly to terraform's locals, like ${input.myvalue}

An interesting idea. I hadn't thought through what the syntax would be likely to reference inputs defined in the current file or imported from the parent. We could use get_input("myvalue") to indicate the current file or input.myvalue.

Indigenuity commented 6 years ago

Oh I was under the impression you could have more than a single layer of includes. Is that not what this test does? https://github.com/gruntwork-io/terragrunt/tree/master/test/fixture-parent-folders/multiple-terragrunt-in-parents

ebarault commented 6 years ago

just as @lorengordon explained, i also separate the concerns by making sure any variable required by my modules are placed in separate files pulled by terragrunt using extra_arguments as in

    extra_arguments "conditional_vars" {
      commands = ["${get_terraform_commands_that_need_vars()}"]

      required_var_files = [
        "${get_tfvars_dir()}/${find_in_parent_folders("global.tfvars")}",
        "${get_tfvars_dir()}/${find_in_parent_folders("env.tfvars")}"
      ]

      optional_var_files = [
        "${get_tfvars_dir()}/component.tfvars"
      ]
    }

Having all the variables in a terragrunt/terraform-logic-free separate file enables a clear separation between the modules code and the variables and eases their use by ops users on a day-to-day basis (read: I build modules and set up overall solutions <--> end-users fine-tunes the variables)

So i won't use the new input block much.

Reading the lines in this thread, i expect this won't be impacted much by the proposed changes so I have no concerns to use a separate terragrunt config file.


Now, regarding the new helper get_output(), I wonder: could we make this interpolation function work inside the .tfvars files used to store the modules variables only?

I guess the separation of concerns postulate would dictate along these lines not to interfere with terraform files anymore, then it would be more a question of an additional feature to add to terraform's core, and the proposed terragrunt implementation should be seen as an interim solution until then.

Thoughts?

lorengordon commented 6 years ago

@ebarault I'd love to see some interpolation in .tfvars files. Looking up ENV values, and many of the "data" resources would be killer. I'd especially like to be able to retrieve values from the SSM Parameter Store. It's quite a pain to expose these implementation-specific "value lookup" constructs in a module (especially if you publish it publicly). Getting this in terraform core would be awesome! 👍 👍 👍

ebarault commented 6 years ago

as of now, i use terragrunt hooks to interpolate "remote" vars in .tfvars files: more specifically all vars nested in more or less complex data structures than cannot be easily used with remote data sources and current terraform's HCL implementation.

I'm yet not quite sure how much terraform's next major release will break this.

jbergknoff commented 6 years ago

Has there been any progress on this? It sounds like TF 0.12 will be released within the next month or so. It would be great to have a migration path for continuing to use Terragrunt with it.

brikis98 commented 6 years ago

No progress yet.

apparentlymart commented 6 years ago

Terraform 0.12 will go through an alpha release, at least one beta release, and at least one release candidate before final, which'll add up to at least a month and a half of release process, so please don't worry about it just surprisingly appearing one day!

The terragrunt-specific configuration migration mechanism we discussed in this thread is unlikely to make it in before the alpha, because the focus of the alpha is in getting early feedback from maintainers of re-usable modules we don't have access to (because they are in private repositories), but the Terraform side of this is definitely still on our radar and likely to go in during a beta, assuming what we discussed above still seems approximately right (we can make small tweaks before final, of course).

lorengordon commented 5 years ago

Looks like there is a new, and pretty stable beta release of tf 0.12. Probably at a good place to start testing whether any changes might be needed to terragrunt itself.

apparentlymart commented 5 years ago

We didn't get a chance to put in terragrunt variables migration yet because we wanted to focus on the main config language migration, but I hope we can still include something for this before rc1 if we have a specific format to target. Could also do it in a minor release later if necessary, though.

lorengordon commented 5 years ago

Do we know the minimum changes needed to user's terraform.tfvars files, and whether those changes will require any updates to terragrunt itself?

apparentlymart commented 5 years ago

Hello again! :wave: It's been a while.

We're feeling close to final 0.12 release now, so I'm pondering what's best to do with what we discussed in this issue.

One option would be to work on upgrade rules for the terraform 0.12upgrade command that can transform a terragrunt pseudo-variable in a .tfvars file into one of the forms we discussed above, though if we do that before Terragrunt supports it I expect it will be confusing and may also impose constraints on a future implementation of loading that format in Terragrunt, since it would need to work with what Terraform generates even though we have no implementation to currently test it against.

A more straightforward change we could try instead is to detect if at least one .tfvars file contains a terragrunt definition that doesn't have a corresponding variable "terragrunt" block in configuration, and just produce a new terragrunt.tf file containing the following content:

variable "terragrunt" {
  type        = any
  description = "Configuration for terragrunt."
}

This should allow Terraform to accept and ignore a terragrunt definition in a .tfvars file without requiring significant changes to it, but it does still leave us with the problem that terraform 0.12upgrade is likely to make the terragrunt object not parseable by Terragrunt by escaping ${ sequences in the strings.

I think the most straightforward answer would be if Terragrunt were able to be configured to read its settings in the current format from some file other than terraform.tfvars that Terraform won't try to read by default, and then we could potentially make the upgrade codepath write the existing setting out verbatim (preserving the HCL 1 syntax using HCL 1's printer) into whatever other file makes sense, to create a short-term compromise: Terraform and Terragrunt look in separate files as I was proposing here, but we don't need to block on defining a whole new file format for the separate file.

We should still have at least a few weeks left before final to see if we can figure something out here. My preference would be to make a relatively small, straightforward change now, to minimize risk on both sides, and then allow Terragrunt's configuration format to evolve as appropriate separately later as you see fit. But mainly I want to be pragmatic here and find any reasonable way that'll allow Terragrunt users to migrate as gracefully as possible to Terraform 0.12. If you have other ideas, please let me know!

brikis98 commented 5 years ago

@apparentlymart Thanks for remembering us and checking in again! I think your idea of moving the Terragrunt config to a separate file, but in its old format, is a good one. That simplifies the upgrades we'll initially have to do to Terragrunt to support Terraform 0.12. I think a terragrunt.hcl file is probably a reasonable target?

apparentlymart commented 5 years ago

Sounds reasonable to me! So, to make sure we're saying the same thing:

The terraform 0.12upgrade tool would look in all of the .tfvars files in the target directory (or maybe just terraform.tfvars?) and check for a definition for a variable named terragrunt. If that variable is not also declared using a variable "terragrunt" block (unlikely, but possible) then it will generate a file called terragrunt.hcl in the same directory and use the HCL 1 printer to print out as accurately as possible the string terragrunt = followed by the AST that was assigned to the variable in the vars file.

If that sounds reasonable, I'll note that in our list of remaining tasks before final release and then, unless some other big issue crops up that we need to prioritize, we'll get that done for the final release.

brikis98 commented 5 years ago

Yes, I think that sounds right, but one issue just occurred to me... One of the key benefits of piggybacking on terraform.tfvars was that you could also define the values to set for the variables in your module, and Terraform would pick those up automatically. If we move the Terragrunt configuration to terragrunt.hcl, then you'll either need two files in every folder (and file sprawl is already a pain), or we'd need to implement extra logic to take the variables in terragrunt.hcl and forward them to Terraform (e.g., via -var).

In the ideas we wrote up way above, this meant moving those variables into an inputs { ... } block. But that brings us back to rewriting the code... Which isn't necessarily all that simple, right?

apparentlymart commented 5 years ago

My instinct is to keep separate things separate: variables are for Terraform, and Terragrunt's configuration is for Terragrunt. Having one fewer file in the directory doesn't seem like a huge payoff for the complexity of having a single file be parsed in two completing ways by two pieces of software. :thinking:

Could we move forward with the proposed compromise in the short term, accepting that it means two files where there was only one before, and then look at ways for Terragrunt to forward variables from the terragrunt.hcl into Terraform itself as a separate step? I feel like once we have decoupled these two concerns it'll be much easier to make gradual incremental usability improvements on both sides, rather than us having to do heavy coordination like this every time.

brikis98 commented 5 years ago

Two files rather than one is no big deal. Four hundred files rather than two hundred files is. As the project scales up, doubling the number of files becomes a serious problem. I think it would be a significant step backwards to do that from a usability / maintenance perspective. Willing to hear other opinions from the community though.

apparentlymart commented 5 years ago

I'd also be interested to learn more about this! I've not typically experienced number of files (specifically when split over many separate directories, not all in one directory) causing a problem; my concern is usually more about total size of all of the files summed together, which I think wouldn't be significantly larger here aside from some additional filesystem metadata.

With that said, if we can find a different way to go here which has similar effort to what we've been discussing so far then I'm all for it. (Keeping in mind that if we do nothing then Terragrunt won't work with Terraform 0.12 at all, which seems worse.)

lorengordon commented 5 years ago

It's the same amount of data, 1 file or 2. No difference in my opinion. If the number of files is an issue for others, one option would be to support a single file or single directory of files that terragrunt reads, which contains the terragrunt config for the entire project.

cyrus-mc commented 5 years ago

Are there any current work-arounds to get Terragrunt working with Terraform 0.12? TF 0.12 is in rc1 right now (something I have been waiting months for), only to find that it doesn't work with Terragrunt yet.

ebarault commented 5 years ago

@brikis98, @apparentlymart

One of the key benefits of piggybacking on terraform.tfvars was that you could also define the values to set for the variables in your module

I already use separate files for terragrunt config and terrraform vars like so:

image

So going from a terraform.tfvars file to a terragrunt.hcl file for the terragrunt configuration is really straight forward in my case. ⚠️ Although i want to make sure that my component.tfvars file as well as common *.tfvars files defined higher in the project tree are not an issue for the terraform 0.12upgrade tool.

apparentlymart commented 5 years ago

terraform 0.12upgrade works on only one directory at a time and is primarily concerned with upgrading .tf files; we intend for it to also do some processing of .tfvars files in the same directory as a convenience, but since .tfvars files can be in any directory in practice it can't possibly find all of the .tfvars files in your setup automatically. In situations where .tfvars files are kept somewhere other than the root module directory some additional work would be required to also "upgrade" the .tfvars files, separately from the configuration files that they are used with.

In practice the .tfvars upgrade rules are pretty simple -- they just deal with the fact that ${ now needs to be escaped to $${ to avoid being understood as a template interpolation -- so I expect most .tfvars existing (non-Terragrunt) files aren't using such sequences and won't need any help at all. You may find in your case that just renaming the terraform.tfvars file to terragrunt.hcl is sufficient, assuming Terragrunt were updated to know to read that file.

maxbog commented 5 years ago

I am quite new to terragrunt and found a reference to an old .terragrunt configuration file name. Would moving back to old .terragrunt config files until the official support for 0.12 is released be a feasible option?

sprutner commented 5 years ago

+20 on getting Terraform v.12 support for Terragrunt!

lorengordon commented 5 years ago

What's the plan? TF 0.12.0 was released today!

rverma-nikiai commented 5 years ago

Even we are eagerly waiting for 0.12 support. 0.12 seems such a helpful release, migrating there asap would be awesome. Even https://github.com/gruntwork-io/terragrunt/issues/418 which is dependent on migrating to 0.12 would be super awesome.

Hoping we get a release soon :)

boldandbusted commented 5 years ago

After reading the whole thread, I'm confused as to what we actually are expected to do. :/ I'm happy to share my present configuration. Are we supposed to go with the two-file solution? terragrunt.hcl? Thanks!

skluck commented 5 years ago

To put some weight behind a direction (single file vs multiple) - we do use multiple files at my company - but we go even further in that we do not commit any terragrunt specific files to our modules. We automatically add terragrunt config files/backend during the CICD process. The reason for not committing the tg config was so that we could use the same module code with different backends.

BUT, one of the reasons we automatically build the terragrunt files in tooling is that multiple files is definitely a complaint we heard from our teams, which is compounded when you have multiple environments and regions.

Maybe we can start with a separate file and find a way to re-integrate into a single file later? Folks are chomping at the bit to use tf 0.12 and that desire probably outweighs the overhead management of multiple files right now.

tamsky commented 5 years ago

It seems as if we're at the point where Jim (@brikis98 ) still has reservations over assenting to the only proposed, agreeable to many, design+migration path with positive feedback:

The config lives in a separate .hcl file. The default name should be terragrunt.hcl. The variables that used to be listed directly in terraform.tfvars now go in a variables = { ... } block.

To the casual observer, the above doesn't sound like a lot of work, but (IMHO) it hides a non-trivial amount of work.

To me, it would appear to involve re-working terragrunt's existing HCL interpolation parser to use HCL2.

If I may, I'd like to offer one layer of simplification, which might provide a faster migration path to 0.12:

Can we name the file generated by the terraform 0.12upgrade tool to include the HCL version?

With a slight change to terragrunt's code to look for this new file, it would allow the current parser and interpolation hacks in terragrunt to be used.

terragrunt could someday include its own terragrunt hcl2-upgrade function once HCL2 support is added. The community will reserve the terragrunt.hcl2 name, and users could expect that someday terragrunt.hcl2 (&&|| terragrunt.hcl) will be valid for HCL2 when that feature is available.

jleeh commented 5 years ago

Happy with the two files approach, as some one who only uses one terraform.tfvars file including terragrunt and terraform configuration.

One feature request that I'd like to see to make a migration less painful, is for terragrunt looking through parent folders until it matches a terragrunt.hcl file rather than requiring one in the same folder.

This way, rather than needing to dupe the terragrunt.hcl files on migration throughout all configurations, I'd be able to define one terragrunt.hcl file at the right parent directory level for each module.

So allowing something like:

└── app
    ├── A
    │   └── terraform.tfvars
    ├── B
    │   └── terraform.tfvars
    ├── C
    │   └── terraform.tfvars
    │   └── terragrunt.hcl
    └── terragrunt.hcl

With that, it'd save me a lot of time in tedious configuration copying and allow for a one change update of module references for multiple deployments.

ekini commented 5 years ago

Another thing to consider is that we usually have global variables for multiple environments, for example, like so:

|---- infrastructure
       |
       |---- production
       |          |---- ecs-cluster
       |          |          |- terraform.tfvars
       |          |---- ecs-service
       |                     |- terraform.tfvars
       |---- staging
       |          |---- ecs-cluster
       |          |          |- terraform.tfvars
       |          |---- ecs-service
       |                     |- terraform.tfvars
       |
       |--- common.tfvars

And common.tfvars contains vpc_name variable, for example, which is defined and used by ecs-cluster, but not defined in ecs-service, and terraform will complain with "Warning: Value for undeclared variable"

josh-padnick commented 5 years ago

Hi all, there is understandably a lot of interest in this GitHub issue and @brikis98 is on vacation this week so I wanted to post a quick update:

@brikis98 has been actively working on changes using the HCL2 library to upgrade Terragrunt so it can read from a terragrunt.hcl file. The file will use HCL2 syntax, and, thanks to the fact that the HCL2 parser natively supports functions and variables, will support first-class expressions everywhere (i.e., use Terragrunt helpers anywhere in the file).

Based on what we know today, it does not seem to be a large change, so our good-faith could-be-wrong estimate is that we will complete this work by the end of next week (June 7). Thanks for your patience.

ekini commented 5 years ago

While we are here, would it be possible to allow to specify path to terraform binary in terragrunt.hcl to ease migration? So that terraform version can be set on per-module basis, or globally for a project. Should fix #265 as well.

brikis98 commented 5 years ago

Update: I've submitted a PR that adds Terraform 0.12 support. I think it's good to go, but a few tests are failing, possibly due to a bug in Terraform 0.12. Follow the PR for details.