github.com/graywolf-at-work-2/terraform-vendor@v1.4.5/internal/command/jsonplan/plan.go (about) 1 package jsonplan 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "sort" 7 "strings" 8 9 "github.com/zclconf/go-cty/cty" 10 ctyjson "github.com/zclconf/go-cty/cty/json" 11 12 "github.com/hashicorp/terraform/internal/addrs" 13 "github.com/hashicorp/terraform/internal/command/jsonchecks" 14 "github.com/hashicorp/terraform/internal/command/jsonconfig" 15 "github.com/hashicorp/terraform/internal/command/jsonstate" 16 "github.com/hashicorp/terraform/internal/configs" 17 "github.com/hashicorp/terraform/internal/plans" 18 "github.com/hashicorp/terraform/internal/states" 19 "github.com/hashicorp/terraform/internal/states/statefile" 20 "github.com/hashicorp/terraform/internal/terraform" 21 "github.com/hashicorp/terraform/version" 22 ) 23 24 // FormatVersion represents the version of the json format and will be 25 // incremented for any change to this format that requires changes to a 26 // consuming parser. 27 const ( 28 FormatVersion = "1.1" 29 30 ResourceInstanceReplaceBecauseCannotUpdate = "replace_because_cannot_update" 31 ResourceInstanceReplaceBecauseTainted = "replace_because_tainted" 32 ResourceInstanceReplaceByRequest = "replace_by_request" 33 ResourceInstanceReplaceByTriggers = "replace_by_triggers" 34 ResourceInstanceDeleteBecauseNoResourceConfig = "delete_because_no_resource_config" 35 ResourceInstanceDeleteBecauseWrongRepetition = "delete_because_wrong_repetition" 36 ResourceInstanceDeleteBecauseCountIndex = "delete_because_count_index" 37 ResourceInstanceDeleteBecauseEachKey = "delete_because_each_key" 38 ResourceInstanceDeleteBecauseNoModule = "delete_because_no_module" 39 ResourceInstanceDeleteBecauseNoMoveTarget = "delete_because_no_move_target" 40 ResourceInstanceReadBecauseConfigUnknown = "read_because_config_unknown" 41 ResourceInstanceReadBecauseDependencyPending = "read_because_dependency_pending" 42 ) 43 44 // Plan is the top-level representation of the json format of a plan. It includes 45 // the complete config and current state. 46 type plan struct { 47 FormatVersion string `json:"format_version,omitempty"` 48 TerraformVersion string `json:"terraform_version,omitempty"` 49 Variables variables `json:"variables,omitempty"` 50 PlannedValues stateValues `json:"planned_values,omitempty"` 51 // ResourceDrift and ResourceChanges are sorted in a user-friendly order 52 // that is undefined at this time, but consistent. 53 ResourceDrift []ResourceChange `json:"resource_drift,omitempty"` 54 ResourceChanges []ResourceChange `json:"resource_changes,omitempty"` 55 OutputChanges map[string]Change `json:"output_changes,omitempty"` 56 PriorState json.RawMessage `json:"prior_state,omitempty"` 57 Config json.RawMessage `json:"configuration,omitempty"` 58 RelevantAttributes []ResourceAttr `json:"relevant_attributes,omitempty"` 59 Checks json.RawMessage `json:"checks,omitempty"` 60 } 61 62 func newPlan() *plan { 63 return &plan{ 64 FormatVersion: FormatVersion, 65 } 66 } 67 68 // ResourceAttr contains the address and attribute of an external for the 69 // RelevantAttributes in the plan. 70 type ResourceAttr struct { 71 Resource string `json:"resource"` 72 Attr json.RawMessage `json:"attribute"` 73 } 74 75 // Change is the representation of a proposed change for an object. 76 type Change struct { 77 // Actions are the actions that will be taken on the object selected by the 78 // properties below. Valid actions values are: 79 // ["no-op"] 80 // ["create"] 81 // ["read"] 82 // ["update"] 83 // ["delete", "create"] 84 // ["create", "delete"] 85 // ["delete"] 86 // The two "replace" actions are represented in this way to allow callers to 87 // e.g. just scan the list for "delete" to recognize all three situations 88 // where the object will be deleted, allowing for any new deletion 89 // combinations that might be added in future. 90 Actions []string `json:"actions,omitempty"` 91 92 // Before and After are representations of the object value both before and 93 // after the action. For ["create"] and ["delete"] actions, either "before" 94 // or "after" is unset (respectively). For ["no-op"], the before and after 95 // values are identical. The "after" value will be incomplete if there are 96 // values within it that won't be known until after apply. 97 Before json.RawMessage `json:"before,omitempty"` 98 After json.RawMessage `json:"after,omitempty"` 99 100 // AfterUnknown is an object value with similar structure to After, but 101 // with all unknown leaf values replaced with true, and all known leaf 102 // values omitted. This can be combined with After to reconstruct a full 103 // value after the action, including values which will only be known after 104 // apply. 105 AfterUnknown json.RawMessage `json:"after_unknown,omitempty"` 106 107 // BeforeSensitive and AfterSensitive are object values with similar 108 // structure to Before and After, but with all sensitive leaf values 109 // replaced with true, and all non-sensitive leaf values omitted. These 110 // objects should be combined with Before and After to prevent accidental 111 // display of sensitive values in user interfaces. 112 BeforeSensitive json.RawMessage `json:"before_sensitive,omitempty"` 113 AfterSensitive json.RawMessage `json:"after_sensitive,omitempty"` 114 115 // ReplacePaths is an array of arrays representing a set of paths into the 116 // object value which resulted in the action being "replace". This will be 117 // omitted if the action is not replace, or if no paths caused the 118 // replacement (for example, if the resource was tainted). Each path 119 // consists of one or more steps, each of which will be a number or a 120 // string. 121 ReplacePaths json.RawMessage `json:"replace_paths,omitempty"` 122 } 123 124 type output struct { 125 Sensitive bool `json:"sensitive"` 126 Type json.RawMessage `json:"type,omitempty"` 127 Value json.RawMessage `json:"value,omitempty"` 128 } 129 130 // variables is the JSON representation of the variables provided to the current 131 // plan. 132 type variables map[string]*variable 133 134 type variable struct { 135 Value json.RawMessage `json:"value,omitempty"` 136 } 137 138 // MarshalForRenderer returns the pre-json encoding changes of the requested 139 // plan, in a format available to the structured renderer. 140 // 141 // This function does a small part of the Marshal function, as it only returns 142 // the part of the plan required by the jsonformat.Plan renderer. 143 func MarshalForRenderer( 144 p *plans.Plan, 145 schemas *terraform.Schemas, 146 ) (map[string]Change, []ResourceChange, []ResourceChange, []ResourceAttr, error) { 147 output := newPlan() 148 149 var err error 150 if output.OutputChanges, err = MarshalOutputChanges(p.Changes); err != nil { 151 return nil, nil, nil, nil, err 152 } 153 154 if output.ResourceChanges, err = MarshalResourceChanges(p.Changes.Resources, schemas); err != nil { 155 return nil, nil, nil, nil, err 156 } 157 158 if len(p.DriftedResources) > 0 { 159 // In refresh-only mode, we render all resources marked as drifted, 160 // including those which have moved without other changes. In other plan 161 // modes, move-only changes will be included in the planned changes, so 162 // we skip them here. 163 var driftedResources []*plans.ResourceInstanceChangeSrc 164 if p.UIMode == plans.RefreshOnlyMode { 165 driftedResources = p.DriftedResources 166 } else { 167 for _, dr := range p.DriftedResources { 168 if dr.Action != plans.NoOp { 169 driftedResources = append(driftedResources, dr) 170 } 171 } 172 } 173 output.ResourceDrift, err = MarshalResourceChanges(driftedResources, schemas) 174 if err != nil { 175 return nil, nil, nil, nil, err 176 } 177 } 178 179 if err := output.marshalRelevantAttrs(p); err != nil { 180 return nil, nil, nil, nil, err 181 } 182 183 return output.OutputChanges, output.ResourceChanges, output.ResourceDrift, output.RelevantAttributes, nil 184 } 185 186 // Marshal returns the json encoding of a terraform plan. 187 func Marshal( 188 config *configs.Config, 189 p *plans.Plan, 190 sf *statefile.File, 191 schemas *terraform.Schemas, 192 ) ([]byte, error) { 193 output := newPlan() 194 output.TerraformVersion = version.String() 195 196 err := output.marshalPlanVariables(p.VariableValues, config.Module.Variables) 197 if err != nil { 198 return nil, fmt.Errorf("error in marshalPlanVariables: %s", err) 199 } 200 201 // output.PlannedValues 202 err = output.marshalPlannedValues(p.Changes, schemas) 203 if err != nil { 204 return nil, fmt.Errorf("error in marshalPlannedValues: %s", err) 205 } 206 207 // output.ResourceDrift 208 if len(p.DriftedResources) > 0 { 209 // In refresh-only mode, we render all resources marked as drifted, 210 // including those which have moved without other changes. In other plan 211 // modes, move-only changes will be included in the planned changes, so 212 // we skip them here. 213 var driftedResources []*plans.ResourceInstanceChangeSrc 214 if p.UIMode == plans.RefreshOnlyMode { 215 driftedResources = p.DriftedResources 216 } else { 217 for _, dr := range p.DriftedResources { 218 if dr.Action != plans.NoOp { 219 driftedResources = append(driftedResources, dr) 220 } 221 } 222 } 223 output.ResourceDrift, err = MarshalResourceChanges(driftedResources, schemas) 224 if err != nil { 225 return nil, fmt.Errorf("error in marshaling resource drift: %s", err) 226 } 227 } 228 229 if err := output.marshalRelevantAttrs(p); err != nil { 230 return nil, fmt.Errorf("error marshaling relevant attributes for external changes: %s", err) 231 } 232 233 // output.ResourceChanges 234 if p.Changes != nil { 235 output.ResourceChanges, err = MarshalResourceChanges(p.Changes.Resources, schemas) 236 if err != nil { 237 return nil, fmt.Errorf("error in marshaling resource changes: %s", err) 238 } 239 } 240 241 // output.OutputChanges 242 if output.OutputChanges, err = MarshalOutputChanges(p.Changes); err != nil { 243 return nil, fmt.Errorf("error in marshaling output changes: %s", err) 244 } 245 246 // output.Checks 247 if p.Checks != nil && p.Checks.ConfigResults.Len() > 0 { 248 output.Checks = jsonchecks.MarshalCheckStates(p.Checks) 249 } 250 251 // output.PriorState 252 if sf != nil && !sf.State.Empty() { 253 output.PriorState, err = jsonstate.Marshal(sf, schemas) 254 if err != nil { 255 return nil, fmt.Errorf("error marshaling prior state: %s", err) 256 } 257 } 258 259 // output.Config 260 output.Config, err = jsonconfig.Marshal(config, schemas) 261 if err != nil { 262 return nil, fmt.Errorf("error marshaling config: %s", err) 263 } 264 265 ret, err := json.Marshal(output) 266 return ret, err 267 } 268 269 func (p *plan) marshalPlanVariables(vars map[string]plans.DynamicValue, decls map[string]*configs.Variable) error { 270 p.Variables = make(variables, len(vars)) 271 272 for k, v := range vars { 273 val, err := v.Decode(cty.DynamicPseudoType) 274 if err != nil { 275 return err 276 } 277 valJSON, err := ctyjson.Marshal(val, val.Type()) 278 if err != nil { 279 return err 280 } 281 p.Variables[k] = &variable{ 282 Value: valJSON, 283 } 284 } 285 286 // In Terraform v1.1 and earlier we had some confusion about which subsystem 287 // of Terraform was the one responsible for substituting in default values 288 // for unset module variables, with root module variables being handled in 289 // three different places while child module variables were only handled 290 // during the Terraform Core graph walk. 291 // 292 // For Terraform v1.2 and later we rationalized that by having the Terraform 293 // Core graph walk always be responsible for selecting defaults regardless 294 // of root vs. child module, but unfortunately our earlier accidental 295 // misbehavior bled out into the public interface by making the defaults 296 // show up in the "vars" map to this function. Those are now correctly 297 // omitted (so that the plan file only records the variables _actually_ 298 // set by the caller) but consumers of the JSON plan format may be depending 299 // on our old behavior and so we'll fake it here just in time so that 300 // outside consumers won't see a behavior change. 301 for name, decl := range decls { 302 if _, ok := p.Variables[name]; ok { 303 continue 304 } 305 if val := decl.Default; val != cty.NilVal { 306 valJSON, err := ctyjson.Marshal(val, val.Type()) 307 if err != nil { 308 return err 309 } 310 p.Variables[name] = &variable{ 311 Value: valJSON, 312 } 313 } 314 } 315 316 if len(p.Variables) == 0 { 317 p.Variables = nil // omit this property if there are no variables to describe 318 } 319 320 return nil 321 } 322 323 // MarshalResourceChanges converts the provided internal representation of 324 // ResourceInstanceChangeSrc objects into the public structured JSON changes. 325 // 326 // This function is referenced directly from the structured renderer tests, to 327 // ensure parity between the renderers. It probably shouldn't be used anywhere 328 // else. 329 func MarshalResourceChanges(resources []*plans.ResourceInstanceChangeSrc, schemas *terraform.Schemas) ([]ResourceChange, error) { 330 var ret []ResourceChange 331 332 var sortedResources []*plans.ResourceInstanceChangeSrc 333 sortedResources = append(sortedResources, resources...) 334 sort.Slice(sortedResources, func(i, j int) bool { 335 if !sortedResources[i].Addr.Equal(sortedResources[j].Addr) { 336 return sortedResources[i].Addr.Less(sortedResources[j].Addr) 337 } 338 return sortedResources[i].DeposedKey < sortedResources[j].DeposedKey 339 }) 340 341 for _, rc := range sortedResources { 342 var r ResourceChange 343 addr := rc.Addr 344 r.Address = addr.String() 345 if !addr.Equal(rc.PrevRunAddr) { 346 r.PreviousAddress = rc.PrevRunAddr.String() 347 } 348 349 dataSource := addr.Resource.Resource.Mode == addrs.DataResourceMode 350 // We create "delete" actions for data resources so we can clean up 351 // their entries in state, but this is an implementation detail that 352 // users shouldn't see. 353 if dataSource && rc.Action == plans.Delete { 354 continue 355 } 356 357 schema, _ := schemas.ResourceTypeConfig( 358 rc.ProviderAddr.Provider, 359 addr.Resource.Resource.Mode, 360 addr.Resource.Resource.Type, 361 ) 362 if schema == nil { 363 return nil, fmt.Errorf("no schema found for %s (in provider %s)", r.Address, rc.ProviderAddr.Provider) 364 } 365 366 changeV, err := rc.Decode(schema.ImpliedType()) 367 if err != nil { 368 return nil, err 369 } 370 // We drop the marks from the change, as decoding is only an 371 // intermediate step to re-encode the values as json 372 changeV.Before, _ = changeV.Before.UnmarkDeep() 373 changeV.After, _ = changeV.After.UnmarkDeep() 374 375 var before, after []byte 376 var beforeSensitive, afterSensitive []byte 377 var afterUnknown cty.Value 378 379 if changeV.Before != cty.NilVal { 380 before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type()) 381 if err != nil { 382 return nil, err 383 } 384 marks := rc.BeforeValMarks 385 if schema.ContainsSensitive() { 386 marks = append(marks, schema.ValueMarks(changeV.Before, nil)...) 387 } 388 bs := jsonstate.SensitiveAsBool(changeV.Before.MarkWithPaths(marks)) 389 beforeSensitive, err = ctyjson.Marshal(bs, bs.Type()) 390 if err != nil { 391 return nil, err 392 } 393 } 394 if changeV.After != cty.NilVal { 395 if changeV.After.IsWhollyKnown() { 396 after, err = ctyjson.Marshal(changeV.After, changeV.After.Type()) 397 if err != nil { 398 return nil, err 399 } 400 afterUnknown = cty.EmptyObjectVal 401 } else { 402 filteredAfter := omitUnknowns(changeV.After) 403 if filteredAfter.IsNull() { 404 after = nil 405 } else { 406 after, err = ctyjson.Marshal(filteredAfter, filteredAfter.Type()) 407 if err != nil { 408 return nil, err 409 } 410 } 411 afterUnknown = unknownAsBool(changeV.After) 412 } 413 marks := rc.AfterValMarks 414 if schema.ContainsSensitive() { 415 marks = append(marks, schema.ValueMarks(changeV.After, nil)...) 416 } 417 as := jsonstate.SensitiveAsBool(changeV.After.MarkWithPaths(marks)) 418 afterSensitive, err = ctyjson.Marshal(as, as.Type()) 419 if err != nil { 420 return nil, err 421 } 422 } 423 424 a, err := ctyjson.Marshal(afterUnknown, afterUnknown.Type()) 425 if err != nil { 426 return nil, err 427 } 428 replacePaths, err := encodePaths(rc.RequiredReplace) 429 if err != nil { 430 return nil, err 431 } 432 433 r.Change = Change{ 434 Actions: actionString(rc.Action.String()), 435 Before: json.RawMessage(before), 436 After: json.RawMessage(after), 437 AfterUnknown: a, 438 BeforeSensitive: json.RawMessage(beforeSensitive), 439 AfterSensitive: json.RawMessage(afterSensitive), 440 ReplacePaths: replacePaths, 441 } 442 443 if rc.DeposedKey != states.NotDeposed { 444 r.Deposed = rc.DeposedKey.String() 445 } 446 447 key := addr.Resource.Key 448 if key != nil { 449 value := key.Value() 450 if r.Index, err = ctyjson.Marshal(value, value.Type()); err != nil { 451 return nil, err 452 } 453 } 454 455 switch addr.Resource.Resource.Mode { 456 case addrs.ManagedResourceMode: 457 r.Mode = jsonstate.ManagedResourceMode 458 case addrs.DataResourceMode: 459 r.Mode = jsonstate.DataResourceMode 460 default: 461 return nil, fmt.Errorf("resource %s has an unsupported mode %s", r.Address, addr.Resource.Resource.Mode.String()) 462 } 463 r.ModuleAddress = addr.Module.String() 464 r.Name = addr.Resource.Resource.Name 465 r.Type = addr.Resource.Resource.Type 466 r.ProviderName = rc.ProviderAddr.Provider.String() 467 468 switch rc.ActionReason { 469 case plans.ResourceInstanceChangeNoReason: 470 r.ActionReason = "" // will be omitted in output 471 case plans.ResourceInstanceReplaceBecauseCannotUpdate: 472 r.ActionReason = ResourceInstanceReplaceBecauseCannotUpdate 473 case plans.ResourceInstanceReplaceBecauseTainted: 474 r.ActionReason = ResourceInstanceReplaceBecauseTainted 475 case plans.ResourceInstanceReplaceByRequest: 476 r.ActionReason = ResourceInstanceReplaceByRequest 477 case plans.ResourceInstanceReplaceByTriggers: 478 r.ActionReason = ResourceInstanceReplaceByTriggers 479 case plans.ResourceInstanceDeleteBecauseNoResourceConfig: 480 r.ActionReason = ResourceInstanceDeleteBecauseNoResourceConfig 481 case plans.ResourceInstanceDeleteBecauseWrongRepetition: 482 r.ActionReason = ResourceInstanceDeleteBecauseWrongRepetition 483 case plans.ResourceInstanceDeleteBecauseCountIndex: 484 r.ActionReason = ResourceInstanceDeleteBecauseCountIndex 485 case plans.ResourceInstanceDeleteBecauseEachKey: 486 r.ActionReason = ResourceInstanceDeleteBecauseEachKey 487 case plans.ResourceInstanceDeleteBecauseNoModule: 488 r.ActionReason = ResourceInstanceDeleteBecauseNoModule 489 case plans.ResourceInstanceDeleteBecauseNoMoveTarget: 490 r.ActionReason = ResourceInstanceDeleteBecauseNoMoveTarget 491 case plans.ResourceInstanceReadBecauseConfigUnknown: 492 r.ActionReason = ResourceInstanceReadBecauseConfigUnknown 493 case plans.ResourceInstanceReadBecauseDependencyPending: 494 r.ActionReason = ResourceInstanceReadBecauseDependencyPending 495 default: 496 return nil, fmt.Errorf("resource %s has an unsupported action reason %s", r.Address, rc.ActionReason) 497 } 498 499 ret = append(ret, r) 500 501 } 502 503 return ret, nil 504 } 505 506 // MarshalOutputChanges converts the provided internal representation of 507 // Changes objects into the structured JSON representation. 508 // 509 // This function is referenced directly from the structured renderer tests, to 510 // ensure parity between the renderers. It probably shouldn't be used anywhere 511 // else. 512 func MarshalOutputChanges(changes *plans.Changes) (map[string]Change, error) { 513 if changes == nil { 514 // Nothing to do! 515 return nil, nil 516 } 517 518 outputChanges := make(map[string]Change, len(changes.Outputs)) 519 for _, oc := range changes.Outputs { 520 521 // Skip output changes that are not from the root module. 522 // These are automatically stripped from plans that are written to disk 523 // elsewhere, we just need to duplicate the logic here in case anyone 524 // is converting this plan directly from memory. 525 if !oc.Addr.Module.IsRoot() { 526 continue 527 } 528 529 changeV, err := oc.Decode() 530 if err != nil { 531 return nil, err 532 } 533 // We drop the marks from the change, as decoding is only an 534 // intermediate step to re-encode the values as json 535 changeV.Before, _ = changeV.Before.UnmarkDeep() 536 changeV.After, _ = changeV.After.UnmarkDeep() 537 538 var before, after []byte 539 var afterUnknown cty.Value 540 541 if changeV.Before != cty.NilVal { 542 before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type()) 543 if err != nil { 544 return nil, err 545 } 546 } 547 if changeV.After != cty.NilVal { 548 if changeV.After.IsWhollyKnown() { 549 after, err = ctyjson.Marshal(changeV.After, changeV.After.Type()) 550 if err != nil { 551 return nil, err 552 } 553 afterUnknown = cty.False 554 } else { 555 filteredAfter := omitUnknowns(changeV.After) 556 if filteredAfter.IsNull() { 557 after = nil 558 } else { 559 after, err = ctyjson.Marshal(filteredAfter, filteredAfter.Type()) 560 if err != nil { 561 return nil, err 562 } 563 } 564 afterUnknown = unknownAsBool(changeV.After) 565 } 566 } 567 568 // The only information we have in the plan about output sensitivity is 569 // a boolean which is true if the output was or is marked sensitive. As 570 // a result, BeforeSensitive and AfterSensitive will be identical, and 571 // either false or true. 572 outputSensitive := cty.False 573 if oc.Sensitive { 574 outputSensitive = cty.True 575 } 576 sensitive, err := ctyjson.Marshal(outputSensitive, outputSensitive.Type()) 577 if err != nil { 578 return nil, err 579 } 580 581 a, _ := ctyjson.Marshal(afterUnknown, afterUnknown.Type()) 582 583 c := Change{ 584 Actions: actionString(oc.Action.String()), 585 Before: json.RawMessage(before), 586 After: json.RawMessage(after), 587 AfterUnknown: a, 588 BeforeSensitive: json.RawMessage(sensitive), 589 AfterSensitive: json.RawMessage(sensitive), 590 } 591 592 outputChanges[oc.Addr.OutputValue.Name] = c 593 } 594 595 return outputChanges, nil 596 } 597 598 func (p *plan) marshalPlannedValues(changes *plans.Changes, schemas *terraform.Schemas) error { 599 // marshal the planned changes into a module 600 plan, err := marshalPlannedValues(changes, schemas) 601 if err != nil { 602 return err 603 } 604 p.PlannedValues.RootModule = plan 605 606 // marshalPlannedOutputs 607 outputs, err := marshalPlannedOutputs(changes) 608 if err != nil { 609 return err 610 } 611 p.PlannedValues.Outputs = outputs 612 613 return nil 614 } 615 616 func (p *plan) marshalRelevantAttrs(plan *plans.Plan) error { 617 for _, ra := range plan.RelevantAttributes { 618 addr := ra.Resource.String() 619 path, err := encodePath(ra.Attr) 620 if err != nil { 621 return err 622 } 623 624 p.RelevantAttributes = append(p.RelevantAttributes, ResourceAttr{addr, path}) 625 } 626 return nil 627 } 628 629 // omitUnknowns recursively walks the src cty.Value and returns a new cty.Value, 630 // omitting any unknowns. 631 // 632 // The result also normalizes some types: all sequence types are turned into 633 // tuple types and all mapping types are converted to object types, since we 634 // assume the result of this is just going to be serialized as JSON (and thus 635 // lose those distinctions) anyway. 636 func omitUnknowns(val cty.Value) cty.Value { 637 ty := val.Type() 638 switch { 639 case val.IsNull(): 640 return val 641 case !val.IsKnown(): 642 return cty.NilVal 643 case ty.IsPrimitiveType(): 644 return val 645 case ty.IsListType() || ty.IsTupleType() || ty.IsSetType(): 646 var vals []cty.Value 647 it := val.ElementIterator() 648 for it.Next() { 649 _, v := it.Element() 650 newVal := omitUnknowns(v) 651 if newVal != cty.NilVal { 652 vals = append(vals, newVal) 653 } else if newVal == cty.NilVal { 654 // element order is how we correlate unknownness, so we must 655 // replace unknowns with nulls 656 vals = append(vals, cty.NullVal(v.Type())) 657 } 658 } 659 // We use tuple types always here, because the work we did above 660 // may have caused the individual elements to have different types, 661 // and we're doing this work to produce JSON anyway and JSON marshalling 662 // represents all of these sequence types as an array. 663 return cty.TupleVal(vals) 664 case ty.IsMapType() || ty.IsObjectType(): 665 vals := make(map[string]cty.Value) 666 it := val.ElementIterator() 667 for it.Next() { 668 k, v := it.Element() 669 newVal := omitUnknowns(v) 670 if newVal != cty.NilVal { 671 vals[k.AsString()] = newVal 672 } 673 } 674 // We use object types always here, because the work we did above 675 // may have caused the individual elements to have different types, 676 // and we're doing this work to produce JSON anyway and JSON marshalling 677 // represents both of these mapping types as an object. 678 return cty.ObjectVal(vals) 679 default: 680 // Should never happen, since the above should cover all types 681 panic(fmt.Sprintf("omitUnknowns cannot handle %#v", val)) 682 } 683 } 684 685 // recursively iterate through a cty.Value, replacing unknown values (including 686 // null) with cty.True and known values with cty.False. 687 // 688 // The result also normalizes some types: all sequence types are turned into 689 // tuple types and all mapping types are converted to object types, since we 690 // assume the result of this is just going to be serialized as JSON (and thus 691 // lose those distinctions) anyway. 692 // 693 // For map/object values, all known attribute values will be omitted instead of 694 // returning false, as this results in a more compact serialization. 695 func unknownAsBool(val cty.Value) cty.Value { 696 ty := val.Type() 697 switch { 698 case val.IsNull(): 699 return cty.False 700 case !val.IsKnown(): 701 if ty.IsPrimitiveType() || ty.Equals(cty.DynamicPseudoType) { 702 return cty.True 703 } 704 fallthrough 705 case ty.IsPrimitiveType(): 706 return cty.BoolVal(!val.IsKnown()) 707 case ty.IsListType() || ty.IsTupleType() || ty.IsSetType(): 708 length := val.LengthInt() 709 if length == 0 { 710 // If there are no elements then we can't have unknowns 711 return cty.EmptyTupleVal 712 } 713 vals := make([]cty.Value, 0, length) 714 it := val.ElementIterator() 715 for it.Next() { 716 _, v := it.Element() 717 vals = append(vals, unknownAsBool(v)) 718 } 719 // The above transform may have changed the types of some of the 720 // elements, so we'll always use a tuple here in case we've now made 721 // different elements have different types. Our ultimate goal is to 722 // marshal to JSON anyway, and all of these sequence types are 723 // indistinguishable in JSON. 724 return cty.TupleVal(vals) 725 case ty.IsMapType() || ty.IsObjectType(): 726 var length int 727 switch { 728 case ty.IsMapType(): 729 length = val.LengthInt() 730 default: 731 length = len(val.Type().AttributeTypes()) 732 } 733 if length == 0 { 734 // If there are no elements then we can't have unknowns 735 return cty.EmptyObjectVal 736 } 737 vals := make(map[string]cty.Value) 738 it := val.ElementIterator() 739 for it.Next() { 740 k, v := it.Element() 741 vAsBool := unknownAsBool(v) 742 // Omit all of the "false"s for known values for more compact 743 // serialization 744 if !vAsBool.RawEquals(cty.False) { 745 vals[k.AsString()] = vAsBool 746 } 747 } 748 // The above transform may have changed the types of some of the 749 // elements, so we'll always use an object here in case we've now made 750 // different elements have different types. Our ultimate goal is to 751 // marshal to JSON anyway, and all of these mapping types are 752 // indistinguishable in JSON. 753 return cty.ObjectVal(vals) 754 default: 755 // Should never happen, since the above should cover all types 756 panic(fmt.Sprintf("unknownAsBool cannot handle %#v", val)) 757 } 758 } 759 760 func actionString(action string) []string { 761 switch { 762 case action == "NoOp": 763 return []string{"no-op"} 764 case action == "Create": 765 return []string{"create"} 766 case action == "Delete": 767 return []string{"delete"} 768 case action == "Update": 769 return []string{"update"} 770 case action == "CreateThenDelete": 771 return []string{"create", "delete"} 772 case action == "Read": 773 return []string{"read"} 774 case action == "DeleteThenCreate": 775 return []string{"delete", "create"} 776 default: 777 return []string{action} 778 } 779 } 780 781 // UnmarshalActions reverses the actionString function. 782 func UnmarshalActions(actions []string) plans.Action { 783 if len(actions) == 2 { 784 if actions[0] == "create" && actions[1] == "delete" { 785 return plans.CreateThenDelete 786 } 787 788 if actions[0] == "delete" && actions[1] == "create" { 789 return plans.DeleteThenCreate 790 } 791 } 792 793 if len(actions) == 1 { 794 switch actions[0] { 795 case "create": 796 return plans.Create 797 case "delete": 798 return plans.Delete 799 case "update": 800 return plans.Update 801 case "read": 802 return plans.Read 803 case "no-op": 804 return plans.NoOp 805 } 806 } 807 808 panic("unrecognized action slice: " + strings.Join(actions, ", ")) 809 } 810 811 // encodePaths lossily encodes a cty.PathSet into an array of arrays of step 812 // values, such as: 813 // 814 // [["length"],["triggers",0,"value"]] 815 // 816 // The lossiness is that we cannot distinguish between an IndexStep with string 817 // key and a GetAttr step. This is fine with JSON output, because JSON's type 818 // system means that those two steps are equivalent anyway: both are object 819 // indexes. 820 // 821 // JavaScript (or similar dynamic language) consumers of these values can 822 // iterate over the the steps starting from the root object to reach the 823 // value that each path is describing. 824 func encodePaths(pathSet cty.PathSet) (json.RawMessage, error) { 825 if pathSet.Empty() { 826 return nil, nil 827 } 828 829 pathList := pathSet.List() 830 jsonPaths := make([]json.RawMessage, 0, len(pathList)) 831 832 for _, path := range pathList { 833 jsonPath, err := encodePath(path) 834 if err != nil { 835 return nil, err 836 } 837 jsonPaths = append(jsonPaths, jsonPath) 838 } 839 840 return json.Marshal(jsonPaths) 841 } 842 843 func encodePath(path cty.Path) (json.RawMessage, error) { 844 steps := make([]json.RawMessage, 0, len(path)) 845 for _, step := range path { 846 switch s := step.(type) { 847 case cty.IndexStep: 848 key, err := ctyjson.Marshal(s.Key, s.Key.Type()) 849 if err != nil { 850 return nil, fmt.Errorf("Failed to marshal index step key %#v: %s", s.Key, err) 851 } 852 steps = append(steps, key) 853 case cty.GetAttrStep: 854 name, err := json.Marshal(s.Name) 855 if err != nil { 856 return nil, fmt.Errorf("Failed to marshal get attr step name %#v: %s", s.Name, err) 857 } 858 steps = append(steps, name) 859 default: 860 return nil, fmt.Errorf("Unsupported path step %#v (%t)", step, step) 861 } 862 } 863 return json.Marshal(steps) 864 }