github.com/kevinklinger/open_terraform@v1.3.6/noninternal/configs/module_merge_body.go (about)

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