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 }