Skip to content

Commit

Permalink
#667 iam_role parsing before evaluation of other HCL blocks (#1807)
Browse files Browse the repository at this point in the history
* IamRole decode in base blocks

* Cleanup

* Test for loading IAM role from file

* Integration tests cleanup

* Updated description

* Upstream merge
Simplified test for get_aws_account_id

* IamRole loading during parsing of config files

* Updated description

* Updated test assertions since locals +1 time

* Added reference to issue name

* Description update

* Documentation update

* IamConfig parsing updates

* Extracted IAM parsing as separated function

* Documentation update

* Renamed extractIamRole to setIAMRole

* Documentation update

* Updated formatting
  • Loading branch information
denis256 authored Oct 1, 2021
1 parent 21334d4 commit 52ecbbb
Show file tree
Hide file tree
Showing 7 changed files with 87 additions and 11 deletions.
23 changes: 23 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
Expand Down
12 changes: 7 additions & 5 deletions docs/_docs/01_getting-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).

Expand Down
10 changes: 8 additions & 2 deletions docs/_docs/04_reference/built-in-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -591,19 +594,22 @@ 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
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
Expand Down
4 changes: 3 additions & 1 deletion docs/_docs/04_reference/config-blocks-and-attributes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 3 additions & 0 deletions test/fixture-read-config/iam_role_in_file/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
terraform {
backend "local" {}
}
13 changes: 13 additions & 0 deletions test/fixture-read-config/iam_role_in_file/terragrunt.hcl
Original file line number Diff line number Diff line change
@@ -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"
}
}
33 changes: 30 additions & 3 deletions test/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 52ecbbb

Please sign in to comment.