diff --git a/README.md b/README.md index 0c801f37de..0d70ba785b 100644 --- a/README.md +++ b/README.md @@ -634,6 +634,10 @@ terragrunt = { arguments = [ "-lock-timeout=20m" ] + + env_vars = { + TF_VAR_var_from_environment = "value" + } } } } @@ -641,7 +645,8 @@ terragrunt = { Each `extra_arguments` block includes an arbitrary name (in the example above, `retry_lock`), a list of `commands` to which the extra arguments should be add, a list of `arguments` or `required_var_files` or `optional_var_files` to add. -With the configuration above, when you run `terragrunt apply`, Terragrunt will call Terraform as follows: +You can also pass custom variables using `env_vars` block, which stores environment variables in key value pairs. With +the configuration above, when you run `terragrunt apply`, Terragrunt will call Terraform as follows: When available, it is preferable to use interpolation functions such as [get_terraform_commands_that_need_locking](#get_terraform_commands_that_need_locking) and diff --git a/cli/args.go b/cli/args.go index 79db13e925..d806e84b71 100644 --- a/cli/args.go +++ b/cli/args.go @@ -142,6 +142,23 @@ func filterTerraformExtraArgs(terragruntOptions *options.TerragruntOptions, terr return out } +func filterTerraformEnvVarsFromExtraArgs(terragruntOptions *options.TerragruntOptions, terragruntConfig *config.TerragruntConfig) map[string]string { + out := map[string]string{} + cmd := util.FirstArg(terragruntOptions.TerraformCliArgs) + + for _, arg := range terragruntConfig.Terraform.ExtraArgs { + for _, argcmd := range arg.Commands { + if cmd == argcmd { + for k, v := range arg.EnvVars { + out[k] = v + } + } + } + } + + return out +} + func parseEnvironmentVariables(environment []string) map[string]string { environmentMap := make(map[string]string) diff --git a/cli/cli_app.go b/cli/cli_app.go index 68a68457ed..7514e2dd9c 100644 --- a/cli/cli_app.go +++ b/cli/cli_app.go @@ -321,6 +321,9 @@ func runTerragruntWithConfig(terragruntOptions *options.TerragruntOptions, terra // Add extra_arguments to the command if terragruntConfig.Terraform != nil && terragruntConfig.Terraform.ExtraArgs != nil && len(terragruntConfig.Terraform.ExtraArgs) > 0 { terragruntOptions.InsertTerraformCliArgs(filterTerraformExtraArgs(terragruntOptions, terragruntConfig)...) + for k, v := range filterTerraformEnvVarsFromExtraArgs(terragruntOptions, terragruntConfig) { + terragruntOptions.Env[k] = v + } } if util.FirstArg(terragruntOptions.TerraformCliArgs) == CMD_INIT { diff --git a/config/config.go b/config/config.go index 2d4b5a8792..90615e86b5 100644 --- a/config/config.go +++ b/config/config.go @@ -119,15 +119,21 @@ func (conf *TerraformConfig) ValidateHooks() error { // TerraformExtraArguments sets a list of arguments to pass to Terraform if command fits any in the `Commands` list type TerraformExtraArguments struct { - Name string `hcl:",key"` - Arguments []string `hcl:"arguments,omitempty"` - RequiredVarFiles []string `hcl:"required_var_files,omitempty"` - OptionalVarFiles []string `hcl:"optional_var_files,omitempty"` - Commands []string `hcl:"commands,omitempty"` + Name string `hcl:",key"` + Arguments []string `hcl:"arguments,omitempty"` + RequiredVarFiles []string `hcl:"required_var_files,omitempty"` + OptionalVarFiles []string `hcl:"optional_var_files,omitempty"` + Commands []string `hcl:"commands,omitempty"` + EnvVars map[string]string `hcl:"env_vars,omitempty"` } func (conf *TerraformExtraArguments) String() string { - return fmt.Sprintf("TerraformArguments{Name = %s, Arguments = %v, Commands = %v}", conf.Name, conf.Arguments, conf.Commands) + return fmt.Sprintf( + "TerraformArguments{Name = %s, Arguments = %v, Commands = %v, EnvVars = %v}", + conf.Name, + conf.Arguments, + conf.Commands, + conf.EnvVars) } // Return the default path to use for the Terragrunt configuration file. The reason this is a method rather than a diff --git a/config/config_test.go b/config/config_test.go index 6f3d0f85fa..b0412eb0e4 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -623,7 +623,10 @@ terragrunt = { "-var-file=terraform.tfvars", "-var-file=terraform-secret.tfvars" ] - commands = ["${get_terraform_commands_that_need_vars()}"] + commands = ["${get_terraform_commands_that_need_vars()}"] + env_vars = { + TEST_VAR = "value" + } } } } @@ -648,6 +651,10 @@ terragrunt = { assert.Equal(t, TERRAFORM_COMMANDS_NEED_VARS, terragruntConfig.Terraform.ExtraArgs[0].Commands) + + assert.Equal(t, + map[string]string{"TEST_VAR": "value"}, + terragruntConfig.Terraform.ExtraArgs[0].EnvVars) } } diff --git a/test/fixture-env-vars-block/main.tf b/test/fixture-env-vars-block/main.tf new file mode 100644 index 0000000000..27d087cad6 --- /dev/null +++ b/test/fixture-env-vars-block/main.tf @@ -0,0 +1,7 @@ +variable "custom_var" { + description = "Provided as TF_VAR_custom_var in terraform.tfvars" +} + +output "test" { + value = "${var.custom_var}" +} diff --git a/test/fixture-env-vars-block/terraform.tfvars b/test/fixture-env-vars-block/terraform.tfvars new file mode 100644 index 0000000000..94036d9bb9 --- /dev/null +++ b/test/fixture-env-vars-block/terraform.tfvars @@ -0,0 +1,19 @@ +terragrunt = { + terraform = { + extra_arguments "test" { + commands = ["apply"] + env_vars = { + TF_VAR_custom_var = "I'm set in extra_arguments env_vars" + } + } + + extra_arguments "shouldnotapply" { + commands = [ "refresh" ] + + env_vars = { + TF_VAR_custom_var = "I'm only set for refresh command, so will be ignored for apply" + } + + } + } +} diff --git a/test/integration_test.go b/test/integration_test.go index e14863a94b..310f65bd37 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -38,6 +38,7 @@ const ( TEST_FIXTURE_OUTPUT_ALL = "fixture-output-all" TEST_FIXTURE_STDOUT = "fixture-download/stdout-test" TEST_FIXTURE_EXTRA_ARGS_PATH = "fixture-extra-args/" + TEST_FIXTURE_ENV_VARS_BLOCK_PATH = "fixture-env-vars-block/" TEST_FIXTURE_LOCAL_DOWNLOAD_PATH = "fixture-download/local" TEST_FIXTURE_REMOTE_DOWNLOAD_PATH = "fixture-download/remote" TEST_FIXTURE_OVERRIDE_DOWNLOAD_PATH = "fixture-download/override" @@ -807,6 +808,13 @@ func TestExtraArgumentsWithEnv(t *testing.T) { assert.Contains(t, out.String(), "Hello, World!") } +func TestExtraArgumentsWithEnvVarBlock(t *testing.T) { + out := new(bytes.Buffer) + runTerragruntRedirectOutput(t, fmt.Sprintf("terragrunt apply --terragrunt-non-interactive --terragrunt-working-dir %s", TEST_FIXTURE_ENV_VARS_BLOCK_PATH), out, os.Stderr) + t.Log(out.String()) + assert.Contains(t, out.String(), "I'm set in extra_arguments env_vars") +} + func TestExtraArgumentsWithRegion(t *testing.T) { // Do not use t.Parallel() on this test, it will infers with the other TestExtraArguments.* tests out := new(bytes.Buffer)