diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/lang/blocktoattr/fixup.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/lang/blocktoattr/fixup.go | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/lang/blocktoattr/fixup.go b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/fixup.go new file mode 100644 index 0000000..d8c2e77 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/fixup.go | |||
@@ -0,0 +1,187 @@ | |||
1 | package blocktoattr | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/hcl" | ||
5 | "github.com/hashicorp/hcl2/hcldec" | ||
6 | "github.com/hashicorp/terraform/configs/configschema" | ||
7 | "github.com/zclconf/go-cty/cty" | ||
8 | ) | ||
9 | |||
10 | // FixUpBlockAttrs takes a raw HCL body and adds some additional normalization | ||
11 | // functionality to allow attributes that are specified as having list or set | ||
12 | // type in the schema to be written with HCL block syntax as multiple nested | ||
13 | // blocks with the attribute name as the block type. | ||
14 | // | ||
15 | // This partially restores some of the block/attribute confusion from HCL 1 | ||
16 | // so that existing patterns that depended on that confusion can continue to | ||
17 | // be used in the short term while we settle on a longer-term strategy. | ||
18 | // | ||
19 | // Most of the fixup work is actually done when the returned body is | ||
20 | // subsequently decoded, so while FixUpBlockAttrs always succeeds, the eventual | ||
21 | // decode of the body might not, if the content of the body is so ambiguous | ||
22 | // that there's no safe way to map it to the schema. | ||
23 | func FixUpBlockAttrs(body hcl.Body, schema *configschema.Block) hcl.Body { | ||
24 | // The schema should never be nil, but in practice it seems to be sometimes | ||
25 | // in the presence of poorly-configured test mocks, so we'll be robust | ||
26 | // by synthesizing an empty one. | ||
27 | if schema == nil { | ||
28 | schema = &configschema.Block{} | ||
29 | } | ||
30 | |||
31 | return &fixupBody{ | ||
32 | original: body, | ||
33 | schema: schema, | ||
34 | names: ambiguousNames(schema), | ||
35 | } | ||
36 | } | ||
37 | |||
38 | type fixupBody struct { | ||
39 | original hcl.Body | ||
40 | schema *configschema.Block | ||
41 | names map[string]struct{} | ||
42 | } | ||
43 | |||
44 | // Content decodes content from the body. The given schema must be the lower-level | ||
45 | // representation of the same schema that was previously passed to FixUpBlockAttrs, | ||
46 | // or else the result is undefined. | ||
47 | func (b *fixupBody) Content(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Diagnostics) { | ||
48 | schema = b.effectiveSchema(schema) | ||
49 | content, diags := b.original.Content(schema) | ||
50 | return b.fixupContent(content), diags | ||
51 | } | ||
52 | |||
53 | func (b *fixupBody) PartialContent(schema *hcl.BodySchema) (*hcl.BodyContent, hcl.Body, hcl.Diagnostics) { | ||
54 | schema = b.effectiveSchema(schema) | ||
55 | content, remain, diags := b.original.PartialContent(schema) | ||
56 | remain = &fixupBody{ | ||
57 | original: remain, | ||
58 | schema: b.schema, | ||
59 | names: b.names, | ||
60 | } | ||
61 | return b.fixupContent(content), remain, diags | ||
62 | } | ||
63 | |||
64 | func (b *fixupBody) JustAttributes() (hcl.Attributes, hcl.Diagnostics) { | ||
65 | // FixUpBlockAttrs is not intended to be used in situations where we'd use | ||
66 | // JustAttributes, so we just pass this through verbatim to complete our | ||
67 | // implementation of hcl.Body. | ||
68 | return b.original.JustAttributes() | ||
69 | } | ||
70 | |||
71 | func (b *fixupBody) MissingItemRange() hcl.Range { | ||
72 | return b.original.MissingItemRange() | ||
73 | } | ||
74 | |||
75 | // effectiveSchema produces a derived *hcl.BodySchema by sniffing the body's | ||
76 | // content to determine whether the author has used attribute or block syntax | ||
77 | // for each of the ambigious attributes where both are permitted. | ||
78 | // | ||
79 | // The resulting schema will always contain all of the same names that are | ||
80 | // in the given schema, but some attribute schemas may instead be replaced by | ||
81 | // block header schemas. | ||
82 | func (b *fixupBody) effectiveSchema(given *hcl.BodySchema) *hcl.BodySchema { | ||
83 | return effectiveSchema(given, b.original, b.names, true) | ||
84 | } | ||
85 | |||
86 | func (b *fixupBody) fixupContent(content *hcl.BodyContent) *hcl.BodyContent { | ||
87 | var ret hcl.BodyContent | ||
88 | ret.Attributes = make(hcl.Attributes) | ||
89 | for name, attr := range content.Attributes { | ||
90 | ret.Attributes[name] = attr | ||
91 | } | ||
92 | blockAttrVals := make(map[string][]*hcl.Block) | ||
93 | for _, block := range content.Blocks { | ||
94 | if _, exists := b.names[block.Type]; exists { | ||
95 | // If we get here then we've found a block type whose instances need | ||
96 | // to be re-interpreted as a list-of-objects attribute. We'll gather | ||
97 | // those up and fix them up below. | ||
98 | blockAttrVals[block.Type] = append(blockAttrVals[block.Type], block) | ||
99 | continue | ||
100 | } | ||
101 | |||
102 | // We need to now re-wrap our inner body so it will be subject to the | ||
103 | // same attribute-as-block fixup when recursively decoded. | ||
104 | retBlock := *block // shallow copy | ||
105 | if blockS, ok := b.schema.BlockTypes[block.Type]; ok { | ||
106 | // Would be weird if not ok, but we'll allow it for robustness; body just won't be fixed up, then | ||
107 | retBlock.Body = FixUpBlockAttrs(retBlock.Body, &blockS.Block) | ||
108 | } | ||
109 | |||
110 | ret.Blocks = append(ret.Blocks, &retBlock) | ||
111 | } | ||
112 | // No we'll install synthetic attributes for each of our fixups. We can't | ||
113 | // do this exactly because HCL's information model expects an attribute | ||
114 | // to be a single decl but we have multiple separate blocks. We'll | ||
115 | // approximate things, then, by using only our first block for the source | ||
116 | // location information. (We are guaranteed at least one by the above logic.) | ||
117 | for name, blocks := range blockAttrVals { | ||
118 | ret.Attributes[name] = &hcl.Attribute{ | ||
119 | Name: name, | ||
120 | Expr: &fixupBlocksExpr{ | ||
121 | blocks: blocks, | ||
122 | ety: b.schema.Attributes[name].Type.ElementType(), | ||
123 | }, | ||
124 | |||
125 | Range: blocks[0].DefRange, | ||
126 | NameRange: blocks[0].TypeRange, | ||
127 | } | ||
128 | } | ||
129 | return &ret | ||
130 | } | ||
131 | |||
132 | type fixupBlocksExpr struct { | ||
133 | blocks hcl.Blocks | ||
134 | ety cty.Type | ||
135 | } | ||
136 | |||
137 | func (e *fixupBlocksExpr) Value(ctx *hcl.EvalContext) (cty.Value, hcl.Diagnostics) { | ||
138 | // In order to produce a suitable value for our expression we need to | ||
139 | // now decode the whole descendent block structure under each of our block | ||
140 | // bodies. | ||
141 | // | ||
142 | // That requires us to do something rather strange: we must construct a | ||
143 | // synthetic block type schema derived from the element type of the | ||
144 | // attribute, thus inverting our usual direction of lowering a schema | ||
145 | // into an implied type. Because a type is less detailed than a schema, | ||
146 | // the result is imprecise and in particular will just consider all | ||
147 | // the attributes to be optional and let the provider eventually decide | ||
148 | // whether to return errors if they turn out to be null when required. | ||
149 | schema := SchemaForCtyElementType(e.ety) // this schema's ImpliedType will match e.ety | ||
150 | spec := schema.DecoderSpec() | ||
151 | |||
152 | vals := make([]cty.Value, len(e.blocks)) | ||
153 | var diags hcl.Diagnostics | ||
154 | for i, block := range e.blocks { | ||
155 | body := FixUpBlockAttrs(block.Body, schema) | ||
156 | val, blockDiags := hcldec.Decode(body, spec, ctx) | ||
157 | diags = append(diags, blockDiags...) | ||
158 | if val == cty.NilVal { | ||
159 | val = cty.UnknownVal(e.ety) | ||
160 | } | ||
161 | vals[i] = val | ||
162 | } | ||
163 | if len(vals) == 0 { | ||
164 | return cty.ListValEmpty(e.ety), diags | ||
165 | } | ||
166 | return cty.ListVal(vals), diags | ||
167 | } | ||
168 | |||
169 | func (e *fixupBlocksExpr) Variables() []hcl.Traversal { | ||
170 | var ret []hcl.Traversal | ||
171 | schema := SchemaForCtyElementType(e.ety) | ||
172 | spec := schema.DecoderSpec() | ||
173 | for _, block := range e.blocks { | ||
174 | ret = append(ret, hcldec.Variables(block.Body, spec)...) | ||
175 | } | ||
176 | return ret | ||
177 | } | ||
178 | |||
179 | func (e *fixupBlocksExpr) Range() hcl.Range { | ||
180 | // This is not really an appropriate range for the expression but it's | ||
181 | // the best we can do from here. | ||
182 | return e.blocks[0].DefRange | ||
183 | } | ||
184 | |||
185 | func (e *fixupBlocksExpr) StartRange() hcl.Range { | ||
186 | return e.blocks[0].DefRange | ||
187 | } | ||