github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/configs/module_merge_body.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package configs
     5  
     6  import (
     7  	"github.com/hashicorp/hcl/v2"
     8  )
     9  
    10  // MergeBodies creates a new HCL body that contains a combination of the
    11  // given base and override bodies. Attributes and blocks defined in the
    12  // override body take precedence over those of the same name defined in
    13  // the base body.
    14  //
    15  // If any block of a particular type appears in "override" then it will
    16  // replace _all_ of the blocks of the same type in "base" in the new
    17  // body.
    18  func MergeBodies(base, override hcl.Body) hcl.Body {
    19  	return mergeBody{
    20  		Base:     base,
    21  		Override: override,
    22  	}
    23  }
    24  
    25  // mergeBody is a hcl.Body implementation that wraps a pair of other bodies
    26  // and allows attributes and blocks within the override to take precedence
    27  // over those defined in the base body.
    28  //
    29  // This is used to deal with dynamically-processed bodies in Module.mergeFile.
    30  // It uses a shallow-only merging strategy where direct attributes defined
    31  // in Override will override attributes of the same name in Base, while any
    32  // blocks defined in Override will hide all blocks of the same type in Base.
    33  //
    34  // This cannot possibly "do the right thing" in all cases, because we don't
    35  // have enough information about user intent. However, this behavior is intended
    36  // to be reasonable for simple overriding use-cases.
    37  type mergeBody struct {
    38  	Base     hcl.Body
    39  	Override hcl.Body
    40  }
    41  
    42  var _ hcl.Body = mergeBody{}
    43  
    44  func (b mergeBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) {
    45  	var diags hcl.Diagnostics
    46  	baseSchema := schemaWithDynamic(schema)
    47  	overrideSchema := schemaWithDynamic(schemaForOverrides(schema))
    48  
    49  	baseContent, _, cDiags := b.Base.PartialContent(baseSchema)
    50  	diags = append(diags, cDiags...)
    51  	overrideContent, _, cDiags := b.Override.PartialContent(overrideSchema)
    52  	diags = append(diags, cDiags...)
    53  
    54  	content := b.prepareContent(baseContent, overrideContent)
    55  
    56  	return content, diags
    57  }
    58  
    59  func (b mergeBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) {
    60  	var diags hcl.Diagnostics
    61  	baseSchema := schemaWithDynamic(schema)
    62  	overrideSchema := schemaWithDynamic(schemaForOverrides(schema))
    63  
    64  	baseContent, baseRemain, cDiags := b.Base.PartialContent(baseSchema)
    65  	diags = append(diags, cDiags...)
    66  	overrideContent, overrideRemain, cDiags := b.Override.PartialContent(overrideSchema)
    67  	diags = append(diags, cDiags...)
    68  
    69  	content := b.prepareContent(baseContent, overrideContent)
    70  
    71  	remain := MergeBodies(baseRemain, overrideRemain)
    72  
    73  	return content, remain, diags
    74  }
    75  
    76  func (b mergeBody) prepareContent(base *hcl.BodyContent, override *hcl.BodyContent) *hcl.BodyContent {
    77  	content := &hcl.BodyContent{
    78  		Attributes: make(hcl.Attributes),
    79  	}
    80  
    81  	// For attributes we just assign from each map in turn and let the override
    82  	// map clobber any matching entries from base.
    83  	for k, a := range base.Attributes {
    84  		content.Attributes[k] = a
    85  	}
    86  	for k, a := range override.Attributes {
    87  		content.Attributes[k] = a
    88  	}
    89  
    90  	// Things are a little more interesting for blocks because they arrive
    91  	// as a flat list. Our merging semantics call for us to suppress blocks
    92  	// from base if at least one block of the same type appears in override.
    93  	// We explicitly do not try to correlate and deeply merge nested blocks,
    94  	// since we don't have enough context here to infer user intent.
    95  
    96  	overriddenBlockTypes := make(map[string]bool)
    97  	for _, block := range override.Blocks {
    98  		if block.Type == "dynamic" {
    99  			overriddenBlockTypes[block.Labels[0]] = true
   100  			continue
   101  		}
   102  		overriddenBlockTypes[block.Type] = true
   103  	}
   104  	for _, block := range base.Blocks {
   105  		// We skip over dynamic blocks whose type label is an overridden type
   106  		// but note that below we do still leave them as dynamic blocks in
   107  		// the result because expanding the dynamic blocks that are left is
   108  		// done much later during the core graph walks, where we can safely
   109  		// evaluate the expressions.
   110  		if block.Type == "dynamic" && overriddenBlockTypes[block.Labels[0]] {
   111  			continue
   112  		}
   113  		if overriddenBlockTypes[block.Type] {
   114  			continue
   115  		}
   116  		content.Blocks = append(content.Blocks, block)
   117  	}
   118  	content.Blocks = append(content.Blocks, override.Blocks...)
   119  
   120  	return content
   121  }
   122  
   123  func (b mergeBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) {
   124  	var diags hcl.Diagnostics
   125  	ret := make(hcl.Attributes)
   126  
   127  	baseAttrs, aDiags := b.Base.JustAttributes()
   128  	diags = append(diags, aDiags...)
   129  	overrideAttrs, aDiags := b.Override.JustAttributes()
   130  	diags = append(diags, aDiags...)
   131  
   132  	for k, a := range baseAttrs {
   133  		ret[k] = a
   134  	}
   135  	for k, a := range overrideAttrs {
   136  		ret[k] = a
   137  	}
   138  
   139  	return ret, diags
   140  }
   141  
   142  func (b mergeBody) MissingItemRange() hcl.Range {
   143  	return b.Base.MissingItemRange()
   144  }