]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - terraform/resource.go
Merge branch 'fix_read_test' of github.com:alexandreFre/terraform-provider-statuscake
[github/fretlink/terraform-provider-statuscake.git] / terraform / resource.go
1 package terraform
2
3 import (
4 "fmt"
5 "reflect"
6 "sort"
7 "strconv"
8 "strings"
9
10 "github.com/hashicorp/terraform/config"
11 "github.com/mitchellh/copystructure"
12 "github.com/mitchellh/reflectwalk"
13 )
14
15 // ResourceProvisionerConfig is used to pair a provisioner
16 // with its provided configuration. This allows us to use singleton
17 // instances of each ResourceProvisioner and to keep the relevant
18 // configuration instead of instantiating a new Provisioner for each
19 // resource.
20 type ResourceProvisionerConfig struct {
21 Type string
22 Provisioner ResourceProvisioner
23 Config *ResourceConfig
24 RawConfig *config.RawConfig
25 ConnInfo *config.RawConfig
26 }
27
28 // Resource encapsulates a resource, its configuration, its provider,
29 // its current state, and potentially a desired diff from the state it
30 // wants to reach.
31 type Resource struct {
32 // These are all used by the new EvalNode stuff.
33 Name string
34 Type string
35 CountIndex int
36
37 // These aren't really used anymore anywhere, but we keep them around
38 // since we haven't done a proper cleanup yet.
39 Id string
40 Info *InstanceInfo
41 Config *ResourceConfig
42 Dependencies []string
43 Diff *InstanceDiff
44 Provider ResourceProvider
45 State *InstanceState
46 Provisioners []*ResourceProvisionerConfig
47 Flags ResourceFlag
48 }
49
50 // ResourceKind specifies what kind of instance we're working with, whether
51 // its a primary instance, a tainted instance, or an orphan.
52 type ResourceFlag byte
53
54 // InstanceInfo is used to hold information about the instance and/or
55 // resource being modified.
56 type InstanceInfo struct {
57 // Id is a unique name to represent this instance. This is not related
58 // to InstanceState.ID in any way.
59 Id string
60
61 // ModulePath is the complete path of the module containing this
62 // instance.
63 ModulePath []string
64
65 // Type is the resource type of this instance
66 Type string
67
68 // uniqueExtra is an internal field that can be populated to supply
69 // extra metadata that is used to identify a unique instance in
70 // the graph walk. This will be appended to HumanID when uniqueId
71 // is called.
72 uniqueExtra string
73 }
74
75 // HumanId is a unique Id that is human-friendly and useful for UI elements.
76 func (i *InstanceInfo) HumanId() string {
77 if i == nil {
78 return "<nil>"
79 }
80
81 if len(i.ModulePath) <= 1 {
82 return i.Id
83 }
84
85 return fmt.Sprintf(
86 "module.%s.%s",
87 strings.Join(i.ModulePath[1:], "."),
88 i.Id)
89 }
90
91 // ResourceAddress returns the address of the resource that the receiver is describing.
92 func (i *InstanceInfo) ResourceAddress() *ResourceAddress {
93 // GROSS: for tainted and deposed instances, their status gets appended
94 // to i.Id to create a unique id for the graph node. Historically these
95 // ids were displayed to the user, so it's designed to be human-readable:
96 // "aws_instance.bar.0 (deposed #0)"
97 //
98 // So here we detect such suffixes and try to interpret them back to
99 // their original meaning so we can then produce a ResourceAddress
100 // with a suitable InstanceType.
101 id := i.Id
102 instanceType := TypeInvalid
103 if idx := strings.Index(id, " ("); idx != -1 {
104 remain := id[idx:]
105 id = id[:idx]
106
107 switch {
108 case strings.Contains(remain, "tainted"):
109 instanceType = TypeTainted
110 case strings.Contains(remain, "deposed"):
111 instanceType = TypeDeposed
112 }
113 }
114
115 addr, err := parseResourceAddressInternal(id)
116 if err != nil {
117 // should never happen, since that would indicate a bug in the
118 // code that constructed this InstanceInfo.
119 panic(fmt.Errorf("InstanceInfo has invalid Id %s", id))
120 }
121 if len(i.ModulePath) > 1 {
122 addr.Path = i.ModulePath[1:] // trim off "root" prefix, which is implied
123 }
124 if instanceType != TypeInvalid {
125 addr.InstanceTypeSet = true
126 addr.InstanceType = instanceType
127 }
128 return addr
129 }
130
131 func (i *InstanceInfo) uniqueId() string {
132 prefix := i.HumanId()
133 if v := i.uniqueExtra; v != "" {
134 prefix += " " + v
135 }
136
137 return prefix
138 }
139
140 // ResourceConfig holds the configuration given for a resource. This is
141 // done instead of a raw `map[string]interface{}` type so that rich
142 // methods can be added to it to make dealing with it easier.
143 type ResourceConfig struct {
144 ComputedKeys []string
145 Raw map[string]interface{}
146 Config map[string]interface{}
147
148 raw *config.RawConfig
149 }
150
151 // NewResourceConfig creates a new ResourceConfig from a config.RawConfig.
152 func NewResourceConfig(c *config.RawConfig) *ResourceConfig {
153 result := &ResourceConfig{raw: c}
154 result.interpolateForce()
155 return result
156 }
157
158 // DeepCopy performs a deep copy of the configuration. This makes it safe
159 // to modify any of the structures that are part of the resource config without
160 // affecting the original configuration.
161 func (c *ResourceConfig) DeepCopy() *ResourceConfig {
162 // DeepCopying a nil should return a nil to avoid panics
163 if c == nil {
164 return nil
165 }
166
167 // Copy, this will copy all the exported attributes
168 copy, err := copystructure.Config{Lock: true}.Copy(c)
169 if err != nil {
170 panic(err)
171 }
172
173 // Force the type
174 result := copy.(*ResourceConfig)
175
176 // For the raw configuration, we can just use its own copy method
177 result.raw = c.raw.Copy()
178
179 return result
180 }
181
182 // Equal checks the equality of two resource configs.
183 func (c *ResourceConfig) Equal(c2 *ResourceConfig) bool {
184 // If either are nil, then they're only equal if they're both nil
185 if c == nil || c2 == nil {
186 return c == c2
187 }
188
189 // Sort the computed keys so they're deterministic
190 sort.Strings(c.ComputedKeys)
191 sort.Strings(c2.ComputedKeys)
192
193 // Two resource configs if their exported properties are equal.
194 // We don't compare "raw" because it is never used again after
195 // initialization and for all intents and purposes they are equal
196 // if the exported properties are equal.
197 check := [][2]interface{}{
198 {c.ComputedKeys, c2.ComputedKeys},
199 {c.Raw, c2.Raw},
200 {c.Config, c2.Config},
201 }
202 for _, pair := range check {
203 if !reflect.DeepEqual(pair[0], pair[1]) {
204 return false
205 }
206 }
207
208 return true
209 }
210
211 // CheckSet checks that the given list of configuration keys is
212 // properly set. If not, errors are returned for each unset key.
213 //
214 // This is useful to be called in the Validate method of a ResourceProvider.
215 func (c *ResourceConfig) CheckSet(keys []string) []error {
216 var errs []error
217
218 for _, k := range keys {
219 if !c.IsSet(k) {
220 errs = append(errs, fmt.Errorf("%s must be set", k))
221 }
222 }
223
224 return errs
225 }
226
227 // Get looks up a configuration value by key and returns the value.
228 //
229 // The second return value is true if the get was successful. Get will
230 // return the raw value if the key is computed, so you should pair this
231 // with IsComputed.
232 func (c *ResourceConfig) Get(k string) (interface{}, bool) {
233 // We aim to get a value from the configuration. If it is computed,
234 // then we return the pure raw value.
235 source := c.Config
236 if c.IsComputed(k) {
237 source = c.Raw
238 }
239
240 return c.get(k, source)
241 }
242
243 // GetRaw looks up a configuration value by key and returns the value,
244 // from the raw, uninterpolated config.
245 //
246 // The second return value is true if the get was successful. Get will
247 // not succeed if the value is being computed.
248 func (c *ResourceConfig) GetRaw(k string) (interface{}, bool) {
249 return c.get(k, c.Raw)
250 }
251
252 // IsComputed returns whether the given key is computed or not.
253 func (c *ResourceConfig) IsComputed(k string) bool {
254 // The next thing we do is check the config if we get a computed
255 // value out of it.
256 v, ok := c.get(k, c.Config)
257 if !ok {
258 return false
259 }
260
261 // If value is nil, then it isn't computed
262 if v == nil {
263 return false
264 }
265
266 // Test if the value contains an unknown value
267 var w unknownCheckWalker
268 if err := reflectwalk.Walk(v, &w); err != nil {
269 panic(err)
270 }
271
272 return w.Unknown
273 }
274
275 // IsSet checks if the key in the configuration is set. A key is set if
276 // it has a value or the value is being computed (is unknown currently).
277 //
278 // This function should be used rather than checking the keys of the
279 // raw configuration itself, since a key may be omitted from the raw
280 // configuration if it is being computed.
281 func (c *ResourceConfig) IsSet(k string) bool {
282 if c == nil {
283 return false
284 }
285
286 if c.IsComputed(k) {
287 return true
288 }
289
290 if _, ok := c.Get(k); ok {
291 return true
292 }
293
294 return false
295 }
296
297 func (c *ResourceConfig) get(
298 k string, raw map[string]interface{}) (interface{}, bool) {
299 parts := strings.Split(k, ".")
300 if len(parts) == 1 && parts[0] == "" {
301 parts = nil
302 }
303
304 var current interface{} = raw
305 var previous interface{} = nil
306 for i, part := range parts {
307 if current == nil {
308 return nil, false
309 }
310
311 cv := reflect.ValueOf(current)
312 switch cv.Kind() {
313 case reflect.Map:
314 previous = current
315 v := cv.MapIndex(reflect.ValueOf(part))
316 if !v.IsValid() {
317 if i > 0 && i != (len(parts)-1) {
318 tryKey := strings.Join(parts[i:], ".")
319 v := cv.MapIndex(reflect.ValueOf(tryKey))
320 if !v.IsValid() {
321 return nil, false
322 }
323
324 return v.Interface(), true
325 }
326
327 return nil, false
328 }
329
330 current = v.Interface()
331 case reflect.Slice:
332 previous = current
333
334 if part == "#" {
335 // If any value in a list is computed, this whole thing
336 // is computed and we can't read any part of it.
337 for i := 0; i < cv.Len(); i++ {
338 if v := cv.Index(i).Interface(); v == unknownValue() {
339 return v, true
340 }
341 }
342
343 current = cv.Len()
344 } else {
345 i, err := strconv.ParseInt(part, 0, 0)
346 if err != nil {
347 return nil, false
348 }
349 if int(i) < 0 || int(i) >= cv.Len() {
350 return nil, false
351 }
352 current = cv.Index(int(i)).Interface()
353 }
354 case reflect.String:
355 // This happens when map keys contain "." and have a common
356 // prefix so were split as path components above.
357 actualKey := strings.Join(parts[i-1:], ".")
358 if prevMap, ok := previous.(map[string]interface{}); ok {
359 v, ok := prevMap[actualKey]
360 return v, ok
361 }
362
363 return nil, false
364 default:
365 panic(fmt.Sprintf("Unknown kind: %s", cv.Kind()))
366 }
367 }
368
369 return current, true
370 }
371
372 // interpolateForce is a temporary thing. We want to get rid of interpolate
373 // above and likewise this, but it can only be done after the f-ast-graph
374 // refactor is complete.
375 func (c *ResourceConfig) interpolateForce() {
376 if c.raw == nil {
377 var err error
378 c.raw, err = config.NewRawConfig(make(map[string]interface{}))
379 if err != nil {
380 panic(err)
381 }
382 }
383
384 c.ComputedKeys = c.raw.UnknownKeys()
385 c.Raw = c.raw.RawMap()
386 c.Config = c.raw.Config()
387 }
388
389 // unknownCheckWalker
390 type unknownCheckWalker struct {
391 Unknown bool
392 }
393
394 func (w *unknownCheckWalker) Primitive(v reflect.Value) error {
395 if v.Interface() == unknownValue() {
396 w.Unknown = true
397 }
398
399 return nil
400 }