github.com/kanishk98/terraform@v1.3.0-dev.0.20220917174235-661ca8088a6a/internal/plans/planfile/tfplan.go (about) 1 package planfile 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 8 "google.golang.org/protobuf/proto" 9 10 "github.com/hashicorp/terraform/internal/addrs" 11 "github.com/hashicorp/terraform/internal/checks" 12 "github.com/hashicorp/terraform/internal/lang/globalref" 13 "github.com/hashicorp/terraform/internal/lang/marks" 14 "github.com/hashicorp/terraform/internal/plans" 15 "github.com/hashicorp/terraform/internal/plans/internal/planproto" 16 "github.com/hashicorp/terraform/internal/states" 17 "github.com/hashicorp/terraform/version" 18 "github.com/zclconf/go-cty/cty" 19 ) 20 21 const tfplanFormatVersion = 3 22 const tfplanFilename = "tfplan" 23 24 // --------------------------------------------------------------------------- 25 // This file deals with the internal structure of the "tfplan" sub-file within 26 // the plan file format. It's all private API, wrapped by methods defined 27 // elsewhere. This is the only file that should import the 28 // ../internal/planproto package, which contains the ugly stubs generated 29 // by the protobuf compiler. 30 // --------------------------------------------------------------------------- 31 32 // readTfplan reads a protobuf-encoded description from the plan portion of 33 // a plan file, which is stored in a special file in the archive called 34 // "tfplan". 35 func readTfplan(r io.Reader) (*plans.Plan, error) { 36 src, err := ioutil.ReadAll(r) 37 if err != nil { 38 return nil, err 39 } 40 41 var rawPlan planproto.Plan 42 err = proto.Unmarshal(src, &rawPlan) 43 if err != nil { 44 return nil, fmt.Errorf("parse error: %s", err) 45 } 46 47 if rawPlan.Version != tfplanFormatVersion { 48 return nil, fmt.Errorf("unsupported plan file format version %d; only version %d is supported", rawPlan.Version, tfplanFormatVersion) 49 } 50 51 if rawPlan.TerraformVersion != version.String() { 52 return nil, fmt.Errorf("plan file was created by Terraform %s, but this is %s; plan files cannot be transferred between different Terraform versions", rawPlan.TerraformVersion, version.String()) 53 } 54 55 plan := &plans.Plan{ 56 VariableValues: map[string]plans.DynamicValue{}, 57 Changes: &plans.Changes{ 58 Outputs: []*plans.OutputChangeSrc{}, 59 Resources: []*plans.ResourceInstanceChangeSrc{}, 60 }, 61 DriftedResources: []*plans.ResourceInstanceChangeSrc{}, 62 Checks: &states.CheckResults{}, 63 } 64 65 switch rawPlan.UiMode { 66 case planproto.Mode_NORMAL: 67 plan.UIMode = plans.NormalMode 68 case planproto.Mode_DESTROY: 69 plan.UIMode = plans.DestroyMode 70 case planproto.Mode_REFRESH_ONLY: 71 plan.UIMode = plans.RefreshOnlyMode 72 default: 73 return nil, fmt.Errorf("plan has invalid mode %s", rawPlan.UiMode) 74 } 75 76 for _, rawOC := range rawPlan.OutputChanges { 77 name := rawOC.Name 78 change, err := changeFromTfplan(rawOC.Change) 79 if err != nil { 80 return nil, fmt.Errorf("invalid plan for output %q: %s", name, err) 81 } 82 83 plan.Changes.Outputs = append(plan.Changes.Outputs, &plans.OutputChangeSrc{ 84 // All output values saved in the plan file are root module outputs, 85 // since we don't retain others. (They can be easily recomputed 86 // during apply). 87 Addr: addrs.OutputValue{Name: name}.Absolute(addrs.RootModuleInstance), 88 ChangeSrc: *change, 89 Sensitive: rawOC.Sensitive, 90 }) 91 } 92 93 plan.Checks.ConfigResults = addrs.MakeMap[addrs.ConfigCheckable, *states.CheckResultAggregate]() 94 for _, rawCRs := range rawPlan.CheckResults { 95 aggr := &states.CheckResultAggregate{} 96 switch rawCRs.Status { 97 case planproto.CheckResults_UNKNOWN: 98 aggr.Status = checks.StatusUnknown 99 case planproto.CheckResults_PASS: 100 aggr.Status = checks.StatusPass 101 case planproto.CheckResults_FAIL: 102 aggr.Status = checks.StatusFail 103 case planproto.CheckResults_ERROR: 104 aggr.Status = checks.StatusError 105 default: 106 return nil, fmt.Errorf("aggregate check results for %s have unsupported status %#v", rawCRs.ConfigAddr, rawCRs.Status) 107 } 108 109 var objKind addrs.CheckableKind 110 switch rawCRs.Kind { 111 case planproto.CheckResults_RESOURCE: 112 objKind = addrs.CheckableResource 113 case planproto.CheckResults_OUTPUT_VALUE: 114 objKind = addrs.CheckableOutputValue 115 default: 116 return nil, fmt.Errorf("aggregate check results for %s have unsupported object kind %s", rawCRs.ConfigAddr, objKind) 117 } 118 119 // Some trickiness here: we only have an address parser for 120 // addrs.Checkable and not for addrs.ConfigCheckable, but that's okay 121 // because once we have an addrs.Checkable we can always derive an 122 // addrs.ConfigCheckable from it, and a ConfigCheckable should always 123 // be the same syntax as a Checkable with no index information and 124 // thus we can reuse the same parser for both here. 125 configAddrProxy, diags := addrs.ParseCheckableStr(objKind, rawCRs.ConfigAddr) 126 if diags.HasErrors() { 127 return nil, diags.Err() 128 } 129 configAddr := configAddrProxy.ConfigCheckable() 130 if configAddr.String() != configAddrProxy.String() { 131 // This is how we catch if the config address included index 132 // information that would be allowed in a Checkable but not 133 // in a ConfigCheckable. 134 return nil, fmt.Errorf("invalid checkable config address %s", rawCRs.ConfigAddr) 135 } 136 137 aggr.ObjectResults = addrs.MakeMap[addrs.Checkable, *states.CheckResultObject]() 138 for _, rawCR := range rawCRs.Objects { 139 objectAddr, diags := addrs.ParseCheckableStr(objKind, rawCR.ObjectAddr) 140 if diags.HasErrors() { 141 return nil, diags.Err() 142 } 143 if !addrs.Equivalent(objectAddr.ConfigCheckable(), configAddr) { 144 return nil, fmt.Errorf("checkable object %s should not be grouped under %s", objectAddr, configAddr) 145 } 146 147 obj := &states.CheckResultObject{ 148 FailureMessages: rawCR.FailureMessages, 149 } 150 switch rawCR.Status { 151 case planproto.CheckResults_UNKNOWN: 152 obj.Status = checks.StatusUnknown 153 case planproto.CheckResults_PASS: 154 obj.Status = checks.StatusPass 155 case planproto.CheckResults_FAIL: 156 obj.Status = checks.StatusFail 157 case planproto.CheckResults_ERROR: 158 obj.Status = checks.StatusError 159 default: 160 return nil, fmt.Errorf("object check results for %s has unsupported status %#v", rawCR.ObjectAddr, rawCR.Status) 161 } 162 163 aggr.ObjectResults.Put(objectAddr, obj) 164 } 165 // If we ended up with no elements in the map then we'll just nil it, 166 // primarily just to make life easier for our round-trip tests. 167 if aggr.ObjectResults.Len() == 0 { 168 aggr.ObjectResults.Elems = nil 169 } 170 171 plan.Checks.ConfigResults.Put(configAddr, aggr) 172 } 173 // If we ended up with no elements in the map then we'll just nil it, 174 // primarily just to make life easier for our round-trip tests. 175 if plan.Checks.ConfigResults.Len() == 0 { 176 plan.Checks.ConfigResults.Elems = nil 177 } 178 179 for _, rawRC := range rawPlan.ResourceChanges { 180 change, err := resourceChangeFromTfplan(rawRC) 181 if err != nil { 182 // errors from resourceChangeFromTfplan already include context 183 return nil, err 184 } 185 186 plan.Changes.Resources = append(plan.Changes.Resources, change) 187 } 188 189 for _, rawRC := range rawPlan.ResourceDrift { 190 change, err := resourceChangeFromTfplan(rawRC) 191 if err != nil { 192 // errors from resourceChangeFromTfplan already include context 193 return nil, err 194 } 195 196 plan.DriftedResources = append(plan.DriftedResources, change) 197 } 198 199 for _, rawRA := range rawPlan.RelevantAttributes { 200 ra, err := resourceAttrFromTfplan(rawRA) 201 if err != nil { 202 return nil, err 203 } 204 plan.RelevantAttributes = append(plan.RelevantAttributes, ra) 205 } 206 207 for _, rawTargetAddr := range rawPlan.TargetAddrs { 208 target, diags := addrs.ParseTargetStr(rawTargetAddr) 209 if diags.HasErrors() { 210 return nil, fmt.Errorf("plan contains invalid target address %q: %s", target, diags.Err()) 211 } 212 plan.TargetAddrs = append(plan.TargetAddrs, target.Subject) 213 } 214 215 for _, rawReplaceAddr := range rawPlan.ForceReplaceAddrs { 216 addr, diags := addrs.ParseAbsResourceInstanceStr(rawReplaceAddr) 217 if diags.HasErrors() { 218 return nil, fmt.Errorf("plan contains invalid force-replace address %q: %s", addr, diags.Err()) 219 } 220 plan.ForceReplaceAddrs = append(plan.ForceReplaceAddrs, addr) 221 } 222 223 for name, rawVal := range rawPlan.Variables { 224 val, err := valueFromTfplan(rawVal) 225 if err != nil { 226 return nil, fmt.Errorf("invalid value for input variable %q: %s", name, err) 227 } 228 plan.VariableValues[name] = val 229 } 230 231 if rawBackend := rawPlan.Backend; rawBackend == nil { 232 return nil, fmt.Errorf("plan file has no backend settings; backend settings are required") 233 } else { 234 config, err := valueFromTfplan(rawBackend.Config) 235 if err != nil { 236 return nil, fmt.Errorf("plan file has invalid backend configuration: %s", err) 237 } 238 plan.Backend = plans.Backend{ 239 Type: rawBackend.Type, 240 Config: config, 241 Workspace: rawBackend.Workspace, 242 } 243 } 244 245 return plan, nil 246 } 247 248 func resourceChangeFromTfplan(rawChange *planproto.ResourceInstanceChange) (*plans.ResourceInstanceChangeSrc, error) { 249 if rawChange == nil { 250 // Should never happen in practice, since protobuf can't represent 251 // a nil value in a list. 252 return nil, fmt.Errorf("resource change object is absent") 253 } 254 255 ret := &plans.ResourceInstanceChangeSrc{} 256 257 if rawChange.Addr == "" { 258 // If "Addr" isn't populated then seems likely that this is a plan 259 // file created by an earlier version of Terraform, which had the 260 // same information spread over various other fields: 261 // ModulePath, Mode, Name, Type, and InstanceKey. 262 return nil, fmt.Errorf("no instance address for resource instance change; perhaps this plan was created by a different version of Terraform?") 263 } 264 265 instAddr, diags := addrs.ParseAbsResourceInstanceStr(rawChange.Addr) 266 if diags.HasErrors() { 267 return nil, fmt.Errorf("invalid resource instance address %q: %w", rawChange.Addr, diags.Err()) 268 } 269 prevRunAddr := instAddr 270 if rawChange.PrevRunAddr != "" { 271 prevRunAddr, diags = addrs.ParseAbsResourceInstanceStr(rawChange.PrevRunAddr) 272 if diags.HasErrors() { 273 return nil, fmt.Errorf("invalid resource instance previous run address %q: %w", rawChange.PrevRunAddr, diags.Err()) 274 } 275 } 276 277 providerAddr, diags := addrs.ParseAbsProviderConfigStr(rawChange.Provider) 278 if diags.HasErrors() { 279 return nil, diags.Err() 280 } 281 ret.ProviderAddr = providerAddr 282 283 ret.Addr = instAddr 284 ret.PrevRunAddr = prevRunAddr 285 286 if rawChange.DeposedKey != "" { 287 if len(rawChange.DeposedKey) != 8 { 288 return nil, fmt.Errorf("deposed object for %s has invalid deposed key %q", ret.Addr, rawChange.DeposedKey) 289 } 290 ret.DeposedKey = states.DeposedKey(rawChange.DeposedKey) 291 } 292 293 ret.RequiredReplace = cty.NewPathSet() 294 for _, p := range rawChange.RequiredReplace { 295 path, err := pathFromTfplan(p) 296 if err != nil { 297 return nil, fmt.Errorf("invalid path in required replace: %s", err) 298 } 299 ret.RequiredReplace.Add(path) 300 } 301 302 change, err := changeFromTfplan(rawChange.Change) 303 if err != nil { 304 return nil, fmt.Errorf("invalid plan for resource %s: %s", ret.Addr, err) 305 } 306 307 ret.ChangeSrc = *change 308 309 switch rawChange.ActionReason { 310 case planproto.ResourceInstanceActionReason_NONE: 311 ret.ActionReason = plans.ResourceInstanceChangeNoReason 312 case planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_CANNOT_UPDATE: 313 ret.ActionReason = plans.ResourceInstanceReplaceBecauseCannotUpdate 314 case planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_TAINTED: 315 ret.ActionReason = plans.ResourceInstanceReplaceBecauseTainted 316 case planproto.ResourceInstanceActionReason_REPLACE_BY_REQUEST: 317 ret.ActionReason = plans.ResourceInstanceReplaceByRequest 318 case planproto.ResourceInstanceActionReason_REPLACE_BY_TRIGGERS: 319 ret.ActionReason = plans.ResourceInstanceReplaceByTriggers 320 case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_RESOURCE_CONFIG: 321 ret.ActionReason = plans.ResourceInstanceDeleteBecauseNoResourceConfig 322 case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_WRONG_REPETITION: 323 ret.ActionReason = plans.ResourceInstanceDeleteBecauseWrongRepetition 324 case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_COUNT_INDEX: 325 ret.ActionReason = plans.ResourceInstanceDeleteBecauseCountIndex 326 case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_EACH_KEY: 327 ret.ActionReason = plans.ResourceInstanceDeleteBecauseEachKey 328 case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE: 329 ret.ActionReason = plans.ResourceInstanceDeleteBecauseNoModule 330 case planproto.ResourceInstanceActionReason_READ_BECAUSE_CONFIG_UNKNOWN: 331 ret.ActionReason = plans.ResourceInstanceReadBecauseConfigUnknown 332 case planproto.ResourceInstanceActionReason_READ_BECAUSE_DEPENDENCY_PENDING: 333 ret.ActionReason = plans.ResourceInstanceReadBecauseDependencyPending 334 case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MOVE_TARGET: 335 ret.ActionReason = plans.ResourceInstanceDeleteBecauseNoMoveTarget 336 default: 337 return nil, fmt.Errorf("resource has invalid action reason %s", rawChange.ActionReason) 338 } 339 340 if len(rawChange.Private) != 0 { 341 ret.Private = rawChange.Private 342 } 343 344 return ret, nil 345 } 346 347 func changeFromTfplan(rawChange *planproto.Change) (*plans.ChangeSrc, error) { 348 if rawChange == nil { 349 return nil, fmt.Errorf("change object is absent") 350 } 351 352 ret := &plans.ChangeSrc{} 353 354 // -1 indicates that there is no index. We'll customize these below 355 // depending on the change action, and then decode. 356 beforeIdx, afterIdx := -1, -1 357 358 switch rawChange.Action { 359 case planproto.Action_NOOP: 360 ret.Action = plans.NoOp 361 beforeIdx = 0 362 afterIdx = 0 363 case planproto.Action_CREATE: 364 ret.Action = plans.Create 365 afterIdx = 0 366 case planproto.Action_READ: 367 ret.Action = plans.Read 368 beforeIdx = 0 369 afterIdx = 1 370 case planproto.Action_UPDATE: 371 ret.Action = plans.Update 372 beforeIdx = 0 373 afterIdx = 1 374 case planproto.Action_DELETE: 375 ret.Action = plans.Delete 376 beforeIdx = 0 377 case planproto.Action_CREATE_THEN_DELETE: 378 ret.Action = plans.CreateThenDelete 379 beforeIdx = 0 380 afterIdx = 1 381 case planproto.Action_DELETE_THEN_CREATE: 382 ret.Action = plans.DeleteThenCreate 383 beforeIdx = 0 384 afterIdx = 1 385 default: 386 return nil, fmt.Errorf("invalid change action %s", rawChange.Action) 387 } 388 389 if beforeIdx != -1 { 390 if l := len(rawChange.Values); l <= beforeIdx { 391 return nil, fmt.Errorf("incorrect number of values (%d) for %s change", l, rawChange.Action) 392 } 393 var err error 394 ret.Before, err = valueFromTfplan(rawChange.Values[beforeIdx]) 395 if err != nil { 396 return nil, fmt.Errorf("invalid \"before\" value: %s", err) 397 } 398 if ret.Before == nil { 399 return nil, fmt.Errorf("missing \"before\" value: %s", err) 400 } 401 } 402 if afterIdx != -1 { 403 if l := len(rawChange.Values); l <= afterIdx { 404 return nil, fmt.Errorf("incorrect number of values (%d) for %s change", l, rawChange.Action) 405 } 406 var err error 407 ret.After, err = valueFromTfplan(rawChange.Values[afterIdx]) 408 if err != nil { 409 return nil, fmt.Errorf("invalid \"after\" value: %s", err) 410 } 411 if ret.After == nil { 412 return nil, fmt.Errorf("missing \"after\" value: %s", err) 413 } 414 } 415 416 sensitive := cty.NewValueMarks(marks.Sensitive) 417 beforeValMarks, err := pathValueMarksFromTfplan(rawChange.BeforeSensitivePaths, sensitive) 418 if err != nil { 419 return nil, fmt.Errorf("failed to decode before sensitive paths: %s", err) 420 } 421 afterValMarks, err := pathValueMarksFromTfplan(rawChange.AfterSensitivePaths, sensitive) 422 if err != nil { 423 return nil, fmt.Errorf("failed to decode after sensitive paths: %s", err) 424 } 425 if len(beforeValMarks) > 0 { 426 ret.BeforeValMarks = beforeValMarks 427 } 428 if len(afterValMarks) > 0 { 429 ret.AfterValMarks = afterValMarks 430 } 431 432 return ret, nil 433 } 434 435 func valueFromTfplan(rawV *planproto.DynamicValue) (plans.DynamicValue, error) { 436 if len(rawV.Msgpack) == 0 { // len(0) because that's the default value for a "bytes" in protobuf 437 return nil, fmt.Errorf("dynamic value does not have msgpack serialization") 438 } 439 440 return plans.DynamicValue(rawV.Msgpack), nil 441 } 442 443 // writeTfplan serializes the given plan into the protobuf-based format used 444 // for the "tfplan" portion of a plan file. 445 func writeTfplan(plan *plans.Plan, w io.Writer) error { 446 if plan == nil { 447 return fmt.Errorf("cannot write plan file for nil plan") 448 } 449 if plan.Changes == nil { 450 return fmt.Errorf("cannot write plan file with nil changeset") 451 } 452 453 rawPlan := &planproto.Plan{ 454 Version: tfplanFormatVersion, 455 TerraformVersion: version.String(), 456 457 Variables: map[string]*planproto.DynamicValue{}, 458 OutputChanges: []*planproto.OutputChange{}, 459 CheckResults: []*planproto.CheckResults{}, 460 ResourceChanges: []*planproto.ResourceInstanceChange{}, 461 ResourceDrift: []*planproto.ResourceInstanceChange{}, 462 } 463 464 switch plan.UIMode { 465 case plans.NormalMode: 466 rawPlan.UiMode = planproto.Mode_NORMAL 467 case plans.DestroyMode: 468 rawPlan.UiMode = planproto.Mode_DESTROY 469 case plans.RefreshOnlyMode: 470 rawPlan.UiMode = planproto.Mode_REFRESH_ONLY 471 default: 472 return fmt.Errorf("plan has unsupported mode %s", plan.UIMode) 473 } 474 475 for _, oc := range plan.Changes.Outputs { 476 // When serializing a plan we only retain the root outputs, since 477 // changes to these are externally-visible side effects (e.g. via 478 // terraform_remote_state). 479 if !oc.Addr.Module.IsRoot() { 480 continue 481 } 482 483 name := oc.Addr.OutputValue.Name 484 485 // Writing outputs as cty.DynamicPseudoType forces the stored values 486 // to also contain dynamic type information, so we can recover the 487 // original type when we read the values back in readTFPlan. 488 protoChange, err := changeToTfplan(&oc.ChangeSrc) 489 if err != nil { 490 return fmt.Errorf("cannot write output value %q: %s", name, err) 491 } 492 493 rawPlan.OutputChanges = append(rawPlan.OutputChanges, &planproto.OutputChange{ 494 Name: name, 495 Change: protoChange, 496 Sensitive: oc.Sensitive, 497 }) 498 } 499 500 if plan.Checks != nil { 501 for _, configElem := range plan.Checks.ConfigResults.Elems { 502 crs := configElem.Value 503 pcrs := &planproto.CheckResults{ 504 ConfigAddr: configElem.Key.String(), 505 } 506 switch crs.Status { 507 case checks.StatusUnknown: 508 pcrs.Status = planproto.CheckResults_UNKNOWN 509 case checks.StatusPass: 510 pcrs.Status = planproto.CheckResults_PASS 511 case checks.StatusFail: 512 pcrs.Status = planproto.CheckResults_FAIL 513 case checks.StatusError: 514 pcrs.Status = planproto.CheckResults_ERROR 515 default: 516 return fmt.Errorf("checkable configuration %s has unsupported aggregate status %s", configElem.Key, crs.Status) 517 } 518 switch kind := configElem.Key.CheckableKind(); kind { 519 case addrs.CheckableResource: 520 pcrs.Kind = planproto.CheckResults_RESOURCE 521 case addrs.CheckableOutputValue: 522 pcrs.Kind = planproto.CheckResults_OUTPUT_VALUE 523 default: 524 return fmt.Errorf("checkable configuration %s has unsupported object type kind %s", configElem.Key, kind) 525 } 526 527 for _, objectElem := range configElem.Value.ObjectResults.Elems { 528 cr := objectElem.Value 529 pcr := &planproto.CheckResults_ObjectResult{ 530 ObjectAddr: objectElem.Key.String(), 531 FailureMessages: objectElem.Value.FailureMessages, 532 } 533 switch cr.Status { 534 case checks.StatusUnknown: 535 pcr.Status = planproto.CheckResults_UNKNOWN 536 case checks.StatusPass: 537 pcr.Status = planproto.CheckResults_PASS 538 case checks.StatusFail: 539 pcr.Status = planproto.CheckResults_FAIL 540 case checks.StatusError: 541 pcr.Status = planproto.CheckResults_ERROR 542 default: 543 return fmt.Errorf("checkable object %s has unsupported status %s", objectElem.Key, crs.Status) 544 } 545 pcrs.Objects = append(pcrs.Objects, pcr) 546 } 547 548 rawPlan.CheckResults = append(rawPlan.CheckResults, pcrs) 549 } 550 } 551 552 for _, rc := range plan.Changes.Resources { 553 rawRC, err := resourceChangeToTfplan(rc) 554 if err != nil { 555 return err 556 } 557 rawPlan.ResourceChanges = append(rawPlan.ResourceChanges, rawRC) 558 } 559 560 for _, rc := range plan.DriftedResources { 561 rawRC, err := resourceChangeToTfplan(rc) 562 if err != nil { 563 return err 564 } 565 rawPlan.ResourceDrift = append(rawPlan.ResourceDrift, rawRC) 566 } 567 568 for _, ra := range plan.RelevantAttributes { 569 rawRA, err := resourceAttrToTfplan(ra) 570 if err != nil { 571 return err 572 } 573 rawPlan.RelevantAttributes = append(rawPlan.RelevantAttributes, rawRA) 574 } 575 576 for _, targetAddr := range plan.TargetAddrs { 577 rawPlan.TargetAddrs = append(rawPlan.TargetAddrs, targetAddr.String()) 578 } 579 580 for _, replaceAddr := range plan.ForceReplaceAddrs { 581 rawPlan.ForceReplaceAddrs = append(rawPlan.ForceReplaceAddrs, replaceAddr.String()) 582 } 583 584 for name, val := range plan.VariableValues { 585 rawPlan.Variables[name] = valueToTfplan(val) 586 } 587 588 if plan.Backend.Type == "" || plan.Backend.Config == nil { 589 // This suggests a bug in the code that created the plan, since it 590 // ought to always have a backend populated, even if it's the default 591 // "local" backend with a local state file. 592 return fmt.Errorf("plan does not have a backend configuration") 593 } 594 595 rawPlan.Backend = &planproto.Backend{ 596 Type: plan.Backend.Type, 597 Config: valueToTfplan(plan.Backend.Config), 598 Workspace: plan.Backend.Workspace, 599 } 600 601 src, err := proto.Marshal(rawPlan) 602 if err != nil { 603 return fmt.Errorf("serialization error: %s", err) 604 } 605 606 _, err = w.Write(src) 607 if err != nil { 608 return fmt.Errorf("failed to write plan to plan file: %s", err) 609 } 610 611 return nil 612 } 613 614 func resourceAttrToTfplan(ra globalref.ResourceAttr) (*planproto.PlanResourceAttr, error) { 615 res := &planproto.PlanResourceAttr{} 616 617 res.Resource = ra.Resource.String() 618 attr, err := pathToTfplan(ra.Attr) 619 if err != nil { 620 return res, err 621 } 622 res.Attr = attr 623 return res, nil 624 } 625 626 func resourceAttrFromTfplan(ra *planproto.PlanResourceAttr) (globalref.ResourceAttr, error) { 627 var res globalref.ResourceAttr 628 if ra.Resource == "" { 629 return res, fmt.Errorf("missing resource address from relevant attribute") 630 } 631 632 instAddr, diags := addrs.ParseAbsResourceInstanceStr(ra.Resource) 633 if diags.HasErrors() { 634 return res, fmt.Errorf("invalid resource instance address %q in relevant attributes: %w", ra.Resource, diags.Err()) 635 } 636 637 res.Resource = instAddr 638 path, err := pathFromTfplan(ra.Attr) 639 if err != nil { 640 return res, fmt.Errorf("invalid path in %q relevant attribute: %s", res.Resource, err) 641 } 642 643 res.Attr = path 644 return res, nil 645 } 646 647 func resourceChangeToTfplan(change *plans.ResourceInstanceChangeSrc) (*planproto.ResourceInstanceChange, error) { 648 ret := &planproto.ResourceInstanceChange{} 649 650 if change.PrevRunAddr.Resource.Resource.Type == "" { 651 // Suggests that an old caller wasn't yet updated to populate this 652 // properly. All code that generates plans should populate this field, 653 // even if it's just to write in the same value as in change.Addr. 654 change.PrevRunAddr = change.Addr 655 } 656 657 ret.Addr = change.Addr.String() 658 ret.PrevRunAddr = change.PrevRunAddr.String() 659 if ret.PrevRunAddr == ret.Addr { 660 // In the on-disk format we leave PrevRunAddr unpopulated in the common 661 // case where it's the same as Addr, and then fill it back in again on 662 // read. 663 ret.PrevRunAddr = "" 664 } 665 666 ret.DeposedKey = string(change.DeposedKey) 667 ret.Provider = change.ProviderAddr.String() 668 669 requiredReplace := change.RequiredReplace.List() 670 ret.RequiredReplace = make([]*planproto.Path, 0, len(requiredReplace)) 671 for _, p := range requiredReplace { 672 path, err := pathToTfplan(p) 673 if err != nil { 674 return nil, fmt.Errorf("invalid path in required replace: %s", err) 675 } 676 ret.RequiredReplace = append(ret.RequiredReplace, path) 677 } 678 679 valChange, err := changeToTfplan(&change.ChangeSrc) 680 if err != nil { 681 return nil, fmt.Errorf("failed to serialize resource %s change: %s", change.Addr, err) 682 } 683 ret.Change = valChange 684 685 switch change.ActionReason { 686 case plans.ResourceInstanceChangeNoReason: 687 ret.ActionReason = planproto.ResourceInstanceActionReason_NONE 688 case plans.ResourceInstanceReplaceBecauseCannotUpdate: 689 ret.ActionReason = planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_CANNOT_UPDATE 690 case plans.ResourceInstanceReplaceBecauseTainted: 691 ret.ActionReason = planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_TAINTED 692 case plans.ResourceInstanceReplaceByRequest: 693 ret.ActionReason = planproto.ResourceInstanceActionReason_REPLACE_BY_REQUEST 694 case plans.ResourceInstanceReplaceByTriggers: 695 ret.ActionReason = planproto.ResourceInstanceActionReason_REPLACE_BY_TRIGGERS 696 case plans.ResourceInstanceDeleteBecauseNoResourceConfig: 697 ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_RESOURCE_CONFIG 698 case plans.ResourceInstanceDeleteBecauseWrongRepetition: 699 ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_WRONG_REPETITION 700 case plans.ResourceInstanceDeleteBecauseCountIndex: 701 ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_COUNT_INDEX 702 case plans.ResourceInstanceDeleteBecauseEachKey: 703 ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_EACH_KEY 704 case plans.ResourceInstanceDeleteBecauseNoModule: 705 ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE 706 case plans.ResourceInstanceReadBecauseConfigUnknown: 707 ret.ActionReason = planproto.ResourceInstanceActionReason_READ_BECAUSE_CONFIG_UNKNOWN 708 case plans.ResourceInstanceReadBecauseDependencyPending: 709 ret.ActionReason = planproto.ResourceInstanceActionReason_READ_BECAUSE_DEPENDENCY_PENDING 710 case plans.ResourceInstanceDeleteBecauseNoMoveTarget: 711 ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MOVE_TARGET 712 default: 713 return nil, fmt.Errorf("resource %s has unsupported action reason %s", change.Addr, change.ActionReason) 714 } 715 716 if len(change.Private) > 0 { 717 ret.Private = change.Private 718 } 719 720 return ret, nil 721 } 722 723 func changeToTfplan(change *plans.ChangeSrc) (*planproto.Change, error) { 724 ret := &planproto.Change{} 725 726 before := valueToTfplan(change.Before) 727 after := valueToTfplan(change.After) 728 729 beforeSensitivePaths, err := pathValueMarksToTfplan(change.BeforeValMarks) 730 if err != nil { 731 return nil, err 732 } 733 afterSensitivePaths, err := pathValueMarksToTfplan(change.AfterValMarks) 734 if err != nil { 735 return nil, err 736 } 737 ret.BeforeSensitivePaths = beforeSensitivePaths 738 ret.AfterSensitivePaths = afterSensitivePaths 739 740 switch change.Action { 741 case plans.NoOp: 742 ret.Action = planproto.Action_NOOP 743 ret.Values = []*planproto.DynamicValue{before} // before and after should be identical 744 case plans.Create: 745 ret.Action = planproto.Action_CREATE 746 ret.Values = []*planproto.DynamicValue{after} 747 case plans.Read: 748 ret.Action = planproto.Action_READ 749 ret.Values = []*planproto.DynamicValue{before, after} 750 case plans.Update: 751 ret.Action = planproto.Action_UPDATE 752 ret.Values = []*planproto.DynamicValue{before, after} 753 case plans.Delete: 754 ret.Action = planproto.Action_DELETE 755 ret.Values = []*planproto.DynamicValue{before} 756 case plans.DeleteThenCreate: 757 ret.Action = planproto.Action_DELETE_THEN_CREATE 758 ret.Values = []*planproto.DynamicValue{before, after} 759 case plans.CreateThenDelete: 760 ret.Action = planproto.Action_CREATE_THEN_DELETE 761 ret.Values = []*planproto.DynamicValue{before, after} 762 default: 763 return nil, fmt.Errorf("invalid change action %s", change.Action) 764 } 765 766 return ret, nil 767 } 768 769 func valueToTfplan(val plans.DynamicValue) *planproto.DynamicValue { 770 if val == nil { 771 // protobuf can't represent nil, so we'll represent it as a 772 // DynamicValue that has no serializations at all. 773 return &planproto.DynamicValue{} 774 } 775 return &planproto.DynamicValue{ 776 Msgpack: []byte(val), 777 } 778 } 779 780 func pathValueMarksFromTfplan(paths []*planproto.Path, marks cty.ValueMarks) ([]cty.PathValueMarks, error) { 781 ret := make([]cty.PathValueMarks, 0, len(paths)) 782 for _, p := range paths { 783 path, err := pathFromTfplan(p) 784 if err != nil { 785 return nil, err 786 } 787 ret = append(ret, cty.PathValueMarks{ 788 Path: path, 789 Marks: marks, 790 }) 791 } 792 return ret, nil 793 } 794 795 func pathValueMarksToTfplan(pvm []cty.PathValueMarks) ([]*planproto.Path, error) { 796 ret := make([]*planproto.Path, 0, len(pvm)) 797 for _, p := range pvm { 798 path, err := pathToTfplan(p.Path) 799 if err != nil { 800 return nil, err 801 } 802 ret = append(ret, path) 803 } 804 return ret, nil 805 } 806 807 func pathFromTfplan(path *planproto.Path) (cty.Path, error) { 808 ret := make([]cty.PathStep, 0, len(path.Steps)) 809 for _, step := range path.Steps { 810 switch s := step.Selector.(type) { 811 case *planproto.Path_Step_ElementKey: 812 dynamicVal, err := valueFromTfplan(s.ElementKey) 813 if err != nil { 814 return nil, fmt.Errorf("error decoding path index step: %s", err) 815 } 816 ty, err := dynamicVal.ImpliedType() 817 if err != nil { 818 return nil, fmt.Errorf("error determining path index type: %s", err) 819 } 820 val, err := dynamicVal.Decode(ty) 821 if err != nil { 822 return nil, fmt.Errorf("error decoding path index value: %s", err) 823 } 824 ret = append(ret, cty.IndexStep{Key: val}) 825 case *planproto.Path_Step_AttributeName: 826 ret = append(ret, cty.GetAttrStep{Name: s.AttributeName}) 827 default: 828 return nil, fmt.Errorf("Unsupported path step %t", step.Selector) 829 } 830 } 831 return ret, nil 832 } 833 834 func pathToTfplan(path cty.Path) (*planproto.Path, error) { 835 steps := make([]*planproto.Path_Step, 0, len(path)) 836 for _, step := range path { 837 switch s := step.(type) { 838 case cty.IndexStep: 839 value, err := plans.NewDynamicValue(s.Key, s.Key.Type()) 840 if err != nil { 841 return nil, fmt.Errorf("Error encoding path step: %s", err) 842 } 843 steps = append(steps, &planproto.Path_Step{ 844 Selector: &planproto.Path_Step_ElementKey{ 845 ElementKey: valueToTfplan(value), 846 }, 847 }) 848 case cty.GetAttrStep: 849 steps = append(steps, &planproto.Path_Step{ 850 Selector: &planproto.Path_Step_AttributeName{ 851 AttributeName: s.Name, 852 }, 853 }) 854 default: 855 return nil, fmt.Errorf("Unsupported path step %#v (%t)", step, step) 856 } 857 } 858 return &planproto.Path{ 859 Steps: steps, 860 }, nil 861 }