Skip to content

Commit

Permalink
Override "terraform" blocks
Browse files Browse the repository at this point in the history
See https://developer.hashicorp.com/terraform/language/files/override#merging-terraform-blocks

The settings within terraform blocks are considered individually when merging.

If the required_providers argument is set, its value is merged on an element-by-element basis, which allows an override block to adjust the constraint for a single provider without affecting the constraints for other providers.

In both the required_version and required_providers settings, each override constraint entirely replaces the constraints for the same component in the original block. If both the base block and the override block both set required_version then the constraints in the base block are entirely ignored.

The presence of a block defining a backend (either cloud or backend) in an override file always takes precedence over a block defining a backend in the original configuration. That is, if a cloud block is set within the original configuration and a backend block is set in the override file, Terraform will use the backend block specified in the override file upon merging. Similarly, if a backend block is set within the original configuration and a cloud block is set in the override file, Terraform will use the cloud block specified in the override file upon merging.
  • Loading branch information
wata727 committed Sep 29, 2024
1 parent 4c28333 commit 30020bc
Show file tree
Hide file tree
Showing 2 changed files with 587 additions and 22 deletions.
132 changes: 110 additions & 22 deletions terraform/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,40 +169,128 @@ func (m *Module) PartialContent(schema *hclext.BodySchema, ctx *Evaluator) (*hcl
// Note that this function returns the overwritten primary blocks
// but has side effects on the primary blocks.
func overrideBlocks(primaries, overrides hclext.Blocks) hclext.Blocks {
dict := map[string]*hclext.Block{}
dict := map[string]hclext.Blocks{}
for _, primary := range primaries {
// A top-level block in an override file merges with a block in a normal configuration file
// that has the same block header.
// The block header is the block type and any quoted labels that follow it.
key := fmt.Sprintf("%s[%s]", primary.Type, strings.Join(primary.Labels, ","))
dict[key] = primary
switch primary.Type {
case "terraform":
// The "terraform" blocks are allowed to be declared multiple times.
dict[primary.Type] = append(dict[primary.Type], primary)

default:
// A top-level block in an override file merges with a block in a normal configuration file
// that has the same block header.
// The block header is the block type and any quoted labels that follow it.
key := fmt.Sprintf("%s[%s]", primary.Type, strings.Join(primary.Labels, ","))
dict[key] = hclext.Blocks{primary}
}
}

newPrimaries := hclext.Blocks{}
for _, override := range overrides {
key := fmt.Sprintf("%s[%s]", override.Type, strings.Join(override.Labels, ","))
if primary, exists := dict[key]; exists {
// Within a top-level block, an attribute argument within an override block
// replaces any argument of the same name in the original block.
for name, attr := range override.Body.Attributes {
primary.Body.Attributes[name] = attr
switch override.Type {
case "terraform":
// Any required_providers that were not used for overrides will be added,
// so we will track whether they were used for overrides or not.
overrideRequiredProviders := override.Body.Blocks.ByType()["required_providers"]

for _, primary := range dict[override.Type] {
// In both the required_version and required_providers settings,
// each override constraint entirely replaces the constraints for
// the same component in the original block.
for name, attr := range override.Body.Attributes {
primary.Body.Attributes[name] = attr
}

for _, overrideInnerBlock := range override.Body.Blocks {
switch overrideInnerBlock.Type {
case "required_providers":
// If the required_providers argument is set, its value is merged on an element-by-element basis
for _, primaryInnerBlock := range primary.Body.Blocks {
if primaryInnerBlock.Type == "required_providers" {
for name, attr := range overrideInnerBlock.Body.Attributes {
if _, exists := primaryInnerBlock.Body.Attributes[name]; exists {
primaryInnerBlock.Body.Attributes[name] = attr
// Remove the required provider that was used to override.
for _, requiredProvider := range overrideRequiredProviders {
delete(requiredProvider.Body.Attributes, name)
}
}
}
}
}

case "cloud", "backend":
// The presence of a block defining a backend (either cloud or backend) in an override file
// always takes precedence over a block defining a backend in the original configuration.
newInnerBlocks := hclext.Blocks{}
for _, primaryInnerBlock := range primary.Body.Blocks {
if primaryInnerBlock.Type != "cloud" && primaryInnerBlock.Type != "backend" {
newInnerBlocks = append(newInnerBlocks, primaryInnerBlock)
}
}
primary.Body.Blocks = append(newInnerBlocks, overrideInnerBlock)

default:
newInnerBlocks := hclext.Blocks{}
for _, primaryInnerBlock := range primary.Body.Blocks {
if primaryInnerBlock.Type != overrideInnerBlock.Type {
newInnerBlocks = append(newInnerBlocks, primaryInnerBlock)
}
}
primary.Body.Blocks = append(newInnerBlocks, overrideInnerBlock)
}
}
}

// Any remaining required providers that aren't overridden will be added as a new block.
newRequiredProviders := hclext.Blocks{}
for _, requiredProvider := range overrideRequiredProviders {
if len(requiredProvider.Body.Attributes) > 0 {
newRequiredProviders = append(newRequiredProviders, requiredProvider)
}
}
if len(newRequiredProviders) > 0 {
newPrimaries = append(newPrimaries, &hclext.Block{
Type: override.Type,
Labels: override.Labels,
Body: &hclext.BodyContent{
Blocks: newRequiredProviders,
},
DefRange: override.DefRange,
TypeRange: override.TypeRange,
LabelRanges: override.LabelRanges,
})
}

default:
key := fmt.Sprintf("%s[%s]", override.Type, strings.Join(override.Labels, ","))
if primaries, exists := dict[key]; exists {
// The general rule, duplicated blocks are not allowed.
primary := primaries[0]

// Within a top-level block, an attribute argument within an override block
// replaces any argument of the same name in the original block.
for name, attr := range override.Body.Attributes {
primary.Body.Attributes[name] = attr
}

// Within a top-level block, any nested blocks within an override block replace
// all blocks of the same type in the original block.
// Any block types that do not appear in the override block remain from the original block.
for _, overrideBlock := range override.Body.Blocks {
overriddenBlocks := hclext.Blocks{}
for _, primaryBlock := range primary.Body.Blocks {
if primaryBlock.Type != overrideBlock.Type {
overriddenBlocks = append(overriddenBlocks, primaryBlock)
// Within a top-level block, any nested blocks within an override block replace
// all blocks of the same type in the original block.
// Any block types that do not appear in the override block remain from the original block.
for _, overrideInnerBlock := range override.Body.Blocks {
newInnerBlocks := hclext.Blocks{}
for _, primaryInnerBlock := range primary.Body.Blocks {
if primaryInnerBlock.Type != overrideInnerBlock.Type {
newInnerBlocks = append(newInnerBlocks, primaryInnerBlock)
}
}
primary.Body.Blocks = append(newInnerBlocks, overrideInnerBlock)
}
primary.Body.Blocks = append(overriddenBlocks, overrideBlock)
}
}
}

return primaries
return append(primaries, newPrimaries...)
}

var moduleSchema = &hclext.BodySchema{
Expand Down
Loading

0 comments on commit 30020bc

Please sign in to comment.