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

Multiple include blocks #1804

Merged
merged 26 commits into from
Sep 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
38bc56f
RDD: Introduce import blocks to support multiple includes
yorinasub17 Sep 9, 2021
0bb9825
Go back to include instead of import
yorinasub17 Sep 10, 2021
2e6099f
WIP: initial attempt to support multiple include blocks
yorinasub17 Sep 9, 2021
9317b5d
Fix panic error
yorinasub17 Sep 9, 2021
5ba9a67
Introduce new import block, instead of include for backward compatibi…
yorinasub17 Sep 9, 2021
a6ff274
gofmt
yorinasub17 Sep 9, 2021
b482a74
Fix path_relative_to_include in child config
yorinasub17 Sep 9, 2021
ac27dc4
Fix bug where path_relative_from_include did not consider the parent …
yorinasub17 Sep 9, 2021
d3ef5b3
Implement multiple imports test for path_relative_from_include unit test
yorinasub17 Sep 10, 2021
5e177db
Use a parse and update approach for dealing with bare include blocks
yorinasub17 Sep 10, 2021
7158540
Implement multiple imports test for path_relative_to_include unit test
yorinasub17 Sep 10, 2021
c7d29b1
Support include reencoding for json config files
yorinasub17 Sep 10, 2021
9678826
Implement multiple imports test for get_parent_terragrunt_dir unit test
yorinasub17 Sep 10, 2021
f2bfd41
WIP: initial integration test - multiple include with deep merge
yorinasub17 Sep 10, 2021
ee500e9
Add comprehensive testing of multiple includes
yorinasub17 Sep 10, 2021
2489e2e
Add json test for multiple includes
yorinasub17 Sep 10, 2021
d906385
Require label when referencing exposed include
yorinasub17 Sep 10, 2021
355c354
Be clear that nested includes are not supported yet
yorinasub17 Sep 13, 2021
afb9d2f
Support backward compatible interface where single bare include expos…
yorinasub17 Sep 13, 2021
ad8311b
Remove unnecessary return
yorinasub17 Sep 13, 2021
ca1a45d
Mention expose in include docs
yorinasub17 Sep 13, 2021
30c9947
Update docs to remove references of bare include
yorinasub17 Sep 13, 2021
70a6572
Regression test for include expose with dependencies
yorinasub17 Sep 13, 2021
fb5d604
Support for dependencies in include when expose is true
yorinasub17 Sep 13, 2021
85c595c
Fix build for locals testing
yorinasub17 Sep 13, 2021
a8bf61d
Partial parse includes locals
yorinasub17 Sep 13, 2021
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
34 changes: 20 additions & 14 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ type terragruntConfigFile struct {
TerraformVersionConstraint *string `hcl:"terraform_version_constraint,attr"`
TerragruntVersionConstraint *string `hcl:"terragrunt_version_constraint,attr"`
Inputs *cty.Value `hcl:"inputs,attr"`
Include *IncludeConfig `hcl:"include,block"`

// We allow users to configure remote state (backend) via blocks:
//
Expand Down Expand Up @@ -112,17 +111,24 @@ type terragruntConfigFile struct {
RetryMaxAttempts *int `hcl:"retry_max_attempts,optional"`
RetrySleepIntervalSec *int `hcl:"retry_sleep_interval_sec,optional"`

// This struct is used for validating and parsing the entire terragrunt config. Since locals are evaluated in a
// completely separate cycle, it should not be evaluated here. Otherwise, we can't support self referencing other
// elements in the same block.
Locals *terragruntLocal `hcl:"locals,block"`
// This struct is used for validating and parsing the entire terragrunt config. Since locals and include are
// evaluated in a completely separate cycle, it should not be evaluated here. Otherwise, we can't support self
// referencing other elements in the same block.
// We don't want to use the special Remain keyword here, as that would cause the checker to support parsing config
// that have extraneous, unsupported blocks and attributes.
Locals *terragruntLocal `hcl:"locals,block"`
Include []terragruntIncludeIgnore `hcl:"include,block"`
}

// We use a struct designed to not parse the block, as locals are parsed and decoded using a special routine that allows
// references to the other locals in the same block.
// We use a struct designed to not parse the block, as locals and includes are parsed and decoded using a special
// routine that allows references to the other locals in the same block.
type terragruntLocal struct {
Remain hcl.Body `hcl:",remain"`
}
type terragruntIncludeIgnore struct {
Name string `hcl:"name,label"`
Remain hcl.Body `hcl:",remain"`
}

// Configuration for Terraform remote state as parsed from a terragrunt.hcl config file
type remoteStateConfigFile struct {
Expand Down Expand Up @@ -187,8 +193,9 @@ type terragruntGenerateBlock struct {
}

// IncludeConfig represents the configuration settings for a parent Terragrunt configuration file that you can
// "include" in a child Terragrunt configuration file
// include into a child Terragrunt configuration file. You can have more than one include config.
type IncludeConfig struct {
Name string `hcl:"name,label"`
Path string `hcl:"path,attr"`
Expose *bool `hcl:"expose,attr"`
MergeStrategy *string `hcl:"merge_strategy,attr"`
Expand Down Expand Up @@ -587,7 +594,7 @@ func ParseConfigString(
}

// Decode just the Base blocks. See the function docs for DecodeBaseBlocks for more info on what base blocks are.
localsAsCty, terragruntInclude, trackInclude, err := DecodeBaseBlocks(terragruntOptions, parser, file, filename, includeFromChild)
localsAsCty, trackInclude, err := DecodeBaseBlocks(terragruntOptions, parser, file, filename, includeFromChild, nil)
if err != nil {
return nil, err
}
Expand All @@ -602,7 +609,7 @@ func ParseConfigString(
if dependencyOutputs == nil {
// Decode just the `dependency` blocks, retrieving the outputs from the target terragrunt config in the
// process.
retrievedOutputs, err := decodeAndRetrieveOutputs(file, filename, terragruntOptions, terragruntInclude.Include, contextExtensions)
retrievedOutputs, err := decodeAndRetrieveOutputs(file, filename, terragruntOptions, trackInclude, contextExtensions)
if err != nil {
return nil, err
}
Expand All @@ -625,11 +632,10 @@ func ParseConfigString(
}

// If this file includes another, parse and merge it. Otherwise just return this config.
if terragruntInclude.Include != nil {
return handleInclude(config, terragruntInclude.Include, terragruntOptions, contextExtensions.DecodedDependencies)
} else {
return config, nil
if trackInclude != nil {
return handleInclude(config, trackInclude, terragruntOptions, contextExtensions.DecodedDependencies)
}
return config, nil
}

func decodeAsTerragruntConfigFile(
Expand Down
181 changes: 128 additions & 53 deletions config/config_helpers.go

Large diffs are not rendered by default.

122 changes: 98 additions & 24 deletions config/config_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,49 +18,67 @@ func TestPathRelativeToInclude(t *testing.T) {
t.Parallel()

testCases := []struct {
include *IncludeConfig
include map[string]IncludeConfig
params []string
terragruntOptions *options.TerragruntOptions
expectedPath string
}{
{
nil,
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/"+DefaultTerragruntConfigPath),
".",
},
{
&IncludeConfig{Path: "../" + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: "../" + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/"+DefaultTerragruntConfigPath),
"child",
},
{
&IncludeConfig{Path: helpers.RootFolder + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: helpers.RootFolder + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/"+DefaultTerragruntConfigPath),
"child",
},
{
&IncludeConfig{Path: "../../../" + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: "../../../" + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/sub-child/sub-sub-child/"+DefaultTerragruntConfigPath),
"child/sub-child/sub-sub-child",
},
{
&IncludeConfig{Path: helpers.RootFolder + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: helpers.RootFolder + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/sub-child/sub-sub-child/"+DefaultTerragruntConfigPath),
"child/sub-child/sub-sub-child",
},
{
&IncludeConfig{Path: "../../other-child/" + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: "../../other-child/" + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/sub-child/"+DefaultTerragruntConfigPath),
"../child/sub-child",
},
{
&IncludeConfig{Path: "../../" + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: "../../" + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, "../child/sub-child/"+DefaultTerragruntConfigPath),
"child/sub-child",
},
{
map[string]IncludeConfig{
"root": IncludeConfig{Path: "../../" + DefaultTerragruntConfigPath},
"child": IncludeConfig{Path: "../../other-child/" + DefaultTerragruntConfigPath},
},
[]string{"child"},
terragruntOptionsForTest(t, "../child/sub-child/"+DefaultTerragruntConfigPath),
"../child/sub-child",
},
}

for _, testCase := range testCases {
actualPath, actualErr := pathRelativeToInclude(testCase.include, testCase.terragruntOptions)
trackInclude := getTrackIncludeFromTestData(testCase.include, testCase.params)
actualPath, actualErr := pathRelativeToInclude(testCase.params, trackInclude, testCase.terragruntOptions)
assert.Nil(t, actualErr, "For include %v and options %v, unexpected error: %v", testCase.include, testCase.terragruntOptions, actualErr)
assert.Equal(t, testCase.expectedPath, actualPath, "For include %v and options %v", testCase.include, testCase.terragruntOptions)
}
Expand All @@ -70,49 +88,67 @@ func TestPathRelativeFromInclude(t *testing.T) {
t.Parallel()

testCases := []struct {
include *IncludeConfig
include map[string]IncludeConfig
params []string
terragruntOptions *options.TerragruntOptions
expectedPath string
}{
{
nil,
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/"+DefaultTerragruntConfigPath),
".",
},
{
&IncludeConfig{Path: "../" + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: "../" + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/"+DefaultTerragruntConfigPath),
"..",
},
{
&IncludeConfig{Path: helpers.RootFolder + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: helpers.RootFolder + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/"+DefaultTerragruntConfigPath),
"..",
},
{
&IncludeConfig{Path: "../../../" + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: "../../../" + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/sub-child/sub-sub-child/"+DefaultTerragruntConfigPath),
"../../..",
},
{
&IncludeConfig{Path: helpers.RootFolder + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: helpers.RootFolder + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/sub-child/sub-sub-child/"+DefaultTerragruntConfigPath),
"../../..",
},
{
&IncludeConfig{Path: "../../other-child/" + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: "../../other-child/" + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/sub-child/"+DefaultTerragruntConfigPath),
"../../other-child",
},
{
&IncludeConfig{Path: "../../" + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: "../../" + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, "../child/sub-child/"+DefaultTerragruntConfigPath),
"../..",
},
{
map[string]IncludeConfig{
"root": IncludeConfig{Path: "../../" + DefaultTerragruntConfigPath},
"child": IncludeConfig{Path: "../../other-child/" + DefaultTerragruntConfigPath},
},
[]string{"child"},
terragruntOptionsForTest(t, "../child/sub-child/"+DefaultTerragruntConfigPath),
"../../other-child",
},
}

for _, testCase := range testCases {
actualPath, actualErr := pathRelativeFromInclude(testCase.include, testCase.terragruntOptions)
trackInclude := getTrackIncludeFromTestData(testCase.include, testCase.params)
actualPath, actualErr := pathRelativeFromInclude(testCase.params, trackInclude, testCase.terragruntOptions)
assert.Nil(t, actualErr, "For include %v and options %v, unexpected error: %v", testCase.include, testCase.terragruntOptions, actualErr)
assert.Equal(t, testCase.expectedPath, actualPath, "For include %v and options %v", testCase.include, testCase.terragruntOptions)
}
Expand Down Expand Up @@ -588,49 +624,67 @@ func TestGetParentTerragruntDir(t *testing.T) {
parentDir := filepath.ToSlash(filepath.Dir(currentDir))

testCases := []struct {
include *IncludeConfig
include map[string]IncludeConfig
params []string
terragruntOptions *options.TerragruntOptions
expectedPath string
}{
{
nil,
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/"+DefaultTerragruntConfigPath),
helpers.RootFolder + "child",
},
{
&IncludeConfig{Path: "../" + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: "../" + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/"+DefaultTerragruntConfigPath),
helpers.RootFolder,
},
{
&IncludeConfig{Path: helpers.RootFolder + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: helpers.RootFolder + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/"+DefaultTerragruntConfigPath),
helpers.RootFolder,
},
{
&IncludeConfig{Path: "../../../" + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: "../../../" + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/sub-child/sub-sub-child/"+DefaultTerragruntConfigPath),
helpers.RootFolder,
},
{
&IncludeConfig{Path: helpers.RootFolder + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: helpers.RootFolder + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/sub-child/sub-sub-child/"+DefaultTerragruntConfigPath),
helpers.RootFolder,
},
{
&IncludeConfig{Path: "../../other-child/" + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: "../../other-child/" + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, helpers.RootFolder+"child/sub-child/"+DefaultTerragruntConfigPath),
fmt.Sprintf("%s/other-child", filepath.VolumeName(parentDir)),
},
{
&IncludeConfig{Path: "../../" + DefaultTerragruntConfigPath},
map[string]IncludeConfig{"": IncludeConfig{Path: "../../" + DefaultTerragruntConfigPath}},
nil,
terragruntOptionsForTest(t, "../child/sub-child/"+DefaultTerragruntConfigPath),
parentDir,
},
{
map[string]IncludeConfig{
"root": IncludeConfig{Path: "../../" + DefaultTerragruntConfigPath},
"child": IncludeConfig{Path: "../../other-child/" + DefaultTerragruntConfigPath},
},
[]string{"child"},
terragruntOptionsForTest(t, helpers.RootFolder+"child/sub-child/"+DefaultTerragruntConfigPath),
fmt.Sprintf("%s/other-child", filepath.VolumeName(parentDir)),
},
}

for _, testCase := range testCases {
actualPath, actualErr := getParentTerragruntDir(testCase.include, testCase.terragruntOptions)
trackInclude := getTrackIncludeFromTestData(testCase.include, testCase.params)
actualPath, actualErr := getParentTerragruntDir(testCase.params, trackInclude, testCase.terragruntOptions)
assert.Nil(t, actualErr, "For include %v and options %v, unexpected error: %v", testCase.include, testCase.terragruntOptions, actualErr)
assert.Equal(t, testCase.expectedPath, actualPath, "For include %v and options %v", testCase.include, testCase.terragruntOptions)
}
Expand Down Expand Up @@ -923,3 +977,23 @@ func getKeys(valueMap map[string]cty.Value) map[string]bool {
}
return keys
}

func getTrackIncludeFromTestData(includeMap map[string]IncludeConfig, params []string) *TrackInclude {
if len(includeMap) == 0 {
return nil
}
currentList := make([]IncludeConfig, len(includeMap))
i := 0
for _, val := range includeMap {
currentList[i] = val
i++
}
trackInclude := &TrackInclude{
CurrentList: currentList,
CurrentMap: includeMap,
}
if len(params) == 0 {
trackInclude.Original = &currentList[0]
}
return trackInclude
}
Loading