diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper')
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 @@ | |||
1 | package didyoumean | ||
2 | |||
3 | import ( | ||
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. | ||
16 | func 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. | ||
6 | package 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 @@ | |||
1 | package plugin | ||
2 | |||
3 | import ( | ||
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 | |||
23 | const 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, | ||
28 | func 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. | ||
40 | type GRPCProviderServer struct { | ||
41 | provider *schema.Provider | ||
42 | } | ||
43 | |||
44 | func (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 | |||
75 | func (s *GRPCProviderServer) getProviderSchemaBlock() *configschema.Block { | ||
76 | return schema.InternalMap(s.provider.Schema).CoreConfigSchema() | ||
77 | } | ||
78 | |||
79 | func (s *GRPCProviderServer) getResourceSchemaBlock(name string) *configschema.Block { | ||
80 | res := s.provider.ResourcesMap[name] | ||
81 | return res.CoreConfigSchema() | ||
82 | } | ||
83 | |||
84 | func (s *GRPCProviderServer) getDatasourceSchemaBlock(name string) *configschema.Block { | ||
85 | dat := s.provider.DataSourcesMap[name] | ||
86 | return dat.CoreConfigSchema() | ||
87 | } | ||
88 | |||
89 | func (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 | |||
193 | func (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 | |||
212 | func (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 | |||
237 | func (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. | ||
303 | func (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 | |||
372 | func (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. | ||
392 | func (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 | |||
441 | func (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 | |||
452 | func (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 | |||
478 | func (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 | |||
541 | func (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 | |||
744 | func (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 | |||
903 | func (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 | |||
958 | func (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 | |||
1015 | func 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. | ||
1052 | func 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. | ||
1090 | func 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 | |||
1108 | func 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. | ||
1139 | func 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. | ||
1296 | func 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 @@ | |||
1 | package plugin | ||
2 | |||
3 | import ( | ||
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, | ||
19 | func 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 | |||
29 | type GRPCProvisionerServer struct { | ||
30 | provisioner *schema.Provisioner | ||
31 | } | ||
32 | |||
33 | func (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 | |||
43 | func (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). | ||
64 | func 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. | ||
87 | type uiOutput struct { | ||
88 | srv proto.Provisioner_ProvisionResourceServer | ||
89 | } | ||
90 | |||
91 | func (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 | |||
100 | func (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 | |||
138 | func (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 @@ | |||
1 | package plugin | ||
2 | |||
3 | import ( | ||
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. | ||
12 | func 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 @@ | |||
1 | package resource | ||
2 | |||
3 | import ( | ||
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. | ||
20 | func 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 @@ | |||
1 | package resource | ||
2 | |||
3 | import ( | ||
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 | ||
17 | func 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 | |||
120 | func 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 | |||
145 | func 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 @@ | |||
1 | package resource | 1 | package resource |
2 | 2 | ||
3 | import ( | 3 | import ( |
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 | 656 | func testProviderFactories(c TestCase) map[string]terraform.ResourceProviderFactory { |
627 | // terraform to match non-test behavior. | 657 | ctxProviders := make(map[string]terraform.ResourceProviderFactory) |
628 | func 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. | ||
674 | func 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 | ||
759 | func testModule(opts terraform.ContextOpts, step TestStep) (*module.Tree, error) { | 807 | func 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 | ||
810 | func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) { | 857 | func 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 |
883 | func TestCheckModuleResourceAttrSet(mp []string, name string, key string) TestCheckFunc { | 930 | func 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 |
917 | func TestCheckModuleResourceAttr(mp []string, name string, key string, value string) TestCheckFunc { | 965 | func 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 | ||
928 | func testCheckResourceAttr(is *terraform.InstanceState, name string, key string, value string) error { | 977 | func 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 |
959 | func TestCheckModuleNoResourceAttr(mp []string, name string, key string) TestCheckFunc { | 1020 | func 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 | ||
970 | func testCheckNoResourceAttr(is *terraform.InstanceState, name string, key string) error { | 1032 | func 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 |
993 | func TestModuleMatchResourceAttr(mp []string, name string, key string, r *regexp.Regexp) TestCheckFunc { | 1068 | func 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 |
1054 | func TestCheckModuleResourceAttrPair(mpFirst []string, nameFirst string, keyFirst string, mpSecond []string, nameSecond string, keySecond string) TestCheckFunc { | 1130 | func 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 | ||
1070 | func testCheckResourceAttrPair(isFirst *terraform.InstanceState, nameFirst string, keyFirst string, isSecond *terraform.InstanceState, nameSecond string, keySecond string) error { | 1148 | func 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. |
1166 | func modulePathPrimaryInstanceState(s *terraform.State, mp []string, name string) (*terraform.InstanceState, error) { | 1262 | func 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. | ||
1281 | type operationError struct { | ||
1282 | OpName string | ||
1283 | Diags tfdiags.Diagnostics | ||
1284 | } | ||
1285 | |||
1286 | func 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. | ||
1292 | func (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(). | ||
1299 | func (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. | ||
1313 | func 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 @@ | |||
1 | package resource | 1 | package resource |
2 | 2 | ||
3 | import ( | 3 | import ( |
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 | ||
21 | func testStep( | 31 | func 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. | ||
192 | func 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. | ||
207 | func 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 | |||
167 | func testStepTaint(state *terraform.State, step TestStep) error { | 391 | func 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 | ||
3 | import ( | 3 | import ( |
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 | ||
41 | func (b *Backend) Input( | 48 | func (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 | |||
54 | func (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 | ||
51 | func (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 | ||
59 | func (b *Backend) Configure(c *terraform.ResourceConfig) error { | 144 | func (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. | ||
184 | func (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 | |||
3 | import ( | 3 | import ( |
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. |
60 | func (s *Schema) coreConfigSchemaAttribute() *configschema.Attribute { | 88 | func (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. |
73 | func (s *Schema) coreConfigSchemaBlock() *configschema.NestedBlock { | 131 | func (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. | ||
153 | func (r *Resource) CoreConfigSchema() *configschema.Block { | 231 | func (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 | |||
301 | func (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. | ||
307 | func (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 | |||
3 | import ( | 3 | import ( |
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. | ||
47 | func 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 | ||
3 | import ( | 3 | import ( |
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 | ||
175 | func (r *DiffFieldReader) readSet( | 175 | func (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 | ||
99 | func (r *MapFieldReader) readSet( | 99 | func (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 | ||
5 | import "strconv" | 5 | import "strconv" |
6 | 6 | ||
7 | func _() { | ||
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 | |||
7 | const ( | 19 | const ( |
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. | ||
303 | func (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. |
300 | func (p *Provider) Refresh( | 316 | func (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. |
312 | func (p *Provider) Resources() []terraform.ResourceType { | 328 | func (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. | ||
126 | func (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. |
125 | func (p *Provisioner) Apply( | 131 | func (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. | ||
160 | func (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. |
140 | type CreateFunc func(*ResourceData, interface{}) error | 180 | type CreateFunc func(*ResourceData, interface{}) error |
141 | 181 | ||
@@ -155,6 +195,27 @@ type ExistsFunc func(*ResourceData, interface{}) (bool, error) | |||
155 | type StateMigrateFunc func( | 195 | type StateMigrateFunc func( |
156 | int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error) | 196 | int, *terraform.InstanceState, interface{}) (*terraform.InstanceState, error) |
157 | 197 | ||
198 | type 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 | ||
217 | type StateUpgradeFunc func(rawState map[string]interface{}, meta interface{}) (map[string]interface{}, error) | ||
218 | |||
158 | // See Resource documentation. | 219 | // See Resource documentation. |
159 | type CustomizeDiffFunc func(*ResourceDiff, interface{}) error | 220 | type 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 | ||
327 | func (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. |
267 | func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) { | 367 | func (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 |
304 | func (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. | ||
407 | func (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. | ||
457 | func (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 | ||
512 | func (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. | ||
760 | func (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. |
517 | func (r *Resource) isTopLevel() bool { | 765 | func (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 | ||
544 | func (r *Resource) recordCurrentSchemaVersion( | 800 | func (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. | ||
55 | func (d *ResourceData) UnsafeSetFieldRaw(key string, value string) { | 57 | func (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 { | |||
246 | func (d *ResourceData) SetId(v string) { | 254 | func (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 @@ | |||
12 | package schema | 12 | package schema |
13 | 13 | ||
14 | import ( | 14 | import ( |
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 |
32 | type contextKey string | 35 | type contextKey string |
33 | 36 | ||
37 | var ( | ||
38 | protoVersionMu sync.Mutex | ||
39 | protoVersion5 = false | ||
40 | ) | ||
41 | |||
42 | func 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. | ||
52 | func 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. | ||
255 | type SchemaConfigMode int | ||
256 | |||
257 | const ( | ||
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. | ||
431 | type 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. |
368 | type schemaMap map[string]*Schema | 434 | type 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. |
613 | func (m schemaMap) InternalValidate(topSchemaMap schemaMap) error { | 683 | func (m schemaMap) InternalValidate(topSchemaMap schemaMap) error { |
684 | return m.internalValidate(topSchemaMap, false) | ||
685 | } | ||
686 | |||
687 | func (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 @@ | |||
1 | package schema | ||
2 | |||
3 | import ( | ||
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. | ||
18 | func 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. | ||
26 | func 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. | ||
49 | func 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. | ||
72 | func 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. | ||
78 | func 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. | ||
94 | func 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. | ||
113 | func 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 | ||
5 | import "strconv" | 5 | import "strconv" |
6 | 6 | ||
7 | func _() { | ||
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 | |||
7 | const _ValueType_name = "TypeInvalidTypeBoolTypeIntTypeFloatTypeStringTypeListTypeMapTypeSettypeObject" | 22 | const _ValueType_name = "TypeInvalidTypeBoolTypeIntTypeFloatTypeStringTypeListTypeMapTypeSettypeObject" |
8 | 23 | ||
9 | var _ValueType_index = [...]uint8{0, 11, 19, 26, 35, 45, 53, 60, 67, 77} | 24 | var _ValueType_index = [...]uint8{0, 11, 19, 26, 35, 45, 53, 60, 67, 77} |