]>
Commit | Line | Data |
---|---|---|
107c1cdb ND |
1 | package configs |
2 | ||
3 | import ( | |
4 | "github.com/hashicorp/hcl2/hcl" | |
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 | for _, block := range override.Blocks { | |
116 | content.Blocks = append(content.Blocks, block) | |
117 | } | |
118 | ||
119 | return content | |
120 | } | |
121 | ||
122 | func (b mergeBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) { | |
123 | var diags hcl.Diagnostics | |
124 | ret := make(hcl.Attributes) | |
125 | ||
126 | baseAttrs, aDiags := b.Base.JustAttributes() | |
127 | diags = append(diags, aDiags...) | |
128 | overrideAttrs, aDiags := b.Override.JustAttributes() | |
129 | diags = append(diags, aDiags...) | |
130 | ||
131 | for k, a := range baseAttrs { | |
132 | ret[k] = a | |
133 | } | |
134 | for k, a := range overrideAttrs { | |
135 | ret[k] = a | |
136 | } | |
137 | ||
138 | return ret, diags | |
139 | } | |
140 | ||
141 | func (b mergeBody) MissingItemRange() hcl.Range { | |
142 | return b.Base.MissingItemRange() | |
143 | } |