diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/lang/blocktoattr')
4 files changed, 380 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/lang/blocktoattr/doc.go b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/doc.go new file mode 100644 index 0000000..8f89909 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/doc.go | |||
@@ -0,0 +1,5 @@ | |||
1 | // Package blocktoattr includes some helper functions that can perform | ||
2 | // preprocessing on a HCL body where a configschema.Block schema is available | ||
3 | // in order to allow list and set attributes defined in the schema to be | ||
4 | // optionally written by the user as block syntax. | ||
5 | package blocktoattr | ||
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 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/blocktoattr/schema.go b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/schema.go new file mode 100644 index 0000000..2f2463a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/schema.go | |||
@@ -0,0 +1,145 @@ | |||
1 | package blocktoattr | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/hcl" | ||
5 | "github.com/hashicorp/terraform/configs/configschema" | ||
6 | "github.com/zclconf/go-cty/cty" | ||
7 | ) | ||
8 | |||
9 | func ambiguousNames(schema *configschema.Block) map[string]struct{} { | ||
10 | if schema == nil { | ||
11 | return nil | ||
12 | } | ||
13 | ambiguousNames := make(map[string]struct{}) | ||
14 | for name, attrS := range schema.Attributes { | ||
15 | aty := attrS.Type | ||
16 | if (aty.IsListType() || aty.IsSetType()) && aty.ElementType().IsObjectType() { | ||
17 | ambiguousNames[name] = struct{}{} | ||
18 | } | ||
19 | } | ||
20 | return ambiguousNames | ||
21 | } | ||
22 | |||
23 | func effectiveSchema(given *hcl.BodySchema, body hcl.Body, ambiguousNames map[string]struct{}, dynamicExpanded bool) *hcl.BodySchema { | ||
24 | ret := &hcl.BodySchema{} | ||
25 | |||
26 | appearsAsBlock := make(map[string]struct{}) | ||
27 | { | ||
28 | // We'll construct some throwaway schemas here just to probe for | ||
29 | // whether each of our ambiguous names seems to be being used as | ||
30 | // an attribute or a block. We need to check both because in JSON | ||
31 | // syntax we rely on the schema to decide between attribute or block | ||
32 | // interpretation and so JSON will always answer yes to both of | ||
33 | // these questions and we want to prefer the attribute interpretation | ||
34 | // in that case. | ||
35 | var probeSchema hcl.BodySchema | ||
36 | |||
37 | for name := range ambiguousNames { | ||
38 | probeSchema = hcl.BodySchema{ | ||
39 | Attributes: []hcl.AttributeSchema{ | ||
40 | { | ||
41 | Name: name, | ||
42 | }, | ||
43 | }, | ||
44 | } | ||
45 | content, _, _ := body.PartialContent(&probeSchema) | ||
46 | if _, exists := content.Attributes[name]; exists { | ||
47 | // Can decode as an attribute, so we'll go with that. | ||
48 | continue | ||
49 | } | ||
50 | probeSchema = hcl.BodySchema{ | ||
51 | Blocks: []hcl.BlockHeaderSchema{ | ||
52 | { | ||
53 | Type: name, | ||
54 | }, | ||
55 | }, | ||
56 | } | ||
57 | content, _, _ = body.PartialContent(&probeSchema) | ||
58 | if len(content.Blocks) > 0 { | ||
59 | // No attribute present and at least one block present, so | ||
60 | // we'll need to rewrite this one as a block for a successful | ||
61 | // result. | ||
62 | appearsAsBlock[name] = struct{}{} | ||
63 | } | ||
64 | } | ||
65 | if !dynamicExpanded { | ||
66 | // If we're deciding for a context where dynamic blocks haven't | ||
67 | // been expanded yet then we need to probe for those too. | ||
68 | probeSchema = hcl.BodySchema{ | ||
69 | Blocks: []hcl.BlockHeaderSchema{ | ||
70 | { | ||
71 | Type: "dynamic", | ||
72 | LabelNames: []string{"type"}, | ||
73 | }, | ||
74 | }, | ||
75 | } | ||
76 | content, _, _ := body.PartialContent(&probeSchema) | ||
77 | for _, block := range content.Blocks { | ||
78 | if _, exists := ambiguousNames[block.Labels[0]]; exists { | ||
79 | appearsAsBlock[block.Labels[0]] = struct{}{} | ||
80 | } | ||
81 | } | ||
82 | } | ||
83 | } | ||
84 | |||
85 | for _, attrS := range given.Attributes { | ||
86 | if _, exists := appearsAsBlock[attrS.Name]; exists { | ||
87 | ret.Blocks = append(ret.Blocks, hcl.BlockHeaderSchema{ | ||
88 | Type: attrS.Name, | ||
89 | }) | ||
90 | } else { | ||
91 | ret.Attributes = append(ret.Attributes, attrS) | ||
92 | } | ||
93 | } | ||
94 | |||
95 | // Anything that is specified as a block type in the input schema remains | ||
96 | // that way by just passing through verbatim. | ||
97 | ret.Blocks = append(ret.Blocks, given.Blocks...) | ||
98 | |||
99 | return ret | ||
100 | } | ||
101 | |||
102 | // SchemaForCtyElementType converts a cty object type into an | ||
103 | // approximately-equivalent configschema.Block representing the element of | ||
104 | // a list or set. If the given type is not an object type then this | ||
105 | // function will panic. | ||
106 | func SchemaForCtyElementType(ty cty.Type) *configschema.Block { | ||
107 | atys := ty.AttributeTypes() | ||
108 | ret := &configschema.Block{ | ||
109 | Attributes: make(map[string]*configschema.Attribute, len(atys)), | ||
110 | } | ||
111 | for name, aty := range atys { | ||
112 | ret.Attributes[name] = &configschema.Attribute{ | ||
113 | Type: aty, | ||
114 | Optional: true, | ||
115 | } | ||
116 | } | ||
117 | return ret | ||
118 | } | ||
119 | |||
120 | // SchemaForCtyContainerType converts a cty list-of-object or set-of-object type | ||
121 | // into an approximately-equivalent configschema.NestedBlock. If the given type | ||
122 | // is not of the expected kind then this function will panic. | ||
123 | func SchemaForCtyContainerType(ty cty.Type) *configschema.NestedBlock { | ||
124 | var nesting configschema.NestingMode | ||
125 | switch { | ||
126 | case ty.IsListType(): | ||
127 | nesting = configschema.NestingList | ||
128 | case ty.IsSetType(): | ||
129 | nesting = configschema.NestingSet | ||
130 | default: | ||
131 | panic("unsuitable type") | ||
132 | } | ||
133 | nested := SchemaForCtyElementType(ty.ElementType()) | ||
134 | return &configschema.NestedBlock{ | ||
135 | Nesting: nesting, | ||
136 | Block: *nested, | ||
137 | } | ||
138 | } | ||
139 | |||
140 | // TypeCanBeBlocks returns true if the given type is a list-of-object or | ||
141 | // set-of-object type, and would thus be subject to the blocktoattr fixup | ||
142 | // if used as an attribute type. | ||
143 | func TypeCanBeBlocks(ty cty.Type) bool { | ||
144 | return (ty.IsListType() || ty.IsSetType()) && ty.ElementType().IsObjectType() | ||
145 | } | ||
diff --git a/vendor/github.com/hashicorp/terraform/lang/blocktoattr/variables.go b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/variables.go new file mode 100644 index 0000000..e123b8a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/lang/blocktoattr/variables.go | |||
@@ -0,0 +1,43 @@ | |||
1 | package blocktoattr | ||
2 | |||
3 | import ( | ||
4 | "github.com/hashicorp/hcl2/ext/dynblock" | ||
5 | "github.com/hashicorp/hcl2/hcl" | ||
6 | "github.com/hashicorp/hcl2/hcldec" | ||
7 | "github.com/hashicorp/terraform/configs/configschema" | ||
8 | ) | ||
9 | |||
10 | // ExpandedVariables finds all of the global variables referenced in the | ||
11 | // given body with the given schema while taking into account the possibilities | ||
12 | // both of "dynamic" blocks being expanded and the possibility of certain | ||
13 | // attributes being written instead as nested blocks as allowed by the | ||
14 | // FixUpBlockAttrs function. | ||
15 | // | ||
16 | // This function exists to allow variables to be analyzed prior to dynamic | ||
17 | // block expansion while also dealing with the fact that dynamic block expansion | ||
18 | // might in turn produce nested blocks that are subject to FixUpBlockAttrs. | ||
19 | // | ||
20 | // This is intended as a drop-in replacement for dynblock.VariablesHCLDec, | ||
21 | // which is itself a drop-in replacement for hcldec.Variables. | ||
22 | func ExpandedVariables(body hcl.Body, schema *configschema.Block) []hcl.Traversal { | ||
23 | rootNode := dynblock.WalkVariables(body) | ||
24 | return walkVariables(rootNode, body, schema) | ||
25 | } | ||
26 | |||
27 | func walkVariables(node dynblock.WalkVariablesNode, body hcl.Body, schema *configschema.Block) []hcl.Traversal { | ||
28 | givenRawSchema := hcldec.ImpliedSchema(schema.DecoderSpec()) | ||
29 | ambiguousNames := ambiguousNames(schema) | ||
30 | effectiveRawSchema := effectiveSchema(givenRawSchema, body, ambiguousNames, false) | ||
31 | vars, children := node.Visit(effectiveRawSchema) | ||
32 | |||
33 | for _, child := range children { | ||
34 | if blockS, exists := schema.BlockTypes[child.BlockTypeName]; exists { | ||
35 | vars = append(vars, walkVariables(child.Node, child.Body(), &blockS.Block)...) | ||
36 | } else if attrS, exists := schema.Attributes[child.BlockTypeName]; exists { | ||
37 | synthSchema := SchemaForCtyElementType(attrS.Type.ElementType()) | ||
38 | vars = append(vars, walkVariables(child.Node, child.Body(), synthSchema)...) | ||
39 | } | ||
40 | } | ||
41 | |||
42 | return vars | ||
43 | } | ||