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

Deep merge strategy #1759

Merged
merged 2 commits into from
Jul 30, 2021
Merged
Show file tree
Hide file tree
Changes from all 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
36 changes: 23 additions & 13 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,8 @@ func (cfg *IncludeConfig) GetMergeStrategy() (MergeStrategyType, error) {
return NoMerge, nil
case string(ShallowMerge):
return ShallowMerge, nil
case string(DeepMerge):
return DeepMerge, nil
default:
return NoMerge, errors.WithStackTrace(InvalidMergeStrategyType(strategy))
}
Expand All @@ -225,6 +227,7 @@ type MergeStrategyType string
const (
NoMerge MergeStrategyType = "no_merge"
ShallowMerge MergeStrategyType = "shallow"
DeepMerge MergeStrategyType = "deep"
)

// ModuleDependencies represents the paths to other Terraform modules that must be applied before the current module
Expand Down Expand Up @@ -523,18 +526,18 @@ func containsTerragruntModule(path string, info os.FileInfo, terragruntOptions *
// Read the Terragrunt config file from its default location
func ReadTerragruntConfig(terragruntOptions *options.TerragruntOptions) (*TerragruntConfig, error) {
terragruntOptions.Logger.Debugf("Reading Terragrunt config file at %s", terragruntOptions.TerragruntConfigPath)
return ParseConfigFile(terragruntOptions.TerragruntConfigPath, terragruntOptions, nil)
return ParseConfigFile(terragruntOptions.TerragruntConfigPath, terragruntOptions, nil, nil)
}

// Parse the Terragrunt config file at the given path. If the include parameter is not nil, then treat this as a config
// included in some other config file when resolving relative paths.
func ParseConfigFile(filename string, terragruntOptions *options.TerragruntOptions, include *IncludeConfig) (*TerragruntConfig, error) {
func ParseConfigFile(filename string, terragruntOptions *options.TerragruntOptions, include *IncludeConfig, dependencyOutputs *cty.Value) (*TerragruntConfig, error) {
configString, err := util.ReadFileAsString(filename)
if err != nil {
return nil, err
}

config, err := ParseConfigString(configString, terragruntOptions, include, filename)
config, err := ParseConfigString(configString, terragruntOptions, include, filename, dependencyOutputs)
if err != nil {
return nil, err
}
Expand All @@ -557,6 +560,8 @@ func ParseConfigFile(filename string, terragruntOptions *options.TerragruntOptio
// 3. Parse dependency blocks. This includes running `terragrunt output` to fetch the output data from another
// terragrunt config, so that it is accessible within the config. See PartialParseConfigString for a way to parse the
// blocks but avoid decoding.
// Note that this step is skipped if we already retrieved all the dependencies (which is the case when parsing
// included config files). This is determined by the dependencyOutputs input parameter.
// Allowed References:
// - locals
// 4. Parse everything else. At this point, all the necessary building blocks for parsing the rest of the config are
Expand All @@ -571,6 +576,7 @@ func ParseConfigString(
terragruntOptions *options.TerragruntOptions,
includeFromChild *IncludeConfig,
filename string,
dependencyOutputs *cty.Value,
) (*TerragruntConfig, error) {
// Parse the HCL string into an AST body that can be decoded multiple times later without having to re-parse
parser := hclparse.NewParser()
Expand All @@ -587,17 +593,20 @@ func ParseConfigString(

// Initialize evaluation context extensions from base blocks.
contextExtensions := EvalContextExtensions{
Locals: localsAsCty,
TrackInclude: trackInclude,
Locals: localsAsCty,
TrackInclude: trackInclude,
DecodedDependencies: dependencyOutputs,
}

// Decode just the `dependency` blocks, retrieving the outputs from the target terragrunt config in the
// process.
retrievedOutputs, err := decodeAndRetrieveOutputs(file, filename, terragruntOptions, contextExtensions)
if err != nil {
return nil, err
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)
if err != nil {
return nil, err
}
contextExtensions.DecodedDependencies = retrievedOutputs
}
contextExtensions.DecodedDependencies = retrievedOutputs

// Decode the rest of the config, passing in this config's `include` block or the child's `include` block, whichever
// is appropriate
Expand All @@ -616,7 +625,7 @@ func ParseConfigString(

// If this file includes another, parse and merge it. Otherwise just return this config.
if terragruntInclude.Include != nil {
return handleInclude(config, terragruntInclude, terragruntOptions)
return handleInclude(config, terragruntInclude.Include, terragruntOptions, contextExtensions.DecodedDependencies)
} else {
return config, nil
}
Expand Down Expand Up @@ -877,9 +886,10 @@ type InvalidMergeStrategyType string

func (err InvalidMergeStrategyType) Error() string {
return fmt.Sprintf(
"Include merge strategy %s is unknown. Valid strategies are: %s, %s",
"Include merge strategy %s is unknown. Valid strategies are: %s, %s, %s",
string(err),
NoMerge,
ShallowMerge,
DeepMerge,
)
}
4 changes: 2 additions & 2 deletions config/config_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ func CreateTerragruntEvalContext(
ctx.Variables["dependency"] = *extensions.DecodedDependencies
}
if extensions.TrackInclude.Current != nil && extensions.TrackInclude.Current.GetExpose() {
includedConfig, err := parseIncludedConfig(extensions.TrackInclude.Current, terragruntOptions)
includedConfig, err := parseIncludedConfig(extensions.TrackInclude.Current, terragruntOptions, extensions.DecodedDependencies)
if err != nil {
return ctx, err
}
Expand Down Expand Up @@ -418,7 +418,7 @@ func readTerragruntConfig(configPath string, defaultVal *cty.Value, terragruntOp

// We update the context of terragruntOptions to the config being read in.
targetOptions := terragruntOptions.Clone(targetConfig)
config, err := ParseConfigFile(targetConfig, targetOptions, nil)
config, err := ParseConfigFile(targetConfig, targetOptions, nil, nil)
if err != nil {
return cty.NilVal, err
}
Expand Down
10 changes: 5 additions & 5 deletions config/config_helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ func TestResolveTerragruntInterpolation(t *testing.T) {
// get updated due to concurrency within the scope of t.Run(..) below
testCase := testCase
t.Run(fmt.Sprintf("%s--%s", testCase.str, testCase.terragruntOptions.TerragruntConfigPath), func(t *testing.T) {
actualOut, actualErr := ParseConfigString(testCase.str, testCase.terragruntOptions, testCase.include, "mock-path-for-test.hcl")
actualOut, actualErr := ParseConfigString(testCase.str, testCase.terragruntOptions, testCase.include, "mock-path-for-test.hcl", nil)
if testCase.expectedErr != "" {
require.Error(t, actualErr)
assert.Contains(t, actualErr.Error(), testCase.expectedErr)
Expand Down Expand Up @@ -413,7 +413,7 @@ func TestResolveEnvInterpolationConfigString(t *testing.T) {
// get updated due to concurrency within the scope of t.Run(..) below
testCase := testCase
t.Run(testCase.str, func(t *testing.T) {
actualOut, actualErr := ParseConfigString(testCase.str, testCase.terragruntOptions, testCase.include, "mock-path-for-test.hcl")
actualOut, actualErr := ParseConfigString(testCase.str, testCase.terragruntOptions, testCase.include, "mock-path-for-test.hcl", nil)
if testCase.expectedErr != "" {
require.Error(t, actualErr)
assert.Contains(t, actualErr.Error(), testCase.expectedErr)
Expand Down Expand Up @@ -460,7 +460,7 @@ func TestResolveCommandsInterpolationConfigString(t *testing.T) {
testCase := testCase

t.Run(testCase.str, func(t *testing.T) {
actualOut, actualErr := ParseConfigString(testCase.str, testCase.terragruntOptions, testCase.include, "mock-path-for-test.hcl")
actualOut, actualErr := ParseConfigString(testCase.str, testCase.terragruntOptions, testCase.include, "mock-path-for-test.hcl", nil)
require.NoError(t, actualErr, "For string '%s' include %v and options %v, unexpected error: %v", testCase.str, testCase.include, testCase.terragruntOptions, actualErr)

require.NotNil(t, actualOut)
Expand Down Expand Up @@ -502,7 +502,7 @@ func TestResolveCliArgsInterpolationConfigString(t *testing.T) {
expectedFooInput,
}
t.Run(testCase.str, func(t *testing.T) {
actualOut, actualErr := ParseConfigString(testCase.str, testCase.terragruntOptions, testCase.include, "mock-path-for-test.hcl")
actualOut, actualErr := ParseConfigString(testCase.str, testCase.terragruntOptions, testCase.include, "mock-path-for-test.hcl", nil)
require.NoError(t, actualErr, "For string '%s' include %v and options %v, unexpected error: %v", testCase.str, testCase.include, testCase.terragruntOptions, actualErr)

require.NotNil(t, actualOut)
Expand Down Expand Up @@ -681,7 +681,7 @@ func TestTerraformBuiltInFunctions(t *testing.T) {
t.Run(testCase.input, func(t *testing.T) {

terragruntOptions := terragruntOptionsForTest(t, "../test/fixture-config-terraform-functions/"+DefaultTerragruntConfigPath)
actual, err := ParseConfigString(fmt.Sprintf("inputs = { test = %s }", testCase.input), terragruntOptions, nil, terragruntOptions.TerragruntConfigPath)
actual, err := ParseConfigString(fmt.Sprintf("inputs = { test = %s }", testCase.input), terragruntOptions, nil, terragruntOptions.TerragruntConfigPath, nil)
require.NoError(t, err, "For hcl '%s' include %v and options %v, unexpected error: %v", testCase.input, nil, terragruntOptions, err)

require.NotNil(t, actual)
Expand Down
7 changes: 1 addition & 6 deletions config/config_partial.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,12 +324,7 @@ func PartialParseConfigString(

// If this file includes another, parse and merge the partial blocks. Otherwise just return this config.
if terragruntInclude.Include != nil {
includedConfig, err := partialParseIncludedConfig(terragruntInclude.Include, terragruntOptions, decodeList)
if err != nil {
return nil, err
}
includedConfig.Merge(&output, terragruntOptions)
return includedConfig, nil
return handleIncludePartial(&output, terragruntInclude.Include, terragruntOptions, decodeList)
}
return &output, nil
}
Expand Down
Loading