diff options
Diffstat (limited to 'vendor/github.com/hashicorp/terraform/helper/plugin')
4 files changed, 1622 insertions, 0 deletions
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 | } | ||