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