diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper/schema/resource.go')
-rw-r--r-- | vendor/github.com/hashicorp/terraform/helper/schema/resource.go | 478 |
1 files changed, 478 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource.go new file mode 100644 index 0000000..c810558 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource.go | |||
@@ -0,0 +1,478 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
4 | "errors" | ||
5 | "fmt" | ||
6 | "log" | ||
7 | "strconv" | ||
8 | |||
9 | "github.com/hashicorp/terraform/terraform" | ||
10 | ) | ||
11 | |||
12 | // Resource represents a thing in Terraform that has a set of configurable | ||
13 | // attributes and a lifecycle (create, read, update, delete). | ||
14 | // | ||
15 | // The Resource schema is an abstraction that allows provider writers to | ||
16 | // worry only about CRUD operations while off-loading validation, diff | ||
17 | // generation, etc. to this higher level library. | ||
18 | // | ||
19 | // In spite of the name, this struct is not used only for terraform resources, | ||
20 | // but also for data sources. In the case of data sources, the Create, | ||
21 | // Update and Delete functions must not be provided. | ||
22 | type Resource struct { | ||
23 | // Schema is the schema for the configuration of this resource. | ||
24 | // | ||
25 | // The keys of this map are the configuration keys, and the values | ||
26 | // describe the schema of the configuration value. | ||
27 | // | ||
28 | // The schema is used to represent both configurable data as well | ||
29 | // as data that might be computed in the process of creating this | ||
30 | // resource. | ||
31 | Schema map[string]*Schema | ||
32 | |||
33 | // SchemaVersion is the version number for this resource's Schema | ||
34 | // definition. The current SchemaVersion stored in the state for each | ||
35 | // resource. Provider authors can increment this version number | ||
36 | // when Schema semantics change. If the State's SchemaVersion is less than | ||
37 | // the current SchemaVersion, the InstanceState is yielded to the | ||
38 | // MigrateState callback, where the provider can make whatever changes it | ||
39 | // needs to update the state to be compatible to the latest version of the | ||
40 | // Schema. | ||
41 | // | ||
42 | // When unset, SchemaVersion defaults to 0, so provider authors can start | ||
43 | // their Versioning at any integer >= 1 | ||
44 | SchemaVersion int | ||
45 | |||
46 | // MigrateState is responsible for updating an InstanceState with an old | ||
47 | // version to the format expected by the current version of the Schema. | ||
48 | // | ||
49 | // It is called during Refresh if the State's stored SchemaVersion is less | ||
50 | // than the current SchemaVersion of the Resource. | ||
51 | // | ||
52 | // The function is yielded the state's stored SchemaVersion and a pointer to | ||
53 | // the InstanceState that needs updating, as well as the configured | ||
54 | // provider's configured meta interface{}, in case the migration process | ||
55 | // needs to make any remote API calls. | ||
56 | MigrateState StateMigrateFunc | ||
57 | |||
58 | // The functions below are the CRUD operations for this resource. | ||
59 | // | ||
60 | // The only optional operation is Update. If Update is not implemented, | ||
61 | // then updates will not be supported for this resource. | ||
62 | // | ||
63 | // The ResourceData parameter in the functions below are used to | ||
64 | // query configuration and changes for the resource as well as to set | ||
65 | // the ID, computed data, etc. | ||
66 | // | ||
67 | // The interface{} parameter is the result of the ConfigureFunc in | ||
68 | // the provider for this resource. If the provider does not define | ||
69 | // a ConfigureFunc, this will be nil. This parameter should be used | ||
70 | // to store API clients, configuration structures, etc. | ||
71 | // | ||
72 | // If any errors occur during each of the operation, an error should be | ||
73 | // returned. If a resource was partially updated, be careful to enable | ||
74 | // partial state mode for ResourceData and use it accordingly. | ||
75 | // | ||
76 | // Exists is a function that is called to check if a resource still | ||
77 | // exists. If this returns false, then this will affect the diff | ||
78 | // accordingly. If this function isn't set, it will not be called. It | ||
79 | // is highly recommended to set it. The *ResourceData passed to Exists | ||
80 | // should _not_ be modified. | ||
81 | Create CreateFunc | ||
82 | Read ReadFunc | ||
83 | Update UpdateFunc | ||
84 | Delete DeleteFunc | ||
85 | Exists ExistsFunc | ||
86 | |||
87 | // Importer is the ResourceImporter implementation for this resource. | ||
88 | // If this is nil, then this resource does not support importing. If | ||
89 | // this is non-nil, then it supports importing and ResourceImporter | ||
90 | // must be validated. The validity of ResourceImporter is verified | ||
91 | // by InternalValidate on Resource. | ||
92 | Importer *ResourceImporter | ||
93 | |||
94 | // If non-empty, this string is emitted as a warning during Validate. | ||
95 | // This is a private interface for now, for use by DataSourceResourceShim, | ||
96 | // and not for general use. (But maybe later...) | ||
97 | deprecationMessage string | ||
98 | |||
99 | // Timeouts allow users to specify specific time durations in which an | ||
100 | // operation should time out, to allow them to extend an action to suit their | ||
101 | // usage. For example, a user may specify a large Creation timeout for their | ||
102 | // AWS RDS Instance due to it's size, or restoring from a snapshot. | ||
103 | // Resource implementors must enable Timeout support by adding the allowed | ||
104 | // actions (Create, Read, Update, Delete, Default) to the Resource struct, and | ||
105 | // accessing them in the matching methods. | ||
106 | Timeouts *ResourceTimeout | ||
107 | } | ||
108 | |||
109 | // See Resource documentation. | ||
110 | type CreateFunc func(*ResourceData, interface{}) error | ||
111 | |||
112 | // See Resource documentation. | ||
113 | type ReadFunc func(*ResourceData, interface{}) error | ||
114 | |||
115 | // See Resource documentation. | ||
116 | type UpdateFunc func(*ResourceData, interface{}) error | ||
117 | |||
118 | // See Resource documentation. | ||
119 | type DeleteFunc func(*ResourceData, interface{}) error | ||
120 | |||
121 | // See Resource documentation. | ||
122 | type ExistsFunc func(*ResourceData, interface{}) (bool, error) | ||
123 | |||
124 | // See Resource documentation. | ||
125 | type StateMigrateFunc func( | ||
126 | int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error) | ||
127 | |||
128 | // Apply creates, updates, and/or deletes a resource. | ||
129 | func (r *Resource) Apply( | ||
130 | s *terraform.InstanceState, | ||
131 | d *terraform.InstanceDiff, | ||
132 | meta interface{}) (*terraform.InstanceState, error) { | ||
133 | data, err := schemaMap(r.Schema).Data(s, d) | ||
134 | if err != nil { | ||
135 | return s, err | ||
136 | } | ||
137 | |||
138 | // Instance Diff shoould have the timeout info, need to copy it over to the | ||
139 | // ResourceData meta | ||
140 | rt := ResourceTimeout{} | ||
141 | if _, ok := d.Meta[TimeoutKey]; ok { | ||
142 | if err := rt.DiffDecode(d); err != nil { | ||
143 | log.Printf("[ERR] Error decoding ResourceTimeout: %s", err) | ||
144 | } | ||
145 | } else { | ||
146 | log.Printf("[DEBUG] No meta timeoutkey found in Apply()") | ||
147 | } | ||
148 | data.timeouts = &rt | ||
149 | |||
150 | if s == nil { | ||
151 | // The Terraform API dictates that this should never happen, but | ||
152 | // it doesn't hurt to be safe in this case. | ||
153 | s = new(terraform.InstanceState) | ||
154 | } | ||
155 | |||
156 | if d.Destroy || d.RequiresNew() { | ||
157 | if s.ID != "" { | ||
158 | // Destroy the resource since it is created | ||
159 | if err := r.Delete(data, meta); err != nil { | ||
160 | return r.recordCurrentSchemaVersion(data.State()), err | ||
161 | } | ||
162 | |||
163 | // Make sure the ID is gone. | ||
164 | data.SetId("") | ||
165 | } | ||
166 | |||
167 | // If we're only destroying, and not creating, then return | ||
168 | // now since we're done! | ||
169 | if !d.RequiresNew() { | ||
170 | return nil, nil | ||
171 | } | ||
172 | |||
173 | // Reset the data to be stateless since we just destroyed | ||
174 | data, err = schemaMap(r.Schema).Data(nil, d) | ||
175 | // data was reset, need to re-apply the parsed timeouts | ||
176 | data.timeouts = &rt | ||
177 | if err != nil { | ||
178 | return nil, err | ||
179 | } | ||
180 | } | ||
181 | |||
182 | err = nil | ||
183 | if data.Id() == "" { | ||
184 | // We're creating, it is a new resource. | ||
185 | data.MarkNewResource() | ||
186 | err = r.Create(data, meta) | ||
187 | } else { | ||
188 | if r.Update == nil { | ||
189 | return s, fmt.Errorf("doesn't support update") | ||
190 | } | ||
191 | |||
192 | err = r.Update(data, meta) | ||
193 | } | ||
194 | |||
195 | return r.recordCurrentSchemaVersion(data.State()), err | ||
196 | } | ||
197 | |||
198 | // Diff returns a diff of this resource and is API compatible with the | ||
199 | // ResourceProvider interface. | ||
200 | func (r *Resource) Diff( | ||
201 | s *terraform.InstanceState, | ||
202 | c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) { | ||
203 | |||
204 | t := &ResourceTimeout{} | ||
205 | err := t.ConfigDecode(r, c) | ||
206 | |||
207 | if err != nil { | ||
208 | return nil, fmt.Errorf("[ERR] Error decoding timeout: %s", err) | ||
209 | } | ||
210 | |||
211 | instanceDiff, err := schemaMap(r.Schema).Diff(s, c) | ||
212 | if err != nil { | ||
213 | return instanceDiff, err | ||
214 | } | ||
215 | |||
216 | if instanceDiff != nil { | ||
217 | if err := t.DiffEncode(instanceDiff); err != nil { | ||
218 | log.Printf("[ERR] Error encoding timeout to instance diff: %s", err) | ||
219 | } | ||
220 | } else { | ||
221 | log.Printf("[DEBUG] Instance Diff is nil in Diff()") | ||
222 | } | ||
223 | |||
224 | return instanceDiff, err | ||
225 | } | ||
226 | |||
227 | // Validate validates the resource configuration against the schema. | ||
228 | func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) { | ||
229 | warns, errs := schemaMap(r.Schema).Validate(c) | ||
230 | |||
231 | if r.deprecationMessage != "" { | ||
232 | warns = append(warns, r.deprecationMessage) | ||
233 | } | ||
234 | |||
235 | return warns, errs | ||
236 | } | ||
237 | |||
238 | // ReadDataApply loads the data for a data source, given a diff that | ||
239 | // describes the configuration arguments and desired computed attributes. | ||
240 | func (r *Resource) ReadDataApply( | ||
241 | d *terraform.InstanceDiff, | ||
242 | meta interface{}, | ||
243 | ) (*terraform.InstanceState, error) { | ||
244 | |||
245 | // Data sources are always built completely from scratch | ||
246 | // on each read, so the source state is always nil. | ||
247 | data, err := schemaMap(r.Schema).Data(nil, d) | ||
248 | if err != nil { | ||
249 | return nil, err | ||
250 | } | ||
251 | |||
252 | err = r.Read(data, meta) | ||
253 | state := data.State() | ||
254 | if state != nil && state.ID == "" { | ||
255 | // Data sources can set an ID if they want, but they aren't | ||
256 | // required to; we'll provide a placeholder if they don't, | ||
257 | // to preserve the invariant that all resources have non-empty | ||
258 | // ids. | ||
259 | state.ID = "-" | ||
260 | } | ||
261 | |||
262 | return r.recordCurrentSchemaVersion(state), err | ||
263 | } | ||
264 | |||
265 | // Refresh refreshes the state of the resource. | ||
266 | func (r *Resource) Refresh( | ||
267 | s *terraform.InstanceState, | ||
268 | meta interface{}) (*terraform.InstanceState, error) { | ||
269 | // If the ID is already somehow blank, it doesn't exist | ||
270 | if s.ID == "" { | ||
271 | return nil, nil | ||
272 | } | ||
273 | |||
274 | rt := ResourceTimeout{} | ||
275 | if _, ok := s.Meta[TimeoutKey]; ok { | ||
276 | if err := rt.StateDecode(s); err != nil { | ||
277 | log.Printf("[ERR] Error decoding ResourceTimeout: %s", err) | ||
278 | } | ||
279 | } | ||
280 | |||
281 | if r.Exists != nil { | ||
282 | // Make a copy of data so that if it is modified it doesn't | ||
283 | // affect our Read later. | ||
284 | data, err := schemaMap(r.Schema).Data(s, nil) | ||
285 | data.timeouts = &rt | ||
286 | |||
287 | if err != nil { | ||
288 | return s, err | ||
289 | } | ||
290 | |||
291 | exists, err := r.Exists(data, meta) | ||
292 | if err != nil { | ||
293 | return s, err | ||
294 | } | ||
295 | if !exists { | ||
296 | return nil, nil | ||
297 | } | ||
298 | } | ||
299 | |||
300 | needsMigration, stateSchemaVersion := r.checkSchemaVersion(s) | ||
301 | if needsMigration && r.MigrateState != nil { | ||
302 | s, err := r.MigrateState(stateSchemaVersion, s, meta) | ||
303 | if err != nil { | ||
304 | return s, err | ||
305 | } | ||
306 | } | ||
307 | |||
308 | data, err := schemaMap(r.Schema).Data(s, nil) | ||
309 | data.timeouts = &rt | ||
310 | if err != nil { | ||
311 | return s, err | ||
312 | } | ||
313 | |||
314 | err = r.Read(data, meta) | ||
315 | state := data.State() | ||
316 | if state != nil && state.ID == "" { | ||
317 | state = nil | ||
318 | } | ||
319 | |||
320 | return r.recordCurrentSchemaVersion(state), err | ||
321 | } | ||
322 | |||
323 | // InternalValidate should be called to validate the structure | ||
324 | // of the resource. | ||
325 | // | ||
326 | // This should be called in a unit test for any resource to verify | ||
327 | // before release that a resource is properly configured for use with | ||
328 | // this library. | ||
329 | // | ||
330 | // Provider.InternalValidate() will automatically call this for all of | ||
331 | // the resources it manages, so you don't need to call this manually if it | ||
332 | // is part of a Provider. | ||
333 | func (r *Resource) InternalValidate(topSchemaMap schemaMap, writable bool) error { | ||
334 | if r == nil { | ||
335 | return errors.New("resource is nil") | ||
336 | } | ||
337 | |||
338 | if !writable { | ||
339 | if r.Create != nil || r.Update != nil || r.Delete != nil { | ||
340 | return fmt.Errorf("must not implement Create, Update or Delete") | ||
341 | } | ||
342 | } | ||
343 | |||
344 | tsm := topSchemaMap | ||
345 | |||
346 | if r.isTopLevel() && writable { | ||
347 | // All non-Computed attributes must be ForceNew if Update is not defined | ||
348 | if r.Update == nil { | ||
349 | nonForceNewAttrs := make([]string, 0) | ||
350 | for k, v := range r.Schema { | ||
351 | if !v.ForceNew && !v.Computed { | ||
352 | nonForceNewAttrs = append(nonForceNewAttrs, k) | ||
353 | } | ||
354 | } | ||
355 | if len(nonForceNewAttrs) > 0 { | ||
356 | return fmt.Errorf( | ||
357 | "No Update defined, must set ForceNew on: %#v", nonForceNewAttrs) | ||
358 | } | ||
359 | } else { | ||
360 | nonUpdateableAttrs := make([]string, 0) | ||
361 | for k, v := range r.Schema { | ||
362 | if v.ForceNew || v.Computed && !v.Optional { | ||
363 | nonUpdateableAttrs = append(nonUpdateableAttrs, k) | ||
364 | } | ||
365 | } | ||
366 | updateableAttrs := len(r.Schema) - len(nonUpdateableAttrs) | ||
367 | if updateableAttrs == 0 { | ||
368 | return fmt.Errorf( | ||
369 | "All fields are ForceNew or Computed w/out Optional, Update is superfluous") | ||
370 | } | ||
371 | } | ||
372 | |||
373 | tsm = schemaMap(r.Schema) | ||
374 | |||
375 | // Destroy, and Read are required | ||
376 | if r.Read == nil { | ||
377 | return fmt.Errorf("Read must be implemented") | ||
378 | } | ||
379 | if r.Delete == nil { | ||
380 | return fmt.Errorf("Delete must be implemented") | ||
381 | } | ||
382 | |||
383 | // If we have an importer, we need to verify the importer. | ||
384 | if r.Importer != nil { | ||
385 | if err := r.Importer.InternalValidate(); err != nil { | ||
386 | return err | ||
387 | } | ||
388 | } | ||
389 | } | ||
390 | |||
391 | return schemaMap(r.Schema).InternalValidate(tsm) | ||
392 | } | ||
393 | |||
394 | // Data returns a ResourceData struct for this Resource. Each return value | ||
395 | // is a separate copy and can be safely modified differently. | ||
396 | // | ||
397 | // The data returned from this function has no actual affect on the Resource | ||
398 | // itself (including the state given to this function). | ||
399 | // | ||
400 | // This function is useful for unit tests and ResourceImporter functions. | ||
401 | func (r *Resource) Data(s *terraform.InstanceState) *ResourceData { | ||
402 | result, err := schemaMap(r.Schema).Data(s, nil) | ||
403 | if err != nil { | ||
404 | // At the time of writing, this isn't possible (Data never returns | ||
405 | // non-nil errors). We panic to find this in the future if we have to. | ||
406 | // I don't see a reason for Data to ever return an error. | ||
407 | panic(err) | ||
408 | } | ||
409 | |||
410 | // Set the schema version to latest by default | ||
411 | result.meta = map[string]interface{}{ | ||
412 | "schema_version": strconv.Itoa(r.SchemaVersion), | ||
413 | } | ||
414 | |||
415 | return result | ||
416 | } | ||
417 | |||
418 | // TestResourceData Yields a ResourceData filled with this resource's schema for use in unit testing | ||
419 | // | ||
420 | // TODO: May be able to be removed with the above ResourceData function. | ||
421 | func (r *Resource) TestResourceData() *ResourceData { | ||
422 | return &ResourceData{ | ||
423 | schema: r.Schema, | ||
424 | } | ||
425 | } | ||
426 | |||
427 | // Returns true if the resource is "top level" i.e. not a sub-resource. | ||
428 | func (r *Resource) isTopLevel() bool { | ||
429 | // TODO: This is a heuristic; replace with a definitive attribute? | ||
430 | return r.Create != nil | ||
431 | } | ||
432 | |||
433 | // Determines if a given InstanceState needs to be migrated by checking the | ||
434 | // stored version number with the current SchemaVersion | ||
435 | func (r *Resource) checkSchemaVersion(is *terraform.InstanceState) (bool, int) { | ||
436 | // Get the raw interface{} value for the schema version. If it doesn't | ||
437 | // exist or is nil then set it to zero. | ||
438 | raw := is.Meta["schema_version"] | ||
439 | if raw == nil { | ||
440 | raw = "0" | ||
441 | } | ||
442 | |||
443 | // Try to convert it to a string. If it isn't a string then we pretend | ||
444 | // that it isn't set at all. It should never not be a string unless it | ||
445 | // was manually tampered with. | ||
446 | rawString, ok := raw.(string) | ||
447 | if !ok { | ||
448 | rawString = "0" | ||
449 | } | ||
450 | |||
451 | stateSchemaVersion, _ := strconv.Atoi(rawString) | ||
452 | return stateSchemaVersion < r.SchemaVersion, stateSchemaVersion | ||
453 | } | ||
454 | |||
455 | func (r *Resource) recordCurrentSchemaVersion( | ||
456 | state *terraform.InstanceState) *terraform.InstanceState { | ||
457 | if state != nil && r.SchemaVersion > 0 { | ||
458 | if state.Meta == nil { | ||
459 | state.Meta = make(map[string]interface{}) | ||
460 | } | ||
461 | state.Meta["schema_version"] = strconv.Itoa(r.SchemaVersion) | ||
462 | } | ||
463 | return state | ||
464 | } | ||
465 | |||
466 | // Noop is a convenience implementation of resource function which takes | ||
467 | // no action and returns no error. | ||
468 | func Noop(*ResourceData, interface{}) error { | ||
469 | return nil | ||
470 | } | ||
471 | |||
472 | // RemoveFromState is a convenience implementation of a resource function | ||
473 | // which sets the resource ID to empty string (to remove it from state) | ||
474 | // and returns no error. | ||
475 | func RemoveFromState(d *ResourceData, _ interface{}) error { | ||
476 | d.SetId("") | ||
477 | return nil | ||
478 | } | ||