diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/configs/configschema/coerce_value.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/configs/configschema/coerce_value.go | 274 |
1 files changed, 274 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 @@ | |||
1 | package configschema | ||
2 | |||
3 | import ( | ||
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. | ||
26 | func (b *Block) CoerceValue(in cty.Value) (cty.Value, error) { | ||
27 | var path cty.Path | ||
28 | return b.coerceValue(in, path) | ||
29 | } | ||
30 | |||
31 | func (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 | |||
268 | func (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 | } | ||