]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package terraform |
2 | ||
3 | import ( | |
4 | "fmt" | |
5 | "reflect" | |
6 | "sort" | |
7 | "strconv" | |
8 | "strings" | |
9 | ||
bae9f6d2 JC |
10 | "github.com/mitchellh/copystructure" |
11 | "github.com/mitchellh/reflectwalk" | |
107c1cdb ND |
12 | "github.com/zclconf/go-cty/cty" |
13 | ||
14 | "github.com/hashicorp/terraform/addrs" | |
15 | "github.com/hashicorp/terraform/config" | |
16 | "github.com/hashicorp/terraform/config/hcl2shim" | |
17 | "github.com/hashicorp/terraform/configs/configschema" | |
bae9f6d2 JC |
18 | ) |
19 | ||
20 | // ResourceProvisionerConfig is used to pair a provisioner | |
21 | // with its provided configuration. This allows us to use singleton | |
22 | // instances of each ResourceProvisioner and to keep the relevant | |
23 | // configuration instead of instantiating a new Provisioner for each | |
24 | // resource. | |
25 | type ResourceProvisionerConfig struct { | |
26 | Type string | |
27 | Provisioner ResourceProvisioner | |
28 | Config *ResourceConfig | |
29 | RawConfig *config.RawConfig | |
30 | ConnInfo *config.RawConfig | |
31 | } | |
32 | ||
107c1cdb ND |
33 | // Resource is a legacy way to identify a particular resource instance. |
34 | // | |
35 | // New code should use addrs.ResourceInstance instead. This is still here | |
36 | // only for codepaths that haven't been updated yet. | |
bae9f6d2 JC |
37 | type Resource struct { |
38 | // These are all used by the new EvalNode stuff. | |
39 | Name string | |
40 | Type string | |
41 | CountIndex int | |
42 | ||
43 | // These aren't really used anymore anywhere, but we keep them around | |
44 | // since we haven't done a proper cleanup yet. | |
45 | Id string | |
46 | Info *InstanceInfo | |
47 | Config *ResourceConfig | |
48 | Dependencies []string | |
49 | Diff *InstanceDiff | |
50 | Provider ResourceProvider | |
51 | State *InstanceState | |
52 | Provisioners []*ResourceProvisionerConfig | |
53 | Flags ResourceFlag | |
54 | } | |
55 | ||
107c1cdb ND |
56 | // NewResource constructs a legacy Resource object from an |
57 | // addrs.ResourceInstance value. | |
58 | // | |
59 | // This is provided to shim to old codepaths that haven't been updated away | |
60 | // from this type yet. Since this old type is not able to represent instances | |
61 | // that have string keys, this function will panic if given a resource address | |
62 | // that has a string key. | |
63 | func NewResource(addr addrs.ResourceInstance) *Resource { | |
64 | ret := &Resource{ | |
65 | Name: addr.Resource.Name, | |
66 | Type: addr.Resource.Type, | |
67 | } | |
68 | ||
69 | if addr.Key != addrs.NoKey { | |
70 | switch tk := addr.Key.(type) { | |
71 | case addrs.IntKey: | |
72 | ret.CountIndex = int(tk) | |
73 | default: | |
74 | panic(fmt.Errorf("resource instance with key %#v is not supported", addr.Key)) | |
75 | } | |
76 | } | |
77 | ||
78 | return ret | |
79 | } | |
80 | ||
bae9f6d2 JC |
81 | // ResourceKind specifies what kind of instance we're working with, whether |
82 | // its a primary instance, a tainted instance, or an orphan. | |
83 | type ResourceFlag byte | |
84 | ||
85 | // InstanceInfo is used to hold information about the instance and/or | |
86 | // resource being modified. | |
87 | type InstanceInfo struct { | |
88 | // Id is a unique name to represent this instance. This is not related | |
89 | // to InstanceState.ID in any way. | |
90 | Id string | |
91 | ||
92 | // ModulePath is the complete path of the module containing this | |
93 | // instance. | |
94 | ModulePath []string | |
95 | ||
96 | // Type is the resource type of this instance | |
97 | Type string | |
98 | ||
99 | // uniqueExtra is an internal field that can be populated to supply | |
100 | // extra metadata that is used to identify a unique instance in | |
101 | // the graph walk. This will be appended to HumanID when uniqueId | |
102 | // is called. | |
103 | uniqueExtra string | |
104 | } | |
105 | ||
107c1cdb ND |
106 | // NewInstanceInfo constructs an InstanceInfo from an addrs.AbsResourceInstance. |
107 | // | |
108 | // InstanceInfo is a legacy type, and uses of it should be gradually replaced | |
109 | // by direct use of addrs.AbsResource or addrs.AbsResourceInstance as | |
110 | // appropriate. | |
111 | // | |
112 | // The legacy InstanceInfo type cannot represent module instances with instance | |
113 | // keys, so this function will panic if given such a path. Uses of this type | |
114 | // should all be removed or replaced before implementing "count" and "for_each" | |
115 | // arguments on modules in order to avoid such panics. | |
116 | // | |
117 | // This legacy type also cannot represent resource instances with string | |
118 | // instance keys. It will panic if the given key is not either NoKey or an | |
119 | // IntKey. | |
120 | func NewInstanceInfo(addr addrs.AbsResourceInstance) *InstanceInfo { | |
121 | // We need an old-style []string module path for InstanceInfo. | |
122 | path := make([]string, len(addr.Module)) | |
123 | for i, step := range addr.Module { | |
124 | if step.InstanceKey != addrs.NoKey { | |
125 | panic("NewInstanceInfo cannot convert module instance with key") | |
126 | } | |
127 | path[i] = step.Name | |
bae9f6d2 JC |
128 | } |
129 | ||
107c1cdb ND |
130 | // This is a funny old meaning of "id" that is no longer current. It should |
131 | // not be used for anything users might see. Note that it does not include | |
132 | // a representation of the resource mode, and so it's impossible to | |
133 | // determine from an InstanceInfo alone whether it is a managed or data | |
134 | // resource that is being referred to. | |
135 | id := fmt.Sprintf("%s.%s", addr.Resource.Resource.Type, addr.Resource.Resource.Name) | |
136 | if addr.Resource.Resource.Mode == addrs.DataResourceMode { | |
137 | id = "data." + id | |
138 | } | |
139 | if addr.Resource.Key != addrs.NoKey { | |
140 | switch k := addr.Resource.Key.(type) { | |
141 | case addrs.IntKey: | |
142 | id = id + fmt.Sprintf(".%d", int(k)) | |
143 | default: | |
144 | panic(fmt.Sprintf("NewInstanceInfo cannot convert resource instance with %T instance key", addr.Resource.Key)) | |
145 | } | |
bae9f6d2 JC |
146 | } |
147 | ||
107c1cdb ND |
148 | return &InstanceInfo{ |
149 | Id: id, | |
150 | ModulePath: path, | |
151 | Type: addr.Resource.Resource.Type, | |
152 | } | |
bae9f6d2 JC |
153 | } |
154 | ||
15c0b25d AP |
155 | // ResourceAddress returns the address of the resource that the receiver is describing. |
156 | func (i *InstanceInfo) ResourceAddress() *ResourceAddress { | |
157 | // GROSS: for tainted and deposed instances, their status gets appended | |
158 | // to i.Id to create a unique id for the graph node. Historically these | |
159 | // ids were displayed to the user, so it's designed to be human-readable: | |
160 | // "aws_instance.bar.0 (deposed #0)" | |
161 | // | |
162 | // So here we detect such suffixes and try to interpret them back to | |
163 | // their original meaning so we can then produce a ResourceAddress | |
164 | // with a suitable InstanceType. | |
165 | id := i.Id | |
166 | instanceType := TypeInvalid | |
167 | if idx := strings.Index(id, " ("); idx != -1 { | |
168 | remain := id[idx:] | |
169 | id = id[:idx] | |
170 | ||
171 | switch { | |
172 | case strings.Contains(remain, "tainted"): | |
173 | instanceType = TypeTainted | |
174 | case strings.Contains(remain, "deposed"): | |
175 | instanceType = TypeDeposed | |
176 | } | |
177 | } | |
178 | ||
179 | addr, err := parseResourceAddressInternal(id) | |
180 | if err != nil { | |
181 | // should never happen, since that would indicate a bug in the | |
182 | // code that constructed this InstanceInfo. | |
183 | panic(fmt.Errorf("InstanceInfo has invalid Id %s", id)) | |
184 | } | |
185 | if len(i.ModulePath) > 1 { | |
186 | addr.Path = i.ModulePath[1:] // trim off "root" prefix, which is implied | |
187 | } | |
188 | if instanceType != TypeInvalid { | |
189 | addr.InstanceTypeSet = true | |
190 | addr.InstanceType = instanceType | |
191 | } | |
192 | return addr | |
193 | } | |
194 | ||
107c1cdb ND |
195 | // ResourceConfig is a legacy type that was formerly used to represent |
196 | // interpolatable configuration blocks. It is now only used to shim to old | |
197 | // APIs that still use this type, via NewResourceConfigShimmed. | |
bae9f6d2 JC |
198 | type ResourceConfig struct { |
199 | ComputedKeys []string | |
200 | Raw map[string]interface{} | |
201 | Config map[string]interface{} | |
202 | ||
203 | raw *config.RawConfig | |
204 | } | |
205 | ||
206 | // NewResourceConfig creates a new ResourceConfig from a config.RawConfig. | |
207 | func NewResourceConfig(c *config.RawConfig) *ResourceConfig { | |
208 | result := &ResourceConfig{raw: c} | |
209 | result.interpolateForce() | |
210 | return result | |
211 | } | |
212 | ||
107c1cdb ND |
213 | // NewResourceConfigShimmed wraps a cty.Value of object type in a legacy |
214 | // ResourceConfig object, so that it can be passed to older APIs that expect | |
215 | // this wrapping. | |
216 | // | |
217 | // The returned ResourceConfig is already interpolated and cannot be | |
218 | // re-interpolated. It is, therefore, useful only to functions that expect | |
219 | // an already-populated ResourceConfig which they then treat as read-only. | |
220 | // | |
221 | // If the given value is not of an object type that conforms to the given | |
222 | // schema then this function will panic. | |
223 | func NewResourceConfigShimmed(val cty.Value, schema *configschema.Block) *ResourceConfig { | |
224 | if !val.Type().IsObjectType() { | |
225 | panic(fmt.Errorf("NewResourceConfigShimmed given %#v; an object type is required", val.Type())) | |
226 | } | |
227 | ret := &ResourceConfig{} | |
228 | ||
229 | legacyVal := hcl2shim.ConfigValueFromHCL2Block(val, schema) | |
230 | if legacyVal != nil { | |
231 | ret.Config = legacyVal | |
232 | ||
233 | // Now we need to walk through our structure and find any unknown values, | |
234 | // producing the separate list ComputedKeys to represent these. We use the | |
235 | // schema here so that we can preserve the expected invariant | |
236 | // that an attribute is always either wholly known or wholly unknown, while | |
237 | // a child block can be partially unknown. | |
238 | ret.ComputedKeys = newResourceConfigShimmedComputedKeys(val, "") | |
239 | } else { | |
240 | ret.Config = make(map[string]interface{}) | |
241 | } | |
242 | ret.Raw = ret.Config | |
243 | ||
244 | return ret | |
245 | } | |
246 | ||
247 | // Record the any config values in ComputedKeys. This field had been unused in | |
248 | // helper/schema, but in the new protocol we're using this so that the SDK can | |
249 | // now handle having an unknown collection. The legacy diff code doesn't | |
250 | // properly handle the unknown, because it can't be expressed in the same way | |
251 | // between the config and diff. | |
252 | func newResourceConfigShimmedComputedKeys(val cty.Value, path string) []string { | |
253 | var ret []string | |
254 | ty := val.Type() | |
255 | ||
256 | if val.IsNull() { | |
257 | return ret | |
258 | } | |
259 | ||
260 | if !val.IsKnown() { | |
261 | // we shouldn't have an entirely unknown resource, but prevent empty | |
262 | // strings just in case | |
263 | if len(path) > 0 { | |
264 | ret = append(ret, path) | |
265 | } | |
266 | return ret | |
267 | } | |
268 | ||
269 | if path != "" { | |
270 | path += "." | |
271 | } | |
272 | switch { | |
273 | case ty.IsListType(), ty.IsTupleType(), ty.IsSetType(): | |
274 | i := 0 | |
275 | for it := val.ElementIterator(); it.Next(); i++ { | |
276 | _, subVal := it.Element() | |
277 | keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%d", path, i)) | |
278 | ret = append(ret, keys...) | |
279 | } | |
280 | ||
281 | case ty.IsMapType(), ty.IsObjectType(): | |
282 | for it := val.ElementIterator(); it.Next(); { | |
283 | subK, subVal := it.Element() | |
284 | keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%s", path, subK.AsString())) | |
285 | ret = append(ret, keys...) | |
286 | } | |
287 | } | |
288 | ||
289 | return ret | |
290 | } | |
291 | ||
bae9f6d2 JC |
292 | // DeepCopy performs a deep copy of the configuration. This makes it safe |
293 | // to modify any of the structures that are part of the resource config without | |
294 | // affecting the original configuration. | |
295 | func (c *ResourceConfig) DeepCopy() *ResourceConfig { | |
296 | // DeepCopying a nil should return a nil to avoid panics | |
297 | if c == nil { | |
298 | return nil | |
299 | } | |
300 | ||
301 | // Copy, this will copy all the exported attributes | |
302 | copy, err := copystructure.Config{Lock: true}.Copy(c) | |
303 | if err != nil { | |
304 | panic(err) | |
305 | } | |
306 | ||
307 | // Force the type | |
308 | result := copy.(*ResourceConfig) | |
309 | ||
310 | // For the raw configuration, we can just use its own copy method | |
311 | result.raw = c.raw.Copy() | |
312 | ||
313 | return result | |
314 | } | |
315 | ||
316 | // Equal checks the equality of two resource configs. | |
317 | func (c *ResourceConfig) Equal(c2 *ResourceConfig) bool { | |
318 | // If either are nil, then they're only equal if they're both nil | |
319 | if c == nil || c2 == nil { | |
320 | return c == c2 | |
321 | } | |
322 | ||
323 | // Sort the computed keys so they're deterministic | |
324 | sort.Strings(c.ComputedKeys) | |
325 | sort.Strings(c2.ComputedKeys) | |
326 | ||
327 | // Two resource configs if their exported properties are equal. | |
328 | // We don't compare "raw" because it is never used again after | |
329 | // initialization and for all intents and purposes they are equal | |
330 | // if the exported properties are equal. | |
331 | check := [][2]interface{}{ | |
332 | {c.ComputedKeys, c2.ComputedKeys}, | |
333 | {c.Raw, c2.Raw}, | |
334 | {c.Config, c2.Config}, | |
335 | } | |
336 | for _, pair := range check { | |
337 | if !reflect.DeepEqual(pair[0], pair[1]) { | |
338 | return false | |
339 | } | |
340 | } | |
341 | ||
342 | return true | |
343 | } | |
344 | ||
345 | // CheckSet checks that the given list of configuration keys is | |
346 | // properly set. If not, errors are returned for each unset key. | |
347 | // | |
348 | // This is useful to be called in the Validate method of a ResourceProvider. | |
349 | func (c *ResourceConfig) CheckSet(keys []string) []error { | |
350 | var errs []error | |
351 | ||
352 | for _, k := range keys { | |
353 | if !c.IsSet(k) { | |
354 | errs = append(errs, fmt.Errorf("%s must be set", k)) | |
355 | } | |
356 | } | |
357 | ||
358 | return errs | |
359 | } | |
360 | ||
361 | // Get looks up a configuration value by key and returns the value. | |
362 | // | |
363 | // The second return value is true if the get was successful. Get will | |
364 | // return the raw value if the key is computed, so you should pair this | |
365 | // with IsComputed. | |
366 | func (c *ResourceConfig) Get(k string) (interface{}, bool) { | |
367 | // We aim to get a value from the configuration. If it is computed, | |
368 | // then we return the pure raw value. | |
369 | source := c.Config | |
370 | if c.IsComputed(k) { | |
371 | source = c.Raw | |
372 | } | |
373 | ||
374 | return c.get(k, source) | |
375 | } | |
376 | ||
377 | // GetRaw looks up a configuration value by key and returns the value, | |
378 | // from the raw, uninterpolated config. | |
379 | // | |
380 | // The second return value is true if the get was successful. Get will | |
381 | // not succeed if the value is being computed. | |
382 | func (c *ResourceConfig) GetRaw(k string) (interface{}, bool) { | |
383 | return c.get(k, c.Raw) | |
384 | } | |
385 | ||
386 | // IsComputed returns whether the given key is computed or not. | |
387 | func (c *ResourceConfig) IsComputed(k string) bool { | |
388 | // The next thing we do is check the config if we get a computed | |
389 | // value out of it. | |
390 | v, ok := c.get(k, c.Config) | |
391 | if !ok { | |
392 | return false | |
393 | } | |
394 | ||
395 | // If value is nil, then it isn't computed | |
396 | if v == nil { | |
397 | return false | |
398 | } | |
399 | ||
400 | // Test if the value contains an unknown value | |
401 | var w unknownCheckWalker | |
402 | if err := reflectwalk.Walk(v, &w); err != nil { | |
403 | panic(err) | |
404 | } | |
405 | ||
406 | return w.Unknown | |
407 | } | |
408 | ||
409 | // IsSet checks if the key in the configuration is set. A key is set if | |
410 | // it has a value or the value is being computed (is unknown currently). | |
411 | // | |
412 | // This function should be used rather than checking the keys of the | |
413 | // raw configuration itself, since a key may be omitted from the raw | |
414 | // configuration if it is being computed. | |
415 | func (c *ResourceConfig) IsSet(k string) bool { | |
416 | if c == nil { | |
417 | return false | |
418 | } | |
419 | ||
420 | if c.IsComputed(k) { | |
421 | return true | |
422 | } | |
423 | ||
424 | if _, ok := c.Get(k); ok { | |
425 | return true | |
426 | } | |
427 | ||
428 | return false | |
429 | } | |
430 | ||
431 | func (c *ResourceConfig) get( | |
432 | k string, raw map[string]interface{}) (interface{}, bool) { | |
433 | parts := strings.Split(k, ".") | |
434 | if len(parts) == 1 && parts[0] == "" { | |
435 | parts = nil | |
436 | } | |
437 | ||
438 | var current interface{} = raw | |
439 | var previous interface{} = nil | |
440 | for i, part := range parts { | |
441 | if current == nil { | |
442 | return nil, false | |
443 | } | |
444 | ||
445 | cv := reflect.ValueOf(current) | |
446 | switch cv.Kind() { | |
447 | case reflect.Map: | |
448 | previous = current | |
449 | v := cv.MapIndex(reflect.ValueOf(part)) | |
450 | if !v.IsValid() { | |
451 | if i > 0 && i != (len(parts)-1) { | |
452 | tryKey := strings.Join(parts[i:], ".") | |
453 | v := cv.MapIndex(reflect.ValueOf(tryKey)) | |
454 | if !v.IsValid() { | |
455 | return nil, false | |
456 | } | |
457 | ||
458 | return v.Interface(), true | |
459 | } | |
460 | ||
461 | return nil, false | |
462 | } | |
463 | ||
464 | current = v.Interface() | |
465 | case reflect.Slice: | |
466 | previous = current | |
467 | ||
468 | if part == "#" { | |
469 | // If any value in a list is computed, this whole thing | |
470 | // is computed and we can't read any part of it. | |
471 | for i := 0; i < cv.Len(); i++ { | |
472 | if v := cv.Index(i).Interface(); v == unknownValue() { | |
473 | return v, true | |
474 | } | |
475 | } | |
476 | ||
477 | current = cv.Len() | |
478 | } else { | |
479 | i, err := strconv.ParseInt(part, 0, 0) | |
480 | if err != nil { | |
481 | return nil, false | |
482 | } | |
15c0b25d | 483 | if int(i) < 0 || int(i) >= cv.Len() { |
bae9f6d2 JC |
484 | return nil, false |
485 | } | |
486 | current = cv.Index(int(i)).Interface() | |
487 | } | |
488 | case reflect.String: | |
489 | // This happens when map keys contain "." and have a common | |
490 | // prefix so were split as path components above. | |
491 | actualKey := strings.Join(parts[i-1:], ".") | |
492 | if prevMap, ok := previous.(map[string]interface{}); ok { | |
493 | v, ok := prevMap[actualKey] | |
494 | return v, ok | |
495 | } | |
496 | ||
497 | return nil, false | |
498 | default: | |
499 | panic(fmt.Sprintf("Unknown kind: %s", cv.Kind())) | |
500 | } | |
501 | } | |
502 | ||
503 | return current, true | |
504 | } | |
505 | ||
506 | // interpolateForce is a temporary thing. We want to get rid of interpolate | |
507 | // above and likewise this, but it can only be done after the f-ast-graph | |
508 | // refactor is complete. | |
509 | func (c *ResourceConfig) interpolateForce() { | |
510 | if c.raw == nil { | |
107c1cdb ND |
511 | // If we don't have a lowercase "raw" but we _do_ have the uppercase |
512 | // Raw populated then this indicates that we're recieving a shim | |
513 | // ResourceConfig created by NewResourceConfigShimmed, which is already | |
514 | // fully evaluated and thus this function doesn't need to do anything. | |
515 | if c.Raw != nil { | |
516 | return | |
517 | } | |
518 | ||
bae9f6d2 JC |
519 | var err error |
520 | c.raw, err = config.NewRawConfig(make(map[string]interface{})) | |
521 | if err != nil { | |
522 | panic(err) | |
523 | } | |
524 | } | |
525 | ||
526 | c.ComputedKeys = c.raw.UnknownKeys() | |
527 | c.Raw = c.raw.RawMap() | |
528 | c.Config = c.raw.Config() | |
529 | } | |
530 | ||
531 | // unknownCheckWalker | |
532 | type unknownCheckWalker struct { | |
533 | Unknown bool | |
534 | } | |
535 | ||
536 | func (w *unknownCheckWalker) Primitive(v reflect.Value) error { | |
537 | if v.Interface() == unknownValue() { | |
538 | w.Unknown = true | |
539 | } | |
540 | ||
541 | return nil | |
542 | } |