aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/lang/blocktoattr
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/lang/blocktoattr')
-rw-r--r--vendor/github.com/hashicorp/terraform/lang/blocktoattr/doc.go5
-rw-r--r--vendor/github.com/hashicorp/terraform/lang/blocktoattr/fixup.go187
-rw-r--r--vendor/github.com/hashicorp/terraform/lang/blocktoattr/schema.go145
-rw-r--r--vendor/github.com/hashicorp/terraform/lang/blocktoattr/variables.go43
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.
5package 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 @@
1package blocktoattr
2
3import (
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.
23func 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
38type 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.
47func (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
53func (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
64func (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
71func (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.
82func (b *fixupBody) effectiveSchema(given *hcl.BodySchema) *hcl.BodySchema {
83 return effectiveSchema(given, b.original, b.names, true)
84}
85
86func (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
132type fixupBlocksExpr struct {
133 blocks hcl.Blocks
134 ety cty.Type
135}
136
137func (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
169func (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
179func (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
185func (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 @@
1package blocktoattr
2
3import (
4 "github.com/hashicorp/hcl2/hcl"
5 "github.com/hashicorp/terraform/configs/configschema"
6 "github.com/zclconf/go-cty/cty"
7)
8
9func 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
23func 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.
106func 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.
123func 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.
143func 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 @@
1package blocktoattr
2
3import (
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.
22func ExpandedVariables(body hcl.Body, schema *configschema.Block) []hcl.Traversal {
23 rootNode := dynblock.WalkVariables(body)
24 return walkVariables(rootNode, body, schema)
25}
26
27func 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}