aboutsummaryrefslogtreecommitdiffhomepage
path: root/vendor/github.com/hashicorp/terraform/helper
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper')
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/didyoumean/name_suggestion.go24
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/plugin/doc.go6
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/plugin/grpc_provider.go1338
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/plugin/grpc_provisioner.go147
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/plugin/unknown.go131
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/grpc_test_provider.go43
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/state.go2
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/state_shim.go163
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/testing.go298
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go334
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/resource/testing_import_state.go113
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/backend.go138
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/core_schema.go192
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/field_reader.go8
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/field_reader_config.go17
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/field_reader_diff.go3
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/field_reader_map.go3
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/field_writer_map.go12
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/getsource_string.go12
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/provider.go22
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/provisioner.go10
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/resource.go270
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go20
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go2
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/resource_timeout.go98
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/schema.go316
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/set.go10
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/shims.go115
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/testing.go2
-rw-r--r--vendor/github.com/hashicorp/terraform/helper/schema/valuetype_string.go15
30 files changed, 3539 insertions, 325 deletions
diff --git a/vendor/github.com/hashicorp/terraform/helper/didyoumean/name_suggestion.go b/vendor/github.com/hashicorp/terraform/helper/didyoumean/name_suggestion.go
new file mode 100644
index 0000000..54899bc
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/helper/didyoumean/name_suggestion.go
@@ -0,0 +1,24 @@
1package didyoumean
2
3import (
4 "github.com/agext/levenshtein"
5)
6
7// NameSuggestion tries to find a name from the given slice of suggested names
8// that is close to the given name and returns it if found. If no suggestion
9// is close enough, returns the empty string.
10//
11// The suggestions are tried in order, so earlier suggestions take precedence
12// if the given string is similar to two or more suggestions.
13//
14// This function is intended to be used with a relatively-small number of
15// suggestions. It's not optimized for hundreds or thousands of them.
16func NameSuggestion(given string, suggestions []string) string {
17 for _, suggestion := range suggestions {
18 dist := levenshtein.Distance(given, suggestion, nil)
19 if dist < 3 { // threshold determined experimentally
20 return suggestion
21 }
22 }
23 return ""
24}
diff --git a/vendor/github.com/hashicorp/terraform/helper/plugin/doc.go b/vendor/github.com/hashicorp/terraform/helper/plugin/doc.go
new file mode 100644
index 0000000..82b5937
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/helper/plugin/doc.go
@@ -0,0 +1,6 @@
1// Package plugin contains types and functions to help Terraform plugins
2// implement the plugin rpc interface.
3// The primary Provider type will be responsible for converting from the grpc
4// wire protocol to the types and methods known to the provider
5// implementations.
6package plugin
diff --git a/vendor/github.com/hashicorp/terraform/helper/plugin/grpc_provider.go b/vendor/github.com/hashicorp/terraform/helper/plugin/grpc_provider.go
new file mode 100644
index 0000000..510f47f
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/helper/plugin/grpc_provider.go
@@ -0,0 +1,1338 @@
1package plugin
2
3import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "log"
8 "strconv"
9
10 "github.com/zclconf/go-cty/cty"
11 ctyconvert "github.com/zclconf/go-cty/cty/convert"
12 "github.com/zclconf/go-cty/cty/msgpack"
13 context "golang.org/x/net/context"
14
15 "github.com/hashicorp/terraform/config/hcl2shim"
16 "github.com/hashicorp/terraform/configs/configschema"
17 "github.com/hashicorp/terraform/helper/schema"
18 proto "github.com/hashicorp/terraform/internal/tfplugin5"
19 "github.com/hashicorp/terraform/plugin/convert"
20 "github.com/hashicorp/terraform/terraform"
21)
22
23const newExtraKey = "_new_extra_shim"
24
25// NewGRPCProviderServerShim wraps a terraform.ResourceProvider in a
26// proto.ProviderServer implementation. If the provided provider is not a
27// *schema.Provider, this will return nil,
28func NewGRPCProviderServerShim(p terraform.ResourceProvider) *GRPCProviderServer {
29 sp, ok := p.(*schema.Provider)
30 if !ok {
31 return nil
32 }
33
34 return &GRPCProviderServer{
35 provider: sp,
36 }
37}
38
39// GRPCProviderServer handles the server, or plugin side of the rpc connection.
40type GRPCProviderServer struct {
41 provider *schema.Provider
42}
43
44func (s *GRPCProviderServer) GetSchema(_ context.Context, req *proto.GetProviderSchema_Request) (*proto.GetProviderSchema_Response, error) {
45 // Here we are certain that the provider is being called through grpc, so
46 // make sure the feature flag for helper/schema is set
47 schema.SetProto5()
48
49 resp := &proto.GetProviderSchema_Response{
50 ResourceSchemas: make(map[string]*proto.Schema),
51 DataSourceSchemas: make(map[string]*proto.Schema),
52 }
53
54 resp.Provider = &proto.Schema{
55 Block: convert.ConfigSchemaToProto(s.getProviderSchemaBlock()),
56 }
57
58 for typ, res := range s.provider.ResourcesMap {
59 resp.ResourceSchemas[typ] = &proto.Schema{
60 Version: int64(res.SchemaVersion),
61 Block: convert.ConfigSchemaToProto(res.CoreConfigSchema()),
62 }
63 }
64
65 for typ, dat := range s.provider.DataSourcesMap {
66 resp.DataSourceSchemas[typ] = &proto.Schema{
67 Version: int64(dat.SchemaVersion),
68 Block: convert.ConfigSchemaToProto(dat.CoreConfigSchema()),
69 }
70 }
71
72 return resp, nil
73}
74
75func (s *GRPCProviderServer) getProviderSchemaBlock() *configschema.Block {
76 return schema.InternalMap(s.provider.Schema).CoreConfigSchema()
77}
78
79func (s *GRPCProviderServer) getResourceSchemaBlock(name string) *configschema.Block {
80 res := s.provider.ResourcesMap[name]
81 return res.CoreConfigSchema()
82}
83
84func (s *GRPCProviderServer) getDatasourceSchemaBlock(name string) *configschema.Block {
85 dat := s.provider.DataSourcesMap[name]
86 return dat.CoreConfigSchema()
87}
88
89func (s *GRPCProviderServer) PrepareProviderConfig(_ context.Context, req *proto.PrepareProviderConfig_Request) (*proto.PrepareProviderConfig_Response, error) {
90 resp := &proto.PrepareProviderConfig_Response{}
91
92 schemaBlock := s.getProviderSchemaBlock()
93
94 configVal, err := msgpack.Unmarshal(req.Config.Msgpack, schemaBlock.ImpliedType())
95 if err != nil {
96 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
97 return resp, nil
98 }
99
100 // lookup any required, top-level attributes that are Null, and see if we
101 // have a Default value available.
102 configVal, err = cty.Transform(configVal, func(path cty.Path, val cty.Value) (cty.Value, error) {
103 // we're only looking for top-level attributes
104 if len(path) != 1 {
105 return val, nil
106 }
107
108 // nothing to do if we already have a value
109 if !val.IsNull() {
110 return val, nil
111 }
112
113 // get the Schema definition for this attribute
114 getAttr, ok := path[0].(cty.GetAttrStep)
115 // these should all exist, but just ignore anything strange
116 if !ok {
117 return val, nil
118 }
119
120 attrSchema := s.provider.Schema[getAttr.Name]
121 // continue to ignore anything that doesn't match
122 if attrSchema == nil {
123 return val, nil
124 }
125
126 // this is deprecated, so don't set it
127 if attrSchema.Deprecated != "" || attrSchema.Removed != "" {
128 return val, nil
129 }
130
131 // find a default value if it exists
132 def, err := attrSchema.DefaultValue()
133 if err != nil {
134 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, fmt.Errorf("error getting default for %q: %s", getAttr.Name, err))
135 return val, err
136 }
137
138 // no default
139 if def == nil {
140 return val, nil
141 }
142
143 // create a cty.Value and make sure it's the correct type
144 tmpVal := hcl2shim.HCL2ValueFromConfigValue(def)
145
146 // helper/schema used to allow setting "" to a bool
147 if val.Type() == cty.Bool && tmpVal.RawEquals(cty.StringVal("")) {
148 // return a warning about the conversion
149 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, "provider set empty string as default value for bool "+getAttr.Name)
150 tmpVal = cty.False
151 }
152
153 val, err = ctyconvert.Convert(tmpVal, val.Type())
154 if err != nil {
155 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, fmt.Errorf("error setting default for %q: %s", getAttr.Name, err))
156 }
157
158 return val, err
159 })
160 if err != nil {
161 // any error here was already added to the diagnostics
162 return resp, nil
163 }
164
165 configVal, err = schemaBlock.CoerceValue(configVal)
166 if err != nil {
167 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
168 return resp, nil
169 }
170
171 // Ensure there are no nulls that will cause helper/schema to panic.
172 if err := validateConfigNulls(configVal, nil); err != nil {
173 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
174 return resp, nil
175 }
176
177 config := terraform.NewResourceConfigShimmed(configVal, schemaBlock)
178
179 warns, errs := s.provider.Validate(config)
180 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, convert.WarnsAndErrsToProto(warns, errs))
181
182 preparedConfigMP, err := msgpack.Marshal(configVal, schemaBlock.ImpliedType())
183 if err != nil {
184 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
185 return resp, nil
186 }
187
188 resp.PreparedConfig = &proto.DynamicValue{Msgpack: preparedConfigMP}
189
190 return resp, nil
191}
192
193func (s *GRPCProviderServer) ValidateResourceTypeConfig(_ context.Context, req *proto.ValidateResourceTypeConfig_Request) (*proto.ValidateResourceTypeConfig_Response, error) {
194 resp := &proto.ValidateResourceTypeConfig_Response{}
195
196 schemaBlock := s.getResourceSchemaBlock(req.TypeName)
197
198 configVal, err := msgpack.Unmarshal(req.Config.Msgpack, schemaBlock.ImpliedType())
199 if err != nil {
200 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
201 return resp, nil
202 }
203
204 config := terraform.NewResourceConfigShimmed(configVal, schemaBlock)
205
206 warns, errs := s.provider.ValidateResource(req.TypeName, config)
207 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, convert.WarnsAndErrsToProto(warns, errs))
208
209 return resp, nil
210}
211
212func (s *GRPCProviderServer) ValidateDataSourceConfig(_ context.Context, req *proto.ValidateDataSourceConfig_Request) (*proto.ValidateDataSourceConfig_Response, error) {
213 resp := &proto.ValidateDataSourceConfig_Response{}
214
215 schemaBlock := s.getDatasourceSchemaBlock(req.TypeName)
216
217 configVal, err := msgpack.Unmarshal(req.Config.Msgpack, schemaBlock.ImpliedType())
218 if err != nil {
219 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
220 return resp, nil
221 }
222
223 // Ensure there are no nulls that will cause helper/schema to panic.
224 if err := validateConfigNulls(configVal, nil); err != nil {
225 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
226 return resp, nil
227 }
228
229 config := terraform.NewResourceConfigShimmed(configVal, schemaBlock)
230
231 warns, errs := s.provider.ValidateDataSource(req.TypeName, config)
232 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, convert.WarnsAndErrsToProto(warns, errs))
233
234 return resp, nil
235}
236
237func (s *GRPCProviderServer) UpgradeResourceState(_ context.Context, req *proto.UpgradeResourceState_Request) (*proto.UpgradeResourceState_Response, error) {
238 resp := &proto.UpgradeResourceState_Response{}
239
240 res := s.provider.ResourcesMap[req.TypeName]
241 schemaBlock := s.getResourceSchemaBlock(req.TypeName)
242
243 version := int(req.Version)
244
245 jsonMap := map[string]interface{}{}
246 var err error
247
248 switch {
249 // We first need to upgrade a flatmap state if it exists.
250 // There should never be both a JSON and Flatmap state in the request.
251 case len(req.RawState.Flatmap) > 0:
252 jsonMap, version, err = s.upgradeFlatmapState(version, req.RawState.Flatmap, res)
253 if err != nil {
254 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
255 return resp, nil
256 }
257 // if there's a JSON state, we need to decode it.
258 case len(req.RawState.Json) > 0:
259 err = json.Unmarshal(req.RawState.Json, &jsonMap)
260 if err != nil {
261 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
262 return resp, nil
263 }
264 default:
265 log.Println("[DEBUG] no state provided to upgrade")
266 return resp, nil
267 }
268
269 // complete the upgrade of the JSON states
270 jsonMap, err = s.upgradeJSONState(version, jsonMap, res)
271 if err != nil {
272 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
273 return resp, nil
274 }
275
276 // The provider isn't required to clean out removed fields
277 s.removeAttributes(jsonMap, schemaBlock.ImpliedType())
278
279 // now we need to turn the state into the default json representation, so
280 // that it can be re-decoded using the actual schema.
281 val, err := schema.JSONMapToStateValue(jsonMap, schemaBlock)
282 if err != nil {
283 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
284 return resp, nil
285 }
286
287 // encode the final state to the expected msgpack format
288 newStateMP, err := msgpack.Marshal(val, schemaBlock.ImpliedType())
289 if err != nil {
290 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
291 return resp, nil
292 }
293
294 resp.UpgradedState = &proto.DynamicValue{Msgpack: newStateMP}
295 return resp, nil
296}
297
298// upgradeFlatmapState takes a legacy flatmap state, upgrades it using Migrate
299// state if necessary, and converts it to the new JSON state format decoded as a
300// map[string]interface{}.
301// upgradeFlatmapState returns the json map along with the corresponding schema
302// version.
303func (s *GRPCProviderServer) upgradeFlatmapState(version int, m map[string]string, res *schema.Resource) (map[string]interface{}, int, error) {
304 // this will be the version we've upgraded so, defaulting to the given
305 // version in case no migration was called.
306 upgradedVersion := version
307
308 // first determine if we need to call the legacy MigrateState func
309 requiresMigrate := version < res.SchemaVersion
310
311 schemaType := res.CoreConfigSchema().ImpliedType()
312
313 // if there are any StateUpgraders, then we need to only compare
314 // against the first version there
315 if len(res.StateUpgraders) > 0 {
316 requiresMigrate = version < res.StateUpgraders[0].Version
317 }
318
319 if requiresMigrate {
320 if res.MigrateState == nil {
321 return nil, 0, errors.New("cannot upgrade state, missing MigrateState function")
322 }
323
324 is := &terraform.InstanceState{
325 ID: m["id"],
326 Attributes: m,
327 Meta: map[string]interface{}{
328 "schema_version": strconv.Itoa(version),
329 },
330 }
331
332 is, err := res.MigrateState(version, is, s.provider.Meta())
333 if err != nil {
334 return nil, 0, err
335 }
336
337 // re-assign the map in case there was a copy made, making sure to keep
338 // the ID
339 m := is.Attributes
340 m["id"] = is.ID
341
342 // if there are further upgraders, then we've only updated that far
343 if len(res.StateUpgraders) > 0 {
344 schemaType = res.StateUpgraders[0].Type
345 upgradedVersion = res.StateUpgraders[0].Version
346 }
347 } else {
348 // the schema version may be newer than the MigrateState functions
349 // handled and older than the current, but still stored in the flatmap
350 // form. If that's the case, we need to find the correct schema type to
351 // convert the state.
352 for _, upgrader := range res.StateUpgraders {
353 if upgrader.Version == version {
354 schemaType = upgrader.Type
355 break
356 }
357 }
358 }
359
360 // now we know the state is up to the latest version that handled the
361 // flatmap format state. Now we can upgrade the format and continue from
362 // there.
363 newConfigVal, err := hcl2shim.HCL2ValueFromFlatmap(m, schemaType)
364 if err != nil {
365 return nil, 0, err
366 }
367
368 jsonMap, err := schema.StateValueToJSONMap(newConfigVal, schemaType)
369 return jsonMap, upgradedVersion, err
370}
371
372func (s *GRPCProviderServer) upgradeJSONState(version int, m map[string]interface{}, res *schema.Resource) (map[string]interface{}, error) {
373 var err error
374
375 for _, upgrader := range res.StateUpgraders {
376 if version != upgrader.Version {
377 continue
378 }
379
380 m, err = upgrader.Upgrade(m, s.provider.Meta())
381 if err != nil {
382 return nil, err
383 }
384 version++
385 }
386
387 return m, nil
388}
389
390// Remove any attributes no longer present in the schema, so that the json can
391// be correctly decoded.
392func (s *GRPCProviderServer) removeAttributes(v interface{}, ty cty.Type) {
393 // we're only concerned with finding maps that corespond to object
394 // attributes
395 switch v := v.(type) {
396 case []interface{}:
397 // If these aren't blocks the next call will be a noop
398 if ty.IsListType() || ty.IsSetType() {
399 eTy := ty.ElementType()
400 for _, eV := range v {
401 s.removeAttributes(eV, eTy)
402 }
403 }
404 return
405 case map[string]interface{}:
406 // map blocks aren't yet supported, but handle this just in case
407 if ty.IsMapType() {
408 eTy := ty.ElementType()
409 for _, eV := range v {
410 s.removeAttributes(eV, eTy)
411 }
412 return
413 }
414
415 if ty == cty.DynamicPseudoType {
416 log.Printf("[DEBUG] ignoring dynamic block: %#v\n", v)
417 return
418 }
419
420 if !ty.IsObjectType() {
421 // This shouldn't happen, and will fail to decode further on, so
422 // there's no need to handle it here.
423 log.Printf("[WARN] unexpected type %#v for map in json state", ty)
424 return
425 }
426
427 attrTypes := ty.AttributeTypes()
428 for attr, attrV := range v {
429 attrTy, ok := attrTypes[attr]
430 if !ok {
431 log.Printf("[DEBUG] attribute %q no longer present in schema", attr)
432 delete(v, attr)
433 continue
434 }
435
436 s.removeAttributes(attrV, attrTy)
437 }
438 }
439}
440
441func (s *GRPCProviderServer) Stop(_ context.Context, _ *proto.Stop_Request) (*proto.Stop_Response, error) {
442 resp := &proto.Stop_Response{}
443
444 err := s.provider.Stop()
445 if err != nil {
446 resp.Error = err.Error()
447 }
448
449 return resp, nil
450}
451
452func (s *GRPCProviderServer) Configure(_ context.Context, req *proto.Configure_Request) (*proto.Configure_Response, error) {
453 resp := &proto.Configure_Response{}
454
455 schemaBlock := s.getProviderSchemaBlock()
456
457 configVal, err := msgpack.Unmarshal(req.Config.Msgpack, schemaBlock.ImpliedType())
458 if err != nil {
459 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
460 return resp, nil
461 }
462
463 s.provider.TerraformVersion = req.TerraformVersion
464
465 // Ensure there are no nulls that will cause helper/schema to panic.
466 if err := validateConfigNulls(configVal, nil); err != nil {
467 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
468 return resp, nil
469 }
470
471 config := terraform.NewResourceConfigShimmed(configVal, schemaBlock)
472 err = s.provider.Configure(config)
473 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
474
475 return resp, nil
476}
477
478func (s *GRPCProviderServer) ReadResource(_ context.Context, req *proto.ReadResource_Request) (*proto.ReadResource_Response, error) {
479 resp := &proto.ReadResource_Response{}
480
481 res := s.provider.ResourcesMap[req.TypeName]
482 schemaBlock := s.getResourceSchemaBlock(req.TypeName)
483
484 stateVal, err := msgpack.Unmarshal(req.CurrentState.Msgpack, schemaBlock.ImpliedType())
485 if err != nil {
486 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
487 return resp, nil
488 }
489
490 instanceState, err := res.ShimInstanceStateFromValue(stateVal)
491 if err != nil {
492 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
493 return resp, nil
494 }
495
496 newInstanceState, err := res.RefreshWithoutUpgrade(instanceState, s.provider.Meta())
497 if err != nil {
498 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
499 return resp, nil
500 }
501
502 if newInstanceState == nil || newInstanceState.ID == "" {
503 // The old provider API used an empty id to signal that the remote
504 // object appears to have been deleted, but our new protocol expects
505 // to see a null value (in the cty sense) in that case.
506 newStateMP, err := msgpack.Marshal(cty.NullVal(schemaBlock.ImpliedType()), schemaBlock.ImpliedType())
507 if err != nil {
508 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
509 }
510 resp.NewState = &proto.DynamicValue{
511 Msgpack: newStateMP,
512 }
513 return resp, nil
514 }
515
516 // helper/schema should always copy the ID over, but do it again just to be safe
517 newInstanceState.Attributes["id"] = newInstanceState.ID
518
519 newStateVal, err := hcl2shim.HCL2ValueFromFlatmap(newInstanceState.Attributes, schemaBlock.ImpliedType())
520 if err != nil {
521 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
522 return resp, nil
523 }
524
525 newStateVal = normalizeNullValues(newStateVal, stateVal, false)
526 newStateVal = copyTimeoutValues(newStateVal, stateVal)
527
528 newStateMP, err := msgpack.Marshal(newStateVal, schemaBlock.ImpliedType())
529 if err != nil {
530 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
531 return resp, nil
532 }
533
534 resp.NewState = &proto.DynamicValue{
535 Msgpack: newStateMP,
536 }
537
538 return resp, nil
539}
540
541func (s *GRPCProviderServer) PlanResourceChange(_ context.Context, req *proto.PlanResourceChange_Request) (*proto.PlanResourceChange_Response, error) {
542 resp := &proto.PlanResourceChange_Response{}
543
544 // This is a signal to Terraform Core that we're doing the best we can to
545 // shim the legacy type system of the SDK onto the Terraform type system
546 // but we need it to cut us some slack. This setting should not be taken
547 // forward to any new SDK implementations, since setting it prevents us
548 // from catching certain classes of provider bug that can lead to
549 // confusing downstream errors.
550 resp.LegacyTypeSystem = true
551
552 res := s.provider.ResourcesMap[req.TypeName]
553 schemaBlock := s.getResourceSchemaBlock(req.TypeName)
554
555 priorStateVal, err := msgpack.Unmarshal(req.PriorState.Msgpack, schemaBlock.ImpliedType())
556 if err != nil {
557 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
558 return resp, nil
559 }
560
561 create := priorStateVal.IsNull()
562
563 proposedNewStateVal, err := msgpack.Unmarshal(req.ProposedNewState.Msgpack, schemaBlock.ImpliedType())
564 if err != nil {
565 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
566 return resp, nil
567 }
568
569 // We don't usually plan destroys, but this can return early in any case.
570 if proposedNewStateVal.IsNull() {
571 resp.PlannedState = req.ProposedNewState
572 return resp, nil
573 }
574
575 info := &terraform.InstanceInfo{
576 Type: req.TypeName,
577 }
578
579 priorState, err := res.ShimInstanceStateFromValue(priorStateVal)
580 if err != nil {
581 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
582 return resp, nil
583 }
584 priorPrivate := make(map[string]interface{})
585 if len(req.PriorPrivate) > 0 {
586 if err := json.Unmarshal(req.PriorPrivate, &priorPrivate); err != nil {
587 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
588 return resp, nil
589 }
590 }
591
592 priorState.Meta = priorPrivate
593
594 // Ensure there are no nulls that will cause helper/schema to panic.
595 if err := validateConfigNulls(proposedNewStateVal, nil); err != nil {
596 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
597 return resp, nil
598 }
599
600 // turn the proposed state into a legacy configuration
601 cfg := terraform.NewResourceConfigShimmed(proposedNewStateVal, schemaBlock)
602
603 diff, err := s.provider.SimpleDiff(info, priorState, cfg)
604 if err != nil {
605 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
606 return resp, nil
607 }
608
609 // if this is a new instance, we need to make sure ID is going to be computed
610 if create {
611 if diff == nil {
612 diff = terraform.NewInstanceDiff()
613 }
614
615 diff.Attributes["id"] = &terraform.ResourceAttrDiff{
616 NewComputed: true,
617 }
618 }
619
620 if diff == nil || len(diff.Attributes) == 0 {
621 // schema.Provider.Diff returns nil if it ends up making a diff with no
622 // changes, but our new interface wants us to return an actual change
623 // description that _shows_ there are no changes. This is always the
624 // prior state, because we force a diff above if this is a new instance.
625 resp.PlannedState = req.PriorState
626 return resp, nil
627 }
628
629 if priorState == nil {
630 priorState = &terraform.InstanceState{}
631 }
632
633 // now we need to apply the diff to the prior state, so get the planned state
634 plannedAttrs, err := diff.Apply(priorState.Attributes, schemaBlock)
635
636 plannedStateVal, err := hcl2shim.HCL2ValueFromFlatmap(plannedAttrs, schemaBlock.ImpliedType())
637 if err != nil {
638 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
639 return resp, nil
640 }
641
642 plannedStateVal, err = schemaBlock.CoerceValue(plannedStateVal)
643 if err != nil {
644 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
645 return resp, nil
646 }
647
648 plannedStateVal = normalizeNullValues(plannedStateVal, proposedNewStateVal, false)
649
650 if err != nil {
651 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
652 return resp, nil
653 }
654
655 plannedStateVal = copyTimeoutValues(plannedStateVal, proposedNewStateVal)
656
657 // The old SDK code has some imprecisions that cause it to sometimes
658 // generate differences that the SDK itself does not consider significant
659 // but Terraform Core would. To avoid producing weird do-nothing diffs
660 // in that case, we'll check if the provider as produced something we
661 // think is "equivalent" to the prior state and just return the prior state
662 // itself if so, thus ensuring that Terraform Core will treat this as
663 // a no-op. See the docs for ValuesSDKEquivalent for some caveats on its
664 // accuracy.
665 forceNoChanges := false
666 if hcl2shim.ValuesSDKEquivalent(priorStateVal, plannedStateVal) {
667 plannedStateVal = priorStateVal
668 forceNoChanges = true
669 }
670
671 // if this was creating the resource, we need to set any remaining computed
672 // fields
673 if create {
674 plannedStateVal = SetUnknowns(plannedStateVal, schemaBlock)
675 }
676
677 plannedMP, err := msgpack.Marshal(plannedStateVal, schemaBlock.ImpliedType())
678 if err != nil {
679 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
680 return resp, nil
681 }
682 resp.PlannedState = &proto.DynamicValue{
683 Msgpack: plannedMP,
684 }
685
686 // Now we need to store any NewExtra values, which are where any actual
687 // StateFunc modified config fields are hidden.
688 privateMap := diff.Meta
689 if privateMap == nil {
690 privateMap = map[string]interface{}{}
691 }
692
693 newExtra := map[string]interface{}{}
694
695 for k, v := range diff.Attributes {
696 if v.NewExtra != nil {
697 newExtra[k] = v.NewExtra
698 }
699 }
700 privateMap[newExtraKey] = newExtra
701
702 // the Meta field gets encoded into PlannedPrivate
703 plannedPrivate, err := json.Marshal(privateMap)
704 if err != nil {
705 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
706 return resp, nil
707 }
708 resp.PlannedPrivate = plannedPrivate
709
710 // collect the attributes that require instance replacement, and convert
711 // them to cty.Paths.
712 var requiresNew []string
713 if !forceNoChanges {
714 for attr, d := range diff.Attributes {
715 if d.RequiresNew {
716 requiresNew = append(requiresNew, attr)
717 }
718 }
719 }
720
721 // If anything requires a new resource already, or the "id" field indicates
722 // that we will be creating a new resource, then we need to add that to
723 // RequiresReplace so that core can tell if the instance is being replaced
724 // even if changes are being suppressed via "ignore_changes".
725 id := plannedStateVal.GetAttr("id")
726 if len(requiresNew) > 0 || id.IsNull() || !id.IsKnown() {
727 requiresNew = append(requiresNew, "id")
728 }
729
730 requiresReplace, err := hcl2shim.RequiresReplace(requiresNew, schemaBlock.ImpliedType())
731 if err != nil {
732 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
733 return resp, nil
734 }
735
736 // convert these to the protocol structures
737 for _, p := range requiresReplace {
738 resp.RequiresReplace = append(resp.RequiresReplace, pathToAttributePath(p))
739 }
740
741 return resp, nil
742}
743
744func (s *GRPCProviderServer) ApplyResourceChange(_ context.Context, req *proto.ApplyResourceChange_Request) (*proto.ApplyResourceChange_Response, error) {
745 resp := &proto.ApplyResourceChange_Response{
746 // Start with the existing state as a fallback
747 NewState: req.PriorState,
748 }
749
750 res := s.provider.ResourcesMap[req.TypeName]
751 schemaBlock := s.getResourceSchemaBlock(req.TypeName)
752
753 priorStateVal, err := msgpack.Unmarshal(req.PriorState.Msgpack, schemaBlock.ImpliedType())
754 if err != nil {
755 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
756 return resp, nil
757 }
758
759 plannedStateVal, err := msgpack.Unmarshal(req.PlannedState.Msgpack, schemaBlock.ImpliedType())
760 if err != nil {
761 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
762 return resp, nil
763 }
764
765 info := &terraform.InstanceInfo{
766 Type: req.TypeName,
767 }
768
769 priorState, err := res.ShimInstanceStateFromValue(priorStateVal)
770 if err != nil {
771 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
772 return resp, nil
773 }
774
775 private := make(map[string]interface{})
776 if len(req.PlannedPrivate) > 0 {
777 if err := json.Unmarshal(req.PlannedPrivate, &private); err != nil {
778 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
779 return resp, nil
780 }
781 }
782
783 var diff *terraform.InstanceDiff
784 destroy := false
785
786 // a null state means we are destroying the instance
787 if plannedStateVal.IsNull() {
788 destroy = true
789 diff = &terraform.InstanceDiff{
790 Attributes: make(map[string]*terraform.ResourceAttrDiff),
791 Meta: make(map[string]interface{}),
792 Destroy: true,
793 }
794 } else {
795 diff, err = schema.DiffFromValues(priorStateVal, plannedStateVal, stripResourceModifiers(res))
796 if err != nil {
797 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
798 return resp, nil
799 }
800 }
801
802 if diff == nil {
803 diff = &terraform.InstanceDiff{
804 Attributes: make(map[string]*terraform.ResourceAttrDiff),
805 Meta: make(map[string]interface{}),
806 }
807 }
808
809 // add NewExtra Fields that may have been stored in the private data
810 if newExtra := private[newExtraKey]; newExtra != nil {
811 for k, v := range newExtra.(map[string]interface{}) {
812 d := diff.Attributes[k]
813
814 if d == nil {
815 d = &terraform.ResourceAttrDiff{}
816 }
817
818 d.NewExtra = v
819 diff.Attributes[k] = d
820 }
821 }
822
823 if private != nil {
824 diff.Meta = private
825 }
826
827 for k, d := range diff.Attributes {
828 // We need to turn off any RequiresNew. There could be attributes
829 // without changes in here inserted by helper/schema, but if they have
830 // RequiresNew then the state will be dropped from the ResourceData.
831 d.RequiresNew = false
832
833 // Check that any "removed" attributes that don't actually exist in the
834 // prior state, or helper/schema will confuse itself
835 if d.NewRemoved {
836 if _, ok := priorState.Attributes[k]; !ok {
837 delete(diff.Attributes, k)
838 }
839 }
840 }
841
842 newInstanceState, err := s.provider.Apply(info, priorState, diff)
843 // we record the error here, but continue processing any returned state.
844 if err != nil {
845 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
846 }
847 newStateVal := cty.NullVal(schemaBlock.ImpliedType())
848
849 // Always return a null value for destroy.
850 // While this is usually indicated by a nil state, check for missing ID or
851 // attributes in the case of a provider failure.
852 if destroy || newInstanceState == nil || newInstanceState.Attributes == nil || newInstanceState.ID == "" {
853 newStateMP, err := msgpack.Marshal(newStateVal, schemaBlock.ImpliedType())
854 if err != nil {
855 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
856 return resp, nil
857 }
858 resp.NewState = &proto.DynamicValue{
859 Msgpack: newStateMP,
860 }
861 return resp, nil
862 }
863
864 // We keep the null val if we destroyed the resource, otherwise build the
865 // entire object, even if the new state was nil.
866 newStateVal, err = schema.StateValueFromInstanceState(newInstanceState, schemaBlock.ImpliedType())
867 if err != nil {
868 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
869 return resp, nil
870 }
871
872 newStateVal = normalizeNullValues(newStateVal, plannedStateVal, true)
873
874 newStateVal = copyTimeoutValues(newStateVal, plannedStateVal)
875
876 newStateMP, err := msgpack.Marshal(newStateVal, schemaBlock.ImpliedType())
877 if err != nil {
878 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
879 return resp, nil
880 }
881 resp.NewState = &proto.DynamicValue{
882 Msgpack: newStateMP,
883 }
884
885 meta, err := json.Marshal(newInstanceState.Meta)
886 if err != nil {
887 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
888 return resp, nil
889 }
890 resp.Private = meta
891
892 // This is a signal to Terraform Core that we're doing the best we can to
893 // shim the legacy type system of the SDK onto the Terraform type system
894 // but we need it to cut us some slack. This setting should not be taken
895 // forward to any new SDK implementations, since setting it prevents us
896 // from catching certain classes of provider bug that can lead to
897 // confusing downstream errors.
898 resp.LegacyTypeSystem = true
899
900 return resp, nil
901}
902
903func (s *GRPCProviderServer) ImportResourceState(_ context.Context, req *proto.ImportResourceState_Request) (*proto.ImportResourceState_Response, error) {
904 resp := &proto.ImportResourceState_Response{}
905
906 info := &terraform.InstanceInfo{
907 Type: req.TypeName,
908 }
909
910 newInstanceStates, err := s.provider.ImportState(info, req.Id)
911 if err != nil {
912 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
913 return resp, nil
914 }
915
916 for _, is := range newInstanceStates {
917 // copy the ID again just to be sure it wasn't missed
918 is.Attributes["id"] = is.ID
919
920 resourceType := is.Ephemeral.Type
921 if resourceType == "" {
922 resourceType = req.TypeName
923 }
924
925 schemaBlock := s.getResourceSchemaBlock(resourceType)
926 newStateVal, err := hcl2shim.HCL2ValueFromFlatmap(is.Attributes, schemaBlock.ImpliedType())
927 if err != nil {
928 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
929 return resp, nil
930 }
931
932 newStateMP, err := msgpack.Marshal(newStateVal, schemaBlock.ImpliedType())
933 if err != nil {
934 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
935 return resp, nil
936 }
937
938 meta, err := json.Marshal(is.Meta)
939 if err != nil {
940 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
941 return resp, nil
942 }
943
944 importedResource := &proto.ImportResourceState_ImportedResource{
945 TypeName: resourceType,
946 State: &proto.DynamicValue{
947 Msgpack: newStateMP,
948 },
949 Private: meta,
950 }
951
952 resp.ImportedResources = append(resp.ImportedResources, importedResource)
953 }
954
955 return resp, nil
956}
957
958func (s *GRPCProviderServer) ReadDataSource(_ context.Context, req *proto.ReadDataSource_Request) (*proto.ReadDataSource_Response, error) {
959 resp := &proto.ReadDataSource_Response{}
960
961 schemaBlock := s.getDatasourceSchemaBlock(req.TypeName)
962
963 configVal, err := msgpack.Unmarshal(req.Config.Msgpack, schemaBlock.ImpliedType())
964 if err != nil {
965 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
966 return resp, nil
967 }
968
969 info := &terraform.InstanceInfo{
970 Type: req.TypeName,
971 }
972
973 // Ensure there are no nulls that will cause helper/schema to panic.
974 if err := validateConfigNulls(configVal, nil); err != nil {
975 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
976 return resp, nil
977 }
978
979 config := terraform.NewResourceConfigShimmed(configVal, schemaBlock)
980
981 // we need to still build the diff separately with the Read method to match
982 // the old behavior
983 diff, err := s.provider.ReadDataDiff(info, config)
984 if err != nil {
985 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
986 return resp, nil
987 }
988
989 // now we can get the new complete data source
990 newInstanceState, err := s.provider.ReadDataApply(info, diff)
991 if err != nil {
992 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
993 return resp, nil
994 }
995
996 newStateVal, err := schema.StateValueFromInstanceState(newInstanceState, schemaBlock.ImpliedType())
997 if err != nil {
998 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
999 return resp, nil
1000 }
1001
1002 newStateVal = copyTimeoutValues(newStateVal, configVal)
1003
1004 newStateMP, err := msgpack.Marshal(newStateVal, schemaBlock.ImpliedType())
1005 if err != nil {
1006 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
1007 return resp, nil
1008 }
1009 resp.State = &proto.DynamicValue{
1010 Msgpack: newStateMP,
1011 }
1012 return resp, nil
1013}
1014
1015func pathToAttributePath(path cty.Path) *proto.AttributePath {
1016 var steps []*proto.AttributePath_Step
1017
1018 for _, step := range path {
1019 switch s := step.(type) {
1020 case cty.GetAttrStep:
1021 steps = append(steps, &proto.AttributePath_Step{
1022 Selector: &proto.AttributePath_Step_AttributeName{
1023 AttributeName: s.Name,
1024 },
1025 })
1026 case cty.IndexStep:
1027 ty := s.Key.Type()
1028 switch ty {
1029 case cty.Number:
1030 i, _ := s.Key.AsBigFloat().Int64()
1031 steps = append(steps, &proto.AttributePath_Step{
1032 Selector: &proto.AttributePath_Step_ElementKeyInt{
1033 ElementKeyInt: i,
1034 },
1035 })
1036 case cty.String:
1037 steps = append(steps, &proto.AttributePath_Step{
1038 Selector: &proto.AttributePath_Step_ElementKeyString{
1039 ElementKeyString: s.Key.AsString(),
1040 },
1041 })
1042 }
1043 }
1044 }
1045
1046 return &proto.AttributePath{Steps: steps}
1047}
1048
1049// helper/schema throws away timeout values from the config and stores them in
1050// the Private/Meta fields. we need to copy those values into the planned state
1051// so that core doesn't see a perpetual diff with the timeout block.
1052func copyTimeoutValues(to cty.Value, from cty.Value) cty.Value {
1053 // if `to` is null we are planning to remove it altogether.
1054 if to.IsNull() {
1055 return to
1056 }
1057 toAttrs := to.AsValueMap()
1058 // We need to remove the key since the hcl2shims will add a non-null block
1059 // because we can't determine if a single block was null from the flatmapped
1060 // values. This needs to conform to the correct schema for marshaling, so
1061 // change the value to null rather than deleting it from the object map.
1062 timeouts, ok := toAttrs[schema.TimeoutsConfigKey]
1063 if ok {
1064 toAttrs[schema.TimeoutsConfigKey] = cty.NullVal(timeouts.Type())
1065 }
1066
1067 // if from is null then there are no timeouts to copy
1068 if from.IsNull() {
1069 return cty.ObjectVal(toAttrs)
1070 }
1071
1072 fromAttrs := from.AsValueMap()
1073 timeouts, ok = fromAttrs[schema.TimeoutsConfigKey]
1074
1075 // timeouts shouldn't be unknown, but don't copy possibly invalid values either
1076 if !ok || timeouts.IsNull() || !timeouts.IsWhollyKnown() {
1077 // no timeouts block to copy
1078 return cty.ObjectVal(toAttrs)
1079 }
1080
1081 toAttrs[schema.TimeoutsConfigKey] = timeouts
1082
1083 return cty.ObjectVal(toAttrs)
1084}
1085
1086// stripResourceModifiers takes a *schema.Resource and returns a deep copy with all
1087// StateFuncs and CustomizeDiffs removed. This will be used during apply to
1088// create a diff from a planned state where the diff modifications have already
1089// been applied.
1090func stripResourceModifiers(r *schema.Resource) *schema.Resource {
1091 if r == nil {
1092 return nil
1093 }
1094 // start with a shallow copy
1095 newResource := new(schema.Resource)
1096 *newResource = *r
1097
1098 newResource.CustomizeDiff = nil
1099 newResource.Schema = map[string]*schema.Schema{}
1100
1101 for k, s := range r.Schema {
1102 newResource.Schema[k] = stripSchema(s)
1103 }
1104
1105 return newResource
1106}
1107
1108func stripSchema(s *schema.Schema) *schema.Schema {
1109 if s == nil {
1110 return nil
1111 }
1112 // start with a shallow copy
1113 newSchema := new(schema.Schema)
1114 *newSchema = *s
1115
1116 newSchema.StateFunc = nil
1117
1118 switch e := newSchema.Elem.(type) {
1119 case *schema.Schema:
1120 newSchema.Elem = stripSchema(e)
1121 case *schema.Resource:
1122 newSchema.Elem = stripResourceModifiers(e)
1123 }
1124
1125 return newSchema
1126}
1127
1128// Zero values and empty containers may be interchanged by the apply process.
1129// When there is a discrepency between src and dst value being null or empty,
1130// prefer the src value. This takes a little more liberty with set types, since
1131// we can't correlate modified set values. In the case of sets, if the src set
1132// was wholly known we assume the value was correctly applied and copy that
1133// entirely to the new value.
1134// While apply prefers the src value, during plan we prefer dst whenever there
1135// is an unknown or a set is involved, since the plan can alter the value
1136// however it sees fit. This however means that a CustomizeDiffFunction may not
1137// be able to change a null to an empty value or vice versa, but that should be
1138// very uncommon nor was it reliable before 0.12 either.
1139func normalizeNullValues(dst, src cty.Value, apply bool) cty.Value {
1140 ty := dst.Type()
1141 if !src.IsNull() && !src.IsKnown() {
1142 // Return src during plan to retain unknown interpolated placeholders,
1143 // which could be lost if we're only updating a resource. If this is a
1144 // read scenario, then there shouldn't be any unknowns at all.
1145 if dst.IsNull() && !apply {
1146 return src
1147 }
1148 return dst
1149 }
1150
1151 // Handle null/empty changes for collections during apply.
1152 // A change between null and empty values prefers src to make sure the state
1153 // is consistent between plan and apply.
1154 if ty.IsCollectionType() && apply {
1155 dstEmpty := !dst.IsNull() && dst.IsKnown() && dst.LengthInt() == 0
1156 srcEmpty := !src.IsNull() && src.IsKnown() && src.LengthInt() == 0
1157
1158 if (src.IsNull() && dstEmpty) || (srcEmpty && dst.IsNull()) {
1159 return src
1160 }
1161 }
1162
1163 if src.IsNull() || !src.IsKnown() || !dst.IsKnown() {
1164 return dst
1165 }
1166
1167 switch {
1168 case ty.IsMapType(), ty.IsObjectType():
1169 var dstMap map[string]cty.Value
1170 if !dst.IsNull() {
1171 dstMap = dst.AsValueMap()
1172 }
1173 if dstMap == nil {
1174 dstMap = map[string]cty.Value{}
1175 }
1176
1177 srcMap := src.AsValueMap()
1178 for key, v := range srcMap {
1179 dstVal, ok := dstMap[key]
1180 if !ok && apply && ty.IsMapType() {
1181 // don't transfer old map values to dst during apply
1182 continue
1183 }
1184
1185 if dstVal == cty.NilVal {
1186 if !apply && ty.IsMapType() {
1187 // let plan shape this map however it wants
1188 continue
1189 }
1190 dstVal = cty.NullVal(v.Type())
1191 }
1192
1193 dstMap[key] = normalizeNullValues(dstVal, v, apply)
1194 }
1195
1196 // you can't call MapVal/ObjectVal with empty maps, but nothing was
1197 // copied in anyway. If the dst is nil, and the src is known, assume the
1198 // src is correct.
1199 if len(dstMap) == 0 {
1200 if dst.IsNull() && src.IsWhollyKnown() && apply {
1201 return src
1202 }
1203 return dst
1204 }
1205
1206 if ty.IsMapType() {
1207 // helper/schema will populate an optional+computed map with
1208 // unknowns which we have to fixup here.
1209 // It would be preferable to simply prevent any known value from
1210 // becoming unknown, but concessions have to be made to retain the
1211 // broken legacy behavior when possible.
1212 for k, srcVal := range srcMap {
1213 if !srcVal.IsNull() && srcVal.IsKnown() {
1214 dstVal, ok := dstMap[k]
1215 if !ok {
1216 continue
1217 }
1218
1219 if !dstVal.IsNull() && !dstVal.IsKnown() {
1220 dstMap[k] = srcVal
1221 }
1222 }
1223 }
1224
1225 return cty.MapVal(dstMap)
1226 }
1227
1228 return cty.ObjectVal(dstMap)
1229
1230 case ty.IsSetType():
1231 // If the original was wholly known, then we expect that is what the
1232 // provider applied. The apply process loses too much information to
1233 // reliably re-create the set.
1234 if src.IsWhollyKnown() && apply {
1235 return src
1236 }
1237
1238 case ty.IsListType(), ty.IsTupleType():
1239 // If the dst is null, and the src is known, then we lost an empty value
1240 // so take the original.
1241 if dst.IsNull() {
1242 if src.IsWhollyKnown() && src.LengthInt() == 0 && apply {
1243 return src
1244 }
1245
1246 // if dst is null and src only contains unknown values, then we lost
1247 // those during a read or plan.
1248 if !apply && !src.IsNull() {
1249 allUnknown := true
1250 for _, v := range src.AsValueSlice() {
1251 if v.IsKnown() {
1252 allUnknown = false
1253 break
1254 }
1255 }
1256 if allUnknown {
1257 return src
1258 }
1259 }
1260
1261 return dst
1262 }
1263
1264 // if the lengths are identical, then iterate over each element in succession.
1265 srcLen := src.LengthInt()
1266 dstLen := dst.LengthInt()
1267 if srcLen == dstLen && srcLen > 0 {
1268 srcs := src.AsValueSlice()
1269 dsts := dst.AsValueSlice()
1270
1271 for i := 0; i < srcLen; i++ {
1272 dsts[i] = normalizeNullValues(dsts[i], srcs[i], apply)
1273 }
1274
1275 if ty.IsTupleType() {
1276 return cty.TupleVal(dsts)
1277 }
1278 return cty.ListVal(dsts)
1279 }
1280
1281 case ty.IsPrimitiveType():
1282 if dst.IsNull() && src.IsWhollyKnown() && apply {
1283 return src
1284 }
1285 }
1286
1287 return dst
1288}
1289
1290// validateConfigNulls checks a config value for unsupported nulls before
1291// attempting to shim the value. While null values can mostly be ignored in the
1292// configuration, since they're not supported in HCL1, the case where a null
1293// appears in a list-like attribute (list, set, tuple) will present a nil value
1294// to helper/schema which can panic. Return an error to the user in this case,
1295// indicating the attribute with the null value.
1296func validateConfigNulls(v cty.Value, path cty.Path) []*proto.Diagnostic {
1297 var diags []*proto.Diagnostic
1298 if v.IsNull() || !v.IsKnown() {
1299 return diags
1300 }
1301
1302 switch {
1303 case v.Type().IsListType() || v.Type().IsSetType() || v.Type().IsTupleType():
1304 it := v.ElementIterator()
1305 for it.Next() {
1306 kv, ev := it.Element()
1307 if ev.IsNull() {
1308 diags = append(diags, &proto.Diagnostic{
1309 Severity: proto.Diagnostic_ERROR,
1310 Summary: "Null value found in list",
1311 Detail: "Null values are not allowed for this attribute value.",
1312 Attribute: convert.PathToAttributePath(append(path, cty.IndexStep{Key: kv})),
1313 })
1314 continue
1315 }
1316
1317 d := validateConfigNulls(ev, append(path, cty.IndexStep{Key: kv}))
1318 diags = convert.AppendProtoDiag(diags, d)
1319 }
1320
1321 case v.Type().IsMapType() || v.Type().IsObjectType():
1322 it := v.ElementIterator()
1323 for it.Next() {
1324 kv, ev := it.Element()
1325 var step cty.PathStep
1326 switch {
1327 case v.Type().IsMapType():
1328 step = cty.IndexStep{Key: kv}
1329 case v.Type().IsObjectType():
1330 step = cty.GetAttrStep{Name: kv.AsString()}
1331 }
1332 d := validateConfigNulls(ev, append(path, step))
1333 diags = convert.AppendProtoDiag(diags, d)
1334 }
1335 }
1336
1337 return diags
1338}
diff --git a/vendor/github.com/hashicorp/terraform/helper/plugin/grpc_provisioner.go b/vendor/github.com/hashicorp/terraform/helper/plugin/grpc_provisioner.go
new file mode 100644
index 0000000..14494e4
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/helper/plugin/grpc_provisioner.go
@@ -0,0 +1,147 @@
1package plugin
2
3import (
4 "log"
5
6 "github.com/hashicorp/terraform/helper/schema"
7 proto "github.com/hashicorp/terraform/internal/tfplugin5"
8 "github.com/hashicorp/terraform/plugin/convert"
9 "github.com/hashicorp/terraform/terraform"
10 "github.com/zclconf/go-cty/cty"
11 ctyconvert "github.com/zclconf/go-cty/cty/convert"
12 "github.com/zclconf/go-cty/cty/msgpack"
13 context "golang.org/x/net/context"
14)
15
16// NewGRPCProvisionerServerShim wraps a terraform.ResourceProvisioner in a
17// proto.ProvisionerServer implementation. If the provided provisioner is not a
18// *schema.Provisioner, this will return nil,
19func NewGRPCProvisionerServerShim(p terraform.ResourceProvisioner) *GRPCProvisionerServer {
20 sp, ok := p.(*schema.Provisioner)
21 if !ok {
22 return nil
23 }
24 return &GRPCProvisionerServer{
25 provisioner: sp,
26 }
27}
28
29type GRPCProvisionerServer struct {
30 provisioner *schema.Provisioner
31}
32
33func (s *GRPCProvisionerServer) GetSchema(_ context.Context, req *proto.GetProvisionerSchema_Request) (*proto.GetProvisionerSchema_Response, error) {
34 resp := &proto.GetProvisionerSchema_Response{}
35
36 resp.Provisioner = &proto.Schema{
37 Block: convert.ConfigSchemaToProto(schema.InternalMap(s.provisioner.Schema).CoreConfigSchema()),
38 }
39
40 return resp, nil
41}
42
43func (s *GRPCProvisionerServer) ValidateProvisionerConfig(_ context.Context, req *proto.ValidateProvisionerConfig_Request) (*proto.ValidateProvisionerConfig_Response, error) {
44 resp := &proto.ValidateProvisionerConfig_Response{}
45
46 cfgSchema := schema.InternalMap(s.provisioner.Schema).CoreConfigSchema()
47
48 configVal, err := msgpack.Unmarshal(req.Config.Msgpack, cfgSchema.ImpliedType())
49 if err != nil {
50 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, err)
51 return resp, nil
52 }
53
54 config := terraform.NewResourceConfigShimmed(configVal, cfgSchema)
55
56 warns, errs := s.provisioner.Validate(config)
57 resp.Diagnostics = convert.AppendProtoDiag(resp.Diagnostics, convert.WarnsAndErrsToProto(warns, errs))
58
59 return resp, nil
60}
61
62// stringMapFromValue converts a cty.Value to a map[stirng]string.
63// This will panic if the val is not a cty.Map(cty.String).
64func stringMapFromValue(val cty.Value) map[string]string {
65 m := map[string]string{}
66 if val.IsNull() || !val.IsKnown() {
67 return m
68 }
69
70 for it := val.ElementIterator(); it.Next(); {
71 ak, av := it.Element()
72 name := ak.AsString()
73
74 if !av.IsKnown() || av.IsNull() {
75 continue
76 }
77
78 av, _ = ctyconvert.Convert(av, cty.String)
79 m[name] = av.AsString()
80 }
81
82 return m
83}
84
85// uiOutput implements the terraform.UIOutput interface to adapt the grpc
86// stream to the legacy Provisioner.Apply method.
87type uiOutput struct {
88 srv proto.Provisioner_ProvisionResourceServer
89}
90
91func (o uiOutput) Output(s string) {
92 err := o.srv.Send(&proto.ProvisionResource_Response{
93 Output: s,
94 })
95 if err != nil {
96 log.Printf("[ERROR] %s", err)
97 }
98}
99
100func (s *GRPCProvisionerServer) ProvisionResource(req *proto.ProvisionResource_Request, srv proto.Provisioner_ProvisionResourceServer) error {
101 // We send back a diagnostics over the stream if there was a
102 // provisioner-side problem.
103 srvResp := &proto.ProvisionResource_Response{}
104
105 cfgSchema := schema.InternalMap(s.provisioner.Schema).CoreConfigSchema()
106 cfgVal, err := msgpack.Unmarshal(req.Config.Msgpack, cfgSchema.ImpliedType())
107 if err != nil {
108 srvResp.Diagnostics = convert.AppendProtoDiag(srvResp.Diagnostics, err)
109 srv.Send(srvResp)
110 return nil
111 }
112 resourceConfig := terraform.NewResourceConfigShimmed(cfgVal, cfgSchema)
113
114 connVal, err := msgpack.Unmarshal(req.Connection.Msgpack, cty.Map(cty.String))
115 if err != nil {
116 srvResp.Diagnostics = convert.AppendProtoDiag(srvResp.Diagnostics, err)
117 srv.Send(srvResp)
118 return nil
119 }
120
121 conn := stringMapFromValue(connVal)
122
123 instanceState := &terraform.InstanceState{
124 Ephemeral: terraform.EphemeralState{
125 ConnInfo: conn,
126 },
127 Meta: make(map[string]interface{}),
128 }
129
130 err = s.provisioner.Apply(uiOutput{srv}, instanceState, resourceConfig)
131 if err != nil {
132 srvResp.Diagnostics = convert.AppendProtoDiag(srvResp.Diagnostics, err)
133 srv.Send(srvResp)
134 }
135 return nil
136}
137
138func (s *GRPCProvisionerServer) Stop(_ context.Context, req *proto.Stop_Request) (*proto.Stop_Response, error) {
139 resp := &proto.Stop_Response{}
140
141 err := s.provisioner.Stop()
142 if err != nil {
143 resp.Error = err.Error()
144 }
145
146 return resp, nil
147}
diff --git a/vendor/github.com/hashicorp/terraform/helper/plugin/unknown.go b/vendor/github.com/hashicorp/terraform/helper/plugin/unknown.go
new file mode 100644
index 0000000..64a6784
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/helper/plugin/unknown.go
@@ -0,0 +1,131 @@
1package plugin
2
3import (
4 "fmt"
5
6 "github.com/hashicorp/terraform/configs/configschema"
7 "github.com/zclconf/go-cty/cty"
8)
9
10// SetUnknowns takes a cty.Value, and compares it to the schema setting any null
11// values which are computed to unknown.
12func SetUnknowns(val cty.Value, schema *configschema.Block) cty.Value {
13 if !val.IsKnown() {
14 return val
15 }
16
17 // If the object was null, we still need to handle the top level attributes
18 // which might be computed, but we don't need to expand the blocks.
19 if val.IsNull() {
20 objMap := map[string]cty.Value{}
21 allNull := true
22 for name, attr := range schema.Attributes {
23 switch {
24 case attr.Computed:
25 objMap[name] = cty.UnknownVal(attr.Type)
26 allNull = false
27 default:
28 objMap[name] = cty.NullVal(attr.Type)
29 }
30 }
31
32 // If this object has no unknown attributes, then we can leave it null.
33 if allNull {
34 return val
35 }
36
37 return cty.ObjectVal(objMap)
38 }
39
40 valMap := val.AsValueMap()
41 newVals := make(map[string]cty.Value)
42
43 for name, attr := range schema.Attributes {
44 v := valMap[name]
45
46 if attr.Computed && v.IsNull() {
47 newVals[name] = cty.UnknownVal(attr.Type)
48 continue
49 }
50
51 newVals[name] = v
52 }
53
54 for name, blockS := range schema.BlockTypes {
55 blockVal := valMap[name]
56 if blockVal.IsNull() || !blockVal.IsKnown() {
57 newVals[name] = blockVal
58 continue
59 }
60
61 blockValType := blockVal.Type()
62 blockElementType := blockS.Block.ImpliedType()
63
64 // This switches on the value type here, so we can correctly switch
65 // between Tuples/Lists and Maps/Objects.
66 switch {
67 case blockS.Nesting == configschema.NestingSingle || blockS.Nesting == configschema.NestingGroup:
68 // NestingSingle is the only exception here, where we treat the
69 // block directly as an object
70 newVals[name] = SetUnknowns(blockVal, &blockS.Block)
71
72 case blockValType.IsSetType(), blockValType.IsListType(), blockValType.IsTupleType():
73 listVals := blockVal.AsValueSlice()
74 newListVals := make([]cty.Value, 0, len(listVals))
75
76 for _, v := range listVals {
77 newListVals = append(newListVals, SetUnknowns(v, &blockS.Block))
78 }
79
80 switch {
81 case blockValType.IsSetType():
82 switch len(newListVals) {
83 case 0:
84 newVals[name] = cty.SetValEmpty(blockElementType)
85 default:
86 newVals[name] = cty.SetVal(newListVals)
87 }
88 case blockValType.IsListType():
89 switch len(newListVals) {
90 case 0:
91 newVals[name] = cty.ListValEmpty(blockElementType)
92 default:
93 newVals[name] = cty.ListVal(newListVals)
94 }
95 case blockValType.IsTupleType():
96 newVals[name] = cty.TupleVal(newListVals)
97 }
98
99 case blockValType.IsMapType(), blockValType.IsObjectType():
100 mapVals := blockVal.AsValueMap()
101 newMapVals := make(map[string]cty.Value)
102
103 for k, v := range mapVals {
104 newMapVals[k] = SetUnknowns(v, &blockS.Block)
105 }
106
107 switch {
108 case blockValType.IsMapType():
109 switch len(newMapVals) {
110 case 0:
111 newVals[name] = cty.MapValEmpty(blockElementType)
112 default:
113 newVals[name] = cty.MapVal(newMapVals)
114 }
115 case blockValType.IsObjectType():
116 if len(newMapVals) == 0 {
117 // We need to populate empty values to make a valid object.
118 for attr, ty := range blockElementType.AttributeTypes() {
119 newMapVals[attr] = cty.NullVal(ty)
120 }
121 }
122 newVals[name] = cty.ObjectVal(newMapVals)
123 }
124
125 default:
126 panic(fmt.Sprintf("failed to set unknown values for nested block %q:%#v", name, blockValType))
127 }
128 }
129
130 return cty.ObjectVal(newVals)
131}
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/grpc_test_provider.go b/vendor/github.com/hashicorp/terraform/helper/resource/grpc_test_provider.go
new file mode 100644
index 0000000..0742e99
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/helper/resource/grpc_test_provider.go
@@ -0,0 +1,43 @@
1package resource
2
3import (
4 "context"
5 "net"
6 "time"
7
8 "github.com/hashicorp/terraform/helper/plugin"
9 proto "github.com/hashicorp/terraform/internal/tfplugin5"
10 tfplugin "github.com/hashicorp/terraform/plugin"
11 "github.com/hashicorp/terraform/providers"
12 "github.com/hashicorp/terraform/terraform"
13 "google.golang.org/grpc"
14 "google.golang.org/grpc/test/bufconn"
15)
16
17// GRPCTestProvider takes a legacy ResourceProvider, wraps it in the new GRPC
18// shim and starts it in a grpc server using an inmem connection. It returns a
19// GRPCClient for this new server to test the shimmed resource provider.
20func GRPCTestProvider(rp terraform.ResourceProvider) providers.Interface {
21 listener := bufconn.Listen(256 * 1024)
22 grpcServer := grpc.NewServer()
23
24 p := plugin.NewGRPCProviderServerShim(rp)
25 proto.RegisterProviderServer(grpcServer, p)
26
27 go grpcServer.Serve(listener)
28
29 conn, err := grpc.Dial("", grpc.WithDialer(func(string, time.Duration) (net.Conn, error) {
30 return listener.Dial()
31 }), grpc.WithInsecure())
32 if err != nil {
33 panic(err)
34 }
35
36 var pp tfplugin.GRPCProviderPlugin
37 client, _ := pp.GRPCClient(context.Background(), nil, conn)
38
39 grpcClient := client.(*tfplugin.GRPCProvider)
40 grpcClient.TestServer = grpcServer
41
42 return grpcClient
43}
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/state.go b/vendor/github.com/hashicorp/terraform/helper/resource/state.go
index c34e21b..88a8396 100644
--- a/vendor/github.com/hashicorp/terraform/helper/resource/state.go
+++ b/vendor/github.com/hashicorp/terraform/helper/resource/state.go
@@ -38,7 +38,7 @@ type StateChangeConf struct {
38// specified in the configuration using the specified Refresh() func, 38// specified in the configuration using the specified Refresh() func,
39// waiting the number of seconds specified in the timeout configuration. 39// waiting the number of seconds specified in the timeout configuration.
40// 40//
41// If the Refresh function returns a error, exit immediately with that error. 41// If the Refresh function returns an error, exit immediately with that error.
42// 42//
43// If the Refresh function returns a state other than the Target state or one 43// If the Refresh function returns a state other than the Target state or one
44// listed in Pending, return immediately with an error. 44// listed in Pending, return immediately with an error.
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/state_shim.go b/vendor/github.com/hashicorp/terraform/helper/resource/state_shim.go
new file mode 100644
index 0000000..b2aff99
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/helper/resource/state_shim.go
@@ -0,0 +1,163 @@
1package resource
2
3import (
4 "fmt"
5
6 "github.com/hashicorp/terraform/addrs"
7 "github.com/zclconf/go-cty/cty"
8
9 "github.com/hashicorp/terraform/config/hcl2shim"
10 "github.com/hashicorp/terraform/helper/schema"
11
12 "github.com/hashicorp/terraform/states"
13 "github.com/hashicorp/terraform/terraform"
14)
15
16// shimState takes a new *states.State and reverts it to a legacy state for the provider ACC tests
17func shimNewState(newState *states.State, providers map[string]terraform.ResourceProvider) (*terraform.State, error) {
18 state := terraform.NewState()
19
20 // in the odd case of a nil state, let the helper packages handle it
21 if newState == nil {
22 return nil, nil
23 }
24
25 for _, newMod := range newState.Modules {
26 mod := state.AddModule(newMod.Addr)
27
28 for name, out := range newMod.OutputValues {
29 outputType := ""
30 val := hcl2shim.ConfigValueFromHCL2(out.Value)
31 ty := out.Value.Type()
32 switch {
33 case ty == cty.String:
34 outputType = "string"
35 case ty.IsTupleType() || ty.IsListType():
36 outputType = "list"
37 case ty.IsMapType():
38 outputType = "map"
39 }
40
41 mod.Outputs[name] = &terraform.OutputState{
42 Type: outputType,
43 Value: val,
44 Sensitive: out.Sensitive,
45 }
46 }
47
48 for _, res := range newMod.Resources {
49 resType := res.Addr.Type
50 providerType := res.ProviderConfig.ProviderConfig.Type
51
52 resource := getResource(providers, providerType, res.Addr)
53
54 for key, i := range res.Instances {
55 flatmap, err := shimmedAttributes(i.Current, resource)
56 if err != nil {
57 return nil, fmt.Errorf("error decoding state for %q: %s", resType, err)
58 }
59
60 resState := &terraform.ResourceState{
61 Type: resType,
62 Primary: &terraform.InstanceState{
63 ID: flatmap["id"],
64 Attributes: flatmap,
65 Tainted: i.Current.Status == states.ObjectTainted,
66 },
67 Provider: res.ProviderConfig.String(),
68 }
69 if i.Current.SchemaVersion != 0 {
70 resState.Primary.Meta = map[string]interface{}{
71 "schema_version": i.Current.SchemaVersion,
72 }
73 }
74
75 for _, dep := range i.Current.Dependencies {
76 resState.Dependencies = append(resState.Dependencies, dep.String())
77 }
78
79 // convert the indexes to the old style flapmap indexes
80 idx := ""
81 switch key.(type) {
82 case addrs.IntKey:
83 // don't add numeric index values to resources with a count of 0
84 if len(res.Instances) > 1 {
85 idx = fmt.Sprintf(".%d", key)
86 }
87 case addrs.StringKey:
88 idx = "." + key.String()
89 }
90
91 mod.Resources[res.Addr.String()+idx] = resState
92
93 // add any deposed instances
94 for _, dep := range i.Deposed {
95 flatmap, err := shimmedAttributes(dep, resource)
96 if err != nil {
97 return nil, fmt.Errorf("error decoding deposed state for %q: %s", resType, err)
98 }
99
100 deposed := &terraform.InstanceState{
101 ID: flatmap["id"],
102 Attributes: flatmap,
103 Tainted: dep.Status == states.ObjectTainted,
104 }
105 if dep.SchemaVersion != 0 {
106 deposed.Meta = map[string]interface{}{
107 "schema_version": dep.SchemaVersion,
108 }
109 }
110
111 resState.Deposed = append(resState.Deposed, deposed)
112 }
113 }
114 }
115 }
116
117 return state, nil
118}
119
120func getResource(providers map[string]terraform.ResourceProvider, providerName string, addr addrs.Resource) *schema.Resource {
121 p := providers[providerName]
122 if p == nil {
123 panic(fmt.Sprintf("provider %q not found in test step", providerName))
124 }
125
126 // this is only for tests, so should only see schema.Providers
127 provider := p.(*schema.Provider)
128
129 switch addr.Mode {
130 case addrs.ManagedResourceMode:
131 resource := provider.ResourcesMap[addr.Type]
132 if resource != nil {
133 return resource
134 }
135 case addrs.DataResourceMode:
136 resource := provider.DataSourcesMap[addr.Type]
137 if resource != nil {
138 return resource
139 }
140 }
141
142 panic(fmt.Sprintf("resource %s not found in test step", addr.Type))
143}
144
145func shimmedAttributes(instance *states.ResourceInstanceObjectSrc, res *schema.Resource) (map[string]string, error) {
146 flatmap := instance.AttrsFlat
147 if flatmap != nil {
148 return flatmap, nil
149 }
150
151 // if we have json attrs, they need to be decoded
152 rio, err := instance.Decode(res.CoreConfigSchema().ImpliedType())
153 if err != nil {
154 return nil, err
155 }
156
157 instanceState, err := res.ShimInstanceStateFromValue(rio.Value)
158 if err != nil {
159 return nil, err
160 }
161
162 return instanceState.Attributes, nil
163}
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/testing.go b/vendor/github.com/hashicorp/terraform/helper/resource/testing.go
index b97673f..aa7454d 100644
--- a/vendor/github.com/hashicorp/terraform/helper/resource/testing.go
+++ b/vendor/github.com/hashicorp/terraform/helper/resource/testing.go
@@ -1,6 +1,7 @@
1package resource 1package resource
2 2
3import ( 3import (
4 "bytes"
4 "flag" 5 "flag"
5 "fmt" 6 "fmt"
6 "io" 7 "io"
@@ -18,9 +19,18 @@ import (
18 "github.com/hashicorp/errwrap" 19 "github.com/hashicorp/errwrap"
19 "github.com/hashicorp/go-multierror" 20 "github.com/hashicorp/go-multierror"
20 "github.com/hashicorp/logutils" 21 "github.com/hashicorp/logutils"
21 "github.com/hashicorp/terraform/config/module" 22 "github.com/mitchellh/colorstring"
23
24 "github.com/hashicorp/terraform/addrs"
25 "github.com/hashicorp/terraform/command/format"
26 "github.com/hashicorp/terraform/configs"
27 "github.com/hashicorp/terraform/configs/configload"
22 "github.com/hashicorp/terraform/helper/logging" 28 "github.com/hashicorp/terraform/helper/logging"
29 "github.com/hashicorp/terraform/internal/initwd"
30 "github.com/hashicorp/terraform/providers"
31 "github.com/hashicorp/terraform/states"
23 "github.com/hashicorp/terraform/terraform" 32 "github.com/hashicorp/terraform/terraform"
33 "github.com/hashicorp/terraform/tfdiags"
24) 34)
25 35
26// flagSweep is a flag available when running tests on the command line. It 36// flagSweep is a flag available when running tests on the command line. It
@@ -373,6 +383,10 @@ type TestStep struct {
373 // be refreshed and don't matter. 383 // be refreshed and don't matter.
374 ImportStateVerify bool 384 ImportStateVerify bool
375 ImportStateVerifyIgnore []string 385 ImportStateVerifyIgnore []string
386
387 // provider s is used internally to maintain a reference to the
388 // underlying providers during the tests
389 providers map[string]terraform.ResourceProvider
376} 390}
377 391
378// Set to a file mask in sprintf format where %s is test name 392// Set to a file mask in sprintf format where %s is test name
@@ -467,10 +481,22 @@ func Test(t TestT, c TestCase) {
467 c.PreCheck() 481 c.PreCheck()
468 } 482 }
469 483
484 // get instances of all providers, so we can use the individual
485 // resources to shim the state during the tests.
486 providers := make(map[string]terraform.ResourceProvider)
487 for name, pf := range testProviderFactories(c) {
488 p, err := pf()
489 if err != nil {
490 t.Fatal(err)
491 }
492 providers[name] = p
493 }
494
470 providerResolver, err := testProviderResolver(c) 495 providerResolver, err := testProviderResolver(c)
471 if err != nil { 496 if err != nil {
472 t.Fatal(err) 497 t.Fatal(err)
473 } 498 }
499
474 opts := terraform.ContextOpts{ProviderResolver: providerResolver} 500 opts := terraform.ContextOpts{ProviderResolver: providerResolver}
475 501
476 // A single state variable to track the lifecycle, starting with no state 502 // A single state variable to track the lifecycle, starting with no state
@@ -481,6 +507,10 @@ func Test(t TestT, c TestCase) {
481 idRefresh := c.IDRefreshName != "" 507 idRefresh := c.IDRefreshName != ""
482 errored := false 508 errored := false
483 for i, step := range c.Steps { 509 for i, step := range c.Steps {
510 // insert the providers into the step so we can get the resources for
511 // shimming the state
512 step.providers = providers
513
484 var err error 514 var err error
485 log.Printf("[DEBUG] Test: Executing step %d", i) 515 log.Printf("[DEBUG] Test: Executing step %d", i)
486 516
@@ -535,8 +565,7 @@ func Test(t TestT, c TestCase) {
535 } 565 }
536 } else { 566 } else {
537 errored = true 567 errored = true
538 t.Error(fmt.Sprintf( 568 t.Error(fmt.Sprintf("Step %d error: %s", i, detailedErrorMessage(err)))
539 "Step %d error: %s", i, err))
540 break 569 break
541 } 570 }
542 } 571 }
@@ -591,6 +620,7 @@ func Test(t TestT, c TestCase) {
591 Destroy: true, 620 Destroy: true,
592 PreventDiskCleanup: lastStep.PreventDiskCleanup, 621 PreventDiskCleanup: lastStep.PreventDiskCleanup,
593 PreventPostDestroyRefresh: c.PreventPostDestroyRefresh, 622 PreventPostDestroyRefresh: c.PreventPostDestroyRefresh,
623 providers: providers,
594 } 624 }
595 625
596 log.Printf("[WARN] Test: Executing destroy step") 626 log.Printf("[WARN] Test: Executing destroy step")
@@ -620,39 +650,50 @@ func testProviderConfig(c TestCase) string {
620 return strings.Join(lines, "") 650 return strings.Join(lines, "")
621} 651}
622 652
623// testProviderResolver is a helper to build a ResourceProviderResolver 653// testProviderFactories combines the fixed Providers and
624// with pre instantiated ResourceProviders, so that we can reset them for the 654// ResourceProviderFactory functions into a single map of
625// test, while only calling the factory function once. 655// ResourceProviderFactory functions.
626// Any errors are stored so that they can be returned by the factory in 656func testProviderFactories(c TestCase) map[string]terraform.ResourceProviderFactory {
627// terraform to match non-test behavior. 657 ctxProviders := make(map[string]terraform.ResourceProviderFactory)
628func testProviderResolver(c TestCase) (terraform.ResourceProviderResolver, error) { 658 for k, pf := range c.ProviderFactories {
629 ctxProviders := c.ProviderFactories 659 ctxProviders[k] = pf
630 if ctxProviders == nil {
631 ctxProviders = make(map[string]terraform.ResourceProviderFactory)
632 } 660 }
633 661
634 // add any fixed providers 662 // add any fixed providers
635 for k, p := range c.Providers { 663 for k, p := range c.Providers {
636 ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) 664 ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p)
637 } 665 }
666 return ctxProviders
667}
668
669// testProviderResolver is a helper to build a ResourceProviderResolver
670// with pre instantiated ResourceProviders, so that we can reset them for the
671// test, while only calling the factory function once.
672// Any errors are stored so that they can be returned by the factory in
673// terraform to match non-test behavior.
674func testProviderResolver(c TestCase) (providers.Resolver, error) {
675 ctxProviders := testProviderFactories(c)
676
677 // wrap the old provider factories in the test grpc server so they can be
678 // called from terraform.
679 newProviders := make(map[string]providers.Factory)
638 680
639 // reset the providers if needed
640 for k, pf := range ctxProviders { 681 for k, pf := range ctxProviders {
641 // we can ignore any errors here, if we don't have a provider to reset 682 factory := pf // must copy to ensure each closure sees its own value
642 // the error will be handled later 683 newProviders[k] = func() (providers.Interface, error) {
643 p, err := pf() 684 p, err := factory()
644 if err != nil {
645 return nil, err
646 }
647 if p, ok := p.(TestProvider); ok {
648 err := p.TestReset()
649 if err != nil { 685 if err != nil {
650 return nil, fmt.Errorf("[ERROR] failed to reset provider %q: %s", k, err) 686 return nil, err
651 } 687 }
688
689 // The provider is wrapped in a GRPCTestProvider so that it can be
690 // passed back to terraform core as a providers.Interface, rather
691 // than the legacy ResourceProvider.
692 return GRPCTestProvider(p), nil
652 } 693 }
653 } 694 }
654 695
655 return terraform.ResourceProviderResolverFixed(ctxProviders), nil 696 return providers.ResolverFixed(newProviders), nil
656} 697}
657 698
658// UnitTest is a helper to force the acceptance testing harness to run in the 699// UnitTest is a helper to force the acceptance testing harness to run in the
@@ -670,33 +711,40 @@ func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r
670 return nil 711 return nil
671 } 712 }
672 713
673 name := fmt.Sprintf("%s.foo", r.Type) 714 addr := addrs.Resource{
715 Mode: addrs.ManagedResourceMode,
716 Type: r.Type,
717 Name: "foo",
718 }.Instance(addrs.NoKey)
719 absAddr := addr.Absolute(addrs.RootModuleInstance)
674 720
675 // Build the state. The state is just the resource with an ID. There 721 // Build the state. The state is just the resource with an ID. There
676 // are no attributes. We only set what is needed to perform a refresh. 722 // are no attributes. We only set what is needed to perform a refresh.
677 state := terraform.NewState() 723 state := states.NewState()
678 state.RootModule().Resources[name] = &terraform.ResourceState{ 724 state.RootModule().SetResourceInstanceCurrent(
679 Type: r.Type, 725 addr,
680 Primary: &terraform.InstanceState{ 726 &states.ResourceInstanceObjectSrc{
681 ID: r.Primary.ID, 727 AttrsFlat: r.Primary.Attributes,
728 Status: states.ObjectReady,
682 }, 729 },
683 } 730 addrs.ProviderConfig{Type: "placeholder"}.Absolute(addrs.RootModuleInstance),
731 )
684 732
685 // Create the config module. We use the full config because Refresh 733 // Create the config module. We use the full config because Refresh
686 // doesn't have access to it and we may need things like provider 734 // doesn't have access to it and we may need things like provider
687 // configurations. The initial implementation of id-only checks used 735 // configurations. The initial implementation of id-only checks used
688 // an empty config module, but that caused the aforementioned problems. 736 // an empty config module, but that caused the aforementioned problems.
689 mod, err := testModule(opts, step) 737 cfg, err := testConfig(opts, step)
690 if err != nil { 738 if err != nil {
691 return err 739 return err
692 } 740 }
693 741
694 // Initialize the context 742 // Initialize the context
695 opts.Module = mod 743 opts.Config = cfg
696 opts.State = state 744 opts.State = state
697 ctx, err := terraform.NewContext(&opts) 745 ctx, ctxDiags := terraform.NewContext(&opts)
698 if err != nil { 746 if ctxDiags.HasErrors() {
699 return err 747 return ctxDiags.Err()
700 } 748 }
701 if diags := ctx.Validate(); len(diags) > 0 { 749 if diags := ctx.Validate(); len(diags) > 0 {
702 if diags.HasErrors() { 750 if diags.HasErrors() {
@@ -707,20 +755,20 @@ func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r
707 } 755 }
708 756
709 // Refresh! 757 // Refresh!
710 state, err = ctx.Refresh() 758 state, refreshDiags := ctx.Refresh()
711 if err != nil { 759 if refreshDiags.HasErrors() {
712 return fmt.Errorf("Error refreshing: %s", err) 760 return refreshDiags.Err()
713 } 761 }
714 762
715 // Verify attribute equivalence. 763 // Verify attribute equivalence.
716 actualR := state.RootModule().Resources[name] 764 actualR := state.ResourceInstance(absAddr)
717 if actualR == nil { 765 if actualR == nil {
718 return fmt.Errorf("Resource gone!") 766 return fmt.Errorf("Resource gone!")
719 } 767 }
720 if actualR.Primary == nil { 768 if actualR.Current == nil {
721 return fmt.Errorf("Resource has no primary instance") 769 return fmt.Errorf("Resource has no primary instance")
722 } 770 }
723 actual := actualR.Primary.Attributes 771 actual := actualR.Current.AttrsFlat
724 expected := r.Primary.Attributes 772 expected := r.Primary.Attributes
725 // Remove fields we're ignoring 773 // Remove fields we're ignoring
726 for _, v := range c.IDRefreshIgnore { 774 for _, v := range c.IDRefreshIgnore {
@@ -756,15 +804,14 @@ func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r
756 return nil 804 return nil
757} 805}
758 806
759func testModule(opts terraform.ContextOpts, step TestStep) (*module.Tree, error) { 807func testConfig(opts terraform.ContextOpts, step TestStep) (*configs.Config, error) {
760 if step.PreConfig != nil { 808 if step.PreConfig != nil {
761 step.PreConfig() 809 step.PreConfig()
762 } 810 }
763 811
764 cfgPath, err := ioutil.TempDir("", "tf-test") 812 cfgPath, err := ioutil.TempDir("", "tf-test")
765 if err != nil { 813 if err != nil {
766 return nil, fmt.Errorf( 814 return nil, fmt.Errorf("Error creating temporary directory for config: %s", err)
767 "Error creating temporary directory for config: %s", err)
768 } 815 }
769 816
770 if step.PreventDiskCleanup { 817 if step.PreventDiskCleanup {
@@ -773,38 +820,38 @@ func testModule(opts terraform.ContextOpts, step TestStep) (*module.Tree, error)
773 defer os.RemoveAll(cfgPath) 820 defer os.RemoveAll(cfgPath)
774 } 821 }
775 822
776 // Write the configuration 823 // Write the main configuration file
777 cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf")) 824 err = ioutil.WriteFile(filepath.Join(cfgPath, "main.tf"), []byte(step.Config), os.ModePerm)
778 if err != nil { 825 if err != nil {
779 return nil, fmt.Errorf( 826 return nil, fmt.Errorf("Error creating temporary file for config: %s", err)
780 "Error creating temporary file for config: %s", err)
781 } 827 }
782 828
783 _, err = io.Copy(cfgF, strings.NewReader(step.Config)) 829 // Create directory for our child modules, if any.
784 cfgF.Close() 830 modulesDir := filepath.Join(cfgPath, ".modules")
831 err = os.Mkdir(modulesDir, os.ModePerm)
785 if err != nil { 832 if err != nil {
786 return nil, fmt.Errorf( 833 return nil, fmt.Errorf("Error creating child modules directory: %s", err)
787 "Error creating temporary file for config: %s", err)
788 } 834 }
789 835
790 // Parse the configuration 836 inst := initwd.NewModuleInstaller(modulesDir, nil)
791 mod, err := module.NewTreeModule("", cfgPath) 837 _, installDiags := inst.InstallModules(cfgPath, true, initwd.ModuleInstallHooksImpl{})
792 if err != nil { 838 if installDiags.HasErrors() {
793 return nil, fmt.Errorf( 839 return nil, installDiags.Err()
794 "Error loading configuration: %s", err)
795 } 840 }
796 841
797 // Load the modules 842 loader, err := configload.NewLoader(&configload.Config{
798 modStorage := &module.Storage{ 843 ModulesDir: modulesDir,
799 StorageDir: filepath.Join(cfgPath, ".tfmodules"), 844 })
800 Mode: module.GetModeGet,
801 }
802 err = mod.Load(modStorage)
803 if err != nil { 845 if err != nil {
804 return nil, fmt.Errorf("Error downloading modules: %s", err) 846 return nil, fmt.Errorf("failed to create config loader: %s", err)
847 }
848
849 config, configDiags := loader.LoadConfig(cfgPath)
850 if configDiags.HasErrors() {
851 return nil, configDiags
805 } 852 }
806 853
807 return mod, nil 854 return config, nil
808} 855}
809 856
810func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) { 857func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) {
@@ -881,8 +928,9 @@ func TestCheckResourceAttrSet(name, key string) TestCheckFunc {
881// TestCheckModuleResourceAttrSet - as per TestCheckResourceAttrSet but with 928// TestCheckModuleResourceAttrSet - as per TestCheckResourceAttrSet but with
882// support for non-root modules 929// support for non-root modules
883func TestCheckModuleResourceAttrSet(mp []string, name string, key string) TestCheckFunc { 930func TestCheckModuleResourceAttrSet(mp []string, name string, key string) TestCheckFunc {
931 mpt := addrs.Module(mp).UnkeyedInstanceShim()
884 return func(s *terraform.State) error { 932 return func(s *terraform.State) error {
885 is, err := modulePathPrimaryInstanceState(s, mp, name) 933 is, err := modulePathPrimaryInstanceState(s, mpt, name)
886 if err != nil { 934 if err != nil {
887 return err 935 return err
888 } 936 }
@@ -915,8 +963,9 @@ func TestCheckResourceAttr(name, key, value string) TestCheckFunc {
915// TestCheckModuleResourceAttr - as per TestCheckResourceAttr but with 963// TestCheckModuleResourceAttr - as per TestCheckResourceAttr but with
916// support for non-root modules 964// support for non-root modules
917func TestCheckModuleResourceAttr(mp []string, name string, key string, value string) TestCheckFunc { 965func TestCheckModuleResourceAttr(mp []string, name string, key string, value string) TestCheckFunc {
966 mpt := addrs.Module(mp).UnkeyedInstanceShim()
918 return func(s *terraform.State) error { 967 return func(s *terraform.State) error {
919 is, err := modulePathPrimaryInstanceState(s, mp, name) 968 is, err := modulePathPrimaryInstanceState(s, mpt, name)
920 if err != nil { 969 if err != nil {
921 return err 970 return err
922 } 971 }
@@ -926,7 +975,19 @@ func TestCheckModuleResourceAttr(mp []string, name string, key string, value str
926} 975}
927 976
928func testCheckResourceAttr(is *terraform.InstanceState, name string, key string, value string) error { 977func testCheckResourceAttr(is *terraform.InstanceState, name string, key string, value string) error {
978 // Empty containers may be elided from the state.
979 // If the intent here is to check for an empty container, allow the key to
980 // also be non-existent.
981 emptyCheck := false
982 if value == "0" && (strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%")) {
983 emptyCheck = true
984 }
985
929 if v, ok := is.Attributes[key]; !ok || v != value { 986 if v, ok := is.Attributes[key]; !ok || v != value {
987 if emptyCheck && !ok {
988 return nil
989 }
990
930 if !ok { 991 if !ok {
931 return fmt.Errorf("%s: Attribute '%s' not found", name, key) 992 return fmt.Errorf("%s: Attribute '%s' not found", name, key)
932 } 993 }
@@ -957,8 +1018,9 @@ func TestCheckNoResourceAttr(name, key string) TestCheckFunc {
957// TestCheckModuleNoResourceAttr - as per TestCheckNoResourceAttr but with 1018// TestCheckModuleNoResourceAttr - as per TestCheckNoResourceAttr but with
958// support for non-root modules 1019// support for non-root modules
959func TestCheckModuleNoResourceAttr(mp []string, name string, key string) TestCheckFunc { 1020func TestCheckModuleNoResourceAttr(mp []string, name string, key string) TestCheckFunc {
1021 mpt := addrs.Module(mp).UnkeyedInstanceShim()
960 return func(s *terraform.State) error { 1022 return func(s *terraform.State) error {
961 is, err := modulePathPrimaryInstanceState(s, mp, name) 1023 is, err := modulePathPrimaryInstanceState(s, mpt, name)
962 if err != nil { 1024 if err != nil {
963 return err 1025 return err
964 } 1026 }
@@ -968,7 +1030,20 @@ func TestCheckModuleNoResourceAttr(mp []string, name string, key string) TestChe
968} 1030}
969 1031
970func testCheckNoResourceAttr(is *terraform.InstanceState, name string, key string) error { 1032func testCheckNoResourceAttr(is *terraform.InstanceState, name string, key string) error {
971 if _, ok := is.Attributes[key]; ok { 1033 // Empty containers may sometimes be included in the state.
1034 // If the intent here is to check for an empty container, allow the value to
1035 // also be "0".
1036 emptyCheck := false
1037 if strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%") {
1038 emptyCheck = true
1039 }
1040
1041 val, exists := is.Attributes[key]
1042 if emptyCheck && val == "0" {
1043 return nil
1044 }
1045
1046 if exists {
972 return fmt.Errorf("%s: Attribute '%s' found when not expected", name, key) 1047 return fmt.Errorf("%s: Attribute '%s' found when not expected", name, key)
973 } 1048 }
974 1049
@@ -991,8 +1066,9 @@ func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc {
991// TestModuleMatchResourceAttr - as per TestMatchResourceAttr but with 1066// TestModuleMatchResourceAttr - as per TestMatchResourceAttr but with
992// support for non-root modules 1067// support for non-root modules
993func TestModuleMatchResourceAttr(mp []string, name string, key string, r *regexp.Regexp) TestCheckFunc { 1068func TestModuleMatchResourceAttr(mp []string, name string, key string, r *regexp.Regexp) TestCheckFunc {
1069 mpt := addrs.Module(mp).UnkeyedInstanceShim()
994 return func(s *terraform.State) error { 1070 return func(s *terraform.State) error {
995 is, err := modulePathPrimaryInstanceState(s, mp, name) 1071 is, err := modulePathPrimaryInstanceState(s, mpt, name)
996 if err != nil { 1072 if err != nil {
997 return err 1073 return err
998 } 1074 }
@@ -1052,13 +1128,15 @@ func TestCheckResourceAttrPair(nameFirst, keyFirst, nameSecond, keySecond string
1052// TestCheckModuleResourceAttrPair - as per TestCheckResourceAttrPair but with 1128// TestCheckModuleResourceAttrPair - as per TestCheckResourceAttrPair but with
1053// support for non-root modules 1129// support for non-root modules
1054func TestCheckModuleResourceAttrPair(mpFirst []string, nameFirst string, keyFirst string, mpSecond []string, nameSecond string, keySecond string) TestCheckFunc { 1130func TestCheckModuleResourceAttrPair(mpFirst []string, nameFirst string, keyFirst string, mpSecond []string, nameSecond string, keySecond string) TestCheckFunc {
1131 mptFirst := addrs.Module(mpFirst).UnkeyedInstanceShim()
1132 mptSecond := addrs.Module(mpSecond).UnkeyedInstanceShim()
1055 return func(s *terraform.State) error { 1133 return func(s *terraform.State) error {
1056 isFirst, err := modulePathPrimaryInstanceState(s, mpFirst, nameFirst) 1134 isFirst, err := modulePathPrimaryInstanceState(s, mptFirst, nameFirst)
1057 if err != nil { 1135 if err != nil {
1058 return err 1136 return err
1059 } 1137 }
1060 1138
1061 isSecond, err := modulePathPrimaryInstanceState(s, mpSecond, nameSecond) 1139 isSecond, err := modulePathPrimaryInstanceState(s, mptSecond, nameSecond)
1062 if err != nil { 1140 if err != nil {
1063 return err 1141 return err
1064 } 1142 }
@@ -1068,14 +1146,32 @@ func TestCheckModuleResourceAttrPair(mpFirst []string, nameFirst string, keyFirs
1068} 1146}
1069 1147
1070func testCheckResourceAttrPair(isFirst *terraform.InstanceState, nameFirst string, keyFirst string, isSecond *terraform.InstanceState, nameSecond string, keySecond string) error { 1148func testCheckResourceAttrPair(isFirst *terraform.InstanceState, nameFirst string, keyFirst string, isSecond *terraform.InstanceState, nameSecond string, keySecond string) error {
1071 vFirst, ok := isFirst.Attributes[keyFirst] 1149 vFirst, okFirst := isFirst.Attributes[keyFirst]
1072 if !ok { 1150 vSecond, okSecond := isSecond.Attributes[keySecond]
1073 return fmt.Errorf("%s: Attribute '%s' not found", nameFirst, keyFirst) 1151
1152 // Container count values of 0 should not be relied upon, and not reliably
1153 // maintained by helper/schema. For the purpose of tests, consider unset and
1154 // 0 to be equal.
1155 if len(keyFirst) > 2 && len(keySecond) > 2 && keyFirst[len(keyFirst)-2:] == keySecond[len(keySecond)-2:] &&
1156 (strings.HasSuffix(keyFirst, ".#") || strings.HasSuffix(keyFirst, ".%")) {
1157 // they have the same suffix, and it is a collection count key.
1158 if vFirst == "0" || vFirst == "" {
1159 okFirst = false
1160 }
1161 if vSecond == "0" || vSecond == "" {
1162 okSecond = false
1163 }
1074 } 1164 }
1075 1165
1076 vSecond, ok := isSecond.Attributes[keySecond] 1166 if okFirst != okSecond {
1077 if !ok { 1167 if !okFirst {
1078 return fmt.Errorf("%s: Attribute '%s' not found", nameSecond, keySecond) 1168 return fmt.Errorf("%s: Attribute %q not set, but %q is set in %s as %q", nameFirst, keyFirst, keySecond, nameSecond, vSecond)
1169 }
1170 return fmt.Errorf("%s: Attribute %q is %q, but %q is not set in %s", nameFirst, keyFirst, vFirst, keySecond, nameSecond)
1171 }
1172 if !(okFirst || okSecond) {
1173 // If they both don't exist then they are equally unset, so that's okay.
1174 return nil
1079 } 1175 }
1080 1176
1081 if vFirst != vSecond { 1177 if vFirst != vSecond {
@@ -1163,7 +1259,7 @@ func modulePrimaryInstanceState(s *terraform.State, ms *terraform.ModuleState, n
1163 1259
1164// modulePathPrimaryInstanceState returns the primary instance state for the 1260// modulePathPrimaryInstanceState returns the primary instance state for the
1165// given resource name in a given module path. 1261// given resource name in a given module path.
1166func modulePathPrimaryInstanceState(s *terraform.State, mp []string, name string) (*terraform.InstanceState, error) { 1262func modulePathPrimaryInstanceState(s *terraform.State, mp addrs.ModuleInstance, name string) (*terraform.InstanceState, error) {
1167 ms := s.ModuleByPath(mp) 1263 ms := s.ModuleByPath(mp)
1168 if ms == nil { 1264 if ms == nil {
1169 return nil, fmt.Errorf("No module found at: %s", mp) 1265 return nil, fmt.Errorf("No module found at: %s", mp)
@@ -1178,3 +1274,47 @@ func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceS
1178 ms := s.RootModule() 1274 ms := s.RootModule()
1179 return modulePrimaryInstanceState(s, ms, name) 1275 return modulePrimaryInstanceState(s, ms, name)
1180} 1276}
1277
1278// operationError is a specialized implementation of error used to describe
1279// failures during one of the several operations performed for a particular
1280// test case.
1281type operationError struct {
1282 OpName string
1283 Diags tfdiags.Diagnostics
1284}
1285
1286func newOperationError(opName string, diags tfdiags.Diagnostics) error {
1287 return operationError{opName, diags}
1288}
1289
1290// Error returns a terse error string containing just the basic diagnostic
1291// messages, for situations where normal Go error behavior is appropriate.
1292func (err operationError) Error() string {
1293 return fmt.Sprintf("errors during %s: %s", err.OpName, err.Diags.Err().Error())
1294}
1295
1296// ErrorDetail is like Error except it includes verbosely-rendered diagnostics
1297// similar to what would come from a normal Terraform run, which include
1298// additional context not included in Error().
1299func (err operationError) ErrorDetail() string {
1300 var buf bytes.Buffer
1301 fmt.Fprintf(&buf, "errors during %s:", err.OpName)
1302 clr := &colorstring.Colorize{Disable: true, Colors: colorstring.DefaultColors}
1303 for _, diag := range err.Diags {
1304 diagStr := format.Diagnostic(diag, nil, clr, 78)
1305 buf.WriteByte('\n')
1306 buf.WriteString(diagStr)
1307 }
1308 return buf.String()
1309}
1310
1311// detailedErrorMessage is a helper for calling ErrorDetail on an error if
1312// it is an operationError or just taking Error otherwise.
1313func detailedErrorMessage(err error) string {
1314 switch tErr := err.(type) {
1315 case operationError:
1316 return tErr.ErrorDetail()
1317 default:
1318 return err.Error()
1319 }
1320}
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go b/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go
index 033f126..311fdb6 100644
--- a/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go
+++ b/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go
@@ -1,13 +1,23 @@
1package resource 1package resource
2 2
3import ( 3import (
4 "bufio"
5 "bytes"
4 "errors" 6 "errors"
5 "fmt" 7 "fmt"
6 "log" 8 "log"
9 "sort"
7 "strings" 10 "strings"
8 11
12 "github.com/hashicorp/terraform/addrs"
13 "github.com/hashicorp/terraform/config"
14 "github.com/hashicorp/terraform/config/hcl2shim"
15 "github.com/hashicorp/terraform/states"
16
9 "github.com/hashicorp/errwrap" 17 "github.com/hashicorp/errwrap"
18 "github.com/hashicorp/terraform/plans"
10 "github.com/hashicorp/terraform/terraform" 19 "github.com/hashicorp/terraform/terraform"
20 "github.com/hashicorp/terraform/tfdiags"
11) 21)
12 22
13// testStepConfig runs a config-mode test step 23// testStepConfig runs a config-mode test step
@@ -18,69 +28,79 @@ func testStepConfig(
18 return testStep(opts, state, step) 28 return testStep(opts, state, step)
19} 29}
20 30
21func testStep( 31func testStep(opts terraform.ContextOpts, state *terraform.State, step TestStep) (*terraform.State, error) {
22 opts terraform.ContextOpts,
23 state *terraform.State,
24 step TestStep) (*terraform.State, error) {
25 // Pre-taint any resources that have been defined in Taint, as long as this
26 // is not a destroy step.
27 if !step.Destroy { 32 if !step.Destroy {
28 if err := testStepTaint(state, step); err != nil { 33 if err := testStepTaint(state, step); err != nil {
29 return state, err 34 return state, err
30 } 35 }
31 } 36 }
32 37
33 mod, err := testModule(opts, step) 38 cfg, err := testConfig(opts, step)
34 if err != nil { 39 if err != nil {
35 return state, err 40 return state, err
36 } 41 }
37 42
43 var stepDiags tfdiags.Diagnostics
44
38 // Build the context 45 // Build the context
39 opts.Module = mod 46 opts.Config = cfg
40 opts.State = state 47 opts.State, err = terraform.ShimLegacyState(state)
41 opts.Destroy = step.Destroy
42 ctx, err := terraform.NewContext(&opts)
43 if err != nil { 48 if err != nil {
44 return state, fmt.Errorf("Error initializing context: %s", err) 49 return nil, err
50 }
51
52 opts.Destroy = step.Destroy
53 ctx, stepDiags := terraform.NewContext(&opts)
54 if stepDiags.HasErrors() {
55 return state, fmt.Errorf("Error initializing context: %s", stepDiags.Err())
45 } 56 }
46 if diags := ctx.Validate(); len(diags) > 0 { 57 if stepDiags := ctx.Validate(); len(stepDiags) > 0 {
47 if diags.HasErrors() { 58 if stepDiags.HasErrors() {
48 return nil, errwrap.Wrapf("config is invalid: {{err}}", diags.Err()) 59 return state, errwrap.Wrapf("config is invalid: {{err}}", stepDiags.Err())
49 } 60 }
50 61
51 log.Printf("[WARN] Config warnings:\n%s", diags) 62 log.Printf("[WARN] Config warnings:\n%s", stepDiags)
52 } 63 }
53 64
54 // Refresh! 65 // Refresh!
55 state, err = ctx.Refresh() 66 newState, stepDiags := ctx.Refresh()
67 // shim the state first so the test can check the state on errors
68
69 state, err = shimNewState(newState, step.providers)
56 if err != nil { 70 if err != nil {
57 return state, fmt.Errorf( 71 return nil, err
58 "Error refreshing: %s", err) 72 }
73 if stepDiags.HasErrors() {
74 return state, newOperationError("refresh", stepDiags)
59 } 75 }
60 76
61 // If this step is a PlanOnly step, skip over this first Plan and subsequent 77 // If this step is a PlanOnly step, skip over this first Plan and subsequent
62 // Apply, and use the follow up Plan that checks for perpetual diffs 78 // Apply, and use the follow up Plan that checks for perpetual diffs
63 if !step.PlanOnly { 79 if !step.PlanOnly {
64 // Plan! 80 // Plan!
65 if p, err := ctx.Plan(); err != nil { 81 if p, stepDiags := ctx.Plan(); stepDiags.HasErrors() {
66 return state, fmt.Errorf( 82 return state, newOperationError("plan", stepDiags)
67 "Error planning: %s", err)
68 } else { 83 } else {
69 log.Printf("[WARN] Test: Step plan: %s", p) 84 log.Printf("[WARN] Test: Step plan: %s", legacyPlanComparisonString(newState, p.Changes))
70 } 85 }
71 86
72 // We need to keep a copy of the state prior to destroying 87 // We need to keep a copy of the state prior to destroying
73 // such that destroy steps can verify their behaviour in the check 88 // such that destroy steps can verify their behavior in the check
74 // function 89 // function
75 stateBeforeApplication := state.DeepCopy() 90 stateBeforeApplication := state.DeepCopy()
76 91
77 // Apply! 92 // Apply the diff, creating real resources.
78 state, err = ctx.Apply() 93 newState, stepDiags = ctx.Apply()
94 // shim the state first so the test can check the state on errors
95 state, err = shimNewState(newState, step.providers)
79 if err != nil { 96 if err != nil {
80 return state, fmt.Errorf("Error applying: %s", err) 97 return nil, err
98 }
99 if stepDiags.HasErrors() {
100 return state, newOperationError("apply", stepDiags)
81 } 101 }
82 102
83 // Check! Excitement! 103 // Run any configured checks
84 if step.Check != nil { 104 if step.Check != nil {
85 if step.Destroy { 105 if step.Destroy {
86 if err := step.Check(stateBeforeApplication); err != nil { 106 if err := step.Check(stateBeforeApplication); err != nil {
@@ -96,31 +116,35 @@ func testStep(
96 116
97 // Now, verify that Plan is now empty and we don't have a perpetual diff issue 117 // Now, verify that Plan is now empty and we don't have a perpetual diff issue
98 // We do this with TWO plans. One without a refresh. 118 // We do this with TWO plans. One without a refresh.
99 var p *terraform.Plan 119 var p *plans.Plan
100 if p, err = ctx.Plan(); err != nil { 120 if p, stepDiags = ctx.Plan(); stepDiags.HasErrors() {
101 return state, fmt.Errorf("Error on follow-up plan: %s", err) 121 return state, newOperationError("follow-up plan", stepDiags)
102 } 122 }
103 if p.Diff != nil && !p.Diff.Empty() { 123 if !p.Changes.Empty() {
104 if step.ExpectNonEmptyPlan { 124 if step.ExpectNonEmptyPlan {
105 log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p) 125 log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", legacyPlanComparisonString(newState, p.Changes))
106 } else { 126 } else {
107 return state, fmt.Errorf( 127 return state, fmt.Errorf(
108 "After applying this step, the plan was not empty:\n\n%s", p) 128 "After applying this step, the plan was not empty:\n\n%s", legacyPlanComparisonString(newState, p.Changes))
109 } 129 }
110 } 130 }
111 131
112 // And another after a Refresh. 132 // And another after a Refresh.
113 if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) { 133 if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) {
114 state, err = ctx.Refresh() 134 newState, stepDiags = ctx.Refresh()
135 if stepDiags.HasErrors() {
136 return state, newOperationError("follow-up refresh", stepDiags)
137 }
138
139 state, err = shimNewState(newState, step.providers)
115 if err != nil { 140 if err != nil {
116 return state, fmt.Errorf( 141 return nil, err
117 "Error on follow-up refresh: %s", err)
118 } 142 }
119 } 143 }
120 if p, err = ctx.Plan(); err != nil { 144 if p, stepDiags = ctx.Plan(); stepDiags.HasErrors() {
121 return state, fmt.Errorf("Error on second follow-up plan: %s", err) 145 return state, newOperationError("second follow-up refresh", stepDiags)
122 } 146 }
123 empty := p.Diff == nil || p.Diff.Empty() 147 empty := p.Changes.Empty()
124 148
125 // Data resources are tricky because they legitimately get instantiated 149 // Data resources are tricky because they legitimately get instantiated
126 // during refresh so that they will be already populated during the 150 // during refresh so that they will be already populated during the
@@ -128,35 +152,28 @@ func testStep(
128 // config we'll end up wanting to destroy them again here. This is 152 // config we'll end up wanting to destroy them again here. This is
129 // acceptable and expected, and we'll treat it as "empty" for the 153 // acceptable and expected, and we'll treat it as "empty" for the
130 // sake of this testing. 154 // sake of this testing.
131 if step.Destroy { 155 if step.Destroy && !empty {
132 empty = true 156 empty = true
133 157 for _, change := range p.Changes.Resources {
134 for _, moduleDiff := range p.Diff.Modules { 158 if change.Addr.Resource.Resource.Mode != addrs.DataResourceMode {
135 for k, instanceDiff := range moduleDiff.Resources { 159 empty = false
136 if !strings.HasPrefix(k, "data.") { 160 break
137 empty = false
138 break
139 }
140
141 if !instanceDiff.Destroy {
142 empty = false
143 }
144 } 161 }
145 } 162 }
146 } 163 }
147 164
148 if !empty { 165 if !empty {
149 if step.ExpectNonEmptyPlan { 166 if step.ExpectNonEmptyPlan {
150 log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", p) 167 log.Printf("[INFO] Got non-empty plan, as expected:\n\n%s", legacyPlanComparisonString(newState, p.Changes))
151 } else { 168 } else {
152 return state, fmt.Errorf( 169 return state, fmt.Errorf(
153 "After applying this step and refreshing, "+ 170 "After applying this step and refreshing, "+
154 "the plan was not empty:\n\n%s", p) 171 "the plan was not empty:\n\n%s", legacyPlanComparisonString(newState, p.Changes))
155 } 172 }
156 } 173 }
157 174
158 // Made it here, but expected a non-empty plan, fail! 175 // Made it here, but expected a non-empty plan, fail!
159 if step.ExpectNonEmptyPlan && (p.Diff == nil || p.Diff.Empty()) { 176 if step.ExpectNonEmptyPlan && empty {
160 return state, fmt.Errorf("Expected a non-empty plan, but got an empty plan!") 177 return state, fmt.Errorf("Expected a non-empty plan, but got an empty plan!")
161 } 178 }
162 179
@@ -164,6 +181,213 @@ func testStep(
164 return state, nil 181 return state, nil
165} 182}
166 183
184// legacyPlanComparisonString produces a string representation of the changes
185// from a plan and a given state togther, as was formerly produced by the
186// String method of terraform.Plan.
187//
188// This is here only for compatibility with existing tests that predate our
189// new plan and state types, and should not be used in new tests. Instead, use
190// a library like "cmp" to do a deep equality and diff on the two
191// data structures.
192func legacyPlanComparisonString(state *states.State, changes *plans.Changes) string {
193 return fmt.Sprintf(
194 "DIFF:\n\n%s\n\nSTATE:\n\n%s",
195 legacyDiffComparisonString(changes),
196 state.String(),
197 )
198}
199
200// legacyDiffComparisonString produces a string representation of the changes
201// from a planned changes object, as was formerly produced by the String method
202// of terraform.Diff.
203//
204// This is here only for compatibility with existing tests that predate our
205// new plan types, and should not be used in new tests. Instead, use a library
206// like "cmp" to do a deep equality check and diff on the two data structures.
207func legacyDiffComparisonString(changes *plans.Changes) string {
208 // The old string representation of a plan was grouped by module, but
209 // our new plan structure is not grouped in that way and so we'll need
210 // to preprocess it in order to produce that grouping.
211 type ResourceChanges struct {
212 Current *plans.ResourceInstanceChangeSrc
213 Deposed map[states.DeposedKey]*plans.ResourceInstanceChangeSrc
214 }
215 byModule := map[string]map[string]*ResourceChanges{}
216 resourceKeys := map[string][]string{}
217 requiresReplace := map[string][]string{}
218 var moduleKeys []string
219 for _, rc := range changes.Resources {
220 if rc.Action == plans.NoOp {
221 // We won't mention no-op changes here at all, since the old plan
222 // model we are emulating here didn't have such a concept.
223 continue
224 }
225 moduleKey := rc.Addr.Module.String()
226 if _, exists := byModule[moduleKey]; !exists {
227 moduleKeys = append(moduleKeys, moduleKey)
228 byModule[moduleKey] = make(map[string]*ResourceChanges)
229 }
230 resourceKey := rc.Addr.Resource.String()
231 if _, exists := byModule[moduleKey][resourceKey]; !exists {
232 resourceKeys[moduleKey] = append(resourceKeys[moduleKey], resourceKey)
233 byModule[moduleKey][resourceKey] = &ResourceChanges{
234 Deposed: make(map[states.DeposedKey]*plans.ResourceInstanceChangeSrc),
235 }
236 }
237
238 if rc.DeposedKey == states.NotDeposed {
239 byModule[moduleKey][resourceKey].Current = rc
240 } else {
241 byModule[moduleKey][resourceKey].Deposed[rc.DeposedKey] = rc
242 }
243
244 rr := []string{}
245 for _, p := range rc.RequiredReplace.List() {
246 rr = append(rr, hcl2shim.FlatmapKeyFromPath(p))
247 }
248 requiresReplace[resourceKey] = rr
249 }
250 sort.Strings(moduleKeys)
251 for _, ks := range resourceKeys {
252 sort.Strings(ks)
253 }
254
255 var buf bytes.Buffer
256
257 for _, moduleKey := range moduleKeys {
258 rcs := byModule[moduleKey]
259 var mBuf bytes.Buffer
260
261 for _, resourceKey := range resourceKeys[moduleKey] {
262 rc := rcs[resourceKey]
263
264 forceNewAttrs := requiresReplace[resourceKey]
265
266 crud := "UPDATE"
267 if rc.Current != nil {
268 switch rc.Current.Action {
269 case plans.DeleteThenCreate:
270 crud = "DESTROY/CREATE"
271 case plans.CreateThenDelete:
272 crud = "CREATE/DESTROY"
273 case plans.Delete:
274 crud = "DESTROY"
275 case plans.Create:
276 crud = "CREATE"
277 }
278 } else {
279 // We must be working on a deposed object then, in which
280 // case destroying is the only possible action.
281 crud = "DESTROY"
282 }
283
284 extra := ""
285 if rc.Current == nil && len(rc.Deposed) > 0 {
286 extra = " (deposed only)"
287 }
288
289 fmt.Fprintf(
290 &mBuf, "%s: %s%s\n",
291 crud, resourceKey, extra,
292 )
293
294 attrNames := map[string]bool{}
295 var oldAttrs map[string]string
296 var newAttrs map[string]string
297 if rc.Current != nil {
298 if before := rc.Current.Before; before != nil {
299 ty, err := before.ImpliedType()
300 if err == nil {
301 val, err := before.Decode(ty)
302 if err == nil {
303 oldAttrs = hcl2shim.FlatmapValueFromHCL2(val)
304 for k := range oldAttrs {
305 attrNames[k] = true
306 }
307 }
308 }
309 }
310 if after := rc.Current.After; after != nil {
311 ty, err := after.ImpliedType()
312 if err == nil {
313 val, err := after.Decode(ty)
314 if err == nil {
315 newAttrs = hcl2shim.FlatmapValueFromHCL2(val)
316 for k := range newAttrs {
317 attrNames[k] = true
318 }
319 }
320 }
321 }
322 }
323 if oldAttrs == nil {
324 oldAttrs = make(map[string]string)
325 }
326 if newAttrs == nil {
327 newAttrs = make(map[string]string)
328 }
329
330 attrNamesOrder := make([]string, 0, len(attrNames))
331 keyLen := 0
332 for n := range attrNames {
333 attrNamesOrder = append(attrNamesOrder, n)
334 if len(n) > keyLen {
335 keyLen = len(n)
336 }
337 }
338 sort.Strings(attrNamesOrder)
339
340 for _, attrK := range attrNamesOrder {
341 v := newAttrs[attrK]
342 u := oldAttrs[attrK]
343
344 if v == config.UnknownVariableValue {
345 v = "<computed>"
346 }
347 // NOTE: we don't support <sensitive> here because we would
348 // need schema to do that. Excluding sensitive values
349 // is now done at the UI layer, and so should not be tested
350 // at the core layer.
351
352 updateMsg := ""
353
354 // This may not be as precise as in the old diff, as it matches
355 // everything under the attribute that was originally marked as
356 // ForceNew, but should help make it easier to determine what
357 // caused replacement here.
358 for _, k := range forceNewAttrs {
359 if strings.HasPrefix(attrK, k) {
360 updateMsg = " (forces new resource)"
361 break
362 }
363 }
364
365 fmt.Fprintf(
366 &mBuf, " %s:%s %#v => %#v%s\n",
367 attrK,
368 strings.Repeat(" ", keyLen-len(attrK)),
369 u, v,
370 updateMsg,
371 )
372 }
373 }
374
375 if moduleKey == "" { // root module
376 buf.Write(mBuf.Bytes())
377 buf.WriteByte('\n')
378 continue
379 }
380
381 fmt.Fprintf(&buf, "%s:\n", moduleKey)
382 s := bufio.NewScanner(&mBuf)
383 for s.Scan() {
384 buf.WriteString(fmt.Sprintf(" %s\n", s.Text()))
385 }
386 }
387
388 return buf.String()
389}
390
167func testStepTaint(state *terraform.State, step TestStep) error { 391func testStepTaint(state *terraform.State, step TestStep) error {
168 for _, p := range step.Taint { 392 for _, p := range step.Taint {
169 m := state.RootModule() 393 m := state.RootModule()
diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/testing_import_state.go b/vendor/github.com/hashicorp/terraform/helper/resource/testing_import_state.go
index 94fef3c..e1b7aea 100644
--- a/vendor/github.com/hashicorp/terraform/helper/resource/testing_import_state.go
+++ b/vendor/github.com/hashicorp/terraform/helper/resource/testing_import_state.go
@@ -7,6 +7,12 @@ import (
7 "strings" 7 "strings"
8 8
9 "github.com/davecgh/go-spew/spew" 9 "github.com/davecgh/go-spew/spew"
10 "github.com/hashicorp/hcl2/hcl"
11 "github.com/hashicorp/hcl2/hcl/hclsyntax"
12
13 "github.com/hashicorp/terraform/addrs"
14 "github.com/hashicorp/terraform/helper/schema"
15 "github.com/hashicorp/terraform/states"
10 "github.com/hashicorp/terraform/terraform" 16 "github.com/hashicorp/terraform/terraform"
11) 17)
12 18
@@ -15,6 +21,7 @@ func testStepImportState(
15 opts terraform.ContextOpts, 21 opts terraform.ContextOpts,
16 state *terraform.State, 22 state *terraform.State,
17 step TestStep) (*terraform.State, error) { 23 step TestStep) (*terraform.State, error) {
24
18 // Determine the ID to import 25 // Determine the ID to import
19 var importId string 26 var importId string
20 switch { 27 switch {
@@ -41,33 +48,53 @@ func testStepImportState(
41 48
42 // Setup the context. We initialize with an empty state. We use the 49 // Setup the context. We initialize with an empty state. We use the
43 // full config for provider configurations. 50 // full config for provider configurations.
44 mod, err := testModule(opts, step) 51 cfg, err := testConfig(opts, step)
45 if err != nil { 52 if err != nil {
46 return state, err 53 return state, err
47 } 54 }
48 55
49 opts.Module = mod 56 opts.Config = cfg
50 opts.State = terraform.NewState() 57
51 ctx, err := terraform.NewContext(&opts) 58 // import tests start with empty state
52 if err != nil { 59 opts.State = states.NewState()
53 return state, err 60
61 ctx, stepDiags := terraform.NewContext(&opts)
62 if stepDiags.HasErrors() {
63 return state, stepDiags.Err()
54 } 64 }
55 65
56 // Do the import! 66 // The test step provides the resource address as a string, so we need
57 newState, err := ctx.Import(&terraform.ImportOpts{ 67 // to parse it to get an addrs.AbsResourceAddress to pass in to the
68 // import method.
69 traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(step.ResourceName), "", hcl.Pos{})
70 if hclDiags.HasErrors() {
71 return nil, hclDiags
72 }
73 importAddr, stepDiags := addrs.ParseAbsResourceInstance(traversal)
74 if stepDiags.HasErrors() {
75 return nil, stepDiags.Err()
76 }
77
78 // Do the import
79 importedState, stepDiags := ctx.Import(&terraform.ImportOpts{
58 // Set the module so that any provider config is loaded 80 // Set the module so that any provider config is loaded
59 Module: mod, 81 Config: cfg,
60 82
61 Targets: []*terraform.ImportTarget{ 83 Targets: []*terraform.ImportTarget{
62 &terraform.ImportTarget{ 84 &terraform.ImportTarget{
63 Addr: step.ResourceName, 85 Addr: importAddr,
64 ID: importId, 86 ID: importId,
65 }, 87 },
66 }, 88 },
67 }) 89 })
90 if stepDiags.HasErrors() {
91 log.Printf("[ERROR] Test: ImportState failure: %s", stepDiags.Err())
92 return state, stepDiags.Err()
93 }
94
95 newState, err := shimNewState(importedState, step.providers)
68 if err != nil { 96 if err != nil {
69 log.Printf("[ERROR] Test: ImportState failure: %s", err) 97 return nil, err
70 return state, err
71 } 98 }
72 99
73 // Go through the new state and verify 100 // Go through the new state and verify
@@ -75,7 +102,9 @@ func testStepImportState(
75 var states []*terraform.InstanceState 102 var states []*terraform.InstanceState
76 for _, r := range newState.RootModule().Resources { 103 for _, r := range newState.RootModule().Resources {
77 if r.Primary != nil { 104 if r.Primary != nil {
78 states = append(states, r.Primary) 105 is := r.Primary.DeepCopy()
106 is.Ephemeral.Type = r.Type // otherwise the check function cannot see the type
107 states = append(states, is)
79 } 108 }
80 } 109 }
81 if err := step.ImportStateCheck(states); err != nil { 110 if err := step.ImportStateCheck(states); err != nil {
@@ -102,30 +131,84 @@ func testStepImportState(
102 r.Primary.ID) 131 r.Primary.ID)
103 } 132 }
104 133
134 // We'll try our best to find the schema for this resource type
135 // so we can ignore Removed fields during validation. If we fail
136 // to find the schema then we won't ignore them and so the test
137 // will need to rely on explicit ImportStateVerifyIgnore, though
138 // this shouldn't happen in any reasonable case.
139 var rsrcSchema *schema.Resource
140 if providerAddr, diags := addrs.ParseAbsProviderConfigStr(r.Provider); !diags.HasErrors() {
141 providerType := providerAddr.ProviderConfig.Type
142 if provider, ok := step.providers[providerType]; ok {
143 if provider, ok := provider.(*schema.Provider); ok {
144 rsrcSchema = provider.ResourcesMap[r.Type]
145 }
146 }
147 }
148
149 // don't add empty flatmapped containers, so we can more easily
150 // compare the attributes
151 skipEmpty := func(k, v string) bool {
152 if strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%") {
153 if v == "0" {
154 return true
155 }
156 }
157 return false
158 }
159
105 // Compare their attributes 160 // Compare their attributes
106 actual := make(map[string]string) 161 actual := make(map[string]string)
107 for k, v := range r.Primary.Attributes { 162 for k, v := range r.Primary.Attributes {
163 if skipEmpty(k, v) {
164 continue
165 }
108 actual[k] = v 166 actual[k] = v
109 } 167 }
168
110 expected := make(map[string]string) 169 expected := make(map[string]string)
111 for k, v := range oldR.Primary.Attributes { 170 for k, v := range oldR.Primary.Attributes {
171 if skipEmpty(k, v) {
172 continue
173 }
112 expected[k] = v 174 expected[k] = v
113 } 175 }
114 176
115 // Remove fields we're ignoring 177 // Remove fields we're ignoring
116 for _, v := range step.ImportStateVerifyIgnore { 178 for _, v := range step.ImportStateVerifyIgnore {
117 for k, _ := range actual { 179 for k := range actual {
118 if strings.HasPrefix(k, v) { 180 if strings.HasPrefix(k, v) {
119 delete(actual, k) 181 delete(actual, k)
120 } 182 }
121 } 183 }
122 for k, _ := range expected { 184 for k := range expected {
123 if strings.HasPrefix(k, v) { 185 if strings.HasPrefix(k, v) {
124 delete(expected, k) 186 delete(expected, k)
125 } 187 }
126 } 188 }
127 } 189 }
128 190
191 // Also remove any attributes that are marked as "Removed" in the
192 // schema, if we have a schema to check that against.
193 if rsrcSchema != nil {
194 for k := range actual {
195 for _, schema := range rsrcSchema.SchemasForFlatmapPath(k) {
196 if schema.Removed != "" {
197 delete(actual, k)
198 break
199 }
200 }
201 }
202 for k := range expected {
203 for _, schema := range rsrcSchema.SchemasForFlatmapPath(k) {
204 if schema.Removed != "" {
205 delete(expected, k)
206 break
207 }
208 }
209 }
210 }
211
129 if !reflect.DeepEqual(actual, expected) { 212 if !reflect.DeepEqual(actual, expected) {
130 // Determine only the different attributes 213 // Determine only the different attributes
131 for k, v := range expected { 214 for k, v := range expected {
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/backend.go b/vendor/github.com/hashicorp/terraform/helper/schema/backend.go
index 57fbba7..c8d8ae2 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/backend.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/backend.go
@@ -2,8 +2,15 @@ package schema
2 2
3import ( 3import (
4 "context" 4 "context"
5 "fmt"
5 6
7 "github.com/hashicorp/terraform/tfdiags"
8 "github.com/zclconf/go-cty/cty"
9
10 "github.com/hashicorp/terraform/config/hcl2shim"
11 "github.com/hashicorp/terraform/configs/configschema"
6 "github.com/hashicorp/terraform/terraform" 12 "github.com/hashicorp/terraform/terraform"
13 ctyconvert "github.com/zclconf/go-cty/cty/convert"
7) 14)
8 15
9// Backend represents a partial backend.Backend implementation and simplifies 16// Backend represents a partial backend.Backend implementation and simplifies
@@ -38,41 +45,123 @@ func FromContextBackendConfig(ctx context.Context) *ResourceData {
38 return ctx.Value(backendConfigKey).(*ResourceData) 45 return ctx.Value(backendConfigKey).(*ResourceData)
39} 46}
40 47
41func (b *Backend) Input( 48func (b *Backend) ConfigSchema() *configschema.Block {
42 input terraform.UIInput, 49 // This is an alias of CoreConfigSchema just to implement the
43 c *terraform.ResourceConfig) (*terraform.ResourceConfig, error) { 50 // backend.Backend interface.
51 return b.CoreConfigSchema()
52}
53
54func (b *Backend) PrepareConfig(configVal cty.Value) (cty.Value, tfdiags.Diagnostics) {
44 if b == nil { 55 if b == nil {
45 return c, nil 56 return configVal, nil
46 } 57 }
58 var diags tfdiags.Diagnostics
59 var err error
47 60
48 return schemaMap(b.Schema).Input(input, c) 61 // In order to use Transform below, this needs to be filled out completely
49} 62 // according the schema.
63 configVal, err = b.CoreConfigSchema().CoerceValue(configVal)
64 if err != nil {
65 return configVal, diags.Append(err)
66 }
50 67
51func (b *Backend) Validate(c *terraform.ResourceConfig) ([]string, []error) { 68 // lookup any required, top-level attributes that are Null, and see if we
52 if b == nil { 69 // have a Default value available.
53 return nil, nil 70 configVal, err = cty.Transform(configVal, func(path cty.Path, val cty.Value) (cty.Value, error) {
71 // we're only looking for top-level attributes
72 if len(path) != 1 {
73 return val, nil
74 }
75
76 // nothing to do if we already have a value
77 if !val.IsNull() {
78 return val, nil
79 }
80
81 // get the Schema definition for this attribute
82 getAttr, ok := path[0].(cty.GetAttrStep)
83 // these should all exist, but just ignore anything strange
84 if !ok {
85 return val, nil
86 }
87
88 attrSchema := b.Schema[getAttr.Name]
89 // continue to ignore anything that doesn't match
90 if attrSchema == nil {
91 return val, nil
92 }
93
94 // this is deprecated, so don't set it
95 if attrSchema.Deprecated != "" || attrSchema.Removed != "" {
96 return val, nil
97 }
98
99 // find a default value if it exists
100 def, err := attrSchema.DefaultValue()
101 if err != nil {
102 diags = diags.Append(fmt.Errorf("error getting default for %q: %s", getAttr.Name, err))
103 return val, err
104 }
105
106 // no default
107 if def == nil {
108 return val, nil
109 }
110
111 // create a cty.Value and make sure it's the correct type
112 tmpVal := hcl2shim.HCL2ValueFromConfigValue(def)
113
114 // helper/schema used to allow setting "" to a bool
115 if val.Type() == cty.Bool && tmpVal.RawEquals(cty.StringVal("")) {
116 // return a warning about the conversion
117 diags = diags.Append("provider set empty string as default value for bool " + getAttr.Name)
118 tmpVal = cty.False
119 }
120
121 val, err = ctyconvert.Convert(tmpVal, val.Type())
122 if err != nil {
123 diags = diags.Append(fmt.Errorf("error setting default for %q: %s", getAttr.Name, err))
124 }
125
126 return val, err
127 })
128 if err != nil {
129 // any error here was already added to the diagnostics
130 return configVal, diags
54 } 131 }
55 132
56 return schemaMap(b.Schema).Validate(c) 133 shimRC := b.shimConfig(configVal)
134 warns, errs := schemaMap(b.Schema).Validate(shimRC)
135 for _, warn := range warns {
136 diags = diags.Append(tfdiags.SimpleWarning(warn))
137 }
138 for _, err := range errs {
139 diags = diags.Append(err)
140 }
141 return configVal, diags
57} 142}
58 143
59func (b *Backend) Configure(c *terraform.ResourceConfig) error { 144func (b *Backend) Configure(obj cty.Value) tfdiags.Diagnostics {
60 if b == nil { 145 if b == nil {
61 return nil 146 return nil
62 } 147 }
63 148
149 var diags tfdiags.Diagnostics
64 sm := schemaMap(b.Schema) 150 sm := schemaMap(b.Schema)
151 shimRC := b.shimConfig(obj)
65 152
66 // Get a ResourceData for this configuration. To do this, we actually 153 // Get a ResourceData for this configuration. To do this, we actually
67 // generate an intermediary "diff" although that is never exposed. 154 // generate an intermediary "diff" although that is never exposed.
68 diff, err := sm.Diff(nil, c, nil, nil) 155 diff, err := sm.Diff(nil, shimRC, nil, nil, true)
69 if err != nil { 156 if err != nil {
70 return err 157 diags = diags.Append(err)
158 return diags
71 } 159 }
72 160
73 data, err := sm.Data(nil, diff) 161 data, err := sm.Data(nil, diff)
74 if err != nil { 162 if err != nil {
75 return err 163 diags = diags.Append(err)
164 return diags
76 } 165 }
77 b.config = data 166 b.config = data
78 167
@@ -80,11 +169,28 @@ func (b *Backend) Configure(c *terraform.ResourceConfig) error {
80 err = b.ConfigureFunc(context.WithValue( 169 err = b.ConfigureFunc(context.WithValue(
81 context.Background(), backendConfigKey, data)) 170 context.Background(), backendConfigKey, data))
82 if err != nil { 171 if err != nil {
83 return err 172 diags = diags.Append(err)
173 return diags
84 } 174 }
85 } 175 }
86 176
87 return nil 177 return diags
178}
179
180// shimConfig turns a new-style cty.Value configuration (which must be of
181// an object type) into a minimal old-style *terraform.ResourceConfig object
182// that should be populated enough to appease the not-yet-updated functionality
183// in this package. This should be removed once everything is updated.
184func (b *Backend) shimConfig(obj cty.Value) *terraform.ResourceConfig {
185 shimMap, ok := hcl2shim.ConfigValueFromHCL2(obj).(map[string]interface{})
186 if !ok {
187 // If the configVal was nil, we still want a non-nil map here.
188 shimMap = map[string]interface{}{}
189 }
190 return &terraform.ResourceConfig{
191 Config: shimMap,
192 Raw: shimMap,
193 }
88} 194}
89 195
90// Config returns the configuration. This is available after Configure is 196// Config returns the configuration. This is available after Configure is
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/core_schema.go b/vendor/github.com/hashicorp/terraform/helper/schema/core_schema.go
index bf952f6..875677e 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/core_schema.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/core_schema.go
@@ -3,7 +3,7 @@ package schema
3import ( 3import (
4 "fmt" 4 "fmt"
5 5
6 "github.com/hashicorp/terraform/config/configschema" 6 "github.com/hashicorp/terraform/configs/configschema"
7 "github.com/zclconf/go-cty/cty" 7 "github.com/zclconf/go-cty/cty"
8) 8)
9 9
@@ -39,14 +39,42 @@ func (m schemaMap) CoreConfigSchema() *configschema.Block {
39 ret.Attributes[name] = schema.coreConfigSchemaAttribute() 39 ret.Attributes[name] = schema.coreConfigSchemaAttribute()
40 continue 40 continue
41 } 41 }
42 switch schema.Elem.(type) { 42 if schema.Type == TypeMap {
43 case *Schema: 43 // For TypeMap in particular, it isn't valid for Elem to be a
44 // *Resource (since that would be ambiguous in flatmap) and
45 // so Elem is treated as a TypeString schema if so. This matches
46 // how the field readers treat this situation, for compatibility
47 // with configurations targeting Terraform 0.11 and earlier.
48 if _, isResource := schema.Elem.(*Resource); isResource {
49 sch := *schema // shallow copy
50 sch.Elem = &Schema{
51 Type: TypeString,
52 }
53 ret.Attributes[name] = sch.coreConfigSchemaAttribute()
54 continue
55 }
56 }
57 switch schema.ConfigMode {
58 case SchemaConfigModeAttr:
44 ret.Attributes[name] = schema.coreConfigSchemaAttribute() 59 ret.Attributes[name] = schema.coreConfigSchemaAttribute()
45 case *Resource: 60 case SchemaConfigModeBlock:
46 ret.BlockTypes[name] = schema.coreConfigSchemaBlock() 61 ret.BlockTypes[name] = schema.coreConfigSchemaBlock()
47 default: 62 default: // SchemaConfigModeAuto, or any other invalid value
48 // Should never happen for a valid schema 63 if schema.Computed && !schema.Optional {
49 panic(fmt.Errorf("invalid Schema.Elem %#v; need *Schema or *Resource", schema.Elem)) 64 // Computed-only schemas are always handled as attributes,
65 // because they never appear in configuration.
66 ret.Attributes[name] = schema.coreConfigSchemaAttribute()
67 continue
68 }
69 switch schema.Elem.(type) {
70 case *Schema, ValueType:
71 ret.Attributes[name] = schema.coreConfigSchemaAttribute()
72 case *Resource:
73 ret.BlockTypes[name] = schema.coreConfigSchemaBlock()
74 default:
75 // Should never happen for a valid schema
76 panic(fmt.Errorf("invalid Schema.Elem %#v; need *Schema or *Resource", schema.Elem))
77 }
50 } 78 }
51 } 79 }
52 80
@@ -58,12 +86,42 @@ func (m schemaMap) CoreConfigSchema() *configschema.Block {
58// Elem is an instance of Schema. Use coreConfigSchemaBlock for collections 86// Elem is an instance of Schema. Use coreConfigSchemaBlock for collections
59// whose elem is a whole resource. 87// whose elem is a whole resource.
60func (s *Schema) coreConfigSchemaAttribute() *configschema.Attribute { 88func (s *Schema) coreConfigSchemaAttribute() *configschema.Attribute {
89 // The Schema.DefaultFunc capability adds some extra weirdness here since
90 // it can be combined with "Required: true" to create a sitution where
91 // required-ness is conditional. Terraform Core doesn't share this concept,
92 // so we must sniff for this possibility here and conditionally turn
93 // off the "Required" flag if it looks like the DefaultFunc is going
94 // to provide a value.
95 // This is not 100% true to the original interface of DefaultFunc but
96 // works well enough for the EnvDefaultFunc and MultiEnvDefaultFunc
97 // situations, which are the main cases we care about.
98 //
99 // Note that this also has a consequence for commands that return schema
100 // information for documentation purposes: running those for certain
101 // providers will produce different results depending on which environment
102 // variables are set. We accept that weirdness in order to keep this
103 // interface to core otherwise simple.
104 reqd := s.Required
105 opt := s.Optional
106 if reqd && s.DefaultFunc != nil {
107 v, err := s.DefaultFunc()
108 // We can't report errors from here, so we'll instead just force
109 // "Required" to false and let the provider try calling its
110 // DefaultFunc again during the validate step, where it can then
111 // return the error.
112 if err != nil || (err == nil && v != nil) {
113 reqd = false
114 opt = true
115 }
116 }
117
61 return &configschema.Attribute{ 118 return &configschema.Attribute{
62 Type: s.coreConfigSchemaType(), 119 Type: s.coreConfigSchemaType(),
63 Optional: s.Optional, 120 Optional: opt,
64 Required: s.Required, 121 Required: reqd,
65 Computed: s.Computed, 122 Computed: s.Computed,
66 Sensitive: s.Sensitive, 123 Sensitive: s.Sensitive,
124 Description: s.Description,
67 } 125 }
68} 126}
69 127
@@ -72,7 +130,7 @@ func (s *Schema) coreConfigSchemaAttribute() *configschema.Attribute {
72// of Resource, and will panic otherwise. 130// of Resource, and will panic otherwise.
73func (s *Schema) coreConfigSchemaBlock() *configschema.NestedBlock { 131func (s *Schema) coreConfigSchemaBlock() *configschema.NestedBlock {
74 ret := &configschema.NestedBlock{} 132 ret := &configschema.NestedBlock{}
75 if nested := s.Elem.(*Resource).CoreConfigSchema(); nested != nil { 133 if nested := s.Elem.(*Resource).coreConfigSchema(); nested != nil {
76 ret.Block = *nested 134 ret.Block = *nested
77 } 135 }
78 switch s.Type { 136 switch s.Type {
@@ -95,6 +153,20 @@ func (s *Schema) coreConfigSchemaBlock() *configschema.NestedBlock {
95 // blocks, but we can fake it by requiring at least one item. 153 // blocks, but we can fake it by requiring at least one item.
96 ret.MinItems = 1 154 ret.MinItems = 1
97 } 155 }
156 if s.Optional && s.MinItems > 0 {
157 // Historically helper/schema would ignore MinItems if Optional were
158 // set, so we must mimic this behavior here to ensure that providers
159 // relying on that undocumented behavior can continue to operate as
160 // they did before.
161 ret.MinItems = 0
162 }
163 if s.Computed && !s.Optional {
164 // MinItems/MaxItems are meaningless for computed nested blocks, since
165 // they are never set by the user anyway. This ensures that we'll never
166 // generate weird errors about them.
167 ret.MinItems = 0
168 ret.MaxItems = 0
169 }
98 170
99 return ret 171 return ret
100} 172}
@@ -117,11 +189,16 @@ func (s *Schema) coreConfigSchemaType() cty.Type {
117 switch set := s.Elem.(type) { 189 switch set := s.Elem.(type) {
118 case *Schema: 190 case *Schema:
119 elemType = set.coreConfigSchemaType() 191 elemType = set.coreConfigSchemaType()
192 case ValueType:
193 // This represents a mistake in the provider code, but it's a
194 // common one so we'll just shim it.
195 elemType = (&Schema{Type: set}).coreConfigSchemaType()
120 case *Resource: 196 case *Resource:
121 // In practice we don't actually use this for normal schema 197 // By default we construct a NestedBlock in this case, but this
122 // construction because we construct a NestedBlock in that 198 // behavior is selected either for computed-only schemas or
123 // case instead. See schemaMap.CoreConfigSchema. 199 // when ConfigMode is explicitly SchemaConfigModeBlock.
124 elemType = set.CoreConfigSchema().ImpliedType() 200 // See schemaMap.CoreConfigSchema for the exact rules.
201 elemType = set.coreConfigSchema().ImpliedType()
125 default: 202 default:
126 if set != nil { 203 if set != nil {
127 // Should never happen for a valid schema 204 // Should never happen for a valid schema
@@ -148,8 +225,85 @@ func (s *Schema) coreConfigSchemaType() cty.Type {
148 } 225 }
149} 226}
150 227
151// CoreConfigSchema is a convenient shortcut for calling CoreConfigSchema 228// CoreConfigSchema is a convenient shortcut for calling CoreConfigSchema on
152// on the resource's schema. 229// the resource's schema. CoreConfigSchema adds the implicitly required "id"
230// attribute for top level resources if it doesn't exist.
153func (r *Resource) CoreConfigSchema() *configschema.Block { 231func (r *Resource) CoreConfigSchema() *configschema.Block {
232 block := r.coreConfigSchema()
233
234 if block.Attributes == nil {
235 block.Attributes = map[string]*configschema.Attribute{}
236 }
237
238 // Add the implicitly required "id" field if it doesn't exist
239 if block.Attributes["id"] == nil {
240 block.Attributes["id"] = &configschema.Attribute{
241 Type: cty.String,
242 Optional: true,
243 Computed: true,
244 }
245 }
246
247 _, timeoutsAttr := block.Attributes[TimeoutsConfigKey]
248 _, timeoutsBlock := block.BlockTypes[TimeoutsConfigKey]
249
250 // Insert configured timeout values into the schema, as long as the schema
251 // didn't define anything else by that name.
252 if r.Timeouts != nil && !timeoutsAttr && !timeoutsBlock {
253 timeouts := configschema.Block{
254 Attributes: map[string]*configschema.Attribute{},
255 }
256
257 if r.Timeouts.Create != nil {
258 timeouts.Attributes[TimeoutCreate] = &configschema.Attribute{
259 Type: cty.String,
260 Optional: true,
261 }
262 }
263
264 if r.Timeouts.Read != nil {
265 timeouts.Attributes[TimeoutRead] = &configschema.Attribute{
266 Type: cty.String,
267 Optional: true,
268 }
269 }
270
271 if r.Timeouts.Update != nil {
272 timeouts.Attributes[TimeoutUpdate] = &configschema.Attribute{
273 Type: cty.String,
274 Optional: true,
275 }
276 }
277
278 if r.Timeouts.Delete != nil {
279 timeouts.Attributes[TimeoutDelete] = &configschema.Attribute{
280 Type: cty.String,
281 Optional: true,
282 }
283 }
284
285 if r.Timeouts.Default != nil {
286 timeouts.Attributes[TimeoutDefault] = &configschema.Attribute{
287 Type: cty.String,
288 Optional: true,
289 }
290 }
291
292 block.BlockTypes[TimeoutsConfigKey] = &configschema.NestedBlock{
293 Nesting: configschema.NestingSingle,
294 Block: timeouts,
295 }
296 }
297
298 return block
299}
300
301func (r *Resource) coreConfigSchema() *configschema.Block {
302 return schemaMap(r.Schema).CoreConfigSchema()
303}
304
305// CoreConfigSchema is a convenient shortcut for calling CoreConfigSchema
306// on the backends's schema.
307func (r *Backend) CoreConfigSchema() *configschema.Block {
154 return schemaMap(r.Schema).CoreConfigSchema() 308 return schemaMap(r.Schema).CoreConfigSchema()
155} 309}
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/field_reader.go b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader.go
index b80b223..2a66a06 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/field_reader.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader.go
@@ -3,6 +3,7 @@ package schema
3import ( 3import (
4 "fmt" 4 "fmt"
5 "strconv" 5 "strconv"
6 "strings"
6) 7)
7 8
8// FieldReaders are responsible for decoding fields out of data into 9// FieldReaders are responsible for decoding fields out of data into
@@ -41,6 +42,13 @@ func (r *FieldReadResult) ValueOrZero(s *Schema) interface{} {
41 return s.ZeroValue() 42 return s.ZeroValue()
42} 43}
43 44
45// SchemasForFlatmapPath tries its best to find a sequence of schemas that
46// the given dot-delimited attribute path traverses through.
47func SchemasForFlatmapPath(path string, schemaMap map[string]*Schema) []*Schema {
48 parts := strings.Split(path, ".")
49 return addrToSchema(parts, schemaMap)
50}
51
44// addrToSchema finds the final element schema for the given address 52// addrToSchema finds the final element schema for the given address
45// and the given schema. It returns all the schemas that led to the final 53// and the given schema. It returns all the schemas that led to the final
46// schema. These are in order of the address (out to in). 54// schema. These are in order of the address (out to in).
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_config.go b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_config.go
index 55a301d..808375c 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_config.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_config.go
@@ -2,6 +2,7 @@ package schema
2 2
3import ( 3import (
4 "fmt" 4 "fmt"
5 "log"
5 "strconv" 6 "strconv"
6 "strings" 7 "strings"
7 "sync" 8 "sync"
@@ -93,6 +94,22 @@ func (r *ConfigFieldReader) readField(
93 } 94 }
94 } 95 }
95 96
97 if protoVersion5 {
98 switch schema.Type {
99 case TypeList, TypeSet, TypeMap, typeObject:
100 // Check if the value itself is unknown.
101 // The new protocol shims will add unknown values to this list of
102 // ComputedKeys. This is the only way we have to indicate that a
103 // collection is unknown in the config
104 for _, unknown := range r.Config.ComputedKeys {
105 if k == unknown {
106 log.Printf("[DEBUG] setting computed for %q from ComputedKeys", k)
107 return FieldReadResult{Computed: true, Exists: true}, nil
108 }
109 }
110 }
111 }
112
96 switch schema.Type { 113 switch schema.Type {
97 case TypeBool, TypeFloat, TypeInt, TypeString: 114 case TypeBool, TypeFloat, TypeInt, TypeString:
98 return r.readPrimitive(k, schema) 115 return r.readPrimitive(k, schema)
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_diff.go b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_diff.go
index d558a5b..ae35b4a 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_diff.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_diff.go
@@ -174,6 +174,9 @@ func (r *DiffFieldReader) readPrimitive(
174 174
175func (r *DiffFieldReader) readSet( 175func (r *DiffFieldReader) readSet(
176 address []string, schema *Schema) (FieldReadResult, error) { 176 address []string, schema *Schema) (FieldReadResult, error) {
177 // copy address to ensure we don't modify the argument
178 address = append([]string(nil), address...)
179
177 prefix := strings.Join(address, ".") + "." 180 prefix := strings.Join(address, ".") + "."
178 181
179 // Create the set that will be our result 182 // Create the set that will be our result
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_map.go b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_map.go
index 054efe0..53f73b7 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_map.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/field_reader_map.go
@@ -98,6 +98,9 @@ func (r *MapFieldReader) readPrimitive(
98 98
99func (r *MapFieldReader) readSet( 99func (r *MapFieldReader) readSet(
100 address []string, schema *Schema) (FieldReadResult, error) { 100 address []string, schema *Schema) (FieldReadResult, error) {
101 // copy address to ensure we don't modify the argument
102 address = append([]string(nil), address...)
103
101 // Get the number of elements in the list 104 // Get the number of elements in the list
102 countRaw, err := r.readPrimitive( 105 countRaw, err := r.readPrimitive(
103 append(address, "#"), &Schema{Type: TypeInt}) 106 append(address, "#"), &Schema{Type: TypeInt})
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/field_writer_map.go b/vendor/github.com/hashicorp/terraform/helper/schema/field_writer_map.go
index 814c7ba..c09358b 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/field_writer_map.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/field_writer_map.go
@@ -297,13 +297,14 @@ func (w *MapFieldWriter) setSet(
297 // we get the proper order back based on the hash code. 297 // we get the proper order back based on the hash code.
298 if v := reflect.ValueOf(value); v.Kind() == reflect.Slice { 298 if v := reflect.ValueOf(value); v.Kind() == reflect.Slice {
299 // Build a temp *ResourceData to use for the conversion 299 // Build a temp *ResourceData to use for the conversion
300 tempAddr := addr[len(addr)-1:]
300 tempSchema := *schema 301 tempSchema := *schema
301 tempSchema.Type = TypeList 302 tempSchema.Type = TypeList
302 tempSchemaMap := map[string]*Schema{addr[0]: &tempSchema} 303 tempSchemaMap := map[string]*Schema{tempAddr[0]: &tempSchema}
303 tempW := &MapFieldWriter{Schema: tempSchemaMap} 304 tempW := &MapFieldWriter{Schema: tempSchemaMap}
304 305
305 // Set the entire list, this lets us get sane values out of it 306 // Set the entire list, this lets us get sane values out of it
306 if err := tempW.WriteField(addr, value); err != nil { 307 if err := tempW.WriteField(tempAddr, value); err != nil {
307 return err 308 return err
308 } 309 }
309 310
@@ -319,7 +320,7 @@ func (w *MapFieldWriter) setSet(
319 } 320 }
320 for i := 0; i < v.Len(); i++ { 321 for i := 0; i < v.Len(); i++ {
321 is := strconv.FormatInt(int64(i), 10) 322 is := strconv.FormatInt(int64(i), 10)
322 result, err := tempR.ReadField(append(addrCopy, is)) 323 result, err := tempR.ReadField(append(tempAddr, is))
323 if err != nil { 324 if err != nil {
324 return err 325 return err
325 } 326 }
@@ -340,6 +341,11 @@ func (w *MapFieldWriter) setSet(
340 // problems when the old data isn't wiped first. 341 // problems when the old data isn't wiped first.
341 w.clearTree(addr) 342 w.clearTree(addr)
342 343
344 if value.(*Set) == nil {
345 w.result[k+".#"] = "0"
346 return nil
347 }
348
343 for code, elem := range value.(*Set).m { 349 for code, elem := range value.(*Set).m {
344 if err := w.set(append(addrCopy, code), elem); err != nil { 350 if err := w.set(append(addrCopy, code), elem); err != nil {
345 return err 351 return err
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/getsource_string.go b/vendor/github.com/hashicorp/terraform/helper/schema/getsource_string.go
index 38cd8c7..0184d7b 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/getsource_string.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/getsource_string.go
@@ -4,6 +4,18 @@ package schema
4 4
5import "strconv" 5import "strconv"
6 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[getSourceState-1]
12 _ = x[getSourceConfig-2]
13 _ = x[getSourceDiff-4]
14 _ = x[getSourceSet-8]
15 _ = x[getSourceExact-16]
16 _ = x[getSourceLevelMask-15]
17}
18
7const ( 19const (
8 _getSource_name_0 = "getSourceStategetSourceConfig" 20 _getSource_name_0 = "getSourceStategetSourceConfig"
9 _getSource_name_1 = "getSourceDiff" 21 _getSource_name_1 = "getSourceDiff"
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/provider.go b/vendor/github.com/hashicorp/terraform/helper/schema/provider.go
index 6cd325d..9702447 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/provider.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/provider.go
@@ -9,7 +9,7 @@ import (
9 9
10 "github.com/hashicorp/go-multierror" 10 "github.com/hashicorp/go-multierror"
11 "github.com/hashicorp/terraform/config" 11 "github.com/hashicorp/terraform/config"
12 "github.com/hashicorp/terraform/config/configschema" 12 "github.com/hashicorp/terraform/configs/configschema"
13 "github.com/hashicorp/terraform/terraform" 13 "github.com/hashicorp/terraform/terraform"
14) 14)
15 15
@@ -64,6 +64,8 @@ type Provider struct {
64 stopCtx context.Context 64 stopCtx context.Context
65 stopCtxCancel context.CancelFunc 65 stopCtxCancel context.CancelFunc
66 stopOnce sync.Once 66 stopOnce sync.Once
67
68 TerraformVersion string
67} 69}
68 70
69// ConfigureFunc is the function used to configure a Provider. 71// ConfigureFunc is the function used to configure a Provider.
@@ -251,7 +253,7 @@ func (p *Provider) Configure(c *terraform.ResourceConfig) error {
251 253
252 // Get a ResourceData for this configuration. To do this, we actually 254 // Get a ResourceData for this configuration. To do this, we actually
253 // generate an intermediary "diff" although that is never exposed. 255 // generate an intermediary "diff" although that is never exposed.
254 diff, err := sm.Diff(nil, c, nil, p.meta) 256 diff, err := sm.Diff(nil, c, nil, p.meta, true)
255 if err != nil { 257 if err != nil {
256 return err 258 return err
257 } 259 }
@@ -296,6 +298,20 @@ func (p *Provider) Diff(
296 return r.Diff(s, c, p.meta) 298 return r.Diff(s, c, p.meta)
297} 299}
298 300
301// SimpleDiff is used by the new protocol wrappers to get a diff that doesn't
302// attempt to calculate ignore_changes.
303func (p *Provider) SimpleDiff(
304 info *terraform.InstanceInfo,
305 s *terraform.InstanceState,
306 c *terraform.ResourceConfig) (*terraform.InstanceDiff, error) {
307 r, ok := p.ResourcesMap[info.Type]
308 if !ok {
309 return nil, fmt.Errorf("unknown resource type: %s", info.Type)
310 }
311
312 return r.simpleDiff(s, c, p.meta)
313}
314
299// Refresh implementation of terraform.ResourceProvider interface. 315// Refresh implementation of terraform.ResourceProvider interface.
300func (p *Provider) Refresh( 316func (p *Provider) Refresh(
301 info *terraform.InstanceInfo, 317 info *terraform.InstanceInfo,
@@ -311,7 +327,7 @@ func (p *Provider) Refresh(
311// Resources implementation of terraform.ResourceProvider interface. 327// Resources implementation of terraform.ResourceProvider interface.
312func (p *Provider) Resources() []terraform.ResourceType { 328func (p *Provider) Resources() []terraform.ResourceType {
313 keys := make([]string, 0, len(p.ResourcesMap)) 329 keys := make([]string, 0, len(p.ResourcesMap))
314 for k, _ := range p.ResourcesMap { 330 for k := range p.ResourcesMap {
315 keys = append(keys, k) 331 keys = append(keys, k)
316 } 332 }
317 sort.Strings(keys) 333 sort.Strings(keys)
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/provisioner.go b/vendor/github.com/hashicorp/terraform/helper/schema/provisioner.go
index a8d42db..637e221 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/provisioner.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/provisioner.go
@@ -8,6 +8,7 @@ import (
8 8
9 "github.com/hashicorp/go-multierror" 9 "github.com/hashicorp/go-multierror"
10 "github.com/hashicorp/terraform/config" 10 "github.com/hashicorp/terraform/config"
11 "github.com/hashicorp/terraform/configs/configschema"
11 "github.com/hashicorp/terraform/terraform" 12 "github.com/hashicorp/terraform/terraform"
12) 13)
13 14
@@ -121,6 +122,11 @@ func (p *Provisioner) Stop() error {
121 return nil 122 return nil
122} 123}
123 124
125// GetConfigSchema implementation of terraform.ResourceProvisioner interface.
126func (p *Provisioner) GetConfigSchema() (*configschema.Block, error) {
127 return schemaMap(p.Schema).CoreConfigSchema(), nil
128}
129
124// Apply implementation of terraform.ResourceProvisioner interface. 130// Apply implementation of terraform.ResourceProvisioner interface.
125func (p *Provisioner) Apply( 131func (p *Provisioner) Apply(
126 o terraform.UIOutput, 132 o terraform.UIOutput,
@@ -146,7 +152,7 @@ func (p *Provisioner) Apply(
146 } 152 }
147 153
148 sm := schemaMap(p.ConnSchema) 154 sm := schemaMap(p.ConnSchema)
149 diff, err := sm.Diff(nil, terraform.NewResourceConfig(c), nil, nil) 155 diff, err := sm.Diff(nil, terraform.NewResourceConfig(c), nil, nil, true)
150 if err != nil { 156 if err != nil {
151 return err 157 return err
152 } 158 }
@@ -160,7 +166,7 @@ func (p *Provisioner) Apply(
160 // Build the configuration data. Doing this requires making a "diff" 166 // Build the configuration data. Doing this requires making a "diff"
161 // even though that's never used. We use that just to get the correct types. 167 // even though that's never used. We use that just to get the correct types.
162 configMap := schemaMap(p.Schema) 168 configMap := schemaMap(p.Schema)
163 diff, err := configMap.Diff(nil, c, nil, nil) 169 diff, err := configMap.Diff(nil, c, nil, nil, true)
164 if err != nil { 170 if err != nil {
165 return err 171 return err
166 } 172 }
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource.go
index d3be2d6..b5e3065 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/resource.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource.go
@@ -8,6 +8,7 @@ import (
8 8
9 "github.com/hashicorp/terraform/config" 9 "github.com/hashicorp/terraform/config"
10 "github.com/hashicorp/terraform/terraform" 10 "github.com/hashicorp/terraform/terraform"
11 "github.com/zclconf/go-cty/cty"
11) 12)
12 13
13// Resource represents a thing in Terraform that has a set of configurable 14// Resource represents a thing in Terraform that has a set of configurable
@@ -44,6 +45,12 @@ type Resource struct {
44 // their Versioning at any integer >= 1 45 // their Versioning at any integer >= 1
45 SchemaVersion int 46 SchemaVersion int
46 47
48 // MigrateState is deprecated and any new changes to a resource's schema
49 // should be handled by StateUpgraders. Existing MigrateState implementations
50 // should remain for compatibility with existing state. MigrateState will
51 // still be called if the stored SchemaVersion is less than the
52 // first version of the StateUpgraders.
53 //
47 // MigrateState is responsible for updating an InstanceState with an old 54 // MigrateState is responsible for updating an InstanceState with an old
48 // version to the format expected by the current version of the Schema. 55 // version to the format expected by the current version of the Schema.
49 // 56 //
@@ -56,6 +63,18 @@ type Resource struct {
56 // needs to make any remote API calls. 63 // needs to make any remote API calls.
57 MigrateState StateMigrateFunc 64 MigrateState StateMigrateFunc
58 65
66 // StateUpgraders contains the functions responsible for upgrading an
67 // existing state with an old schema version to a newer schema. It is
68 // called specifically by Terraform when the stored schema version is less
69 // than the current SchemaVersion of the Resource.
70 //
71 // StateUpgraders map specific schema versions to a StateUpgrader
72 // function. The registered versions are expected to be ordered,
73 // consecutive values. The initial value may be greater than 0 to account
74 // for legacy schemas that weren't recorded and can be handled by
75 // MigrateState.
76 StateUpgraders []StateUpgrader
77
59 // The functions below are the CRUD operations for this resource. 78 // The functions below are the CRUD operations for this resource.
60 // 79 //
61 // The only optional operation is Update. If Update is not implemented, 80 // The only optional operation is Update. If Update is not implemented,
@@ -136,6 +155,27 @@ type Resource struct {
136 Timeouts *ResourceTimeout 155 Timeouts *ResourceTimeout
137} 156}
138 157
158// ShimInstanceStateFromValue converts a cty.Value to a
159// terraform.InstanceState.
160func (r *Resource) ShimInstanceStateFromValue(state cty.Value) (*terraform.InstanceState, error) {
161 // Get the raw shimmed value. While this is correct, the set hashes don't
162 // match those from the Schema.
163 s := terraform.NewInstanceStateShimmedFromValue(state, r.SchemaVersion)
164
165 // We now rebuild the state through the ResourceData, so that the set indexes
166 // match what helper/schema expects.
167 data, err := schemaMap(r.Schema).Data(s, nil)
168 if err != nil {
169 return nil, err
170 }
171
172 s = data.State()
173 if s == nil {
174 s = &terraform.InstanceState{}
175 }
176 return s, nil
177}
178
139// See Resource documentation. 179// See Resource documentation.
140type CreateFunc func(*ResourceData, interface{}) error 180type CreateFunc func(*ResourceData, interface{}) error
141 181
@@ -155,6 +195,27 @@ type ExistsFunc func(*ResourceData, interface{}) (bool, error)
155type StateMigrateFunc func( 195type StateMigrateFunc func(
156 int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error) 196 int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error)
157 197
198type StateUpgrader struct {
199 // Version is the version schema that this Upgrader will handle, converting
200 // it to Version+1.
201 Version int
202
203 // Type describes the schema that this function can upgrade. Type is
204 // required to decode the schema if the state was stored in a legacy
205 // flatmap format.
206 Type cty.Type
207
208 // Upgrade takes the JSON encoded state and the provider meta value, and
209 // upgrades the state one single schema version. The provided state is
210 // deocded into the default json types using a map[string]interface{}. It
211 // is up to the StateUpgradeFunc to ensure that the returned value can be
212 // encoded using the new schema.
213 Upgrade StateUpgradeFunc
214}
215
216// See StateUpgrader
217type StateUpgradeFunc func(rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error)
218
158// See Resource documentation. 219// See Resource documentation.
159type CustomizeDiffFunc func(*ResourceDiff, interface{}) error 220type CustomizeDiffFunc func(*ResourceDiff, interface{}) error
160 221
@@ -247,7 +308,7 @@ func (r *Resource) Diff(
247 return nil, fmt.Errorf("[ERR] Error decoding timeout: %s", err) 308 return nil, fmt.Errorf("[ERR] Error decoding timeout: %s", err)
248 } 309 }
249 310
250 instanceDiff, err := schemaMap(r.Schema).Diff(s, c, r.CustomizeDiff, meta) 311 instanceDiff, err := schemaMap(r.Schema).Diff(s, c, r.CustomizeDiff, meta, true)
251 if err != nil { 312 if err != nil {
252 return instanceDiff, err 313 return instanceDiff, err
253 } 314 }
@@ -263,6 +324,45 @@ func (r *Resource) Diff(
263 return instanceDiff, err 324 return instanceDiff, err
264} 325}
265 326
327func (r *Resource) simpleDiff(
328 s *terraform.InstanceState,
329 c *terraform.ResourceConfig,
330 meta interface{}) (*terraform.InstanceDiff, error) {
331
332 t := &ResourceTimeout{}
333 err := t.ConfigDecode(r, c)
334
335 if err != nil {
336 return nil, fmt.Errorf("[ERR] Error decoding timeout: %s", err)
337 }
338
339 instanceDiff, err := schemaMap(r.Schema).Diff(s, c, r.CustomizeDiff, meta, false)
340 if err != nil {
341 return instanceDiff, err
342 }
343
344 if instanceDiff == nil {
345 log.Printf("[DEBUG] Instance Diff is nil in SimpleDiff()")
346 return nil, err
347 }
348
349 // Make sure the old value is set in each of the instance diffs.
350 // This was done by the RequiresNew logic in the full legacy Diff.
351 for k, attr := range instanceDiff.Attributes {
352 if attr == nil {
353 continue
354 }
355 if s != nil {
356 attr.Old = s.Attributes[k]
357 }
358 }
359
360 if err := t.DiffEncode(instanceDiff); err != nil {
361 log.Printf("[ERR] Error encoding timeout to instance diff: %s", err)
362 }
363 return instanceDiff, err
364}
365
266// Validate validates the resource configuration against the schema. 366// Validate validates the resource configuration against the schema.
267func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) { 367func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) {
268 warns, errs := schemaMap(r.Schema).Validate(c) 368 warns, errs := schemaMap(r.Schema).Validate(c)
@@ -300,8 +400,11 @@ func (r *Resource) ReadDataApply(
300 return r.recordCurrentSchemaVersion(state), err 400 return r.recordCurrentSchemaVersion(state), err
301} 401}
302 402
303// Refresh refreshes the state of the resource. 403// RefreshWithoutUpgrade reads the instance state, but does not call
304func (r *Resource) Refresh( 404// MigrateState or the StateUpgraders, since those are now invoked in a
405// separate API call.
406// RefreshWithoutUpgrade is part of the new plugin shims.
407func (r *Resource) RefreshWithoutUpgrade(
305 s *terraform.InstanceState, 408 s *terraform.InstanceState,
306 meta interface{}) (*terraform.InstanceState, error) { 409 meta interface{}) (*terraform.InstanceState, error) {
307 // If the ID is already somehow blank, it doesn't exist 410 // If the ID is already somehow blank, it doesn't exist
@@ -335,12 +438,60 @@ func (r *Resource) Refresh(
335 } 438 }
336 } 439 }
337 440
338 needsMigration, stateSchemaVersion := r.checkSchemaVersion(s) 441 data, err := schemaMap(r.Schema).Data(s, nil)
339 if needsMigration && r.MigrateState != nil { 442 data.timeouts = &rt
340 s, err := r.MigrateState(stateSchemaVersion, s, meta) 443 if err != nil {
444 return s, err
445 }
446
447 err = r.Read(data, meta)
448 state := data.State()
449 if state != nil && state.ID == "" {
450 state = nil
451 }
452
453 return r.recordCurrentSchemaVersion(state), err
454}
455
456// Refresh refreshes the state of the resource.
457func (r *Resource) Refresh(
458 s *terraform.InstanceState,
459 meta interface{}) (*terraform.InstanceState, error) {
460 // If the ID is already somehow blank, it doesn't exist
461 if s.ID == "" {
462 return nil, nil
463 }
464
465 rt := ResourceTimeout{}
466 if _, ok := s.Meta[TimeoutKey]; ok {
467 if err := rt.StateDecode(s); err != nil {
468 log.Printf("[ERR] Error decoding ResourceTimeout: %s", err)
469 }
470 }
471
472 if r.Exists != nil {
473 // Make a copy of data so that if it is modified it doesn't
474 // affect our Read later.
475 data, err := schemaMap(r.Schema).Data(s, nil)
476 data.timeouts = &rt
477
341 if err != nil { 478 if err != nil {
342 return s, err 479 return s, err
343 } 480 }
481
482 exists, err := r.Exists(data, meta)
483 if err != nil {
484 return s, err
485 }
486 if !exists {
487 return nil, nil
488 }
489 }
490
491 // there may be new StateUpgraders that need to be run
492 s, err := r.upgradeState(s, meta)
493 if err != nil {
494 return s, err
344 } 495 }
345 496
346 data, err := schemaMap(r.Schema).Data(s, nil) 497 data, err := schemaMap(r.Schema).Data(s, nil)
@@ -358,6 +509,71 @@ func (r *Resource) Refresh(
358 return r.recordCurrentSchemaVersion(state), err 509 return r.recordCurrentSchemaVersion(state), err
359} 510}
360 511
512func (r *Resource) upgradeState(s *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) {
513 var err error
514
515 needsMigration, stateSchemaVersion := r.checkSchemaVersion(s)
516 migrate := needsMigration && r.MigrateState != nil
517
518 if migrate {
519 s, err = r.MigrateState(stateSchemaVersion, s, meta)
520 if err != nil {
521 return s, err
522 }
523 }
524
525 if len(r.StateUpgraders) == 0 {
526 return s, nil
527 }
528
529 // If we ran MigrateState, then the stateSchemaVersion value is no longer
530 // correct. We can expect the first upgrade function to be the correct
531 // schema type version.
532 if migrate {
533 stateSchemaVersion = r.StateUpgraders[0].Version
534 }
535
536 schemaType := r.CoreConfigSchema().ImpliedType()
537 // find the expected type to convert the state
538 for _, upgrader := range r.StateUpgraders {
539 if stateSchemaVersion == upgrader.Version {
540 schemaType = upgrader.Type
541 }
542 }
543
544 // StateUpgraders only operate on the new JSON format state, so the state
545 // need to be converted.
546 stateVal, err := StateValueFromInstanceState(s, schemaType)
547 if err != nil {
548 return nil, err
549 }
550
551 jsonState, err := StateValueToJSONMap(stateVal, schemaType)
552 if err != nil {
553 return nil, err
554 }
555
556 for _, upgrader := range r.StateUpgraders {
557 if stateSchemaVersion != upgrader.Version {
558 continue
559 }
560
561 jsonState, err = upgrader.Upgrade(jsonState, meta)
562 if err != nil {
563 return nil, err
564 }
565 stateSchemaVersion++
566 }
567
568 // now we need to re-flatmap the new state
569 stateVal, err = JSONMapToStateValue(jsonState, r.CoreConfigSchema())
570 if err != nil {
571 return nil, err
572 }
573
574 return r.ShimInstanceStateFromValue(stateVal)
575}
576
361// InternalValidate should be called to validate the structure 577// InternalValidate should be called to validate the structure
362// of the resource. 578// of the resource.
363// 579//
@@ -437,6 +653,31 @@ func (r *Resource) InternalValidate(topSchemaMap schemaMap, writable bool) error
437 } 653 }
438 } 654 }
439 655
656 lastVersion := -1
657 for _, u := range r.StateUpgraders {
658 if lastVersion >= 0 && u.Version-lastVersion > 1 {
659 return fmt.Errorf("missing schema version between %d and %d", lastVersion, u.Version)
660 }
661
662 if u.Version >= r.SchemaVersion {
663 return fmt.Errorf("StateUpgrader version %d is >= current version %d", u.Version, r.SchemaVersion)
664 }
665
666 if !u.Type.IsObjectType() {
667 return fmt.Errorf("StateUpgrader %d type is not cty.Object", u.Version)
668 }
669
670 if u.Upgrade == nil {
671 return fmt.Errorf("StateUpgrader %d missing StateUpgradeFunc", u.Version)
672 }
673
674 lastVersion = u.Version
675 }
676
677 if lastVersion >= 0 && lastVersion != r.SchemaVersion-1 {
678 return fmt.Errorf("missing StateUpgrader between %d and %d", lastVersion, r.SchemaVersion)
679 }
680
440 // Data source 681 // Data source
441 if r.isTopLevel() && !writable { 682 if r.isTopLevel() && !writable {
442 tsm = schemaMap(r.Schema) 683 tsm = schemaMap(r.Schema)
@@ -513,6 +754,13 @@ func (r *Resource) TestResourceData() *ResourceData {
513 } 754 }
514} 755}
515 756
757// SchemasForFlatmapPath tries its best to find a sequence of schemas that
758// the given dot-delimited attribute path traverses through in the schema
759// of the receiving Resource.
760func (r *Resource) SchemasForFlatmapPath(path string) []*Schema {
761 return SchemasForFlatmapPath(path, r.Schema)
762}
763
516// Returns true if the resource is "top level" i.e. not a sub-resource. 764// Returns true if the resource is "top level" i.e. not a sub-resource.
517func (r *Resource) isTopLevel() bool { 765func (r *Resource) isTopLevel() bool {
518 // TODO: This is a heuristic; replace with a definitive attribute? 766 // TODO: This is a heuristic; replace with a definitive attribute?
@@ -538,7 +786,15 @@ func (r *Resource) checkSchemaVersion(is *terraform.InstanceState) (bool, int) {
538 } 786 }
539 787
540 stateSchemaVersion, _ := strconv.Atoi(rawString) 788 stateSchemaVersion, _ := strconv.Atoi(rawString)
541 return stateSchemaVersion < r.SchemaVersion, stateSchemaVersion 789
790 // Don't run MigrateState if the version is handled by a StateUpgrader,
791 // since StateMigrateFuncs are not required to handle unknown versions
792 maxVersion := r.SchemaVersion
793 if len(r.StateUpgraders) > 0 {
794 maxVersion = r.StateUpgraders[0].Version
795 }
796
797 return stateSchemaVersion < maxVersion, stateSchemaVersion
542} 798}
543 799
544func (r *Resource) recordCurrentSchemaVersion( 800func (r *Resource) recordCurrentSchemaVersion(
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go
index 6cc01ee..1c39070 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go
@@ -52,6 +52,8 @@ type getResult struct {
52// UnsafeSetFieldRaw allows setting arbitrary values in state to arbitrary 52// UnsafeSetFieldRaw allows setting arbitrary values in state to arbitrary
53// values, bypassing schema. This MUST NOT be used in normal circumstances - 53// values, bypassing schema. This MUST NOT be used in normal circumstances -
54// it exists only to support the remote_state data source. 54// it exists only to support the remote_state data source.
55//
56// Deprecated: Fully define schema attributes and use Set() instead.
55func (d *ResourceData) UnsafeSetFieldRaw(key string, value string) { 57func (d *ResourceData) UnsafeSetFieldRaw(key string, value string) {
56 d.once.Do(d.init) 58 d.once.Do(d.init)
57 59
@@ -219,10 +221,16 @@ func (d *ResourceData) Id() string {
219 221
220 if d.state != nil { 222 if d.state != nil {
221 result = d.state.ID 223 result = d.state.ID
224 if result == "" {
225 result = d.state.Attributes["id"]
226 }
222 } 227 }
223 228
224 if d.newState != nil { 229 if d.newState != nil {
225 result = d.newState.ID 230 result = d.newState.ID
231 if result == "" {
232 result = d.newState.Attributes["id"]
233 }
226 } 234 }
227 235
228 return result 236 return result
@@ -246,6 +254,18 @@ func (d *ResourceData) ConnInfo() map[string]string {
246func (d *ResourceData) SetId(v string) { 254func (d *ResourceData) SetId(v string) {
247 d.once.Do(d.init) 255 d.once.Do(d.init)
248 d.newState.ID = v 256 d.newState.ID = v
257
258 // once we transition away from the legacy state types, "id" will no longer
259 // be a special field, and will become a normal attribute.
260 // set the attribute normally
261 d.setWriter.unsafeWriteField("id", v)
262
263 // Make sure the newState is also set, otherwise the old value
264 // may get precedence.
265 if d.newState.Attributes == nil {
266 d.newState.Attributes = map[string]string{}
267 }
268 d.newState.Attributes["id"] = v
249} 269}
250 270
251// SetConnInfo sets the connection info for a resource. 271// SetConnInfo sets the connection info for a resource.
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go
index 7db3dec..47b5481 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go
@@ -367,7 +367,7 @@ func (d *ResourceDiff) Get(key string) interface{} {
367} 367}
368 368
369// GetChange gets the change between the state and diff, checking first to see 369// GetChange gets the change between the state and diff, checking first to see
370// if a overridden diff exists. 370// if an overridden diff exists.
371// 371//
372// This implementation differs from ResourceData's in the way that we first get 372// This implementation differs from ResourceData's in the way that we first get
373// results from the exact levels for the new diff, then from state and diff as 373// results from the exact levels for the new diff, then from state and diff as
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource_timeout.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource_timeout.go
index 445819f..9e422c1 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/resource_timeout.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource_timeout.go
@@ -5,6 +5,7 @@ import (
5 "log" 5 "log"
6 "time" 6 "time"
7 7
8 "github.com/hashicorp/terraform/config"
8 "github.com/hashicorp/terraform/terraform" 9 "github.com/hashicorp/terraform/terraform"
9 "github.com/mitchellh/copystructure" 10 "github.com/mitchellh/copystructure"
10) 11)
@@ -62,55 +63,70 @@ func (t *ResourceTimeout) ConfigDecode(s *Resource, c *terraform.ResourceConfig)
62 } 63 }
63 64
64 if raw, ok := c.Config[TimeoutsConfigKey]; ok { 65 if raw, ok := c.Config[TimeoutsConfigKey]; ok {
65 if configTimeouts, ok := raw.([]map[string]interface{}); ok { 66 var rawTimeouts []map[string]interface{}
66 for _, timeoutValues := range configTimeouts { 67 switch raw := raw.(type) {
67 // loop through each Timeout given in the configuration and validate they 68 case map[string]interface{}:
68 // the Timeout defined in the resource 69 rawTimeouts = append(rawTimeouts, raw)
69 for timeKey, timeValue := range timeoutValues { 70 case []map[string]interface{}:
70 // validate that we're dealing with the normal CRUD actions 71 rawTimeouts = raw
71 var found bool 72 case string:
72 for _, key := range timeoutKeys() { 73 if raw == config.UnknownVariableValue {
73 if timeKey == key { 74 // Timeout is not defined in the config
74 found = true 75 // Defaults will be used instead
75 break 76 return nil
76 } 77 } else {
77 } 78 log.Printf("[ERROR] Invalid timeout value: %q", raw)
79 return fmt.Errorf("Invalid Timeout value found")
80 }
81 default:
82 log.Printf("[ERROR] Invalid timeout structure: %#v", raw)
83 return fmt.Errorf("Invalid Timeout structure found")
84 }
78 85
79 if !found { 86 for _, timeoutValues := range rawTimeouts {
80 return fmt.Errorf("Unsupported Timeout configuration key found (%s)", timeKey) 87 for timeKey, timeValue := range timeoutValues {
88 // validate that we're dealing with the normal CRUD actions
89 var found bool
90 for _, key := range timeoutKeys() {
91 if timeKey == key {
92 found = true
93 break
81 } 94 }
95 }
82 96
83 // Get timeout 97 if !found {
84 rt, err := time.ParseDuration(timeValue.(string)) 98 return fmt.Errorf("Unsupported Timeout configuration key found (%s)", timeKey)
85 if err != nil { 99 }
86 return fmt.Errorf("Error parsing Timeout for (%s): %s", timeKey, err)
87 }
88 100
89 var timeout *time.Duration 101 // Get timeout
90 switch timeKey { 102 rt, err := time.ParseDuration(timeValue.(string))
91 case TimeoutCreate: 103 if err != nil {
92 timeout = t.Create 104 return fmt.Errorf("Error parsing %q timeout: %s", timeKey, err)
93 case TimeoutUpdate: 105 }
94 timeout = t.Update
95 case TimeoutRead:
96 timeout = t.Read
97 case TimeoutDelete:
98 timeout = t.Delete
99 case TimeoutDefault:
100 timeout = t.Default
101 }
102 106
103 // If the resource has not delcared this in the definition, then error 107 var timeout *time.Duration
104 // with an unsupported message 108 switch timeKey {
105 if timeout == nil { 109 case TimeoutCreate:
106 return unsupportedTimeoutKeyError(timeKey) 110 timeout = t.Create
107 } 111 case TimeoutUpdate:
112 timeout = t.Update
113 case TimeoutRead:
114 timeout = t.Read
115 case TimeoutDelete:
116 timeout = t.Delete
117 case TimeoutDefault:
118 timeout = t.Default
119 }
108 120
109 *timeout = rt 121 // If the resource has not delcared this in the definition, then error
122 // with an unsupported message
123 if timeout == nil {
124 return unsupportedTimeoutKeyError(timeKey)
110 } 125 }
126
127 *timeout = rt
111 } 128 }
112 } else { 129 return nil
113 log.Printf("[WARN] Invalid Timeout structure found, skipping timeouts")
114 } 130 }
115 } 131 }
116 132
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/schema.go b/vendor/github.com/hashicorp/terraform/helper/schema/schema.go
index 0ea5aad..6a3c15a 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/schema.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/schema.go
@@ -12,6 +12,7 @@
12package schema 12package schema
13 13
14import ( 14import (
15 "context"
15 "fmt" 16 "fmt"
16 "os" 17 "os"
17 "reflect" 18 "reflect"
@@ -19,7 +20,9 @@ import (
19 "sort" 20 "sort"
20 "strconv" 21 "strconv"
21 "strings" 22 "strings"
23 "sync"
22 24
25 "github.com/hashicorp/terraform/config"
23 "github.com/hashicorp/terraform/terraform" 26 "github.com/hashicorp/terraform/terraform"
24 "github.com/mitchellh/copystructure" 27 "github.com/mitchellh/copystructure"
25 "github.com/mitchellh/mapstructure" 28 "github.com/mitchellh/mapstructure"
@@ -31,6 +34,27 @@ const PanicOnErr = "TF_SCHEMA_PANIC_ON_ERROR"
31// type used for schema package context keys 34// type used for schema package context keys
32type contextKey string 35type contextKey string
33 36
37var (
38 protoVersionMu sync.Mutex
39 protoVersion5 = false
40)
41
42func isProto5() bool {
43 protoVersionMu.Lock()
44 defer protoVersionMu.Unlock()
45 return protoVersion5
46
47}
48
49// SetProto5 enables a feature flag for any internal changes required required
50// to work with the new plugin protocol. This should not be called by
51// provider.
52func SetProto5() {
53 protoVersionMu.Lock()
54 defer protoVersionMu.Unlock()
55 protoVersion5 = true
56}
57
34// Schema is used to describe the structure of a value. 58// Schema is used to describe the structure of a value.
35// 59//
36// Read the documentation of the struct elements for important details. 60// Read the documentation of the struct elements for important details.
@@ -51,6 +75,26 @@ type Schema struct {
51 // 75 //
52 Type ValueType 76 Type ValueType
53 77
78 // ConfigMode allows for overriding the default behaviors for mapping
79 // schema entries onto configuration constructs.
80 //
81 // By default, the Elem field is used to choose whether a particular
82 // schema is represented in configuration as an attribute or as a nested
83 // block; if Elem is a *schema.Resource then it's a block and it's an
84 // attribute otherwise.
85 //
86 // If Elem is *schema.Resource then setting ConfigMode to
87 // SchemaConfigModeAttr will force it to be represented in configuration
88 // as an attribute, which means that the Computed flag can be used to
89 // provide default elements when the argument isn't set at all, while still
90 // allowing the user to force zero elements by explicitly assigning an
91 // empty list.
92 //
93 // When Computed is set without Optional, the attribute is not settable
94 // in configuration at all and so SchemaConfigModeAttr is the automatic
95 // behavior, and SchemaConfigModeBlock is not permitted.
96 ConfigMode SchemaConfigMode
97
54 // If one of these is set, then this item can come from the configuration. 98 // If one of these is set, then this item can come from the configuration.
55 // Both cannot be set. If Optional is set, the value is optional. If 99 // Both cannot be set. If Optional is set, the value is optional. If
56 // Required is set, the value is required. 100 // Required is set, the value is required.
@@ -123,7 +167,8 @@ type Schema struct {
123 // The following fields are only set for a TypeList, TypeSet, or TypeMap. 167 // The following fields are only set for a TypeList, TypeSet, or TypeMap.
124 // 168 //
125 // Elem represents the element type. For a TypeMap, it must be a *Schema 169 // Elem represents the element type. For a TypeMap, it must be a *Schema
126 // with a Type of TypeString, otherwise it may be either a *Schema or a 170 // with a Type that is one of the primitives: TypeString, TypeBool,
171 // TypeInt, or TypeFloat. Otherwise it may be either a *Schema or a
127 // *Resource. If it is *Schema, the element type is just a simple value. 172 // *Resource. If it is *Schema, the element type is just a simple value.
128 // If it is *Resource, the element type is a complex structure, 173 // If it is *Resource, the element type is a complex structure,
129 // potentially with its own lifecycle. 174 // potentially with its own lifecycle.
@@ -141,13 +186,17 @@ type Schema struct {
141 // used to wrap a complex structure, however less than one instance would 186 // used to wrap a complex structure, however less than one instance would
142 // cause instability. 187 // cause instability.
143 // 188 //
144 // PromoteSingle, if true, will allow single elements to be standalone 189 // If the field Optional is set to true then MinItems is ignored and thus
145 // and promote them to a list. For example "foo" would be promoted to 190 // effectively zero.
146 // ["foo"] automatically. This is primarily for legacy reasons and the 191 MaxItems int
147 // ambiguity is not recommended for new usage. Promotion is only allowed 192 MinItems int
148 // for primitive element types. 193
149 MaxItems int 194 // PromoteSingle originally allowed for a single element to be assigned
150 MinItems int 195 // where a primitive list was expected, but this no longer works from
196 // Terraform v0.12 onwards (Terraform Core will require a list to be set
197 // regardless of what this is set to) and so only applies to Terraform v0.11
198 // and earlier, and so should be used only to retain this functionality
199 // for those still using v0.11 with a provider that formerly used this.
151 PromoteSingle bool 200 PromoteSingle bool
152 201
153 // The following fields are only valid for a TypeSet type. 202 // The following fields are only valid for a TypeSet type.
@@ -189,7 +238,8 @@ type Schema struct {
189 // guaranteed to be of the proper Schema type, and it can yield warnings or 238 // guaranteed to be of the proper Schema type, and it can yield warnings or
190 // errors based on inspection of that value. 239 // errors based on inspection of that value.
191 // 240 //
192 // ValidateFunc currently only works for primitive types. 241 // ValidateFunc is honored only when the schema's Type is set to TypeInt,
242 // TypeFloat, TypeString, TypeBool, or TypeMap. It is ignored for all other types.
193 ValidateFunc SchemaValidateFunc 243 ValidateFunc SchemaValidateFunc
194 244
195 // Sensitive ensures that the attribute's value does not get displayed in 245 // Sensitive ensures that the attribute's value does not get displayed in
@@ -199,6 +249,17 @@ type Schema struct {
199 Sensitive bool 249 Sensitive bool
200} 250}
201 251
252// SchemaConfigMode is used to influence how a schema item is mapped into a
253// corresponding configuration construct, using the ConfigMode field of
254// Schema.
255type SchemaConfigMode int
256
257const (
258 SchemaConfigModeAuto SchemaConfigMode = iota
259 SchemaConfigModeAttr
260 SchemaConfigModeBlock
261)
262
202// SchemaDiffSuppressFunc is a function which can be used to determine 263// SchemaDiffSuppressFunc is a function which can be used to determine
203// whether a detected diff on a schema element is "valid" or not, and 264// whether a detected diff on a schema element is "valid" or not, and
204// suppress it from the plan if necessary. 265// suppress it from the plan if necessary.
@@ -364,6 +425,11 @@ func (s *Schema) finalizeDiff(d *terraform.ResourceAttrDiff, customized bool) *t
364 return d 425 return d
365} 426}
366 427
428// InternalMap is used to aid in the transition to the new schema types and
429// protocol. The name is not meant to convey any usefulness, as this is not to
430// be used directly by any providers.
431type InternalMap = schemaMap
432
367// schemaMap is a wrapper that adds nice functions on top of schemas. 433// schemaMap is a wrapper that adds nice functions on top of schemas.
368type schemaMap map[string]*Schema 434type schemaMap map[string]*Schema
369 435
@@ -404,7 +470,8 @@ func (m schemaMap) Diff(
404 s *terraform.InstanceState, 470 s *terraform.InstanceState,
405 c *terraform.ResourceConfig, 471 c *terraform.ResourceConfig,
406 customizeDiff CustomizeDiffFunc, 472 customizeDiff CustomizeDiffFunc,
407 meta interface{}) (*terraform.InstanceDiff, error) { 473 meta interface{},
474 handleRequiresNew bool) (*terraform.InstanceDiff, error) {
408 result := new(terraform.InstanceDiff) 475 result := new(terraform.InstanceDiff)
409 result.Attributes = make(map[string]*terraform.ResourceAttrDiff) 476 result.Attributes = make(map[string]*terraform.ResourceAttrDiff)
410 477
@@ -450,82 +517,85 @@ func (m schemaMap) Diff(
450 } 517 }
451 } 518 }
452 519
453 // If the diff requires a new resource, then we recompute the diff 520 if handleRequiresNew {
454 // so we have the complete new resource diff, and preserve the 521 // If the diff requires a new resource, then we recompute the diff
455 // RequiresNew fields where necessary so the user knows exactly what 522 // so we have the complete new resource diff, and preserve the
456 // caused that. 523 // RequiresNew fields where necessary so the user knows exactly what
457 if result.RequiresNew() { 524 // caused that.
458 // Create the new diff 525 if result.RequiresNew() {
459 result2 := new(terraform.InstanceDiff) 526 // Create the new diff
460 result2.Attributes = make(map[string]*terraform.ResourceAttrDiff) 527 result2 := new(terraform.InstanceDiff)
461 528 result2.Attributes = make(map[string]*terraform.ResourceAttrDiff)
462 // Preserve the DestroyTainted flag
463 result2.DestroyTainted = result.DestroyTainted
464 529
465 // Reset the data to not contain state. We have to call init() 530 // Preserve the DestroyTainted flag
466 // again in order to reset the FieldReaders. 531 result2.DestroyTainted = result.DestroyTainted
467 d.state = nil
468 d.init()
469 532
470 // Perform the diff again 533 // Reset the data to not contain state. We have to call init()
471 for k, schema := range m { 534 // again in order to reset the FieldReaders.
472 err := m.diff(k, schema, result2, d, false) 535 d.state = nil
473 if err != nil { 536 d.init()
474 return nil, err
475 }
476 }
477 537
478 // Re-run customization 538 // Perform the diff again
479 if !result2.DestroyTainted && customizeDiff != nil { 539 for k, schema := range m {
480 mc := m.DeepCopy() 540 err := m.diff(k, schema, result2, d, false)
481 rd := newResourceDiff(mc, c, d.state, result2)
482 if err := customizeDiff(rd, meta); err != nil {
483 return nil, err
484 }
485 for _, k := range rd.UpdatedKeys() {
486 err := m.diff(k, mc[k], result2, rd, false)
487 if err != nil { 541 if err != nil {
488 return nil, err 542 return nil, err
489 } 543 }
490 } 544 }
491 }
492 545
493 // Force all the fields to not force a new since we know what we 546 // Re-run customization
494 // want to force new. 547 if !result2.DestroyTainted && customizeDiff != nil {
495 for k, attr := range result2.Attributes { 548 mc := m.DeepCopy()
496 if attr == nil { 549 rd := newResourceDiff(mc, c, d.state, result2)
497 continue 550 if err := customizeDiff(rd, meta); err != nil {
551 return nil, err
552 }
553 for _, k := range rd.UpdatedKeys() {
554 err := m.diff(k, mc[k], result2, rd, false)
555 if err != nil {
556 return nil, err
557 }
558 }
498 } 559 }
499 560
500 if attr.RequiresNew { 561 // Force all the fields to not force a new since we know what we
501 attr.RequiresNew = false 562 // want to force new.
502 } 563 for k, attr := range result2.Attributes {
564 if attr == nil {
565 continue
566 }
503 567
504 if s != nil { 568 if attr.RequiresNew {
505 attr.Old = s.Attributes[k] 569 attr.RequiresNew = false
506 } 570 }
507 }
508 571
509 // Now copy in all the requires new diffs... 572 if s != nil {
510 for k, attr := range result.Attributes { 573 attr.Old = s.Attributes[k]
511 if attr == nil { 574 }
512 continue
513 } 575 }
514 576
515 newAttr, ok := result2.Attributes[k] 577 // Now copy in all the requires new diffs...
516 if !ok { 578 for k, attr := range result.Attributes {
517 newAttr = attr 579 if attr == nil {
518 } 580 continue
581 }
519 582
520 if attr.RequiresNew { 583 newAttr, ok := result2.Attributes[k]
521 newAttr.RequiresNew = true 584 if !ok {
585 newAttr = attr
586 }
587
588 if attr.RequiresNew {
589 newAttr.RequiresNew = true
590 }
591
592 result2.Attributes[k] = newAttr
522 } 593 }
523 594
524 result2.Attributes[k] = newAttr 595 // And set the diff!
596 result = result2
525 } 597 }
526 598
527 // And set the diff!
528 result = result2
529 } 599 }
530 600
531 // Go through and detect all of the ComputedWhens now that we've 601 // Go through and detect all of the ComputedWhens now that we've
@@ -611,6 +681,10 @@ func (m schemaMap) Validate(c *terraform.ResourceConfig) ([]string, []error) {
611// from a unit test (and not in user-path code) to verify that a schema 681// from a unit test (and not in user-path code) to verify that a schema
612// is properly built. 682// is properly built.
613func (m schemaMap) InternalValidate(topSchemaMap schemaMap) error { 683func (m schemaMap) InternalValidate(topSchemaMap schemaMap) error {
684 return m.internalValidate(topSchemaMap, false)
685}
686
687func (m schemaMap) internalValidate(topSchemaMap schemaMap, attrsOnly bool) error {
614 if topSchemaMap == nil { 688 if topSchemaMap == nil {
615 topSchemaMap = m 689 topSchemaMap = m
616 } 690 }
@@ -631,6 +705,34 @@ func (m schemaMap) InternalValidate(topSchemaMap schemaMap) error {
631 return fmt.Errorf("%s: One of optional, required, or computed must be set", k) 705 return fmt.Errorf("%s: One of optional, required, or computed must be set", k)
632 } 706 }
633 707
708 computedOnly := v.Computed && !v.Optional
709
710 switch v.ConfigMode {
711 case SchemaConfigModeBlock:
712 if _, ok := v.Elem.(*Resource); !ok {
713 return fmt.Errorf("%s: ConfigMode of block is allowed only when Elem is *schema.Resource", k)
714 }
715 if attrsOnly {
716 return fmt.Errorf("%s: ConfigMode of block cannot be used in child of schema with ConfigMode of attribute", k)
717 }
718 if computedOnly {
719 return fmt.Errorf("%s: ConfigMode of block cannot be used for computed schema", k)
720 }
721 case SchemaConfigModeAttr:
722 // anything goes
723 case SchemaConfigModeAuto:
724 // Since "Auto" for Elem: *Resource would create a nested block,
725 // and that's impossible inside an attribute, we require it to be
726 // explicitly overridden as mode "Attr" for clarity.
727 if _, ok := v.Elem.(*Resource); ok {
728 if attrsOnly {
729 return fmt.Errorf("%s: in *schema.Resource with ConfigMode of attribute, so must also have ConfigMode of attribute", k)
730 }
731 }
732 default:
733 return fmt.Errorf("%s: invalid ConfigMode value", k)
734 }
735
634 if v.Computed && v.Default != nil { 736 if v.Computed && v.Default != nil {
635 return fmt.Errorf("%s: Default must be nil if computed", k) 737 return fmt.Errorf("%s: Default must be nil if computed", k)
636 } 738 }
@@ -695,7 +797,9 @@ func (m schemaMap) InternalValidate(topSchemaMap schemaMap) error {
695 797
696 switch t := v.Elem.(type) { 798 switch t := v.Elem.(type) {
697 case *Resource: 799 case *Resource:
698 if err := t.InternalValidate(topSchemaMap, true); err != nil { 800 attrsOnly := attrsOnly || v.ConfigMode == SchemaConfigModeAttr
801
802 if err := schemaMap(t.Schema).internalValidate(topSchemaMap, attrsOnly); err != nil {
699 return err 803 return err
700 } 804 }
701 case *Schema: 805 case *Schema:
@@ -785,10 +889,19 @@ func (m schemaMap) diff(
785 for attrK, attrV := range unsupressedDiff.Attributes { 889 for attrK, attrV := range unsupressedDiff.Attributes {
786 switch rd := d.(type) { 890 switch rd := d.(type) {
787 case *ResourceData: 891 case *ResourceData:
788 if schema.DiffSuppressFunc != nil && 892 if schema.DiffSuppressFunc != nil && attrV != nil &&
789 attrV != nil &&
790 schema.DiffSuppressFunc(attrK, attrV.Old, attrV.New, rd) { 893 schema.DiffSuppressFunc(attrK, attrV.Old, attrV.New, rd) {
791 continue 894 // If this attr diff is suppressed, we may still need it in the
895 // overall diff if it's contained within a set. Rather than
896 // dropping the diff, make it a NOOP.
897 if !all {
898 continue
899 }
900
901 attrV = &terraform.ResourceAttrDiff{
902 Old: attrV.Old,
903 New: attrV.Old,
904 }
792 } 905 }
793 } 906 }
794 diff.Attributes[attrK] = attrV 907 diff.Attributes[attrK] = attrV
@@ -1171,7 +1284,7 @@ func (m schemaMap) diffString(
1171 return fmt.Errorf("%s: %s", k, err) 1284 return fmt.Errorf("%s: %s", k, err)
1172 } 1285 }
1173 1286
1174 if os == ns && !all { 1287 if os == ns && !all && !computed {
1175 // They're the same value. If there old value is not blank or we 1288 // They're the same value. If there old value is not blank or we
1176 // have an ID, then return right away since we're already setup. 1289 // have an ID, then return right away since we're already setup.
1177 if os != "" || d.Id() != "" { 1290 if os != "" || d.Id() != "" {
@@ -1179,7 +1292,7 @@ func (m schemaMap) diffString(
1179 } 1292 }
1180 1293
1181 // Otherwise, only continue if we're computed 1294 // Otherwise, only continue if we're computed
1182 if !schema.Computed && !computed { 1295 if !schema.Computed {
1183 return nil 1296 return nil
1184 } 1297 }
1185 } 1298 }
@@ -1210,7 +1323,7 @@ func (m schemaMap) inputString(
1210 input terraform.UIInput, 1323 input terraform.UIInput,
1211 k string, 1324 k string,
1212 schema *Schema) (interface{}, error) { 1325 schema *Schema) (interface{}, error) {
1213 result, err := input.Input(&terraform.InputOpts{ 1326 result, err := input.Input(context.Background(), &terraform.InputOpts{
1214 Id: k, 1327 Id: k,
1215 Query: k, 1328 Query: k,
1216 Description: schema.Description, 1329 Description: schema.Description,
@@ -1252,6 +1365,13 @@ func (m schemaMap) validate(
1252 "%q: this field cannot be set", k)} 1365 "%q: this field cannot be set", k)}
1253 } 1366 }
1254 1367
1368 if raw == config.UnknownVariableValue {
1369 // If the value is unknown then we can't validate it yet.
1370 // In particular, this avoids spurious type errors where downstream
1371 // validation code sees UnknownVariableValue as being just a string.
1372 return nil, nil
1373 }
1374
1255 err := m.validateConflictingAttributes(k, schema, c) 1375 err := m.validateConflictingAttributes(k, schema, c)
1256 if err != nil { 1376 if err != nil {
1257 return nil, []error{err} 1377 return nil, []error{err}
@@ -1269,10 +1389,15 @@ func (m schemaMap) validateConflictingAttributes(
1269 return nil 1389 return nil
1270 } 1390 }
1271 1391
1272 for _, conflicting_key := range schema.ConflictsWith { 1392 for _, conflictingKey := range schema.ConflictsWith {
1273 if _, ok := c.Get(conflicting_key); ok { 1393 if raw, ok := c.Get(conflictingKey); ok {
1394 if raw == config.UnknownVariableValue {
1395 // An unknown value might become unset (null) once known, so
1396 // we must defer validation until it's known.
1397 continue
1398 }
1274 return fmt.Errorf( 1399 return fmt.Errorf(
1275 "%q: conflicts with %s", k, conflicting_key) 1400 "%q: conflicts with %s", k, conflictingKey)
1276 } 1401 }
1277 } 1402 }
1278 1403
@@ -1284,6 +1409,13 @@ func (m schemaMap) validateList(
1284 raw interface{}, 1409 raw interface{},
1285 schema *Schema, 1410 schema *Schema,
1286 c *terraform.ResourceConfig) ([]string, []error) { 1411 c *terraform.ResourceConfig) ([]string, []error) {
1412 // first check if the list is wholly unknown
1413 if s, ok := raw.(string); ok {
1414 if s == config.UnknownVariableValue {
1415 return nil, nil
1416 }
1417 }
1418
1287 // We use reflection to verify the slice because you can't 1419 // We use reflection to verify the slice because you can't
1288 // case to []interface{} unless the slice is exactly that type. 1420 // case to []interface{} unless the slice is exactly that type.
1289 rawV := reflect.ValueOf(raw) 1421 rawV := reflect.ValueOf(raw)
@@ -1355,6 +1487,13 @@ func (m schemaMap) validateMap(
1355 raw interface{}, 1487 raw interface{},
1356 schema *Schema, 1488 schema *Schema,
1357 c *terraform.ResourceConfig) ([]string, []error) { 1489 c *terraform.ResourceConfig) ([]string, []error) {
1490 // first check if the list is wholly unknown
1491 if s, ok := raw.(string); ok {
1492 if s == config.UnknownVariableValue {
1493 return nil, nil
1494 }
1495 }
1496
1358 // We use reflection to verify the slice because you can't 1497 // We use reflection to verify the slice because you can't
1359 // case to []interface{} unless the slice is exactly that type. 1498 // case to []interface{} unless the slice is exactly that type.
1360 rawV := reflect.ValueOf(raw) 1499 rawV := reflect.ValueOf(raw)
@@ -1556,12 +1695,25 @@ func (m schemaMap) validatePrimitive(
1556 } 1695 }
1557 decoded = n 1696 decoded = n
1558 case TypeInt: 1697 case TypeInt:
1559 // Verify that we can parse this as an int 1698 switch {
1560 var n int 1699 case isProto5():
1561 if err := mapstructure.WeakDecode(raw, &n); err != nil { 1700 // We need to verify the type precisely, because WeakDecode will
1562 return nil, []error{fmt.Errorf("%s: %s", k, err)} 1701 // decode a float as an integer.
1702
1703 // the config shims only use int for integral number values
1704 if v, ok := raw.(int); ok {
1705 decoded = v
1706 } else {
1707 return nil, []error{fmt.Errorf("%s: must be a whole number, got %v", k, raw)}
1708 }
1709 default:
1710 // Verify that we can parse this as an int
1711 var n int
1712 if err := mapstructure.WeakDecode(raw, &n); err != nil {
1713 return nil, []error{fmt.Errorf("%s: %s", k, err)}
1714 }
1715 decoded = n
1563 } 1716 }
1564 decoded = n
1565 case TypeFloat: 1717 case TypeFloat:
1566 // Verify that we can parse this as an int 1718 // Verify that we can parse this as an int
1567 var n float64 1719 var n float64
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/set.go b/vendor/github.com/hashicorp/terraform/helper/schema/set.go
index cba2890..8ee89e4 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/set.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/set.go
@@ -198,6 +198,16 @@ func (s *Set) add(item interface{}, computed bool) string {
198 code := s.hash(item) 198 code := s.hash(item)
199 if computed { 199 if computed {
200 code = "~" + code 200 code = "~" + code
201
202 if isProto5() {
203 tmpCode := code
204 count := 0
205 for _, exists := s.m[tmpCode]; exists; _, exists = s.m[tmpCode] {
206 count++
207 tmpCode = fmt.Sprintf("%s%d", code, count)
208 }
209 code = tmpCode
210 }
201 } 211 }
202 212
203 if _, ok := s.m[code]; !ok { 213 if _, ok := s.m[code]; !ok {
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/shims.go b/vendor/github.com/hashicorp/terraform/helper/schema/shims.go
new file mode 100644
index 0000000..203d017
--- /dev/null
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/shims.go
@@ -0,0 +1,115 @@
1package schema
2
3import (
4 "encoding/json"
5
6 "github.com/zclconf/go-cty/cty"
7 ctyjson "github.com/zclconf/go-cty/cty/json"
8
9 "github.com/hashicorp/terraform/config"
10 "github.com/hashicorp/terraform/configs/configschema"
11 "github.com/hashicorp/terraform/terraform"
12)
13
14// DiffFromValues takes the current state and desired state as cty.Values and
15// derives a terraform.InstanceDiff to give to the legacy providers. This is
16// used to take the states provided by the new ApplyResourceChange method and
17// convert them to a state+diff required for the legacy Apply method.
18func DiffFromValues(prior, planned cty.Value, res *Resource) (*terraform.InstanceDiff, error) {
19 return diffFromValues(prior, planned, res, nil)
20}
21
22// diffFromValues takes an additional CustomizeDiffFunc, so we can generate our
23// test fixtures from the legacy tests. In the new provider protocol the diff
24// only needs to be created for the apply operation, and any customizations
25// have already been done.
26func diffFromValues(prior, planned cty.Value, res *Resource, cust CustomizeDiffFunc) (*terraform.InstanceDiff, error) {
27 instanceState, err := res.ShimInstanceStateFromValue(prior)
28 if err != nil {
29 return nil, err
30 }
31
32 configSchema := res.CoreConfigSchema()
33
34 cfg := terraform.NewResourceConfigShimmed(planned, configSchema)
35 removeConfigUnknowns(cfg.Config)
36 removeConfigUnknowns(cfg.Raw)
37
38 diff, err := schemaMap(res.Schema).Diff(instanceState, cfg, cust, nil, false)
39 if err != nil {
40 return nil, err
41 }
42
43 return diff, err
44}
45
46// During apply the only unknown values are those which are to be computed by
47// the resource itself. These may have been marked as unknown config values, and
48// need to be removed to prevent the UnknownVariableValue from appearing the diff.
49func removeConfigUnknowns(cfg map[string]interface{}) {
50 for k, v := range cfg {
51 switch v := v.(type) {
52 case string:
53 if v == config.UnknownVariableValue {
54 delete(cfg, k)
55 }
56 case []interface{}:
57 for _, i := range v {
58 if m, ok := i.(map[string]interface{}); ok {
59 removeConfigUnknowns(m)
60 }
61 }
62 case map[string]interface{}:
63 removeConfigUnknowns(v)
64 }
65 }
66}
67
68// ApplyDiff takes a cty.Value state and applies a terraform.InstanceDiff to
69// get a new cty.Value state. This is used to convert the diff returned from
70// the legacy provider Diff method to the state required for the new
71// PlanResourceChange method.
72func ApplyDiff(base cty.Value, d *terraform.InstanceDiff, schema *configschema.Block) (cty.Value, error) {
73 return d.ApplyToValue(base, schema)
74}
75
76// StateValueToJSONMap converts a cty.Value to generic JSON map via the cty JSON
77// encoding.
78func StateValueToJSONMap(val cty.Value, ty cty.Type) (map[string]interface{}, error) {
79 js, err := ctyjson.Marshal(val, ty)
80 if err != nil {
81 return nil, err
82 }
83
84 var m map[string]interface{}
85 if err := json.Unmarshal(js, &m); err != nil {
86 return nil, err
87 }
88
89 return m, nil
90}
91
92// JSONMapToStateValue takes a generic json map[string]interface{} and converts it
93// to the specific type, ensuring that the values conform to the schema.
94func JSONMapToStateValue(m map[string]interface{}, block *configschema.Block) (cty.Value, error) {
95 var val cty.Value
96
97 js, err := json.Marshal(m)
98 if err != nil {
99 return val, err
100 }
101
102 val, err = ctyjson.Unmarshal(js, block.ImpliedType())
103 if err != nil {
104 return val, err
105 }
106
107 return block.CoerceValue(val)
108}
109
110// StateValueFromInstanceState converts a terraform.InstanceState to a
111// cty.Value as described by the provided cty.Type, and maintains the resource
112// ID as the "id" attribute.
113func StateValueFromInstanceState(is *terraform.InstanceState, ty cty.Type) (cty.Value, error) {
114 return is.AttrsAsObjectValue(ty)
115}
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/testing.go b/vendor/github.com/hashicorp/terraform/helper/schema/testing.go
index da754ac..a367a1f 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/testing.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/testing.go
@@ -18,7 +18,7 @@ func TestResourceDataRaw(
18 } 18 }
19 19
20 sm := schemaMap(schema) 20 sm := schemaMap(schema)
21 diff, err := sm.Diff(nil, terraform.NewResourceConfig(c), nil, nil) 21 diff, err := sm.Diff(nil, terraform.NewResourceConfig(c), nil, nil, true)
22 if err != nil { 22 if err != nil {
23 t.Fatalf("err: %s", err) 23 t.Fatalf("err: %s", err)
24 } 24 }
diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/valuetype_string.go b/vendor/github.com/hashicorp/terraform/helper/schema/valuetype_string.go
index 3bc3ac4..914ca32 100644
--- a/vendor/github.com/hashicorp/terraform/helper/schema/valuetype_string.go
+++ b/vendor/github.com/hashicorp/terraform/helper/schema/valuetype_string.go
@@ -4,6 +4,21 @@ package schema
4 4
5import "strconv" 5import "strconv"
6 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[TypeInvalid-0]
12 _ = x[TypeBool-1]
13 _ = x[TypeInt-2]
14 _ = x[TypeFloat-3]
15 _ = x[TypeString-4]
16 _ = x[TypeList-5]
17 _ = x[TypeMap-6]
18 _ = x[TypeSet-7]
19 _ = x[typeObject-8]
20}
21
7const _ValueType_name = "TypeInvalidTypeBoolTypeIntTypeFloatTypeStringTypeListTypeMapTypeSettypeObject" 22const _ValueType_name = "TypeInvalidTypeBoolTypeIntTypeFloatTypeStringTypeListTypeMapTypeSettypeObject"
8 23
9var _ValueType_index = [...]uint8{0, 11, 19, 26, 35, 45, 53, 60, 67, 77} 24var _ValueType_index = [...]uint8{0, 11, 19, 26, 35, 45, 53, 60, 67, 77}