package configs import ( "github.com/hashicorp/hcl/v2" ) // MergeBodies creates a new HCL body that contains a combination of the // given base and override bodies. Attributes and blocks defined in the // override body take precedence over those of the same name defined in // the base body. // // If any block of a particular type appears in "override" then it will // replace _all_ of the blocks of the same type in "base" in the new // body. func MergeBodies(base, override hcl.Body) hcl.Body { return mergeBody{ Base: base, Override: override, } } // mergeBody is a hcl.Body implementation that wraps a pair of other bodies // and allows attributes and blocks within the override to take precedence // over those defined in the base body. // // This is used to deal with dynamically-processed bodies in Module.mergeFile. // It uses a shallow-only merging strategy where direct attributes defined // in Override will override attributes of the same name in Base, while any // blocks defined in Override will hide all blocks of the same type in Base. // // This cannot possibly "do the right thing" in all cases, because we don't // have enough information about user intent. However, this behavior is intended // to be reasonable for simple overriding use-cases. type mergeBody struct { Base hcl.Body Override hcl.Body } var _ hcl.Body = mergeBody{} func (b mergeBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) { var diags hcl.Diagnostics baseSchema := schemaWithDynamic(schema) overrideSchema := schemaWithDynamic(schemaForOverrides(schema)) baseContent, _, cDiags := b.Base.PartialContent(baseSchema) diags = append(diags, cDiags...) overrideContent, _, cDiags := b.Override.PartialContent(overrideSchema) diags = append(diags, cDiags...) content := b.prepareContent(baseContent, overrideContent) return content, diags } func (b mergeBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) { var diags hcl.Diagnostics baseSchema := schemaWithDynamic(schema) overrideSchema := schemaWithDynamic(schemaForOverrides(schema)) baseContent, baseRemain, cDiags := b.Base.PartialContent(baseSchema) diags = append(diags, cDiags...) overrideContent, overrideRemain, cDiags := b.Override.PartialContent(overrideSchema) diags = append(diags, cDiags...) content := b.prepareContent(baseContent, overrideContent) remain := MergeBodies(baseRemain, overrideRemain) return content, remain, diags } func (b mergeBody) prepareContent(base *hcl.BodyContent, override *hcl.BodyContent) *hcl.BodyContent { content := &hcl.BodyContent{ Attributes: make(hcl.Attributes), } // For attributes we just assign from each map in turn and let the override // map clobber any matching entries from base. for k, a := range base.Attributes { content.Attributes[k] = a } for k, a := range override.Attributes { content.Attributes[k] = a } // Things are a little more interesting for blocks because they arrive // as a flat list. Our merging semantics call for us to suppress blocks // from base if at least one block of the same type appears in override. // We explicitly do not try to correlate and deeply merge nested blocks, // since we don't have enough context here to infer user intent. overriddenBlockTypes := make(map[string]bool) for _, block := range override.Blocks { if block.Type == "dynamic" { overriddenBlockTypes[block.Labels[0]] = true continue } overriddenBlockTypes[block.Type] = true } for _, block := range base.Blocks { // We skip over dynamic blocks whose type label is an overridden type // but note that below we do still leave them as dynamic blocks in // the result because expanding the dynamic blocks that are left is // done much later during the core graph walks, where we can safely // evaluate the expressions. if block.Type == "dynamic" && overriddenBlockTypes[block.Labels[0]] { continue } if overriddenBlockTypes[block.Type] { continue } content.Blocks = append(content.Blocks, block) } for _, block := range override.Blocks { content.Blocks = append(content.Blocks, block) } return content } func (b mergeBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) { var diags hcl.Diagnostics ret := make(hcl.Attributes) baseAttrs, aDiags := b.Base.JustAttributes() diags = append(diags, aDiags...) overrideAttrs, aDiags := b.Override.JustAttributes() diags = append(diags, aDiags...) for k, a := range baseAttrs { ret[k] = a } for k, a := range overrideAttrs { ret[k] = a } return ret, diags } func (b mergeBody) MissingItemRange() hcl.Range { return b.Base.MissingItemRange() }