]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/terraform/lang/blocktoattr/fixup.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / terraform / lang / blocktoattr / fixup.go
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 }