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

#667 iam_role parsing before evaluation of other HCL blocks #1807

Merged
merged 21 commits into from
Oct 1, 2021
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 := extractIamRole(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
}

// extractIamRole - extract IAM role details from Terragrunt flags block
func extractIamRole(configString string, terragruntOptions *options.TerragruntOptions, filename string) error {
Copy link
Contributor

Choose a reason for hiding this comment

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

NIT: Rename to setIAMRole, to indicate that this function has side effects (modifying attribute on terragruntOptions.

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
7 changes: 5 additions & 2 deletions docs/_docs/04_reference/built-in-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,19 +591,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
Comment on lines +611 to +612
Copy link
Member

Choose a reason for hiding this comment

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

NIT: are these changes related to this PR?

Copy link
Contributor

Choose a reason for hiding this comment

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

Yes. It's the result of the change in the parsing order.

* 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 @@ -3623,6 +3624,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)
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 actually try to assume the fake IAM role and lead to a permissions error?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, and it will fail to create state file with IAM role ID


// 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 @@ -4304,9 +4331,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 TestNoFailureForModulesWithoutOutputs(t *testing.T) {
Expand Down