github.com/muratcelep/terraform@v1.1.0-beta2-not-internal-4/not-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/muratcelep/terraform/not-internal/addrs" 11 "github.com/muratcelep/terraform/not-internal/lang/marks" 12 "github.com/muratcelep/terraform/not-internal/plans" 13 "github.com/muratcelep/terraform/not-internal/plans/internal/planproto" 14 "github.com/muratcelep/terraform/not-internal/states" 15 "github.com/muratcelep/terraform/version" 16 "github.com/zclconf/go-cty/cty" 17 ) 18 19 const tfplanFormatVersion = 3 20 const tfplanFilename = "tfplan" 21 22 // --------------------------------------------------------------------------- 23 // This file deals with the not-internal structure of the "tfplan" sub-file within 24 // the plan file format. It's all private API, wrapped by methods defined 25 // elsewhere. This is the only file that should import the 26 // ../not-internal/planproto package, which contains the ugly stubs generated 27 // by the protobuf compiler. 28 // --------------------------------------------------------------------------- 29 30 // readTfplan reads a protobuf-encoded description from the plan portion of 31 // a plan file, which is stored in a special file in the archive called 32 // "tfplan". 33 func readTfplan(r io.Reader) (*plans.Plan, error) { 34 src, err := ioutil.ReadAll(r) 35 if err != nil { 36 return nil, err 37 } 38 39 var rawPlan planproto.Plan 40 err = proto.Unmarshal(src, &rawPlan) 41 if err != nil { 42 return nil, fmt.Errorf("parse error: %s", err) 43 } 44 45 if rawPlan.Version != tfplanFormatVersion { 46 return nil, fmt.Errorf("unsupported plan file format version %d; only version %d is supported", rawPlan.Version, tfplanFormatVersion) 47 } 48 49 if rawPlan.TerraformVersion != version.String() { 50 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()) 51 } 52 53 plan := &plans.Plan{ 54 VariableValues: map[string]plans.DynamicValue{}, 55 Changes: &plans.Changes{ 56 Outputs: []*plans.OutputChangeSrc{}, 57 Resources: []*plans.ResourceInstanceChangeSrc{}, 58 }, 59 DriftedResources: []*plans.ResourceInstanceChangeSrc{}, 60 } 61 62 switch rawPlan.UiMode { 63 case planproto.Mode_NORMAL: 64 plan.UIMode = plans.NormalMode 65 case planproto.Mode_DESTROY: 66 plan.UIMode = plans.DestroyMode 67 case planproto.Mode_REFRESH_ONLY: 68 plan.UIMode = plans.RefreshOnlyMode 69 default: 70 return nil, fmt.Errorf("plan has invalid mode %s", rawPlan.UiMode) 71 } 72 73 for _, rawOC := range rawPlan.OutputChanges { 74 name := rawOC.Name 75 change, err := changeFromTfplan(rawOC.Change) 76 if err != nil { 77 return nil, fmt.Errorf("invalid plan for output %q: %s", name, err) 78 } 79 80 plan.Changes.Outputs = append(plan.Changes.Outputs, &plans.OutputChangeSrc{ 81 // All output values saved in the plan file are root module outputs, 82 // since we don't retain others. (They can be easily recomputed 83 // during apply). 84 Addr: addrs.OutputValue{Name: name}.Absolute(addrs.RootModuleInstance), 85 ChangeSrc: *change, 86 Sensitive: rawOC.Sensitive, 87 }) 88 } 89 90 for _, rawRC := range rawPlan.ResourceChanges { 91 change, err := resourceChangeFromTfplan(rawRC) 92 if err != nil { 93 // errors from resourceChangeFromTfplan already include context 94 return nil, err 95 } 96 97 plan.Changes.Resources = append(plan.Changes.Resources, change) 98 } 99 100 for _, rawRC := range rawPlan.ResourceDrift { 101 change, err := resourceChangeFromTfplan(rawRC) 102 if err != nil { 103 // errors from resourceChangeFromTfplan already include context 104 return nil, err 105 } 106 107 plan.DriftedResources = append(plan.DriftedResources, change) 108 } 109 110 for _, rawTargetAddr := range rawPlan.TargetAddrs { 111 target, diags := addrs.ParseTargetStr(rawTargetAddr) 112 if diags.HasErrors() { 113 return nil, fmt.Errorf("plan contains invalid target address %q: %s", target, diags.Err()) 114 } 115 plan.TargetAddrs = append(plan.TargetAddrs, target.Subject) 116 } 117 118 for _, rawReplaceAddr := range rawPlan.ForceReplaceAddrs { 119 addr, diags := addrs.ParseAbsResourceInstanceStr(rawReplaceAddr) 120 if diags.HasErrors() { 121 return nil, fmt.Errorf("plan contains invalid force-replace address %q: %s", addr, diags.Err()) 122 } 123 plan.ForceReplaceAddrs = append(plan.ForceReplaceAddrs, addr) 124 } 125 126 for name, rawVal := range rawPlan.Variables { 127 val, err := valueFromTfplan(rawVal) 128 if err != nil { 129 return nil, fmt.Errorf("invalid value for input variable %q: %s", name, err) 130 } 131 plan.VariableValues[name] = val 132 } 133 134 if rawBackend := rawPlan.Backend; rawBackend == nil { 135 return nil, fmt.Errorf("plan file has no backend settings; backend settings are required") 136 } else { 137 config, err := valueFromTfplan(rawBackend.Config) 138 if err != nil { 139 return nil, fmt.Errorf("plan file has invalid backend configuration: %s", err) 140 } 141 plan.Backend = plans.Backend{ 142 Type: rawBackend.Type, 143 Config: config, 144 Workspace: rawBackend.Workspace, 145 } 146 } 147 148 return plan, nil 149 } 150 151 func resourceChangeFromTfplan(rawChange *planproto.ResourceInstanceChange) (*plans.ResourceInstanceChangeSrc, error) { 152 if rawChange == nil { 153 // Should never happen in practice, since protobuf can't represent 154 // a nil value in a list. 155 return nil, fmt.Errorf("resource change object is absent") 156 } 157 158 ret := &plans.ResourceInstanceChangeSrc{} 159 160 if rawChange.Addr == "" { 161 // If "Addr" isn't populated then seems likely that this is a plan 162 // file created by an earlier version of Terraform, which had the 163 // same information spread over various other fields: 164 // ModulePath, Mode, Name, Type, and InstanceKey. 165 return nil, fmt.Errorf("no instance address for resource instance change; perhaps this plan was created by a different version of Terraform?") 166 } 167 168 instAddr, diags := addrs.ParseAbsResourceInstanceStr(rawChange.Addr) 169 if diags.HasErrors() { 170 return nil, fmt.Errorf("invalid resource instance address %q: %w", rawChange.Addr, diags.Err()) 171 } 172 prevRunAddr := instAddr 173 if rawChange.PrevRunAddr != "" { 174 prevRunAddr, diags = addrs.ParseAbsResourceInstanceStr(rawChange.PrevRunAddr) 175 if diags.HasErrors() { 176 return nil, fmt.Errorf("invalid resource instance previous run address %q: %w", rawChange.PrevRunAddr, diags.Err()) 177 } 178 } 179 180 providerAddr, diags := addrs.ParseAbsProviderConfigStr(rawChange.Provider) 181 if diags.HasErrors() { 182 return nil, diags.Err() 183 } 184 ret.ProviderAddr = providerAddr 185 186 ret.Addr = instAddr 187 ret.PrevRunAddr = prevRunAddr 188 189 if rawChange.DeposedKey != "" { 190 if len(rawChange.DeposedKey) != 8 { 191 return nil, fmt.Errorf("deposed object for %s has invalid deposed key %q", ret.Addr, rawChange.DeposedKey) 192 } 193 ret.DeposedKey = states.DeposedKey(rawChange.DeposedKey) 194 } 195 196 ret.RequiredReplace = cty.NewPathSet() 197 for _, p := range rawChange.RequiredReplace { 198 path, err := pathFromTfplan(p) 199 if err != nil { 200 return nil, fmt.Errorf("invalid path in required replace: %s", err) 201 } 202 ret.RequiredReplace.Add(path) 203 } 204 205 change, err := changeFromTfplan(rawChange.Change) 206 if err != nil { 207 return nil, fmt.Errorf("invalid plan for resource %s: %s", ret.Addr, err) 208 } 209 210 ret.ChangeSrc = *change 211 212 switch rawChange.ActionReason { 213 case planproto.ResourceInstanceActionReason_NONE: 214 ret.ActionReason = plans.ResourceInstanceChangeNoReason 215 case planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_CANNOT_UPDATE: 216 ret.ActionReason = plans.ResourceInstanceReplaceBecauseCannotUpdate 217 case planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_TAINTED: 218 ret.ActionReason = plans.ResourceInstanceReplaceBecauseTainted 219 case planproto.ResourceInstanceActionReason_REPLACE_BY_REQUEST: 220 ret.ActionReason = plans.ResourceInstanceReplaceByRequest 221 case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_RESOURCE_CONFIG: 222 ret.ActionReason = plans.ResourceInstanceDeleteBecauseNoResourceConfig 223 case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_WRONG_REPETITION: 224 ret.ActionReason = plans.ResourceInstanceDeleteBecauseWrongRepetition 225 case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_COUNT_INDEX: 226 ret.ActionReason = plans.ResourceInstanceDeleteBecauseCountIndex 227 case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_EACH_KEY: 228 ret.ActionReason = plans.ResourceInstanceDeleteBecauseEachKey 229 case planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE: 230 ret.ActionReason = plans.ResourceInstanceDeleteBecauseNoModule 231 default: 232 return nil, fmt.Errorf("resource has invalid action reason %s", rawChange.ActionReason) 233 } 234 235 if len(rawChange.Private) != 0 { 236 ret.Private = rawChange.Private 237 } 238 239 return ret, nil 240 } 241 242 func changeFromTfplan(rawChange *planproto.Change) (*plans.ChangeSrc, error) { 243 if rawChange == nil { 244 return nil, fmt.Errorf("change object is absent") 245 } 246 247 ret := &plans.ChangeSrc{} 248 249 // -1 indicates that there is no index. We'll customize these below 250 // depending on the change action, and then decode. 251 beforeIdx, afterIdx := -1, -1 252 253 switch rawChange.Action { 254 case planproto.Action_NOOP: 255 ret.Action = plans.NoOp 256 beforeIdx = 0 257 afterIdx = 0 258 case planproto.Action_CREATE: 259 ret.Action = plans.Create 260 afterIdx = 0 261 case planproto.Action_READ: 262 ret.Action = plans.Read 263 beforeIdx = 0 264 afterIdx = 1 265 case planproto.Action_UPDATE: 266 ret.Action = plans.Update 267 beforeIdx = 0 268 afterIdx = 1 269 case planproto.Action_DELETE: 270 ret.Action = plans.Delete 271 beforeIdx = 0 272 case planproto.Action_CREATE_THEN_DELETE: 273 ret.Action = plans.CreateThenDelete 274 beforeIdx = 0 275 afterIdx = 1 276 case planproto.Action_DELETE_THEN_CREATE: 277 ret.Action = plans.DeleteThenCreate 278 beforeIdx = 0 279 afterIdx = 1 280 default: 281 return nil, fmt.Errorf("invalid change action %s", rawChange.Action) 282 } 283 284 if beforeIdx != -1 { 285 if l := len(rawChange.Values); l <= beforeIdx { 286 return nil, fmt.Errorf("incorrect number of values (%d) for %s change", l, rawChange.Action) 287 } 288 var err error 289 ret.Before, err = valueFromTfplan(rawChange.Values[beforeIdx]) 290 if err != nil { 291 return nil, fmt.Errorf("invalid \"before\" value: %s", err) 292 } 293 if ret.Before == nil { 294 return nil, fmt.Errorf("missing \"before\" value: %s", err) 295 } 296 } 297 if afterIdx != -1 { 298 if l := len(rawChange.Values); l <= afterIdx { 299 return nil, fmt.Errorf("incorrect number of values (%d) for %s change", l, rawChange.Action) 300 } 301 var err error 302 ret.After, err = valueFromTfplan(rawChange.Values[afterIdx]) 303 if err != nil { 304 return nil, fmt.Errorf("invalid \"after\" value: %s", err) 305 } 306 if ret.After == nil { 307 return nil, fmt.Errorf("missing \"after\" value: %s", err) 308 } 309 } 310 311 sensitive := cty.NewValueMarks(marks.Sensitive) 312 beforeValMarks, err := pathValueMarksFromTfplan(rawChange.BeforeSensitivePaths, sensitive) 313 if err != nil { 314 return nil, fmt.Errorf("failed to decode before sensitive paths: %s", err) 315 } 316 afterValMarks, err := pathValueMarksFromTfplan(rawChange.AfterSensitivePaths, sensitive) 317 if err != nil { 318 return nil, fmt.Errorf("failed to decode after sensitive paths: %s", err) 319 } 320 if len(beforeValMarks) > 0 { 321 ret.BeforeValMarks = beforeValMarks 322 } 323 if len(afterValMarks) > 0 { 324 ret.AfterValMarks = afterValMarks 325 } 326 327 return ret, nil 328 } 329 330 func valueFromTfplan(rawV *planproto.DynamicValue) (plans.DynamicValue, error) { 331 if len(rawV.Msgpack) == 0 { // len(0) because that's the default value for a "bytes" in protobuf 332 return nil, fmt.Errorf("dynamic value does not have msgpack serialization") 333 } 334 335 return plans.DynamicValue(rawV.Msgpack), nil 336 } 337 338 // writeTfplan serializes the given plan into the protobuf-based format used 339 // for the "tfplan" portion of a plan file. 340 func writeTfplan(plan *plans.Plan, w io.Writer) error { 341 if plan == nil { 342 return fmt.Errorf("cannot write plan file for nil plan") 343 } 344 if plan.Changes == nil { 345 return fmt.Errorf("cannot write plan file with nil changeset") 346 } 347 348 rawPlan := &planproto.Plan{ 349 Version: tfplanFormatVersion, 350 TerraformVersion: version.String(), 351 352 Variables: map[string]*planproto.DynamicValue{}, 353 OutputChanges: []*planproto.OutputChange{}, 354 ResourceChanges: []*planproto.ResourceInstanceChange{}, 355 ResourceDrift: []*planproto.ResourceInstanceChange{}, 356 } 357 358 switch plan.UIMode { 359 case plans.NormalMode: 360 rawPlan.UiMode = planproto.Mode_NORMAL 361 case plans.DestroyMode: 362 rawPlan.UiMode = planproto.Mode_DESTROY 363 case plans.RefreshOnlyMode: 364 rawPlan.UiMode = planproto.Mode_REFRESH_ONLY 365 default: 366 return fmt.Errorf("plan has unsupported mode %s", plan.UIMode) 367 } 368 369 for _, oc := range plan.Changes.Outputs { 370 // When serializing a plan we only retain the root outputs, since 371 // changes to these are externally-visible side effects (e.g. via 372 // terraform_remote_state). 373 if !oc.Addr.Module.IsRoot() { 374 continue 375 } 376 377 name := oc.Addr.OutputValue.Name 378 379 // Writing outputs as cty.DynamicPseudoType forces the stored values 380 // to also contain dynamic type information, so we can recover the 381 // original type when we read the values back in readTFPlan. 382 protoChange, err := changeToTfplan(&oc.ChangeSrc) 383 if err != nil { 384 return fmt.Errorf("cannot write output value %q: %s", name, err) 385 } 386 387 rawPlan.OutputChanges = append(rawPlan.OutputChanges, &planproto.OutputChange{ 388 Name: name, 389 Change: protoChange, 390 Sensitive: oc.Sensitive, 391 }) 392 } 393 394 for _, rc := range plan.Changes.Resources { 395 rawRC, err := resourceChangeToTfplan(rc) 396 if err != nil { 397 return err 398 } 399 rawPlan.ResourceChanges = append(rawPlan.ResourceChanges, rawRC) 400 } 401 402 for _, rc := range plan.DriftedResources { 403 rawRC, err := resourceChangeToTfplan(rc) 404 if err != nil { 405 return err 406 } 407 rawPlan.ResourceDrift = append(rawPlan.ResourceDrift, rawRC) 408 } 409 410 for _, targetAddr := range plan.TargetAddrs { 411 rawPlan.TargetAddrs = append(rawPlan.TargetAddrs, targetAddr.String()) 412 } 413 414 for _, replaceAddr := range plan.ForceReplaceAddrs { 415 rawPlan.ForceReplaceAddrs = append(rawPlan.ForceReplaceAddrs, replaceAddr.String()) 416 } 417 418 for name, val := range plan.VariableValues { 419 rawPlan.Variables[name] = valueToTfplan(val) 420 } 421 422 if plan.Backend.Type == "" || plan.Backend.Config == nil { 423 // This suggests a bug in the code that created the plan, since it 424 // ought to always have a backend populated, even if it's the default 425 // "local" backend with a local state file. 426 return fmt.Errorf("plan does not have a backend configuration") 427 } 428 429 rawPlan.Backend = &planproto.Backend{ 430 Type: plan.Backend.Type, 431 Config: valueToTfplan(plan.Backend.Config), 432 Workspace: plan.Backend.Workspace, 433 } 434 435 src, err := proto.Marshal(rawPlan) 436 if err != nil { 437 return fmt.Errorf("serialization error: %s", err) 438 } 439 440 _, err = w.Write(src) 441 if err != nil { 442 return fmt.Errorf("failed to write plan to plan file: %s", err) 443 } 444 445 return nil 446 } 447 448 func resourceChangeToTfplan(change *plans.ResourceInstanceChangeSrc) (*planproto.ResourceInstanceChange, error) { 449 ret := &planproto.ResourceInstanceChange{} 450 451 if change.PrevRunAddr.Resource.Resource.Type == "" { 452 // Suggests that an old caller wasn't yet updated to populate this 453 // properly. All code that generates plans should populate this field, 454 // even if it's just to write in the same value as in change.Addr. 455 change.PrevRunAddr = change.Addr 456 } 457 458 ret.Addr = change.Addr.String() 459 ret.PrevRunAddr = change.PrevRunAddr.String() 460 if ret.PrevRunAddr == ret.Addr { 461 // In the on-disk format we leave PrevRunAddr unpopulated in the common 462 // case where it's the same as Addr, and then fill it back in again on 463 // read. 464 ret.PrevRunAddr = "" 465 } 466 467 ret.DeposedKey = string(change.DeposedKey) 468 ret.Provider = change.ProviderAddr.String() 469 470 requiredReplace := change.RequiredReplace.List() 471 ret.RequiredReplace = make([]*planproto.Path, 0, len(requiredReplace)) 472 for _, p := range requiredReplace { 473 path, err := pathToTfplan(p) 474 if err != nil { 475 return nil, fmt.Errorf("invalid path in required replace: %s", err) 476 } 477 ret.RequiredReplace = append(ret.RequiredReplace, path) 478 } 479 480 valChange, err := changeToTfplan(&change.ChangeSrc) 481 if err != nil { 482 return nil, fmt.Errorf("failed to serialize resource %s change: %s", change.Addr, err) 483 } 484 ret.Change = valChange 485 486 switch change.ActionReason { 487 case plans.ResourceInstanceChangeNoReason: 488 ret.ActionReason = planproto.ResourceInstanceActionReason_NONE 489 case plans.ResourceInstanceReplaceBecauseCannotUpdate: 490 ret.ActionReason = planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_CANNOT_UPDATE 491 case plans.ResourceInstanceReplaceBecauseTainted: 492 ret.ActionReason = planproto.ResourceInstanceActionReason_REPLACE_BECAUSE_TAINTED 493 case plans.ResourceInstanceReplaceByRequest: 494 ret.ActionReason = planproto.ResourceInstanceActionReason_REPLACE_BY_REQUEST 495 case plans.ResourceInstanceDeleteBecauseNoResourceConfig: 496 ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_RESOURCE_CONFIG 497 case plans.ResourceInstanceDeleteBecauseWrongRepetition: 498 ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_WRONG_REPETITION 499 case plans.ResourceInstanceDeleteBecauseCountIndex: 500 ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_COUNT_INDEX 501 case plans.ResourceInstanceDeleteBecauseEachKey: 502 ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_EACH_KEY 503 case plans.ResourceInstanceDeleteBecauseNoModule: 504 ret.ActionReason = planproto.ResourceInstanceActionReason_DELETE_BECAUSE_NO_MODULE 505 default: 506 return nil, fmt.Errorf("resource %s has unsupported action reason %s", change.Addr, change.ActionReason) 507 } 508 509 if len(change.Private) > 0 { 510 ret.Private = change.Private 511 } 512 513 return ret, nil 514 } 515 516 func changeToTfplan(change *plans.ChangeSrc) (*planproto.Change, error) { 517 ret := &planproto.Change{} 518 519 before := valueToTfplan(change.Before) 520 after := valueToTfplan(change.After) 521 522 beforeSensitivePaths, err := pathValueMarksToTfplan(change.BeforeValMarks) 523 if err != nil { 524 return nil, err 525 } 526 afterSensitivePaths, err := pathValueMarksToTfplan(change.AfterValMarks) 527 if err != nil { 528 return nil, err 529 } 530 ret.BeforeSensitivePaths = beforeSensitivePaths 531 ret.AfterSensitivePaths = afterSensitivePaths 532 533 switch change.Action { 534 case plans.NoOp: 535 ret.Action = planproto.Action_NOOP 536 ret.Values = []*planproto.DynamicValue{before} // before and after should be identical 537 case plans.Create: 538 ret.Action = planproto.Action_CREATE 539 ret.Values = []*planproto.DynamicValue{after} 540 case plans.Read: 541 ret.Action = planproto.Action_READ 542 ret.Values = []*planproto.DynamicValue{before, after} 543 case plans.Update: 544 ret.Action = planproto.Action_UPDATE 545 ret.Values = []*planproto.DynamicValue{before, after} 546 case plans.Delete: 547 ret.Action = planproto.Action_DELETE 548 ret.Values = []*planproto.DynamicValue{before} 549 case plans.DeleteThenCreate: 550 ret.Action = planproto.Action_DELETE_THEN_CREATE 551 ret.Values = []*planproto.DynamicValue{before, after} 552 case plans.CreateThenDelete: 553 ret.Action = planproto.Action_CREATE_THEN_DELETE 554 ret.Values = []*planproto.DynamicValue{before, after} 555 default: 556 return nil, fmt.Errorf("invalid change action %s", change.Action) 557 } 558 559 return ret, nil 560 } 561 562 func valueToTfplan(val plans.DynamicValue) *planproto.DynamicValue { 563 if val == nil { 564 // protobuf can't represent nil, so we'll represent it as a 565 // DynamicValue that has no serializations at all. 566 return &planproto.DynamicValue{} 567 } 568 return &planproto.DynamicValue{ 569 Msgpack: []byte(val), 570 } 571 } 572 573 func pathValueMarksFromTfplan(paths []*planproto.Path, marks cty.ValueMarks) ([]cty.PathValueMarks, error) { 574 ret := make([]cty.PathValueMarks, 0, len(paths)) 575 for _, p := range paths { 576 path, err := pathFromTfplan(p) 577 if err != nil { 578 return nil, err 579 } 580 ret = append(ret, cty.PathValueMarks{ 581 Path: path, 582 Marks: marks, 583 }) 584 } 585 return ret, nil 586 } 587 588 func pathValueMarksToTfplan(pvm []cty.PathValueMarks) ([]*planproto.Path, error) { 589 ret := make([]*planproto.Path, 0, len(pvm)) 590 for _, p := range pvm { 591 path, err := pathToTfplan(p.Path) 592 if err != nil { 593 return nil, err 594 } 595 ret = append(ret, path) 596 } 597 return ret, nil 598 } 599 600 func pathFromTfplan(path *planproto.Path) (cty.Path, error) { 601 ret := make([]cty.PathStep, 0, len(path.Steps)) 602 for _, step := range path.Steps { 603 switch s := step.Selector.(type) { 604 case *planproto.Path_Step_ElementKey: 605 dynamicVal, err := valueFromTfplan(s.ElementKey) 606 if err != nil { 607 return nil, fmt.Errorf("error decoding path index step: %s", err) 608 } 609 ty, err := dynamicVal.ImpliedType() 610 if err != nil { 611 return nil, fmt.Errorf("error determining path index type: %s", err) 612 } 613 val, err := dynamicVal.Decode(ty) 614 if err != nil { 615 return nil, fmt.Errorf("error decoding path index value: %s", err) 616 } 617 ret = append(ret, cty.IndexStep{Key: val}) 618 case *planproto.Path_Step_AttributeName: 619 ret = append(ret, cty.GetAttrStep{Name: s.AttributeName}) 620 default: 621 return nil, fmt.Errorf("Unsupported path step %t", step.Selector) 622 } 623 } 624 return ret, nil 625 } 626 627 func pathToTfplan(path cty.Path) (*planproto.Path, error) { 628 steps := make([]*planproto.Path_Step, 0, len(path)) 629 for _, step := range path { 630 switch s := step.(type) { 631 case cty.IndexStep: 632 value, err := plans.NewDynamicValue(s.Key, s.Key.Type()) 633 if err != nil { 634 return nil, fmt.Errorf("Error encoding path step: %s", err) 635 } 636 steps = append(steps, &planproto.Path_Step{ 637 Selector: &planproto.Path_Step_ElementKey{ 638 ElementKey: valueToTfplan(value), 639 }, 640 }) 641 case cty.GetAttrStep: 642 steps = append(steps, &planproto.Path_Step{ 643 Selector: &planproto.Path_Step_AttributeName{ 644 AttributeName: s.Name, 645 }, 646 }) 647 default: 648 return nil, fmt.Errorf("Unsupported path step %#v (%t)", step, step) 649 } 650 } 651 return &planproto.Path{ 652 Steps: steps, 653 }, nil 654 }