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

Update terragrunt to generate tfvars.json file instead of using TF_VAR env var #1267

Closed
wants to merge 9 commits into from

Conversation

yorinasub17
Copy link
Contributor

Fixes #752

Extends #1263 to always generate tfvars json file instead of relying on a debug mode.

NOTE: I decided to open a new PR on a new branch so that we have the other branch in case we decide the debug mode is better.


for varName, varValue := range vars {
envVarName := fmt.Sprintf("TF_VAR_%s", varName)
variables, err := terraformModuleVariables(terragruntOptions)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sanity check: is it possible that we're somehow concealing a bug by only generating the .tfvars.json file with the input vars we find?

I guess one possibility is that in terragrunt.hcl, you have a typo: e.g., you set instance_typ = "t3.micro" instead of instance_type = "t3.micro". Terragrunt wouldn't find an instance_typ variable in your module's inputs and therefore wouldn't include it in the .tfvars.json file. I guess this would be a bit confusing during debugging. That said, today, with env vars, you have the exact same problem, but it's less visible.

If we were to output a .tfvars file instead of tfvars.json, we could include a commented out section of variables that were in your inputs, but for which there were no matching input vars. That would make things a lot clearer...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is valid, but as you mentioned, we already have this problem, and unlike with env vars, you would spot that the instance_type field is missing from the json when you load it. I think the benefit is marginal (e.g., it is just as easy to gloss over comments).

I agree that we should generate tfvars, but in the interest of having something ship sooner, I think we should punt on this to the next iteration. Generating hcl with comments in a robust manner will be complex since AFAIK, you can't just output the cty; you need to manipulate the AST to inject comment tokens.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roger. Could file issue to track generating a .tfvars in future.


out[envVarName] = string(envVarValue)
fileName := filepath.Join(terragruntOptions.WorkingDir, TerragruntTFVarsFileName)
if err := ioutil.WriteFile(fileName, fileContents, os.FileMode(int(0600))); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we always override the previous file? Is it ever worth checking if the user has a file with such a name there already?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should always override it to avoid the element of surprise for now. I added a warning log when terragrunt detects the file: 6814a3a

When we convert to hcl, I think we should switch to the same behavior as overwrite_terragrunt mode of generate blocks, which is:

  • Inject a signature as a comment to the generated file.
  • If file does not exist, generate it.
  • If file exists and has signature, overwrite it.
  • If file exists but no signature, error out.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roger

cli/args.go Outdated
return out, nil
terragruntOptions.Logger.Printf("Variables passed to terraform are located in \"%s\"", fileName)
terragruntOptions.Logger.Printf("Run this command to replicate how terraform was invoked:")
terragruntOptions.Logger.Printf("\tterraform apply \"%s\"", terragruntOptions.WorkingDir)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not always going to be terraform apply, right? E.g., It could be plan, destroy, etc.

Moreover, due to extra_arguments, we pass other args along too... Perhaps we should log the full, exact command we're executing more clearly for users?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Since we already log the terraform command with the args as part of RunCommandWithOutput, and since I don't have access to the full args passed in at this stage, I decided to remove these logs and update the runcommand args to include the working dir.

6814a3a

Comment on lines +405 to +443
} else if varIsInEnv {
util.Debugf(
terragruntOptions.Logger,
"WARN: The variable %s was omitted from the debug file because the env var %s is already set.",
varName, nameAsEnvVar,
)
} else if !varIsDefined {
util.Debugf(
terragruntOptions.Logger,
"WARN: The variable %s was omitted because it is not defined in the terraform module.",
varName,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we generated a .tfvars instead of .tfvars.json, we could include a commented-out section that has this exact info.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See above re:my thoughts on tfvars hcl.

docs/_docs/02_features/inputs.md Outdated Show resolved Hide resolved
brikis98
brikis98 previously approved these changes Jul 27, 2020
Copy link
Member

@brikis98 brikis98 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work Yori 👍

cli/args.go Outdated
// writeTFVarsFile will create a tfvars file that can be used to invoke the terraform module with the inputs generated
// in terragrunt.
func writeTFVarsFile(terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig) error {
terragruntOptions.Logger.Printf("Generating tfvars file")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: specify what file is being generated (TerragruntTFVarsFileName).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done: f599a09


for varName, varValue := range vars {
envVarName := fmt.Sprintf("TF_VAR_%s", varName)
variables, err := terraformModuleVariables(terragruntOptions)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roger. Could file issue to track generating a .tfvars in future.

fileName := filepath.Join(terragruntOptions.WorkingDir, TerragruntTFVarsFileName)

// If the file already exists, log a warning indicating that we will overwrite it.
if util.FileExists(fileName) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this warning appear if you re-run a Terragrunt command and there's a previously generated file in there?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately yes, as currently there is no way to distinguish between a file that is user created and a file that is terragrunt generated (since we don't have comments to inject a signature).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To reduce noise, should we only log it as a debug?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point: done in c7c4b71


out[envVarName] = string(envVarValue)
fileName := filepath.Join(terragruntOptions.WorkingDir, TerragruntTFVarsFileName)
if err := ioutil.WriteFile(fileName, fileContents, os.FileMode(int(0600))); err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Roger

@yorinasub17
Copy link
Contributor Author

@brikis98

I believe this is ready to merge now, but quick sanity check. When we first visited this idea, you had some reservations in #752 (comment). I have an answer to all but one of your questions (about secrets), which is worth thinking through prior to merging this in:

What file name to use? What if the file exists already? Do we generate a randomly named temp file? What about on subsequent re-runs of terragrunt commands?

The file is named terragrunt-generated.auto.tfvars.json which hopefully is directed enough that a user won't already be using this. Since the name is static, this file will always be regenerated and replaced on each terragrunt run, given that the contents could dynamically change by env var (e.g., TF_VAR_varname would cause terragrunt to omit that var from the json if it is defined in inputs).

In future iterations we can better handle a user provided terragrunt-generated.auto.tfvars.json file by replacing with .tfvars and using the terragrunt_overwrite behavior of code generation.

Do we delete the file after?

No, so that it can be debugged. However, we should consider if that is the right approach given the next bullet:

What if the values in the inputs block are secrets? Writing them to disk may be a bad idea.

**** I don't have a solution for this. Perhaps this is what forces us to delete this file after each run?

What if the user has -var-file arguments already in extra_arguments? Does the generated file go before or after those?

Based on the terraform resolution order, these args will merge into the generated inputs. That is, any var defined in -var-file will override vars defined in terragrunt-generated.auto.tfvars.json. This is validated with the tests.

I believe this was originally asked in the ticket because we were considering passing in the generated tfvars file with -var-file, but since we are using the automatic injection, this point is moot.

What if the user has -var-file arguments already when running terragrunt (e.g., terragrunt apply -var-file=foo? Does the generated file go before or after those?

Same as above bullet.

@brikis98
Copy link
Member

brikis98 commented Jul 31, 2020

Do we delete the file after?

No, so that it can be debugged. However, we should consider if that is the right approach given the next bullet:

What if the values in the inputs block are secrets? Writing them to disk may be a bad idea.

**** I don't have a solution for this. Perhaps this is what forces us to delete this file after each run?

Hm, yea, this is a tough one. Some options to think through:

  1. Could we use an in-memory-only file? This would be by top choice... I'm pretty sure we could make that work on *nix, but I don't know Windows well enough to see if there's a way to do it there. Example approaches:
  2. We could delete the file right after each run. Of course, this makes debugging harder, so we'd need some --debug flag to force the file to stick around. We'd have to be extra careful to ensure that Terragrunt deletes the file even if there's an error or panic. Not much we could do about a forced kill (e.g., via CTRL+C).
  3. If neither of the above works well enough, we go back to env vars, and only write the file when a --debug flag is passed.

@yorinasub17
Copy link
Contributor Author

yorinasub17 commented Aug 1, 2020

I decided to go with option (2). Now the tfvars file is deleted with a defer call, which can be skipped with --terragrunt-debug. 150b34b

We could delete the file right after each run. Of course, this makes debugging harder, so we'd need some --debug flag to force the file to stick around. We'd have to be extra careful to ensure that Terragrunt deletes the file even if there's an error or panic. Not much we could do about a forced kill (e.g., via CTRL+C).

Looking at the docs for panic, it looks like all defer calls are executed normally (emphasis mine):

anic is a built-in function that stops the ordinary flow of control and begins panicking. When the function F calls panic, execution of F stops, any deferred functions in F are executed normally, and then F returns to its caller. To the caller, F then behaves like a call to panic....

So I think as long as the defer is done before the call to writeTFVarsFile (which I am doing), we are guaranteed to run this cleanup routine, which should mean that we will properly clean up the file in the face of error and panic.

@brikis98
Copy link
Member

brikis98 commented Aug 1, 2020

Hm, I still a bit nervous about potentially writing secrets to disk:

  1. terragrunt apply could run for an hour... That's a long time to have plaintext secrets sitting on disk.
  2. For a variety of reasons, the Terragrunt process could be killed before the file is cleaned up. We should prob have tests that validate we clean up the file correctly, including in the face of a panic, but CTRL+C, kill, and a dozen other things would leave behind files on disk even if our code works perfectly.

Is it worth spending 30 min looking for a way on Windows to pass a file purely in memory? I know it can be done on *nix...

@yorinasub17
Copy link
Contributor Author

We should prob have tests that validate we clean up the file correctly, including in the face of a panic

I don't think this kind of testing is going to be very easy without introducing a potential avenue for bugs in terragrunt (I am nervous about injecting something in the pipeline to induce a panic intentionally)...


Is it worth spending 30 min looking for a way on Windows to pass a file purely in memory? I know it can be done on *nix...

Ok will look into it, but it'll be a while before I'll have time available to set up my dev env on my windows boxes again.


Let's then just revert back to the original debug mode version (option 3) for now so that we can ship something useful to satisfy this need of terragrunt debugging, and we don't block that on my ability to dedicate free nights and weekends to work on this.

@yorinasub17
Copy link
Contributor Author

UPDATE: rebased on #1263 so that this PR will automatically become an extension of that branch when that is merged, so that we don't throw away the work here.

@brikis98
Copy link
Member

brikis98 commented Aug 2, 2020

We should prob have tests that validate we clean up the file correctly, including in the face of a panic

I don't think this kind of testing is going to be very easy without introducing a potential avenue for bugs in terragrunt (I am nervous about injecting something in the pipeline to induce a panic intentionally)...

Is it worth spending 30 min looking for a way on Windows to pass a file purely in memory? I know it can be done on *nix...

Ok will look into it, but it'll be a while before I'll have time available to set up my dev env on my windows boxes again.

Let's then just revert back to the original debug mode version (option 3) for now so that we can ship something useful to satisfy this need of terragrunt debugging, and we don't block that on my ability to dedicate free nights and weekends to work on this.

Roger

@abeluck
Copy link
Contributor

abeluck commented Sep 10, 2020

Just wanted to drop a comment here thanking you for considering security in this case.

If terragrunt started writing secrets to disk we'd have to immediately stop using it. We've stringent security policies in place that disallow secrets being written to plaintext on disk (even though the workstations/servers are full disk encrypted).... even for a few seconds.

It is common for workstation backup software to run in a continuous mode that constantly backs up changes to disk as they happen. So writing secrets to disk could easily result in them being swept into a backup archive somewhere.

That said, I'm definitely interested in the original motivation of this issue.

@lorengordon
Copy link
Contributor

To manage normal values vs secret values, could we maybe use two different inputs blocks, or a function? One input block would always write a tfvars file, the other would always export ENVs. Or check if that value is wrapped in the function, and exclude it from the tfvars and instead export it as an env...

@yorinasub17
Copy link
Contributor Author

Closing as stale

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Don't convert inputs to env vars. Instead, generate terragrunt.tfvars.json and use as -var-file
4 participants