diff --git a/config/config.go b/config/config.go index ed7e7c8445..3dc87c2816 100644 --- a/config/config.go +++ b/config/config.go @@ -599,6 +599,12 @@ func ParseConfigString( return nil, err } + // Initial evaluation of configuration to load flags like IamRole which will be used for final parsing + // https://github.com/gruntwork-io/terragrunt/issues/667 + if err := setIAMRole(configString, terragruntOptions, filename); err != nil { + return nil, err + } + // Decode just the Base blocks. See the function docs for DecodeBaseBlocks for more info on what base blocks are. localsAsCty, trackInclude, err := DecodeBaseBlocks(terragruntOptions, parser, file, filename, includeFromChild, nil) if err != nil { @@ -647,6 +653,23 @@ func ParseConfigString( return config, nil } +// setIAMRole - extract IAM role details from Terragrunt flags block +func setIAMRole(configString string, terragruntOptions *options.TerragruntOptions, filename string) error { + if terragruntOptions.IamRole == "" { + iamConfig, err := PartialParseConfigString(configString, terragruntOptions, nil, filename, []PartialDecodeSectionType{TerragruntFlags}) + if err != nil { + return err + } + if iamConfig.IamRole != "" { + terragruntOptions.IamRole = iamConfig.IamRole + } + if iamConfig.IamAssumeRoleDuration != nil { + terragruntOptions.IamAssumeRoleDuration = *iamConfig.IamAssumeRoleDuration + } + } + return nil +} + func decodeAsTerragruntConfigFile( file *hcl.File, filename string, diff --git a/docs/_docs/01_getting-started/configuration.md b/docs/_docs/01_getting-started/configuration.md index 28021043fa..ca0ca963bc 100644 --- a/docs/_docs/01_getting-started/configuration.md +++ b/docs/_docs/01_getting-started/configuration.md @@ -55,15 +55,17 @@ Currently terragrunt parses the config in the following order: 2. `locals` block -3. `dependencies` block +3. Evaluation of values for `iam_role` and `iam_assume_role_duration` attributes, if defined -4. `dependency` blocks, including calling `terragrunt output` on the dependent modules to retrieve the outputs +4. `dependencies` block -5. Everything else +5. `dependency` blocks, including calling `terragrunt output` on the dependent modules to retrieve the outputs -6. The config referenced by `include` +6. Everything else -7. A merge operation between the config referenced by `include` and the current config. +7. The config referenced by `include` + +8. A merge operation between the config referenced by `include` and the current config. Blocks that are parsed earlier in the process will be made available for use in the parsing of later blocks. Similarly, you cannot use blocks that are parsed later earlier in the process (e.g you can’t reference `dependency` in `locals`, `include`, or `dependencies` blocks). diff --git a/docs/_docs/04_reference/built-in-functions.md b/docs/_docs/04_reference/built-in-functions.md index 3817941956..65af73d465 100644 --- a/docs/_docs/04_reference/built-in-functions.md +++ b/docs/_docs/04_reference/built-in-functions.md @@ -490,6 +490,7 @@ remote_state { } } ``` +**Note:** value returned by `get_aws_account_id()` can change during parsing of HCL code, for example after evaluation of `iam_role` attribute. ## get\_aws\_caller\_identity\_arn @@ -500,6 +501,7 @@ inputs = { caller_arn = get_aws_caller_identity_arn() } ``` +**Note:** value returned by `get_aws_caller_identity_arn()` can change during parsing of HCL code, for example after evaluation of `iam_role` attribute. ## get\_terraform\_command @@ -543,6 +545,7 @@ terraform { } } ``` +**Note:** value returned by `get_aws_caller_identity_user_id()` can change during parsing of HCL code, for example after evaluation of `iam_role` attribute. ## run\_cmd @@ -591,6 +594,7 @@ Output: $ terragrunt init uuid1 b48379e1-924d-2403-8789-c72d50be964c uuid1 9f3a8398-b11f-5314-7783-dad176ee487d +uuid1 649ac501-e5db-c935-1499-c59fb7a75625 uuid2 2d65972b-3fa9-181f-64fe-dcd574d944d0 uuid3 e345de60-9cfa-0455-79b7-af0d053a15a5 potato @@ -598,12 +602,14 @@ uuid3 7f90a4ed-96e3-1dd8-5fee-91b8c8e07650 uuid2 8638fe79-c589-bebd-2a2a-3e6b96f7fc34 uuid3 310d0447-f0a6-3f67-efda-e6b1521fa1fb uuid4 f8e80cc6-1892-8db7-bd63-6089fef00c01 +uuid2 289ff371-8021-54c6-2254-72de9d11392a +uuid3 baa19863-1d99-e0ef-11f2-ede830d1c58a carrot ``` **Notes:** * Output contains only once `carrot` and `potato`, because other invocations got cached, caching works for all sections - * Output contains twice `uuid1` and `uuid2` because during HCL evaluation each `run_cmd` in `locals` section is evaluated twice, and value is cached under different key since `uuid()` add random value in key - * Output contains three times `uuid3` - 2 prints because `uuid3` was declared in `locals`, once because it is declared in `inputs` + * Output contains multiple times `uuid1` and `uuid2` because during HCL evaluation each `run_cmd` in `locals` is evaluated multiple times and random argument generated from `uuid()` save cached value under different key each time + * Output contains multiple times `uuid3`, +1 more output comparing to `uuid1` and `uuid2` - because `uuid3` is declared in locals and inputs which add one more evaluation * Output contains only once `uuid4` since it is declared only once in `inputs`, `inputs` is not evaluated twice ## read\_terragrunt\_config diff --git a/docs/_docs/04_reference/config-blocks-and-attributes.md b/docs/_docs/04_reference/config-blocks-and-attributes.md index 89b2dad822..e03679b155 100644 --- a/docs/_docs/04_reference/config-blocks-and-attributes.md +++ b/docs/_docs/04_reference/config-blocks-and-attributes.md @@ -1128,7 +1128,9 @@ Example: ```hcl iam_role = "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME" ``` - +**Notes:** + * Value of `iam_role` can reference local variables + * Definitions of `iam_role` included from other HCL files through `include` ### iam_assume_role_duration diff --git a/test/fixture-read-config/iam_role_in_file/main.tf b/test/fixture-read-config/iam_role_in_file/main.tf new file mode 100644 index 0000000000..f966bbb901 --- /dev/null +++ b/test/fixture-read-config/iam_role_in_file/main.tf @@ -0,0 +1,3 @@ +terraform { + backend "local" {} +} diff --git a/test/fixture-read-config/iam_role_in_file/terragrunt.hcl b/test/fixture-read-config/iam_role_in_file/terragrunt.hcl new file mode 100644 index 0000000000..d43e3ebd68 --- /dev/null +++ b/test/fixture-read-config/iam_role_in_file/terragrunt.hcl @@ -0,0 +1,13 @@ +iam_role = "arn:aws:iam::666666666666:role/terragrunttest" + +remote_state { + backend = "local" + generate = { + // state file should load value from iam_role + path = "${get_aws_account_id()}.txt" + if_exists = "overwrite" + } + config = { + path = "terraform.tfstate" + } +} diff --git a/test/integration_test.go b/test/integration_test.go index 370c70c661..78ceae65bc 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -118,6 +118,7 @@ const ( TEST_FIXTURE_LOCAL_RUN_MULTIPLE = "fixture-locals/run-multiple" TEST_FIXTURE_LOCALS_IN_INCLUDE_CHILD_REL_PATH = "qa/my-app" TEST_FIXTURE_READ_CONFIG = "fixture-read-config" + TEST_FIXTURE_READ_IAM_ROLE = "fixture-read-config/iam_role_in_file" TEST_FIXTURE_AWS_GET_CALLER_IDENTITY = "fixture-get-aws-caller-identity" TEST_FIXTURE_GET_PLATFORM = "fixture-get-platform" TEST_FIXTURE_GET_TERRAGRUNT_SOURCE_HCL = "fixture-get-terragrunt-source-hcl" @@ -3622,6 +3623,32 @@ func TestTerragruntVersionConstraints(t *testing.T) { } } +func TestReadTerragruntConfigIamRole(t *testing.T) { + t.Parallel() + + identityArn, err := aws_helper.GetAWSIdentityArn(nil, &options.TerragruntOptions{ + IamRole: "", + }) + assert.NoError(t, err) + + cleanupTerraformFolder(t, TEST_FIXTURE_READ_IAM_ROLE) + + // Execution outputs to be verified + stdout := bytes.Buffer{} + stderr := bytes.Buffer{} + + // Invoke terragrunt and verify used IAM role + err = runTerragruntCommand(t, fmt.Sprintf("terragrunt init --terragrunt-working-dir %s", TEST_FIXTURE_READ_IAM_ROLE), &stdout, &stderr) + + // Since are used not existing AWS accounts, for validation are used success and error outputs + output := fmt.Sprintf("%v %v %v", string(stderr.Bytes()), string(stdout.Bytes()), err.Error()) + + // Check that output contains value defined in IAM role + assert.Equal(t, 1, strings.Count(output, "666666666666")) + // Ensure that state file wasn't created with default IAM value + assert.True(t, util.FileNotExists(util.JoinPath(TEST_FIXTURE_READ_IAM_ROLE, identityArn+".txt"))) +} + func TestTerragruntVersionConstraintsPartialParse(t *testing.T) { fixturePath := "fixture-partial-parse/terragrunt-version-constraint" cleanupTerragruntFolder(t, fixturePath) @@ -4303,9 +4330,9 @@ func TestTerragruntInitRunCmd(t *testing.T) { assert.Equal(t, 1, strings.Count(errout, "input_variable")) // Commands executed multiple times because of different arguments - assert.Equal(t, 3, strings.Count(errout, "uuid")) - assert.Equal(t, 4, strings.Count(errout, "random_arg")) - assert.Equal(t, 3, strings.Count(errout, "another_arg")) + assert.Equal(t, 4, strings.Count(errout, "uuid")) + assert.Equal(t, 6, strings.Count(errout, "random_arg")) + assert.Equal(t, 4, strings.Count(errout, "another_arg")) } func TestShowWarningWithDependentModulesBeforeDestroy(t *testing.T) {