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