github.com/hashicorp/terraform-plugin-sdk@v1.17.2/internal/plans/planfile/tfplan.go (about) 1 package planfile 2 3 import ( 4 "fmt" 5 "io" 6 "io/ioutil" 7 8 "github.com/golang/protobuf/proto" 9 10 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 11 "github.com/hashicorp/terraform-plugin-sdk/internal/plans" 12 "github.com/hashicorp/terraform-plugin-sdk/internal/plans/internal/planproto" 13 "github.com/hashicorp/terraform-plugin-sdk/internal/states" 14 "github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags" 15 "github.com/hashicorp/terraform-plugin-sdk/internal/version" 16 ) 17 18 const tfplanFormatVersion = 3 19 const tfplanFilename = "tfplan" 20 21 // --------------------------------------------------------------------------- 22 // This file deals with the internal structure of the "tfplan" sub-file within 23 // the plan file format. It's all private API, wrapped by methods defined 24 // elsewhere. This is the only file that should import the 25 // ../internal/planproto package, which contains the ugly stubs generated 26 // by the protobuf compiler. 27 // --------------------------------------------------------------------------- 28 29 // readTfplan reads a protobuf-encoded description from the plan portion of 30 // a plan file, which is stored in a special file in the archive called 31 // "tfplan". 32 func readTfplan(r io.Reader) (*plans.Plan, error) { 33 src, err := ioutil.ReadAll(r) 34 if err != nil { 35 return nil, err 36 } 37 38 var rawPlan planproto.Plan 39 err = proto.Unmarshal(src, &rawPlan) 40 if err != nil { 41 return nil, fmt.Errorf("parse error: %s", err) 42 } 43 44 if rawPlan.Version != tfplanFormatVersion { 45 return nil, fmt.Errorf("unsupported plan file format version %d; only version %d is supported", rawPlan.Version, tfplanFormatVersion) 46 } 47 48 if rawPlan.TerraformVersion != version.String() { 49 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()) 50 } 51 52 plan := &plans.Plan{ 53 VariableValues: map[string]plans.DynamicValue{}, 54 Changes: &plans.Changes{ 55 Outputs: []*plans.OutputChangeSrc{}, 56 Resources: []*plans.ResourceInstanceChangeSrc{}, 57 }, 58 59 ProviderSHA256s: map[string][]byte{}, 60 } 61 62 for _, rawOC := range rawPlan.OutputChanges { 63 name := rawOC.Name 64 change, err := changeFromTfplan(rawOC.Change) 65 if err != nil { 66 return nil, fmt.Errorf("invalid plan for output %q: %s", name, err) 67 } 68 69 plan.Changes.Outputs = append(plan.Changes.Outputs, &plans.OutputChangeSrc{ 70 // All output values saved in the plan file are root module outputs, 71 // since we don't retain others. (They can be easily recomputed 72 // during apply). 73 Addr: addrs.OutputValue{Name: name}.Absolute(addrs.RootModuleInstance), 74 ChangeSrc: *change, 75 Sensitive: rawOC.Sensitive, 76 }) 77 } 78 79 for _, rawRC := range rawPlan.ResourceChanges { 80 change, err := resourceChangeFromTfplan(rawRC) 81 if err != nil { 82 // errors from resourceChangeFromTfplan already include context 83 return nil, err 84 } 85 86 plan.Changes.Resources = append(plan.Changes.Resources, change) 87 } 88 89 for _, rawTargetAddr := range rawPlan.TargetAddrs { 90 target, diags := addrs.ParseTargetStr(rawTargetAddr) 91 if diags.HasErrors() { 92 return nil, fmt.Errorf("plan contains invalid target address %q: %s", target, diags.Err()) 93 } 94 plan.TargetAddrs = append(plan.TargetAddrs, target.Subject) 95 } 96 97 for name, rawHashObj := range rawPlan.ProviderHashes { 98 if len(rawHashObj.Sha256) == 0 { 99 return nil, fmt.Errorf("no SHA256 hash for provider %q plugin", name) 100 } 101 102 plan.ProviderSHA256s[name] = rawHashObj.Sha256 103 } 104 105 for name, rawVal := range rawPlan.Variables { 106 val, err := valueFromTfplan(rawVal) 107 if err != nil { 108 return nil, fmt.Errorf("invalid value for input variable %q: %s", name, err) 109 } 110 plan.VariableValues[name] = val 111 } 112 113 if rawBackend := rawPlan.Backend; rawBackend == nil { 114 return nil, fmt.Errorf("plan file has no backend settings; backend settings are required") 115 } else { 116 config, err := valueFromTfplan(rawBackend.Config) 117 if err != nil { 118 return nil, fmt.Errorf("plan file has invalid backend configuration: %s", err) 119 } 120 plan.Backend = plans.Backend{ 121 Type: rawBackend.Type, 122 Config: config, 123 Workspace: rawBackend.Workspace, 124 } 125 } 126 127 return plan, nil 128 } 129 130 func resourceChangeFromTfplan(rawChange *planproto.ResourceInstanceChange) (*plans.ResourceInstanceChangeSrc, error) { 131 if rawChange == nil { 132 // Should never happen in practice, since protobuf can't represent 133 // a nil value in a list. 134 return nil, fmt.Errorf("resource change object is absent") 135 } 136 137 ret := &plans.ResourceInstanceChangeSrc{} 138 139 moduleAddr := addrs.RootModuleInstance 140 if rawChange.ModulePath != "" { 141 var diags tfdiags.Diagnostics 142 moduleAddr, diags = addrs.ParseModuleInstanceStr(rawChange.ModulePath) 143 if diags.HasErrors() { 144 return nil, diags.Err() 145 } 146 } 147 148 providerAddr, diags := addrs.ParseAbsProviderConfigStr(rawChange.Provider) 149 if diags.HasErrors() { 150 return nil, diags.Err() 151 } 152 ret.ProviderAddr = providerAddr 153 154 var mode addrs.ResourceMode 155 switch rawChange.Mode { 156 case planproto.ResourceInstanceChange_managed: 157 mode = addrs.ManagedResourceMode 158 case planproto.ResourceInstanceChange_data: 159 mode = addrs.DataResourceMode 160 default: 161 return nil, fmt.Errorf("resource has invalid mode %s", rawChange.Mode) 162 } 163 164 typeName := rawChange.Type 165 name := rawChange.Name 166 167 resAddr := addrs.Resource{ 168 Mode: mode, 169 Type: typeName, 170 Name: name, 171 } 172 173 var instKey addrs.InstanceKey 174 switch rawTk := rawChange.InstanceKey.(type) { 175 case nil: 176 case *planproto.ResourceInstanceChange_Int: 177 instKey = addrs.IntKey(rawTk.Int) 178 case *planproto.ResourceInstanceChange_Str: 179 instKey = addrs.StringKey(rawTk.Str) 180 default: 181 return nil, fmt.Errorf("instance of %s has invalid key type %T", resAddr.Absolute(moduleAddr), rawChange.InstanceKey) 182 } 183 184 ret.Addr = resAddr.Instance(instKey).Absolute(moduleAddr) 185 186 if rawChange.DeposedKey != "" { 187 if len(rawChange.DeposedKey) != 8 { 188 return nil, fmt.Errorf("deposed object for %s has invalid deposed key %q", ret.Addr, rawChange.DeposedKey) 189 } 190 ret.DeposedKey = states.DeposedKey(rawChange.DeposedKey) 191 } 192 193 change, err := changeFromTfplan(rawChange.Change) 194 if err != nil { 195 return nil, fmt.Errorf("invalid plan for resource %s: %s", ret.Addr, err) 196 } 197 198 ret.ChangeSrc = *change 199 200 if len(rawChange.Private) != 0 { 201 ret.Private = rawChange.Private 202 } 203 204 return ret, nil 205 } 206 207 func changeFromTfplan(rawChange *planproto.Change) (*plans.ChangeSrc, error) { 208 if rawChange == nil { 209 return nil, fmt.Errorf("change object is absent") 210 } 211 212 ret := &plans.ChangeSrc{} 213 214 // -1 indicates that there is no index. We'll customize these below 215 // depending on the change action, and then decode. 216 beforeIdx, afterIdx := -1, -1 217 218 switch rawChange.Action { 219 case planproto.Action_NOOP: 220 ret.Action = plans.NoOp 221 beforeIdx = 0 222 afterIdx = 0 223 case planproto.Action_CREATE: 224 ret.Action = plans.Create 225 afterIdx = 0 226 case planproto.Action_READ: 227 ret.Action = plans.Read 228 beforeIdx = 0 229 afterIdx = 1 230 case planproto.Action_UPDATE: 231 ret.Action = plans.Update 232 beforeIdx = 0 233 afterIdx = 1 234 case planproto.Action_DELETE: 235 ret.Action = plans.Delete 236 beforeIdx = 0 237 case planproto.Action_CREATE_THEN_DELETE: 238 ret.Action = plans.CreateThenDelete 239 beforeIdx = 0 240 afterIdx = 1 241 case planproto.Action_DELETE_THEN_CREATE: 242 ret.Action = plans.DeleteThenCreate 243 beforeIdx = 0 244 afterIdx = 1 245 default: 246 return nil, fmt.Errorf("invalid change action %s", rawChange.Action) 247 } 248 249 if beforeIdx != -1 { 250 if l := len(rawChange.Values); l <= beforeIdx { 251 return nil, fmt.Errorf("incorrect number of values (%d) for %s change", l, rawChange.Action) 252 } 253 var err error 254 ret.Before, err = valueFromTfplan(rawChange.Values[beforeIdx]) 255 if err != nil { 256 return nil, fmt.Errorf("invalid \"before\" value: %s", err) 257 } 258 if ret.Before == nil { 259 return nil, fmt.Errorf("missing \"before\" value: %s", err) 260 } 261 } 262 if afterIdx != -1 { 263 if l := len(rawChange.Values); l <= afterIdx { 264 return nil, fmt.Errorf("incorrect number of values (%d) for %s change", l, rawChange.Action) 265 } 266 var err error 267 ret.After, err = valueFromTfplan(rawChange.Values[afterIdx]) 268 if err != nil { 269 return nil, fmt.Errorf("invalid \"after\" value: %s", err) 270 } 271 if ret.After == nil { 272 return nil, fmt.Errorf("missing \"after\" value: %s", err) 273 } 274 } 275 276 return ret, nil 277 } 278 279 func valueFromTfplan(rawV *planproto.DynamicValue) (plans.DynamicValue, error) { 280 if len(rawV.Msgpack) == 0 { // len(0) because that's the default value for a "bytes" in protobuf 281 return nil, fmt.Errorf("dynamic value does not have msgpack serialization") 282 } 283 284 return plans.DynamicValue(rawV.Msgpack), nil 285 } 286 287 // writeTfplan serializes the given plan into the protobuf-based format used 288 // for the "tfplan" portion of a plan file. 289 func writeTfplan(plan *plans.Plan, w io.Writer) error { 290 if plan == nil { 291 return fmt.Errorf("cannot write plan file for nil plan") 292 } 293 if plan.Changes == nil { 294 return fmt.Errorf("cannot write plan file with nil changeset") 295 } 296 297 rawPlan := &planproto.Plan{ 298 Version: tfplanFormatVersion, 299 TerraformVersion: version.String(), 300 ProviderHashes: map[string]*planproto.Hash{}, 301 302 Variables: map[string]*planproto.DynamicValue{}, 303 OutputChanges: []*planproto.OutputChange{}, 304 ResourceChanges: []*planproto.ResourceInstanceChange{}, 305 } 306 307 for _, oc := range plan.Changes.Outputs { 308 // When serializing a plan we only retain the root outputs, since 309 // changes to these are externally-visible side effects (e.g. via 310 // terraform_remote_state). 311 if !oc.Addr.Module.IsRoot() { 312 continue 313 } 314 315 name := oc.Addr.OutputValue.Name 316 317 // Writing outputs as cty.DynamicPseudoType forces the stored values 318 // to also contain dynamic type information, so we can recover the 319 // original type when we read the values back in readTFPlan. 320 protoChange, err := changeToTfplan(&oc.ChangeSrc) 321 if err != nil { 322 return fmt.Errorf("cannot write output value %q: %s", name, err) 323 } 324 325 rawPlan.OutputChanges = append(rawPlan.OutputChanges, &planproto.OutputChange{ 326 Name: name, 327 Change: protoChange, 328 Sensitive: oc.Sensitive, 329 }) 330 } 331 332 for _, rc := range plan.Changes.Resources { 333 rawRC, err := resourceChangeToTfplan(rc) 334 if err != nil { 335 return err 336 } 337 rawPlan.ResourceChanges = append(rawPlan.ResourceChanges, rawRC) 338 } 339 340 for _, targetAddr := range plan.TargetAddrs { 341 rawPlan.TargetAddrs = append(rawPlan.TargetAddrs, targetAddr.String()) 342 } 343 344 for name, hash := range plan.ProviderSHA256s { 345 rawPlan.ProviderHashes[name] = &planproto.Hash{ 346 Sha256: hash, 347 } 348 } 349 350 for name, val := range plan.VariableValues { 351 rawPlan.Variables[name] = valueToTfplan(val) 352 } 353 354 if plan.Backend.Type == "" || plan.Backend.Config == nil { 355 // This suggests a bug in the code that created the plan, since it 356 // ought to always have a backend populated, even if it's the default 357 // "local" backend with a local state file. 358 return fmt.Errorf("plan does not have a backend configuration") 359 } 360 361 rawPlan.Backend = &planproto.Backend{ 362 Type: plan.Backend.Type, 363 Config: valueToTfplan(plan.Backend.Config), 364 Workspace: plan.Backend.Workspace, 365 } 366 367 src, err := proto.Marshal(rawPlan) 368 if err != nil { 369 return fmt.Errorf("serialization error: %s", err) 370 } 371 372 _, err = w.Write(src) 373 if err != nil { 374 return fmt.Errorf("failed to write plan to plan file: %s", err) 375 } 376 377 return nil 378 } 379 380 func resourceChangeToTfplan(change *plans.ResourceInstanceChangeSrc) (*planproto.ResourceInstanceChange, error) { 381 ret := &planproto.ResourceInstanceChange{} 382 383 ret.ModulePath = change.Addr.Module.String() 384 385 relAddr := change.Addr.Resource 386 387 switch relAddr.Resource.Mode { 388 case addrs.ManagedResourceMode: 389 ret.Mode = planproto.ResourceInstanceChange_managed 390 case addrs.DataResourceMode: 391 ret.Mode = planproto.ResourceInstanceChange_data 392 default: 393 return nil, fmt.Errorf("resource %s has unsupported mode %s", relAddr, relAddr.Resource.Mode) 394 } 395 396 ret.Type = relAddr.Resource.Type 397 ret.Name = relAddr.Resource.Name 398 399 switch tk := relAddr.Key.(type) { 400 case nil: 401 // Nothing to do, then. 402 case addrs.IntKey: 403 ret.InstanceKey = &planproto.ResourceInstanceChange_Int{ 404 Int: int64(tk), 405 } 406 case addrs.StringKey: 407 ret.InstanceKey = &planproto.ResourceInstanceChange_Str{ 408 Str: string(tk), 409 } 410 default: 411 return nil, fmt.Errorf("resource %s has unsupported instance key type %T", relAddr, relAddr.Key) 412 } 413 414 ret.DeposedKey = string(change.DeposedKey) 415 ret.Provider = change.ProviderAddr.String() 416 417 valChange, err := changeToTfplan(&change.ChangeSrc) 418 if err != nil { 419 return nil, fmt.Errorf("failed to serialize resource %s change: %s", relAddr, err) 420 } 421 ret.Change = valChange 422 423 if len(change.Private) > 0 { 424 ret.Private = change.Private 425 } 426 427 return ret, nil 428 } 429 430 func changeToTfplan(change *plans.ChangeSrc) (*planproto.Change, error) { 431 ret := &planproto.Change{} 432 433 before := valueToTfplan(change.Before) 434 after := valueToTfplan(change.After) 435 436 switch change.Action { 437 case plans.NoOp: 438 ret.Action = planproto.Action_NOOP 439 ret.Values = []*planproto.DynamicValue{before} // before and after should be identical 440 case plans.Create: 441 ret.Action = planproto.Action_CREATE 442 ret.Values = []*planproto.DynamicValue{after} 443 case plans.Read: 444 ret.Action = planproto.Action_READ 445 ret.Values = []*planproto.DynamicValue{before, after} 446 case plans.Update: 447 ret.Action = planproto.Action_UPDATE 448 ret.Values = []*planproto.DynamicValue{before, after} 449 case plans.Delete: 450 ret.Action = planproto.Action_DELETE 451 ret.Values = []*planproto.DynamicValue{before} 452 case plans.DeleteThenCreate: 453 ret.Action = planproto.Action_DELETE_THEN_CREATE 454 ret.Values = []*planproto.DynamicValue{before, after} 455 case plans.CreateThenDelete: 456 ret.Action = planproto.Action_CREATE_THEN_DELETE 457 ret.Values = []*planproto.DynamicValue{before, after} 458 default: 459 return nil, fmt.Errorf("invalid change action %s", change.Action) 460 } 461 462 return ret, nil 463 } 464 465 func valueToTfplan(val plans.DynamicValue) *planproto.DynamicValue { 466 if val == nil { 467 // protobuf can't represent nil, so we'll represent it as a 468 // DynamicValue that has no serializations at all. 469 return &planproto.DynamicValue{} 470 } 471 return &planproto.DynamicValue{ 472 Msgpack: []byte(val), 473 } 474 }