github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-internal/command/jsonplan/plan.go (about) 1 package jsonplan 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "sort" 7 8 "github.com/zclconf/go-cty/cty" 9 ctyjson "github.com/zclconf/go-cty/cty/json" 10 11 "github.com/muratcelep/terraform/not-internal/addrs" 12 "github.com/muratcelep/terraform/not-internal/command/jsonconfig" 13 "github.com/muratcelep/terraform/not-internal/command/jsonstate" 14 "github.com/muratcelep/terraform/not-internal/configs" 15 "github.com/muratcelep/terraform/not-internal/plans" 16 "github.com/muratcelep/terraform/not-internal/states" 17 "github.com/muratcelep/terraform/not-internal/states/statefile" 18 "github.com/muratcelep/terraform/not-internal/terraform" 19 "github.com/muratcelep/terraform/version" 20 ) 21 22 // FormatVersion represents the version of the json format and will be 23 // incremented for any change to this format that requires changes to a 24 // consuming parser. 25 const FormatVersion = "1.0" 26 27 // Plan is the top-level representation of the json format of a plan. It includes 28 // the complete config and current state. 29 type plan struct { 30 FormatVersion string `json:"format_version,omitempty"` 31 TerraformVersion string `json:"terraform_version,omitempty"` 32 Variables variables `json:"variables,omitempty"` 33 PlannedValues stateValues `json:"planned_values,omitempty"` 34 // ResourceDrift and ResourceChanges are sorted in a user-friendly order 35 // that is undefined at this time, but consistent. 36 ResourceDrift []resourceChange `json:"resource_drift,omitempty"` 37 ResourceChanges []resourceChange `json:"resource_changes,omitempty"` 38 OutputChanges map[string]change `json:"output_changes,omitempty"` 39 PriorState json.RawMessage `json:"prior_state,omitempty"` 40 Config json.RawMessage `json:"configuration,omitempty"` 41 } 42 43 func newPlan() *plan { 44 return &plan{ 45 FormatVersion: FormatVersion, 46 } 47 } 48 49 // Change is the representation of a proposed change for an object. 50 type change struct { 51 // Actions are the actions that will be taken on the object selected by the 52 // properties below. Valid actions values are: 53 // ["no-op"] 54 // ["create"] 55 // ["read"] 56 // ["update"] 57 // ["delete", "create"] 58 // ["create", "delete"] 59 // ["delete"] 60 // The two "replace" actions are represented in this way to allow callers to 61 // e.g. just scan the list for "delete" to recognize all three situations 62 // where the object will be deleted, allowing for any new deletion 63 // combinations that might be added in future. 64 Actions []string `json:"actions,omitempty"` 65 66 // Before and After are representations of the object value both before and 67 // after the action. For ["create"] and ["delete"] actions, either "before" 68 // or "after" is unset (respectively). For ["no-op"], the before and after 69 // values are identical. The "after" value will be incomplete if there are 70 // values within it that won't be known until after apply. 71 Before json.RawMessage `json:"before,omitempty"` 72 After json.RawMessage `json:"after,omitempty"` 73 74 // AfterUnknown is an object value with similar structure to After, but 75 // with all unknown leaf values replaced with true, and all known leaf 76 // values omitted. This can be combined with After to reconstruct a full 77 // value after the action, including values which will only be known after 78 // apply. 79 AfterUnknown json.RawMessage `json:"after_unknown,omitempty"` 80 81 // BeforeSensitive and AfterSensitive are object values with similar 82 // structure to Before and After, but with all sensitive leaf values 83 // replaced with true, and all non-sensitive leaf values omitted. These 84 // objects should be combined with Before and After to prevent accidental 85 // display of sensitive values in user interfaces. 86 BeforeSensitive json.RawMessage `json:"before_sensitive,omitempty"` 87 AfterSensitive json.RawMessage `json:"after_sensitive,omitempty"` 88 89 // ReplacePaths is an array of arrays representing a set of paths into the 90 // object value which resulted in the action being "replace". This will be 91 // omitted if the action is not replace, or if no paths caused the 92 // replacement (for example, if the resource was tainted). Each path 93 // consists of one or more steps, each of which will be a number or a 94 // string. 95 ReplacePaths json.RawMessage `json:"replace_paths,omitempty"` 96 } 97 98 type output struct { 99 Sensitive bool `json:"sensitive"` 100 Value json.RawMessage `json:"value,omitempty"` 101 } 102 103 // variables is the JSON representation of the variables provided to the current 104 // plan. 105 type variables map[string]*variable 106 107 type variable struct { 108 Value json.RawMessage `json:"value,omitempty"` 109 } 110 111 // Marshal returns the json encoding of a terraform plan. 112 func Marshal( 113 config *configs.Config, 114 p *plans.Plan, 115 sf *statefile.File, 116 schemas *terraform.Schemas, 117 ) ([]byte, error) { 118 output := newPlan() 119 output.TerraformVersion = version.String() 120 121 err := output.marshalPlanVariables(p.VariableValues, schemas) 122 if err != nil { 123 return nil, fmt.Errorf("error in marshalPlanVariables: %s", err) 124 } 125 126 // output.PlannedValues 127 err = output.marshalPlannedValues(p.Changes, schemas) 128 if err != nil { 129 return nil, fmt.Errorf("error in marshalPlannedValues: %s", err) 130 } 131 132 // output.ResourceDrift 133 if len(p.DriftedResources) > 0 { 134 // In refresh-only mode, we render all resources marked as drifted, 135 // including those which have moved without other changes. In other plan 136 // modes, move-only changes will be included in the planned changes, so 137 // we skip them here. 138 var driftedResources []*plans.ResourceInstanceChangeSrc 139 if p.UIMode == plans.RefreshOnlyMode { 140 driftedResources = p.DriftedResources 141 } else { 142 for _, dr := range p.DriftedResources { 143 if dr.Action != plans.NoOp { 144 driftedResources = append(driftedResources, dr) 145 } 146 } 147 } 148 output.ResourceDrift, err = output.marshalResourceChanges(driftedResources, schemas) 149 if err != nil { 150 return nil, fmt.Errorf("error in marshaling resource drift: %s", err) 151 } 152 } 153 154 // output.ResourceChanges 155 if p.Changes != nil { 156 output.ResourceChanges, err = output.marshalResourceChanges(p.Changes.Resources, schemas) 157 if err != nil { 158 return nil, fmt.Errorf("error in marshaling resource changes: %s", err) 159 } 160 } 161 162 // output.OutputChanges 163 err = output.marshalOutputChanges(p.Changes) 164 if err != nil { 165 return nil, fmt.Errorf("error in marshaling output changes: %s", err) 166 } 167 168 // output.PriorState 169 if sf != nil && !sf.State.Empty() { 170 output.PriorState, err = jsonstate.Marshal(sf, schemas) 171 if err != nil { 172 return nil, fmt.Errorf("error marshaling prior state: %s", err) 173 } 174 } 175 176 // output.Config 177 output.Config, err = jsonconfig.Marshal(config, schemas) 178 if err != nil { 179 return nil, fmt.Errorf("error marshaling config: %s", err) 180 } 181 182 ret, err := json.Marshal(output) 183 return ret, err 184 } 185 186 func (p *plan) marshalPlanVariables(vars map[string]plans.DynamicValue, schemas *terraform.Schemas) error { 187 if len(vars) == 0 { 188 return nil 189 } 190 191 p.Variables = make(variables, len(vars)) 192 193 for k, v := range vars { 194 val, err := v.Decode(cty.DynamicPseudoType) 195 if err != nil { 196 return err 197 } 198 valJSON, err := ctyjson.Marshal(val, val.Type()) 199 if err != nil { 200 return err 201 } 202 p.Variables[k] = &variable{ 203 Value: valJSON, 204 } 205 } 206 return nil 207 } 208 209 func (p *plan) marshalResourceChanges(resources []*plans.ResourceInstanceChangeSrc, schemas *terraform.Schemas) ([]resourceChange, error) { 210 var ret []resourceChange 211 212 for _, rc := range resources { 213 var r resourceChange 214 addr := rc.Addr 215 r.Address = addr.String() 216 if !addr.Equal(rc.PrevRunAddr) { 217 r.PreviousAddress = rc.PrevRunAddr.String() 218 } 219 220 dataSource := addr.Resource.Resource.Mode == addrs.DataResourceMode 221 // We create "delete" actions for data resources so we can clean up 222 // their entries in state, but this is an implementation detail that 223 // users shouldn't see. 224 if dataSource && rc.Action == plans.Delete { 225 continue 226 } 227 228 schema, _ := schemas.ResourceTypeConfig( 229 rc.ProviderAddr.Provider, 230 addr.Resource.Resource.Mode, 231 addr.Resource.Resource.Type, 232 ) 233 if schema == nil { 234 return nil, fmt.Errorf("no schema found for %s (in provider %s)", r.Address, rc.ProviderAddr.Provider) 235 } 236 237 changeV, err := rc.Decode(schema.ImpliedType()) 238 if err != nil { 239 return nil, err 240 } 241 // We drop the marks from the change, as decoding is only an 242 // intermediate step to re-encode the values as json 243 changeV.Before, _ = changeV.Before.UnmarkDeep() 244 changeV.After, _ = changeV.After.UnmarkDeep() 245 246 var before, after []byte 247 var beforeSensitive, afterSensitive []byte 248 var afterUnknown cty.Value 249 250 if changeV.Before != cty.NilVal { 251 before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type()) 252 if err != nil { 253 return nil, err 254 } 255 marks := rc.BeforeValMarks 256 if schema.ContainsSensitive() { 257 marks = append(marks, schema.ValueMarks(changeV.Before, nil)...) 258 } 259 bs := jsonstate.SensitiveAsBool(changeV.Before.MarkWithPaths(marks)) 260 beforeSensitive, err = ctyjson.Marshal(bs, bs.Type()) 261 if err != nil { 262 return nil, err 263 } 264 } 265 if changeV.After != cty.NilVal { 266 if changeV.After.IsWhollyKnown() { 267 after, err = ctyjson.Marshal(changeV.After, changeV.After.Type()) 268 if err != nil { 269 return nil, err 270 } 271 afterUnknown = cty.EmptyObjectVal 272 } else { 273 filteredAfter := omitUnknowns(changeV.After) 274 if filteredAfter.IsNull() { 275 after = nil 276 } else { 277 after, err = ctyjson.Marshal(filteredAfter, filteredAfter.Type()) 278 if err != nil { 279 return nil, err 280 } 281 } 282 afterUnknown = unknownAsBool(changeV.After) 283 } 284 marks := rc.AfterValMarks 285 if schema.ContainsSensitive() { 286 marks = append(marks, schema.ValueMarks(changeV.After, nil)...) 287 } 288 as := jsonstate.SensitiveAsBool(changeV.After.MarkWithPaths(marks)) 289 afterSensitive, err = ctyjson.Marshal(as, as.Type()) 290 if err != nil { 291 return nil, err 292 } 293 } 294 295 a, err := ctyjson.Marshal(afterUnknown, afterUnknown.Type()) 296 if err != nil { 297 return nil, err 298 } 299 replacePaths, err := encodePaths(rc.RequiredReplace) 300 if err != nil { 301 return nil, err 302 } 303 304 r.Change = change{ 305 Actions: actionString(rc.Action.String()), 306 Before: json.RawMessage(before), 307 After: json.RawMessage(after), 308 AfterUnknown: a, 309 BeforeSensitive: json.RawMessage(beforeSensitive), 310 AfterSensitive: json.RawMessage(afterSensitive), 311 ReplacePaths: replacePaths, 312 } 313 314 if rc.DeposedKey != states.NotDeposed { 315 r.Deposed = rc.DeposedKey.String() 316 } 317 318 key := addr.Resource.Key 319 if key != nil { 320 r.Index = key 321 } 322 323 switch addr.Resource.Resource.Mode { 324 case addrs.ManagedResourceMode: 325 r.Mode = "managed" 326 case addrs.DataResourceMode: 327 r.Mode = "data" 328 default: 329 return nil, fmt.Errorf("resource %s has an unsupported mode %s", r.Address, addr.Resource.Resource.Mode.String()) 330 } 331 r.ModuleAddress = addr.Module.String() 332 r.Name = addr.Resource.Resource.Name 333 r.Type = addr.Resource.Resource.Type 334 r.ProviderName = rc.ProviderAddr.Provider.String() 335 336 switch rc.ActionReason { 337 case plans.ResourceInstanceChangeNoReason: 338 r.ActionReason = "" // will be omitted in output 339 case plans.ResourceInstanceReplaceBecauseCannotUpdate: 340 r.ActionReason = "replace_because_cannot_update" 341 case plans.ResourceInstanceReplaceBecauseTainted: 342 r.ActionReason = "replace_because_tainted" 343 case plans.ResourceInstanceReplaceByRequest: 344 r.ActionReason = "replace_by_request" 345 case plans.ResourceInstanceDeleteBecauseNoResourceConfig: 346 r.ActionReason = "delete_because_no_resource_config" 347 case plans.ResourceInstanceDeleteBecauseWrongRepetition: 348 r.ActionReason = "delete_because_wrong_repetition" 349 case plans.ResourceInstanceDeleteBecauseCountIndex: 350 r.ActionReason = "delete_because_count_index" 351 case plans.ResourceInstanceDeleteBecauseEachKey: 352 r.ActionReason = "delete_because_each_key" 353 case plans.ResourceInstanceDeleteBecauseNoModule: 354 r.ActionReason = "delete_because_no_module" 355 default: 356 return nil, fmt.Errorf("resource %s has an unsupported action reason %s", r.Address, rc.ActionReason) 357 } 358 359 ret = append(ret, r) 360 361 } 362 363 sort.Slice(ret, func(i, j int) bool { 364 return ret[i].Address < ret[j].Address 365 }) 366 367 return ret, nil 368 } 369 370 func (p *plan) marshalOutputChanges(changes *plans.Changes) error { 371 if changes == nil { 372 // Nothing to do! 373 return nil 374 } 375 376 p.OutputChanges = make(map[string]change, len(changes.Outputs)) 377 for _, oc := range changes.Outputs { 378 changeV, err := oc.Decode() 379 if err != nil { 380 return err 381 } 382 // We drop the marks from the change, as decoding is only an 383 // intermediate step to re-encode the values as json 384 changeV.Before, _ = changeV.Before.UnmarkDeep() 385 changeV.After, _ = changeV.After.UnmarkDeep() 386 387 var before, after []byte 388 afterUnknown := cty.False 389 if changeV.Before != cty.NilVal { 390 before, err = ctyjson.Marshal(changeV.Before, changeV.Before.Type()) 391 if err != nil { 392 return err 393 } 394 } 395 if changeV.After != cty.NilVal { 396 if changeV.After.IsWhollyKnown() { 397 after, err = ctyjson.Marshal(changeV.After, changeV.After.Type()) 398 if err != nil { 399 return err 400 } 401 } else { 402 afterUnknown = cty.True 403 } 404 } 405 406 // The only information we have in the plan about output sensitivity is 407 // a boolean which is true if the output was or is marked sensitive. As 408 // a result, BeforeSensitive and AfterSensitive will be identical, and 409 // either false or true. 410 outputSensitive := cty.False 411 if oc.Sensitive { 412 outputSensitive = cty.True 413 } 414 sensitive, err := ctyjson.Marshal(outputSensitive, outputSensitive.Type()) 415 if err != nil { 416 return err 417 } 418 419 a, _ := ctyjson.Marshal(afterUnknown, afterUnknown.Type()) 420 421 c := change{ 422 Actions: actionString(oc.Action.String()), 423 Before: json.RawMessage(before), 424 After: json.RawMessage(after), 425 AfterUnknown: a, 426 BeforeSensitive: json.RawMessage(sensitive), 427 AfterSensitive: json.RawMessage(sensitive), 428 } 429 430 p.OutputChanges[oc.Addr.OutputValue.Name] = c 431 } 432 433 return nil 434 } 435 436 func (p *plan) marshalPlannedValues(changes *plans.Changes, schemas *terraform.Schemas) error { 437 // marshal the planned changes into a module 438 plan, err := marshalPlannedValues(changes, schemas) 439 if err != nil { 440 return err 441 } 442 p.PlannedValues.RootModule = plan 443 444 // marshalPlannedOutputs 445 outputs, err := marshalPlannedOutputs(changes) 446 if err != nil { 447 return err 448 } 449 p.PlannedValues.Outputs = outputs 450 451 return nil 452 } 453 454 // omitUnknowns recursively walks the src cty.Value and returns a new cty.Value, 455 // omitting any unknowns. 456 // 457 // The result also normalizes some types: all sequence types are turned into 458 // tuple types and all mapping types are converted to object types, since we 459 // assume the result of this is just going to be serialized as JSON (and thus 460 // lose those distinctions) anyway. 461 func omitUnknowns(val cty.Value) cty.Value { 462 ty := val.Type() 463 switch { 464 case val.IsNull(): 465 return val 466 case !val.IsKnown(): 467 return cty.NilVal 468 case ty.IsPrimitiveType(): 469 return val 470 case ty.IsListType() || ty.IsTupleType() || ty.IsSetType(): 471 var vals []cty.Value 472 it := val.ElementIterator() 473 for it.Next() { 474 _, v := it.Element() 475 newVal := omitUnknowns(v) 476 if newVal != cty.NilVal { 477 vals = append(vals, newVal) 478 } else if newVal == cty.NilVal && ty.IsListType() { 479 // list length may be significant, so we will turn unknowns into nulls 480 vals = append(vals, cty.NullVal(v.Type())) 481 } 482 } 483 // We use tuple types always here, because the work we did above 484 // may have caused the individual elements to have different types, 485 // and we're doing this work to produce JSON anyway and JSON marshalling 486 // represents all of these sequence types as an array. 487 return cty.TupleVal(vals) 488 case ty.IsMapType() || ty.IsObjectType(): 489 vals := make(map[string]cty.Value) 490 it := val.ElementIterator() 491 for it.Next() { 492 k, v := it.Element() 493 newVal := omitUnknowns(v) 494 if newVal != cty.NilVal { 495 vals[k.AsString()] = newVal 496 } 497 } 498 // We use object types always here, because the work we did above 499 // may have caused the individual elements to have different types, 500 // and we're doing this work to produce JSON anyway and JSON marshalling 501 // represents both of these mapping types as an object. 502 return cty.ObjectVal(vals) 503 default: 504 // Should never happen, since the above should cover all types 505 panic(fmt.Sprintf("omitUnknowns cannot handle %#v", val)) 506 } 507 } 508 509 // recursively iterate through a cty.Value, replacing unknown values (including 510 // null) with cty.True and known values with cty.False. 511 // 512 // The result also normalizes some types: all sequence types are turned into 513 // tuple types and all mapping types are converted to object types, since we 514 // assume the result of this is just going to be serialized as JSON (and thus 515 // lose those distinctions) anyway. 516 // 517 // For map/object values, all known attribute values will be omitted instead of 518 // returning false, as this results in a more compact serialization. 519 func unknownAsBool(val cty.Value) cty.Value { 520 ty := val.Type() 521 switch { 522 case val.IsNull(): 523 return cty.False 524 case !val.IsKnown(): 525 if ty.IsPrimitiveType() || ty.Equals(cty.DynamicPseudoType) { 526 return cty.True 527 } 528 fallthrough 529 case ty.IsPrimitiveType(): 530 return cty.BoolVal(!val.IsKnown()) 531 case ty.IsListType() || ty.IsTupleType() || ty.IsSetType(): 532 length := val.LengthInt() 533 if length == 0 { 534 // If there are no elements then we can't have unknowns 535 return cty.EmptyTupleVal 536 } 537 vals := make([]cty.Value, 0, length) 538 it := val.ElementIterator() 539 for it.Next() { 540 _, v := it.Element() 541 vals = append(vals, unknownAsBool(v)) 542 } 543 // The above transform may have changed the types of some of the 544 // elements, so we'll always use a tuple here in case we've now made 545 // different elements have different types. Our ultimate goal is to 546 // marshal to JSON anyway, and all of these sequence types are 547 // indistinguishable in JSON. 548 return cty.TupleVal(vals) 549 case ty.IsMapType() || ty.IsObjectType(): 550 var length int 551 switch { 552 case ty.IsMapType(): 553 length = val.LengthInt() 554 default: 555 length = len(val.Type().AttributeTypes()) 556 } 557 if length == 0 { 558 // If there are no elements then we can't have unknowns 559 return cty.EmptyObjectVal 560 } 561 vals := make(map[string]cty.Value) 562 it := val.ElementIterator() 563 for it.Next() { 564 k, v := it.Element() 565 vAsBool := unknownAsBool(v) 566 // Omit all of the "false"s for known values for more compact 567 // serialization 568 if !vAsBool.RawEquals(cty.False) { 569 vals[k.AsString()] = unknownAsBool(v) 570 } 571 } 572 // The above transform may have changed the types of some of the 573 // elements, so we'll always use an object here in case we've now made 574 // different elements have different types. Our ultimate goal is to 575 // marshal to JSON anyway, and all of these mapping types are 576 // indistinguishable in JSON. 577 return cty.ObjectVal(vals) 578 default: 579 // Should never happen, since the above should cover all types 580 panic(fmt.Sprintf("unknownAsBool cannot handle %#v", val)) 581 } 582 } 583 584 func actionString(action string) []string { 585 switch { 586 case action == "NoOp": 587 return []string{"no-op"} 588 case action == "Create": 589 return []string{"create"} 590 case action == "Delete": 591 return []string{"delete"} 592 case action == "Update": 593 return []string{"update"} 594 case action == "CreateThenDelete": 595 return []string{"create", "delete"} 596 case action == "Read": 597 return []string{"read"} 598 case action == "DeleteThenCreate": 599 return []string{"delete", "create"} 600 default: 601 return []string{action} 602 } 603 } 604 605 // encodePaths lossily encodes a cty.PathSet into an array of arrays of step 606 // values, such as: 607 // 608 // [["length"],["triggers",0,"value"]] 609 // 610 // The lossiness is that we cannot distinguish between an IndexStep with string 611 // key and a GetAttr step. This is fine with JSON output, because JSON's type 612 // system means that those two steps are equivalent anyway: both are object 613 // indexes. 614 // 615 // JavaScript (or similar dynamic language) consumers of these values can 616 // recursively apply the steps to a given object using an index operation for 617 // each step. 618 func encodePaths(pathSet cty.PathSet) (json.RawMessage, error) { 619 if pathSet.Empty() { 620 return nil, nil 621 } 622 623 pathList := pathSet.List() 624 jsonPaths := make([]json.RawMessage, 0, len(pathList)) 625 626 for _, path := range pathList { 627 steps := make([]json.RawMessage, 0, len(path)) 628 for _, step := range path { 629 switch s := step.(type) { 630 case cty.IndexStep: 631 key, err := ctyjson.Marshal(s.Key, s.Key.Type()) 632 if err != nil { 633 return nil, fmt.Errorf("Failed to marshal index step key %#v: %s", s.Key, err) 634 } 635 steps = append(steps, key) 636 case cty.GetAttrStep: 637 name, err := json.Marshal(s.Name) 638 if err != nil { 639 return nil, fmt.Errorf("Failed to marshal get attr step name %#v: %s", s.Name, err) 640 } 641 steps = append(steps, name) 642 default: 643 return nil, fmt.Errorf("Unsupported path step %#v (%t)", step, step) 644 } 645 } 646 jsonPath, err := json.Marshal(steps) 647 if err != nil { 648 return nil, err 649 } 650 jsonPaths = append(jsonPaths, jsonPath) 651 } 652 653 return json.Marshal(jsonPaths) 654 }