aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/configs/configschema
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/configs/configschema')
-rw-r--r--vendor/github.com/hashicorp/terraform/configs/configschema/coerce_value.go274
-rw-r--r--vendor/github.com/hashicorp/terraform/configs/configschema/decoder_spec.go117
-rw-r--r--vendor/github.com/hashicorp/terraform/configs/configschema/doc.go14
-rw-r--r--vendor/github.com/hashicorp/terraform/configs/configschema/empty_value.go59
-rw-r--r--vendor/github.com/hashicorp/terraform/configs/configschema/implied_type.go42
-rw-r--r--vendor/github.com/hashicorp/terraform/configs/configschema/internal_validate.go105
-rw-r--r--vendor/github.com/hashicorp/terraform/configs/configschema/nestingmode_string.go28
-rw-r--r--vendor/github.com/hashicorp/terraform/configs/configschema/none_required.go38
-rw-r--r--vendor/github.com/hashicorp/terraform/configs/configschema/schema.go130
-rw-r--r--vendor/github.com/hashicorp/terraform/configs/configschema/validate_traversal.go173
10 files changed, 980 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/configs/configschema/coerce_value.go b/vendor/github.com/hashicorp/terraform/configs/configschema/coerce_value.go
new file mode 100644
index 0000000..e59f58d
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/configs/configschema/coerce_value.go
@@ -0,0 +1,274 @@
1package configschema
2
3import (
4 "fmt"
5
6 "github.com/zclconf/go-cty/cty"
7 "github.com/zclconf/go-cty/cty/convert"
8)
9
10// CoerceValue attempts to force the given value to conform to the type
11// implied by the receiever, while also applying the same validation and
12// transformation rules that would be applied by the decoder specification
13// returned by method DecoderSpec.
14//
15// This is useful in situations where a configuration must be derived from
16// an already-decoded value. It is always better to decode directly from
17// configuration where possible since then source location information is
18// still available to produce diagnostics, but in special situations this
19// function allows a compatible result to be obtained even if the
20// configuration objects are not available.
21//
22// If the given value cannot be converted to conform to the receiving schema
23// then an error is returned describing one of possibly many problems. This
24// error may be a cty.PathError indicating a position within the nested
25// data structure where the problem applies.
26func (b *Block) CoerceValue(in cty.Value) (cty.Value, error) {
27 var path cty.Path
28 return b.coerceValue(in, path)
29}
30
31func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
32 switch {
33 case in.IsNull():
34 return cty.NullVal(b.ImpliedType()), nil
35 case !in.IsKnown():
36 return cty.UnknownVal(b.ImpliedType()), nil
37 }
38
39 ty := in.Type()
40 if !ty.IsObjectType() {
41 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("an object is required")
42 }
43
44 for name := range ty.AttributeTypes() {
45 if _, defined := b.Attributes[name]; defined {
46 continue
47 }
48 if _, defined := b.BlockTypes[name]; defined {
49 continue
50 }
51 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("unexpected attribute %q", name)
52 }
53
54 attrs := make(map[string]cty.Value)
55
56 for name, attrS := range b.Attributes {
57 var val cty.Value
58 switch {
59 case ty.HasAttribute(name):
60 val = in.GetAttr(name)
61 case attrS.Computed || attrS.Optional:
62 val = cty.NullVal(attrS.Type)
63 default:
64 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", name)
65 }
66
67 val, err := attrS.coerceValue(val, append(path, cty.GetAttrStep{Name: name}))
68 if err != nil {
69 return cty.UnknownVal(b.ImpliedType()), err
70 }
71
72 attrs[name] = val
73 }
74 for typeName, blockS := range b.BlockTypes {
75 switch blockS.Nesting {
76
77 case NestingSingle, NestingGroup:
78 switch {
79 case ty.HasAttribute(typeName):
80 var err error
81 val := in.GetAttr(typeName)
82 attrs[typeName], err = blockS.coerceValue(val, append(path, cty.GetAttrStep{Name: typeName}))
83 if err != nil {
84 return cty.UnknownVal(b.ImpliedType()), err
85 }
86 case blockS.MinItems != 1 && blockS.MaxItems != 1:
87 if blockS.Nesting == NestingGroup {
88 attrs[typeName] = blockS.EmptyValue()
89 } else {
90 attrs[typeName] = cty.NullVal(blockS.ImpliedType())
91 }
92 default:
93 // We use the word "attribute" here because we're talking about
94 // the cty sense of that word rather than the HCL sense.
95 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", typeName)
96 }
97
98 case NestingList:
99 switch {
100 case ty.HasAttribute(typeName):
101 coll := in.GetAttr(typeName)
102
103 switch {
104 case coll.IsNull():
105 attrs[typeName] = cty.NullVal(cty.List(blockS.ImpliedType()))
106 continue
107 case !coll.IsKnown():
108 attrs[typeName] = cty.UnknownVal(cty.List(blockS.ImpliedType()))
109 continue
110 }
111
112 if !coll.CanIterateElements() {
113 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a list")
114 }
115 l := coll.LengthInt()
116 if l < blockS.MinItems {
117 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("insufficient items for attribute %q; must have at least %d", typeName, blockS.MinItems)
118 }
119 if l > blockS.MaxItems && blockS.MaxItems > 0 {
120 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("too many items for attribute %q; cannot have more than %d", typeName, blockS.MaxItems)
121 }
122 if l == 0 {
123 attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType())
124 continue
125 }
126 elems := make([]cty.Value, 0, l)
127 {
128 path = append(path, cty.GetAttrStep{Name: typeName})
129 for it := coll.ElementIterator(); it.Next(); {
130 var err error
131 idx, val := it.Element()
132 val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
133 if err != nil {
134 return cty.UnknownVal(b.ImpliedType()), err
135 }
136 elems = append(elems, val)
137 }
138 }
139 attrs[typeName] = cty.ListVal(elems)
140 case blockS.MinItems == 0:
141 attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType())
142 default:
143 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", typeName)
144 }
145
146 case NestingSet:
147 switch {
148 case ty.HasAttribute(typeName):
149 coll := in.GetAttr(typeName)
150
151 switch {
152 case coll.IsNull():
153 attrs[typeName] = cty.NullVal(cty.Set(blockS.ImpliedType()))
154 continue
155 case !coll.IsKnown():
156 attrs[typeName] = cty.UnknownVal(cty.Set(blockS.ImpliedType()))
157 continue
158 }
159
160 if !coll.CanIterateElements() {
161 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a set")
162 }
163 l := coll.LengthInt()
164 if l < blockS.MinItems {
165 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("insufficient items for attribute %q; must have at least %d", typeName, blockS.MinItems)
166 }
167 if l > blockS.MaxItems && blockS.MaxItems > 0 {
168 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("too many items for attribute %q; cannot have more than %d", typeName, blockS.MaxItems)
169 }
170 if l == 0 {
171 attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType())
172 continue
173 }
174 elems := make([]cty.Value, 0, l)
175 {
176 path = append(path, cty.GetAttrStep{Name: typeName})
177 for it := coll.ElementIterator(); it.Next(); {
178 var err error
179 idx, val := it.Element()
180 val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx}))
181 if err != nil {
182 return cty.UnknownVal(b.ImpliedType()), err
183 }
184 elems = append(elems, val)
185 }
186 }
187 attrs[typeName] = cty.SetVal(elems)
188 case blockS.MinItems == 0:
189 attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType())
190 default:
191 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", typeName)
192 }
193
194 case NestingMap:
195 switch {
196 case ty.HasAttribute(typeName):
197 coll := in.GetAttr(typeName)
198
199 switch {
200 case coll.IsNull():
201 attrs[typeName] = cty.NullVal(cty.Map(blockS.ImpliedType()))
202 continue
203 case !coll.IsKnown():
204 attrs[typeName] = cty.UnknownVal(cty.Map(blockS.ImpliedType()))
205 continue
206 }
207
208 if !coll.CanIterateElements() {
209 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
210 }
211 l := coll.LengthInt()
212 if l == 0 {
213 attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType())
214 continue
215 }
216 elems := make(map[string]cty.Value)
217 {
218 path = append(path, cty.GetAttrStep{Name: typeName})
219 for it := coll.ElementIterator(); it.Next(); {
220 var err error
221 key, val := it.Element()
222 if key.Type() != cty.String || key.IsNull() || !key.IsKnown() {
223 return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map")
224 }
225 val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: key}))
226 if err != nil {
227 return cty.UnknownVal(b.ImpliedType()), err
228 }
229 elems[key.AsString()] = val
230 }
231 }
232
233 // If the attribute values here contain any DynamicPseudoTypes,
234 // the concrete type must be an object.
235 useObject := false
236 switch {
237 case coll.Type().IsObjectType():
238 useObject = true
239 default:
240 // It's possible that we were given a map, and need to coerce it to an object
241 ety := coll.Type().ElementType()
242 for _, v := range elems {
243 if !v.Type().Equals(ety) {
244 useObject = true
245 break
246 }
247 }
248 }
249
250 if useObject {
251 attrs[typeName] = cty.ObjectVal(elems)
252 } else {
253 attrs[typeName] = cty.MapVal(elems)
254 }
255 default:
256 attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType())
257 }
258
259 default:
260 // should never happen because above is exhaustive
261 panic(fmt.Errorf("unsupported nesting mode %#v", blockS.Nesting))
262 }
263 }
264
265 return cty.ObjectVal(attrs), nil
266}
267
268func (a *Attribute) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) {
269 val, err := convert.Convert(in, a.Type)
270 if err != nil {
271 return cty.UnknownVal(a.Type), path.NewError(err)
272 }
273 return val, nil
274}
diff --git a/vendor/github.com/hashicorp/terraform/configs/configschema/decoder_spec.go b/vendor/github.com/hashicorp/terraform/configs/configschema/decoder_spec.go
new file mode 100644
index 0000000..d8f41ea
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/configs/configschema/decoder_spec.go
@@ -0,0 +1,117 @@
1package configschema
2
3import (
4 "github.com/hashicorp/hcl2/hcldec"
5)
6
7var mapLabelNames = []string{"key"}
8
9// DecoderSpec returns a hcldec.Spec that can be used to decode a HCL Body
10// using the facilities in the hcldec package.
11//
12// The returned specification is guaranteed to return a value of the same type
13// returned by method ImpliedType, but it may contain null values if any of the
14// block attributes are defined as optional and/or computed respectively.
15func (b *Block) DecoderSpec() hcldec.Spec {
16 ret := hcldec.ObjectSpec{}
17 if b == nil {
18 return ret
19 }
20
21 for name, attrS := range b.Attributes {
22 ret[name] = attrS.decoderSpec(name)
23 }
24
25 for name, blockS := range b.BlockTypes {
26 if _, exists := ret[name]; exists {
27 // This indicates an invalid schema, since it's not valid to
28 // define both an attribute and a block type of the same name.
29 // However, we don't raise this here since it's checked by
30 // InternalValidate.
31 continue
32 }
33
34 childSpec := blockS.Block.DecoderSpec()
35
36 switch blockS.Nesting {
37 case NestingSingle, NestingGroup:
38 ret[name] = &hcldec.BlockSpec{
39 TypeName: name,
40 Nested: childSpec,
41 Required: blockS.MinItems == 1 && blockS.MaxItems >= 1,
42 }
43 if blockS.Nesting == NestingGroup {
44 ret[name] = &hcldec.DefaultSpec{
45 Primary: ret[name],
46 Default: &hcldec.LiteralSpec{
47 Value: blockS.EmptyValue(),
48 },
49 }
50 }
51 case NestingList:
52 // We prefer to use a list where possible, since it makes our
53 // implied type more complete, but if there are any
54 // dynamically-typed attributes inside we must use a tuple
55 // instead, at the expense of our type then not being predictable.
56 if blockS.Block.ImpliedType().HasDynamicTypes() {
57 ret[name] = &hcldec.BlockTupleSpec{
58 TypeName: name,
59 Nested: childSpec,
60 MinItems: blockS.MinItems,
61 MaxItems: blockS.MaxItems,
62 }
63 } else {
64 ret[name] = &hcldec.BlockListSpec{
65 TypeName: name,
66 Nested: childSpec,
67 MinItems: blockS.MinItems,
68 MaxItems: blockS.MaxItems,
69 }
70 }
71 case NestingSet:
72 // We forbid dynamically-typed attributes inside NestingSet in
73 // InternalValidate, so we don't do anything special to handle
74 // that here. (There is no set analog to tuple and object types,
75 // because cty's set implementation depends on knowing the static
76 // type in order to properly compute its internal hashes.)
77 ret[name] = &hcldec.BlockSetSpec{
78 TypeName: name,
79 Nested: childSpec,
80 MinItems: blockS.MinItems,
81 MaxItems: blockS.MaxItems,
82 }
83 case NestingMap:
84 // We prefer to use a list where possible, since it makes our
85 // implied type more complete, but if there are any
86 // dynamically-typed attributes inside we must use a tuple
87 // instead, at the expense of our type then not being predictable.
88 if blockS.Block.ImpliedType().HasDynamicTypes() {
89 ret[name] = &hcldec.BlockObjectSpec{
90 TypeName: name,
91 Nested: childSpec,
92 LabelNames: mapLabelNames,
93 }
94 } else {
95 ret[name] = &hcldec.BlockMapSpec{
96 TypeName: name,
97 Nested: childSpec,
98 LabelNames: mapLabelNames,
99 }
100 }
101 default:
102 // Invalid nesting type is just ignored. It's checked by
103 // InternalValidate.
104 continue
105 }
106 }
107
108 return ret
109}
110
111func (a *Attribute) decoderSpec(name string) hcldec.Spec {
112 return &hcldec.AttrSpec{
113 Name: name,
114 Type: a.Type,
115 Required: a.Required,
116 }
117}
diff --git a/vendor/github.com/hashicorp/terraform/configs/configschema/doc.go b/vendor/github.com/hashicorp/terraform/configs/configschema/doc.go
new file mode 100644
index 0000000..caf8d73
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/configs/configschema/doc.go
@@ -0,0 +1,14 @@
1// Package configschema contains types for describing the expected structure
2// of a configuration block whose shape is not known until runtime.
3//
4// For example, this is used to describe the expected contents of a resource
5// configuration block, which is defined by the corresponding provider plugin
6// and thus not compiled into Terraform core.
7//
8// A configschema primarily describes the shape of configuration, but it is
9// also suitable for use with other structures derived from the configuration,
10// such as the cached state of a resource or a resource diff.
11//
12// This package should not be confused with the package helper/schema, which
13// is the higher-level helper library used to implement providers themselves.
14package configschema
diff --git a/vendor/github.com/hashicorp/terraform/configs/configschema/empty_value.go b/vendor/github.com/hashicorp/terraform/configs/configschema/empty_value.go
new file mode 100644
index 0000000..005da56
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/configs/configschema/empty_value.go
@@ -0,0 +1,59 @@
1package configschema
2
3import (
4 "github.com/zclconf/go-cty/cty"
5)
6
7// EmptyValue returns the "empty value" for the recieving block, which for
8// a block type is a non-null object where all of the attribute values are
9// the empty values of the block's attributes and nested block types.
10//
11// In other words, it returns the value that would be returned if an empty
12// block were decoded against the recieving schema, assuming that no required
13// attribute or block constraints were honored.
14func (b *Block) EmptyValue() cty.Value {
15 vals := make(map[string]cty.Value)
16 for name, attrS := range b.Attributes {
17 vals[name] = attrS.EmptyValue()
18 }
19 for name, blockS := range b.BlockTypes {
20 vals[name] = blockS.EmptyValue()
21 }
22 return cty.ObjectVal(vals)
23}
24
25// EmptyValue returns the "empty value" for the receiving attribute, which is
26// the value that would be returned if there were no definition of the attribute
27// at all, ignoring any required constraint.
28func (a *Attribute) EmptyValue() cty.Value {
29 return cty.NullVal(a.Type)
30}
31
32// EmptyValue returns the "empty value" for when there are zero nested blocks
33// present of the receiving type.
34func (b *NestedBlock) EmptyValue() cty.Value {
35 switch b.Nesting {
36 case NestingSingle:
37 return cty.NullVal(b.Block.ImpliedType())
38 case NestingGroup:
39 return b.Block.EmptyValue()
40 case NestingList:
41 if ty := b.Block.ImpliedType(); ty.HasDynamicTypes() {
42 return cty.EmptyTupleVal
43 } else {
44 return cty.ListValEmpty(ty)
45 }
46 case NestingMap:
47 if ty := b.Block.ImpliedType(); ty.HasDynamicTypes() {
48 return cty.EmptyObjectVal
49 } else {
50 return cty.MapValEmpty(ty)
51 }
52 case NestingSet:
53 return cty.SetValEmpty(b.Block.ImpliedType())
54 default:
55 // Should never get here because the above is intended to be exhaustive,
56 // but we'll be robust and return a result nonetheless.
57 return cty.NullVal(cty.DynamicPseudoType)
58 }
59}
diff --git a/vendor/github.com/hashicorp/terraform/configs/configschema/implied_type.go b/vendor/github.com/hashicorp/terraform/configs/configschema/implied_type.go
new file mode 100644
index 0000000..c0ee841
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/configs/configschema/implied_type.go
@@ -0,0 +1,42 @@
1package configschema
2
3import (
4 "github.com/hashicorp/hcl2/hcldec"
5 "github.com/zclconf/go-cty/cty"
6)
7
8// ImpliedType returns the cty.Type that would result from decoding a
9// configuration block using the receiving block schema.
10//
11// ImpliedType always returns a result, even if the given schema is
12// inconsistent. Code that creates configschema.Block objects should be
13// tested using the InternalValidate method to detect any inconsistencies
14// that would cause this method to fall back on defaults and assumptions.
15func (b *Block) ImpliedType() cty.Type {
16 if b == nil {
17 return cty.EmptyObject
18 }
19
20 return hcldec.ImpliedType(b.DecoderSpec())
21}
22
23// ContainsSensitive returns true if any of the attributes of the receiving
24// block or any of its descendent blocks are marked as sensitive.
25//
26// Blocks themselves cannot be sensitive as a whole -- sensitivity is a
27// per-attribute idea -- but sometimes we want to include a whole object
28// decoded from a block in some UI output, and that is safe to do only if
29// none of the contained attributes are sensitive.
30func (b *Block) ContainsSensitive() bool {
31 for _, attrS := range b.Attributes {
32 if attrS.Sensitive {
33 return true
34 }
35 }
36 for _, blockS := range b.BlockTypes {
37 if blockS.ContainsSensitive() {
38 return true
39 }
40 }
41 return false
42}
diff --git a/vendor/github.com/hashicorp/terraform/configs/configschema/internal_validate.go b/vendor/github.com/hashicorp/terraform/configs/configschema/internal_validate.go
new file mode 100644
index 0000000..ebf1abb
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/configs/configschema/internal_validate.go
@@ -0,0 +1,105 @@
1package configschema
2
3import (
4 "fmt"
5 "regexp"
6
7 "github.com/zclconf/go-cty/cty"
8
9 multierror "github.com/hashicorp/go-multierror"
10)
11
12var validName = regexp.MustCompile(`^[a-z0-9_]+$`)
13
14// InternalValidate returns an error if the receiving block and its child
15// schema definitions have any consistencies with the documented rules for
16// valid schema.
17//
18// This is intended to be used within unit tests to detect when a given
19// schema is invalid.
20func (b *Block) InternalValidate() error {
21 if b == nil {
22 return fmt.Errorf("top-level block schema is nil")
23 }
24 return b.internalValidate("", nil)
25
26}
27
28func (b *Block) internalValidate(prefix string, err error) error {
29 for name, attrS := range b.Attributes {
30 if attrS == nil {
31 err = multierror.Append(err, fmt.Errorf("%s%s: attribute schema is nil", prefix, name))
32 continue
33 }
34 if !validName.MatchString(name) {
35 err = multierror.Append(err, fmt.Errorf("%s%s: name may contain only lowercase letters, digits and underscores", prefix, name))
36 }
37 if attrS.Optional == false && attrS.Required == false && attrS.Computed == false {
38 err = multierror.Append(err, fmt.Errorf("%s%s: must set Optional, Required or Computed", prefix, name))
39 }
40 if attrS.Optional && attrS.Required {
41 err = multierror.Append(err, fmt.Errorf("%s%s: cannot set both Optional and Required", prefix, name))
42 }
43 if attrS.Computed && attrS.Required {
44 err = multierror.Append(err, fmt.Errorf("%s%s: cannot set both Computed and Required", prefix, name))
45 }
46 if attrS.Type == cty.NilType {
47 err = multierror.Append(err, fmt.Errorf("%s%s: Type must be set to something other than cty.NilType", prefix, name))
48 }
49 }
50
51 for name, blockS := range b.BlockTypes {
52 if blockS == nil {
53 err = multierror.Append(err, fmt.Errorf("%s%s: block schema is nil", prefix, name))
54 continue
55 }
56
57 if _, isAttr := b.Attributes[name]; isAttr {
58 err = multierror.Append(err, fmt.Errorf("%s%s: name defined as both attribute and child block type", prefix, name))
59 } else if !validName.MatchString(name) {
60 err = multierror.Append(err, fmt.Errorf("%s%s: name may contain only lowercase letters, digits and underscores", prefix, name))
61 }
62
63 if blockS.MinItems < 0 || blockS.MaxItems < 0 {
64 err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must both be greater than zero", prefix, name))
65 }
66
67 switch blockS.Nesting {
68 case NestingSingle:
69 switch {
70 case blockS.MinItems != blockS.MaxItems:
71 err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must match in NestingSingle mode", prefix, name))
72 case blockS.MinItems < 0 || blockS.MinItems > 1:
73 err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must be set to either 0 or 1 in NestingSingle mode", prefix, name))
74 }
75 case NestingGroup:
76 if blockS.MinItems != 0 || blockS.MaxItems != 0 {
77 err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems cannot be used in NestingGroup mode", prefix, name))
78 }
79 case NestingList, NestingSet:
80 if blockS.MinItems > blockS.MaxItems && blockS.MaxItems != 0 {
81 err = multierror.Append(err, fmt.Errorf("%s%s: MinItems must be less than or equal to MaxItems in %s mode", prefix, name, blockS.Nesting))
82 }
83 if blockS.Nesting == NestingSet {
84 ety := blockS.Block.ImpliedType()
85 if ety.HasDynamicTypes() {
86 // This is not permitted because the HCL (cty) set implementation
87 // needs to know the exact type of set elements in order to
88 // properly hash them, and so can't support mixed types.
89 err = multierror.Append(err, fmt.Errorf("%s%s: NestingSet blocks may not contain attributes of cty.DynamicPseudoType", prefix, name))
90 }
91 }
92 case NestingMap:
93 if blockS.MinItems != 0 || blockS.MaxItems != 0 {
94 err = multierror.Append(err, fmt.Errorf("%s%s: MinItems and MaxItems must both be 0 in NestingMap mode", prefix, name))
95 }
96 default:
97 err = multierror.Append(err, fmt.Errorf("%s%s: invalid nesting mode %s", prefix, name, blockS.Nesting))
98 }
99
100 subPrefix := prefix + name + "."
101 err = blockS.Block.internalValidate(subPrefix, err)
102 }
103
104 return err
105}
diff --git a/vendor/github.com/hashicorp/terraform/configs/configschema/nestingmode_string.go b/vendor/github.com/hashicorp/terraform/configs/configschema/nestingmode_string.go
new file mode 100644
index 0000000..febe743
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/configs/configschema/nestingmode_string.go
@@ -0,0 +1,28 @@
1// Code generated by "stringer -type=NestingMode"; DO NOT EDIT.
2
3package configschema
4
5import "strconv"
6
7func _() {
8 // An "invalid array index" compiler error signifies that the constant values have changed.
9 // Re-run the stringer command to generate them again.
10 var x [1]struct{}
11 _ = x[nestingModeInvalid-0]
12 _ = x[NestingSingle-1]
13 _ = x[NestingGroup-2]
14 _ = x[NestingList-3]
15 _ = x[NestingSet-4]
16 _ = x[NestingMap-5]
17}
18
19const _NestingMode_name = "nestingModeInvalidNestingSingleNestingGroupNestingListNestingSetNestingMap"
20
21var _NestingMode_index = [...]uint8{0, 18, 31, 43, 54, 64, 74}
22
23func (i NestingMode) String() string {
24 if i < 0 || i >= NestingMode(len(_NestingMode_index)-1) {
25 return "NestingMode(" + strconv.FormatInt(int64(i), 10) + ")"
26 }
27 return _NestingMode_name[_NestingMode_index[i]:_NestingMode_index[i+1]]
28}
diff --git a/vendor/github.com/hashicorp/terraform/configs/configschema/none_required.go b/vendor/github.com/hashicorp/terraform/configs/configschema/none_required.go
new file mode 100644
index 0000000..0be3b8f
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/configs/configschema/none_required.go
@@ -0,0 +1,38 @@
1package configschema
2
3// NoneRequired returns a deep copy of the receiver with any required
4// attributes translated to optional.
5func (b *Block) NoneRequired() *Block {
6 ret := &Block{}
7
8 if b.Attributes != nil {
9 ret.Attributes = make(map[string]*Attribute, len(b.Attributes))
10 }
11 for name, attrS := range b.Attributes {
12 ret.Attributes[name] = attrS.forceOptional()
13 }
14
15 if b.BlockTypes != nil {
16 ret.BlockTypes = make(map[string]*NestedBlock, len(b.BlockTypes))
17 }
18 for name, blockS := range b.BlockTypes {
19 ret.BlockTypes[name] = blockS.noneRequired()
20 }
21
22 return ret
23}
24
25func (b *NestedBlock) noneRequired() *NestedBlock {
26 ret := *b
27 ret.Block = *(ret.Block.NoneRequired())
28 ret.MinItems = 0
29 ret.MaxItems = 0
30 return &ret
31}
32
33func (a *Attribute) forceOptional() *Attribute {
34 ret := *a
35 ret.Optional = true
36 ret.Required = false
37 return &ret
38}
diff --git a/vendor/github.com/hashicorp/terraform/configs/configschema/schema.go b/vendor/github.com/hashicorp/terraform/configs/configschema/schema.go
new file mode 100644
index 0000000..5a67334
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/configs/configschema/schema.go
@@ -0,0 +1,130 @@
1package configschema
2
3import (
4 "github.com/zclconf/go-cty/cty"
5)
6
7// Block represents a configuration block.
8//
9// "Block" here is a logical grouping construct, though it happens to map
10// directly onto the physical block syntax of Terraform's native configuration
11// syntax. It may be a more a matter of convention in other syntaxes, such as
12// JSON.
13//
14// When converted to a value, a Block always becomes an instance of an object
15// type derived from its defined attributes and nested blocks
16type Block struct {
17 // Attributes describes any attributes that may appear directly inside
18 // the block.
19 Attributes map[string]*Attribute
20
21 // BlockTypes describes any nested block types that may appear directly
22 // inside the block.
23 BlockTypes map[string]*NestedBlock
24}
25
26// Attribute represents a configuration attribute, within a block.
27type Attribute struct {
28 // Type is a type specification that the attribute's value must conform to.
29 Type cty.Type
30
31 // Description is an English-language description of the purpose and
32 // usage of the attribute. A description should be concise and use only
33 // one or two sentences, leaving full definition to longer-form
34 // documentation defined elsewhere.
35 Description string
36
37 // Required, if set to true, specifies that an omitted or null value is
38 // not permitted.
39 Required bool
40
41 // Optional, if set to true, specifies that an omitted or null value is
42 // permitted. This field conflicts with Required.
43 Optional bool
44
45 // Computed, if set to true, specifies that the value comes from the
46 // provider rather than from configuration. If combined with Optional,
47 // then the config may optionally provide an overridden value.
48 Computed bool
49
50 // Sensitive, if set to true, indicates that an attribute may contain
51 // sensitive information.
52 //
53 // At present nothing is done with this information, but callers are
54 // encouraged to set it where appropriate so that it may be used in the
55 // future to help Terraform mask sensitive information. (Terraform
56 // currently achieves this in a limited sense via other mechanisms.)
57 Sensitive bool
58}
59
60// NestedBlock represents the embedding of one block within another.
61type NestedBlock struct {
62 // Block is the description of the block that's nested.
63 Block
64
65 // Nesting provides the nesting mode for the child block, which determines
66 // how many instances of the block are allowed, how many labels it expects,
67 // and how the resulting data will be converted into a data structure.
68 Nesting NestingMode
69
70 // MinItems and MaxItems set, for the NestingList and NestingSet nesting
71 // modes, lower and upper limits on the number of child blocks allowed
72 // of the given type. If both are left at zero, no limit is applied.
73 //
74 // As a special case, both values can be set to 1 for NestingSingle in
75 // order to indicate that a particular single block is required.
76 //
77 // These fields are ignored for other nesting modes and must both be left
78 // at zero.
79 MinItems, MaxItems int
80}
81
82// NestingMode is an enumeration of modes for nesting blocks inside other
83// blocks.
84type NestingMode int
85
86//go:generate stringer -type=NestingMode
87
88const (
89 nestingModeInvalid NestingMode = iota
90
91 // NestingSingle indicates that only a single instance of a given
92 // block type is permitted, with no labels, and its content should be
93 // provided directly as an object value.
94 NestingSingle
95
96 // NestingGroup is similar to NestingSingle in that it calls for only a
97 // single instance of a given block type with no labels, but it additonally
98 // guarantees that its result will never be null, even if the block is
99 // absent, and instead the nested attributes and blocks will be treated
100 // as absent in that case. (Any required attributes or blocks within the
101 // nested block are not enforced unless the block is explicitly present
102 // in the configuration, so they are all effectively optional when the
103 // block is not present.)
104 //
105 // This is useful for the situation where a remote API has a feature that
106 // is always enabled but has a group of settings related to that feature
107 // that themselves have default values. By using NestingGroup instead of
108 // NestingSingle in that case, generated plans will show the block as
109 // present even when not present in configuration, thus allowing any
110 // default values within to be displayed to the user.
111 NestingGroup
112
113 // NestingList indicates that multiple blocks of the given type are
114 // permitted, with no labels, and that their corresponding objects should
115 // be provided in a list.
116 NestingList
117
118 // NestingSet indicates that multiple blocks of the given type are
119 // permitted, with no labels, and that their corresponding objects should
120 // be provided in a set.
121 NestingSet
122
123 // NestingMap indicates that multiple blocks of the given type are
124 // permitted, each with a single label, and that their corresponding
125 // objects should be provided in a map whose keys are the labels.
126 //
127 // It's an error, therefore, to use the same label value on multiple
128 // blocks.
129 NestingMap
130)
diff --git a/vendor/github.com/hashicorp/terraform/configs/configschema/validate_traversal.go b/vendor/github.com/hashicorp/terraform/configs/configschema/validate_traversal.go
new file mode 100644
index 0000000..a41e930
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/configs/configschema/validate_traversal.go
@@ -0,0 +1,173 @@
1package configschema
2
3import (
4 "fmt"
5 "sort"
6
7 "github.com/hashicorp/hcl2/hcl"
8 "github.com/hashicorp/hcl2/hcl/hclsyntax"
9 "github.com/zclconf/go-cty/cty"
10
11 "github.com/hashicorp/terraform/helper/didyoumean"
12 "github.com/hashicorp/terraform/tfdiags"
13)
14
15// StaticValidateTraversal checks whether the given traversal (which must be
16// relative) refers to a construct in the receiving schema, returning error
17// diagnostics if any problems are found.
18//
19// This method is "optimistic" in that it will not return errors for possible
20// problems that cannot be detected statically. It is possible that an
21// traversal which passed static validation will still fail when evaluated.
22func (b *Block) StaticValidateTraversal(traversal hcl.Traversal) tfdiags.Diagnostics {
23 if !traversal.IsRelative() {
24 panic("StaticValidateTraversal on absolute traversal")
25 }
26 if len(traversal) == 0 {
27 return nil
28 }
29
30 var diags tfdiags.Diagnostics
31
32 next := traversal[0]
33 after := traversal[1:]
34
35 var name string
36 switch step := next.(type) {
37 case hcl.TraverseAttr:
38 name = step.Name
39 case hcl.TraverseIndex:
40 // No other traversal step types are allowed directly at a block.
41 // If it looks like the user was trying to use index syntax to
42 // access an attribute then we'll produce a specialized message.
43 key := step.Key
44 if key.Type() == cty.String && key.IsKnown() && !key.IsNull() {
45 maybeName := key.AsString()
46 if hclsyntax.ValidIdentifier(maybeName) {
47 diags = diags.Append(&hcl.Diagnostic{
48 Severity: hcl.DiagError,
49 Summary: `Invalid index operation`,
50 Detail: fmt.Sprintf(`Only attribute access is allowed here. Did you mean to access attribute %q using the dot operator?`, maybeName),
51 Subject: &step.SrcRange,
52 })
53 return diags
54 }
55 }
56 // If it looks like some other kind of index then we'll use a generic error.
57 diags = diags.Append(&hcl.Diagnostic{
58 Severity: hcl.DiagError,
59 Summary: `Invalid index operation`,
60 Detail: `Only attribute access is allowed here, using the dot operator.`,
61 Subject: &step.SrcRange,
62 })
63 return diags
64 default:
65 // No other traversal types should appear in a normal valid traversal,
66 // but we'll handle this with a generic error anyway to be robust.
67 diags = diags.Append(&hcl.Diagnostic{
68 Severity: hcl.DiagError,
69 Summary: `Invalid operation`,
70 Detail: `Only attribute access is allowed here, using the dot operator.`,
71 Subject: next.SourceRange().Ptr(),
72 })
73 return diags
74 }
75
76 if attrS, exists := b.Attributes[name]; exists {
77 // For attribute validation we will just apply the rest of the
78 // traversal to an unknown value of the attribute type and pass
79 // through HCL's own errors, since we don't want to replicate all of
80 // HCL's type checking rules here.
81 val := cty.UnknownVal(attrS.Type)
82 _, hclDiags := after.TraverseRel(val)
83 diags = diags.Append(hclDiags)
84 return diags
85 }
86
87 if blockS, exists := b.BlockTypes[name]; exists {
88 moreDiags := blockS.staticValidateTraversal(name, after)
89 diags = diags.Append(moreDiags)
90 return diags
91 }
92
93 // If we get here then the name isn't valid at all. We'll collect up
94 // all of the names that _are_ valid to use as suggestions.
95 var suggestions []string
96 for name := range b.Attributes {
97 suggestions = append(suggestions, name)
98 }
99 for name := range b.BlockTypes {
100 suggestions = append(suggestions, name)
101 }
102 sort.Strings(suggestions)
103 suggestion := didyoumean.NameSuggestion(name, suggestions)
104 if suggestion != "" {
105 suggestion = fmt.Sprintf(" Did you mean %q?", suggestion)
106 }
107 diags = diags.Append(&hcl.Diagnostic{
108 Severity: hcl.DiagError,
109 Summary: `Unsupported attribute`,
110 Detail: fmt.Sprintf(`This object has no argument, nested block, or exported attribute named %q.%s`, name, suggestion),
111 Subject: next.SourceRange().Ptr(),
112 })
113
114 return diags
115}
116
117func (b *NestedBlock) staticValidateTraversal(typeName string, traversal hcl.Traversal) tfdiags.Diagnostics {
118 if b.Nesting == NestingSingle || b.Nesting == NestingGroup {
119 // Single blocks are easy: just pass right through.
120 return b.Block.StaticValidateTraversal(traversal)
121 }
122
123 if len(traversal) == 0 {
124 // It's always valid to access a nested block's attribute directly.
125 return nil
126 }
127
128 var diags tfdiags.Diagnostics
129 next := traversal[0]
130 after := traversal[1:]
131
132 switch b.Nesting {
133
134 case NestingSet:
135 // Can't traverse into a set at all, since it does not have any keys
136 // to index with.
137 diags = diags.Append(&hcl.Diagnostic{
138 Severity: hcl.DiagError,
139 Summary: `Cannot index a set value`,
140 Detail: fmt.Sprintf(`Block type %q is represented by a set of objects, and set elements do not have addressable keys. To find elements matching specific criteria, use a "for" expression with an "if" clause.`, typeName),
141 Subject: next.SourceRange().Ptr(),
142 })
143 return diags
144
145 case NestingList:
146 if _, ok := next.(hcl.TraverseIndex); ok {
147 moreDiags := b.Block.StaticValidateTraversal(after)
148 diags = diags.Append(moreDiags)
149 } else {
150 diags = diags.Append(&hcl.Diagnostic{
151 Severity: hcl.DiagError,
152 Summary: `Invalid operation`,
153 Detail: fmt.Sprintf(`Block type %q is represented by a list of objects, so it must be indexed using a numeric key, like .%s[0].`, typeName, typeName),
154 Subject: next.SourceRange().Ptr(),
155 })
156 }
157 return diags
158
159 case NestingMap:
160 // Both attribute and index steps are valid for maps, so we'll just
161 // pass through here and let normal evaluation catch an
162 // incorrectly-typed index key later, if present.
163 moreDiags := b.Block.StaticValidateTraversal(after)
164 diags = diags.Append(moreDiags)
165 return diags
166
167 default:
168 // Invalid nesting type is just ignored. It's checked by
169 // InternalValidate. (Note that we handled NestingSingle separately
170 // back at the start of this function.)
171 return nil
172 }
173}