github.com/hashicorp/terraform-plugin-sdk@v1.17.2/terraform/eval_state.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "log" 6 7 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 8 "github.com/hashicorp/terraform-plugin-sdk/internal/configs" 9 "github.com/hashicorp/terraform-plugin-sdk/internal/providers" 10 "github.com/hashicorp/terraform-plugin-sdk/internal/states" 11 "github.com/hashicorp/terraform-plugin-sdk/internal/tfdiags" 12 ) 13 14 // EvalReadState is an EvalNode implementation that reads the 15 // current object for a specific instance in the state. 16 type EvalReadState struct { 17 // Addr is the address of the instance to read state for. 18 Addr addrs.ResourceInstance 19 20 // ProviderSchema is the schema for the provider given in Provider. 21 ProviderSchema **ProviderSchema 22 23 // Provider is the provider that will subsequently perform actions on 24 // the the state object. This is used to perform any schema upgrades 25 // that might be required to prepare the stored data for use. 26 Provider *providers.Interface 27 28 // Output will be written with a pointer to the retrieved object. 29 Output **states.ResourceInstanceObject 30 } 31 32 func (n *EvalReadState) Eval(ctx EvalContext) (interface{}, error) { 33 if n.Provider == nil || *n.Provider == nil { 34 panic("EvalReadState used with no Provider object") 35 } 36 if n.ProviderSchema == nil || *n.ProviderSchema == nil { 37 panic("EvalReadState used with no ProviderSchema object") 38 } 39 40 absAddr := n.Addr.Absolute(ctx.Path()) 41 log.Printf("[TRACE] EvalReadState: reading state for %s", absAddr) 42 43 src := ctx.State().ResourceInstanceObject(absAddr, states.CurrentGen) 44 if src == nil { 45 // Presumably we only have deposed objects, then. 46 log.Printf("[TRACE] EvalReadState: no state present for %s", absAddr) 47 return nil, nil 48 } 49 50 schema, currentVersion := (*n.ProviderSchema).SchemaForResourceAddr(n.Addr.ContainingResource()) 51 if schema == nil { 52 // Shouldn't happen since we should've failed long ago if no schema is present 53 return nil, fmt.Errorf("no schema available for %s while reading state; this is a bug in Terraform and should be reported", absAddr) 54 } 55 var diags tfdiags.Diagnostics 56 src, diags = UpgradeResourceState(absAddr, *n.Provider, src, schema, currentVersion) 57 if diags.HasErrors() { 58 // Note that we don't have any channel to return warnings here. We'll 59 // accept that for now since warnings during a schema upgrade would 60 // be pretty weird anyway, since this operation is supposed to seem 61 // invisible to the user. 62 return nil, diags.Err() 63 } 64 65 obj, err := src.Decode(schema.ImpliedType()) 66 if err != nil { 67 return nil, err 68 } 69 70 if n.Output != nil { 71 *n.Output = obj 72 } 73 return obj, nil 74 } 75 76 // EvalReadStateDeposed is an EvalNode implementation that reads the 77 // deposed InstanceState for a specific resource out of the state 78 type EvalReadStateDeposed struct { 79 // Addr is the address of the instance to read state for. 80 Addr addrs.ResourceInstance 81 82 // Key identifies which deposed object we will read. 83 Key states.DeposedKey 84 85 // ProviderSchema is the schema for the provider given in Provider. 86 ProviderSchema **ProviderSchema 87 88 // Provider is the provider that will subsequently perform actions on 89 // the the state object. This is used to perform any schema upgrades 90 // that might be required to prepare the stored data for use. 91 Provider *providers.Interface 92 93 // Output will be written with a pointer to the retrieved object. 94 Output **states.ResourceInstanceObject 95 } 96 97 func (n *EvalReadStateDeposed) Eval(ctx EvalContext) (interface{}, error) { 98 if n.Provider == nil || *n.Provider == nil { 99 panic("EvalReadStateDeposed used with no Provider object") 100 } 101 if n.ProviderSchema == nil || *n.ProviderSchema == nil { 102 panic("EvalReadStateDeposed used with no ProviderSchema object") 103 } 104 105 key := n.Key 106 if key == states.NotDeposed { 107 return nil, fmt.Errorf("EvalReadStateDeposed used with no instance key; this is a bug in Terraform and should be reported") 108 } 109 absAddr := n.Addr.Absolute(ctx.Path()) 110 log.Printf("[TRACE] EvalReadStateDeposed: reading state for %s deposed object %s", absAddr, n.Key) 111 112 src := ctx.State().ResourceInstanceObject(absAddr, key) 113 if src == nil { 114 // Presumably we only have deposed objects, then. 115 log.Printf("[TRACE] EvalReadStateDeposed: no state present for %s deposed object %s", absAddr, n.Key) 116 return nil, nil 117 } 118 119 schema, currentVersion := (*n.ProviderSchema).SchemaForResourceAddr(n.Addr.ContainingResource()) 120 if schema == nil { 121 // Shouldn't happen since we should've failed long ago if no schema is present 122 return nil, fmt.Errorf("no schema available for %s while reading state; this is a bug in Terraform and should be reported", absAddr) 123 } 124 var diags tfdiags.Diagnostics 125 src, diags = UpgradeResourceState(absAddr, *n.Provider, src, schema, currentVersion) 126 if diags.HasErrors() { 127 // Note that we don't have any channel to return warnings here. We'll 128 // accept that for now since warnings during a schema upgrade would 129 // be pretty weird anyway, since this operation is supposed to seem 130 // invisible to the user. 131 return nil, diags.Err() 132 } 133 134 obj, err := src.Decode(schema.ImpliedType()) 135 if err != nil { 136 return nil, err 137 } 138 if n.Output != nil { 139 *n.Output = obj 140 } 141 return obj, nil 142 } 143 144 // EvalRequireState is an EvalNode implementation that exits early if the given 145 // object is null. 146 type EvalRequireState struct { 147 State **states.ResourceInstanceObject 148 } 149 150 func (n *EvalRequireState) Eval(ctx EvalContext) (interface{}, error) { 151 if n.State == nil { 152 return nil, EvalEarlyExitError{} 153 } 154 155 state := *n.State 156 if state == nil || state.Value.IsNull() { 157 return nil, EvalEarlyExitError{} 158 } 159 160 return nil, nil 161 } 162 163 // EvalUpdateStateHook is an EvalNode implementation that calls the 164 // PostStateUpdate hook with the current state. 165 type EvalUpdateStateHook struct{} 166 167 func (n *EvalUpdateStateHook) Eval(ctx EvalContext) (interface{}, error) { 168 // In principle we could grab the lock here just long enough to take a 169 // deep copy and then pass that to our hooks below, but we'll instead 170 // hold the hook for the duration to avoid the potential confusing 171 // situation of us racing to call PostStateUpdate concurrently with 172 // different state snapshots. 173 stateSync := ctx.State() 174 state := stateSync.Lock().DeepCopy() 175 defer stateSync.Unlock() 176 177 // Call the hook 178 err := ctx.Hook(func(h Hook) (HookAction, error) { 179 return h.PostStateUpdate(state) 180 }) 181 if err != nil { 182 return nil, err 183 } 184 185 return nil, nil 186 } 187 188 // EvalWriteState is an EvalNode implementation that saves the given object 189 // as the current object for the selected resource instance. 190 type EvalWriteState struct { 191 // Addr is the address of the instance to read state for. 192 Addr addrs.ResourceInstance 193 194 // State is the object state to save. 195 State **states.ResourceInstanceObject 196 197 // ProviderSchema is the schema for the provider given in ProviderAddr. 198 ProviderSchema **ProviderSchema 199 200 // ProviderAddr is the address of the provider configuration that 201 // produced the given object. 202 ProviderAddr addrs.AbsProviderConfig 203 } 204 205 func (n *EvalWriteState) Eval(ctx EvalContext) (interface{}, error) { 206 if n.State == nil { 207 // Note that a pointer _to_ nil is valid here, indicating the total 208 // absense of an object as we'd see during destroy. 209 panic("EvalWriteState used with no ResourceInstanceObject") 210 } 211 212 absAddr := n.Addr.Absolute(ctx.Path()) 213 state := ctx.State() 214 215 if n.ProviderAddr.ProviderConfig.Type == "" { 216 return nil, fmt.Errorf("failed to write state for %s, missing provider type", absAddr) 217 } 218 219 obj := *n.State 220 if obj == nil || obj.Value.IsNull() { 221 // No need to encode anything: we'll just write it directly. 222 state.SetResourceInstanceCurrent(absAddr, nil, n.ProviderAddr) 223 log.Printf("[TRACE] EvalWriteState: removing state object for %s", absAddr) 224 return nil, nil 225 } 226 if n.ProviderSchema == nil || *n.ProviderSchema == nil { 227 // Should never happen, unless our state object is nil 228 panic("EvalWriteState used with pointer to nil ProviderSchema object") 229 } 230 231 if obj != nil { 232 log.Printf("[TRACE] EvalWriteState: writing current state object for %s", absAddr) 233 } else { 234 log.Printf("[TRACE] EvalWriteState: removing current state object for %s", absAddr) 235 } 236 237 schema, currentVersion := (*n.ProviderSchema).SchemaForResourceAddr(n.Addr.ContainingResource()) 238 if schema == nil { 239 // It shouldn't be possible to get this far in any real scenario 240 // without a schema, but we might end up here in contrived tests that 241 // fail to set up their world properly. 242 return nil, fmt.Errorf("failed to encode %s in state: no resource type schema available", absAddr) 243 } 244 src, err := obj.Encode(schema.ImpliedType(), currentVersion) 245 if err != nil { 246 return nil, fmt.Errorf("failed to encode %s in state: %s", absAddr, err) 247 } 248 249 state.SetResourceInstanceCurrent(absAddr, src, n.ProviderAddr) 250 return nil, nil 251 } 252 253 // EvalWriteStateDeposed is an EvalNode implementation that writes 254 // an InstanceState out to the Deposed list of a resource in the state. 255 type EvalWriteStateDeposed struct { 256 // Addr is the address of the instance to read state for. 257 Addr addrs.ResourceInstance 258 259 // Key indicates which deposed object to write to. 260 Key states.DeposedKey 261 262 // State is the object state to save. 263 State **states.ResourceInstanceObject 264 265 // ProviderSchema is the schema for the provider given in ProviderAddr. 266 ProviderSchema **ProviderSchema 267 268 // ProviderAddr is the address of the provider configuration that 269 // produced the given object. 270 ProviderAddr addrs.AbsProviderConfig 271 } 272 273 func (n *EvalWriteStateDeposed) Eval(ctx EvalContext) (interface{}, error) { 274 if n.State == nil { 275 // Note that a pointer _to_ nil is valid here, indicating the total 276 // absense of an object as we'd see during destroy. 277 panic("EvalWriteStateDeposed used with no ResourceInstanceObject") 278 } 279 280 absAddr := n.Addr.Absolute(ctx.Path()) 281 key := n.Key 282 state := ctx.State() 283 284 if key == states.NotDeposed { 285 // should never happen 286 return nil, fmt.Errorf("can't save deposed object for %s without a deposed key; this is a bug in Terraform that should be reported", absAddr) 287 } 288 289 obj := *n.State 290 if obj == nil { 291 // No need to encode anything: we'll just write it directly. 292 state.SetResourceInstanceDeposed(absAddr, key, nil, n.ProviderAddr) 293 log.Printf("[TRACE] EvalWriteStateDeposed: removing state object for %s deposed %s", absAddr, key) 294 return nil, nil 295 } 296 if n.ProviderSchema == nil || *n.ProviderSchema == nil { 297 // Should never happen, unless our state object is nil 298 panic("EvalWriteStateDeposed used with no ProviderSchema object") 299 } 300 301 schema, currentVersion := (*n.ProviderSchema).SchemaForResourceAddr(n.Addr.ContainingResource()) 302 if schema == nil { 303 // It shouldn't be possible to get this far in any real scenario 304 // without a schema, but we might end up here in contrived tests that 305 // fail to set up their world properly. 306 return nil, fmt.Errorf("failed to encode %s in state: no resource type schema available", absAddr) 307 } 308 src, err := obj.Encode(schema.ImpliedType(), currentVersion) 309 if err != nil { 310 return nil, fmt.Errorf("failed to encode %s in state: %s", absAddr, err) 311 } 312 313 log.Printf("[TRACE] EvalWriteStateDeposed: writing state object for %s deposed %s", absAddr, key) 314 state.SetResourceInstanceDeposed(absAddr, key, src, n.ProviderAddr) 315 return nil, nil 316 } 317 318 // EvalDeposeState is an EvalNode implementation that moves the current object 319 // for the given instance to instead be a deposed object, leaving the instance 320 // with no current object. 321 // This is used at the beginning of a create-before-destroy replace action so 322 // that the create can create while preserving the old state of the 323 // to-be-destroyed object. 324 type EvalDeposeState struct { 325 Addr addrs.ResourceInstance 326 327 // ForceKey, if a value other than states.NotDeposed, will be used as the 328 // key for the newly-created deposed object that results from this action. 329 // If set to states.NotDeposed (the zero value), a new unique key will be 330 // allocated. 331 ForceKey states.DeposedKey 332 333 // OutputKey, if non-nil, will be written with the deposed object key that 334 // was generated for the object. This can then be passed to 335 // EvalUndeposeState.Key so it knows which deposed instance to forget. 336 OutputKey *states.DeposedKey 337 } 338 339 // TODO: test 340 func (n *EvalDeposeState) Eval(ctx EvalContext) (interface{}, error) { 341 absAddr := n.Addr.Absolute(ctx.Path()) 342 state := ctx.State() 343 344 var key states.DeposedKey 345 if n.ForceKey == states.NotDeposed { 346 key = state.DeposeResourceInstanceObject(absAddr) 347 } else { 348 key = n.ForceKey 349 state.DeposeResourceInstanceObjectForceKey(absAddr, key) 350 } 351 log.Printf("[TRACE] EvalDeposeState: prior object for %s now deposed with key %s", absAddr, key) 352 353 if n.OutputKey != nil { 354 *n.OutputKey = key 355 } 356 357 return nil, nil 358 } 359 360 // EvalMaybeRestoreDeposedObject is an EvalNode implementation that will 361 // restore a particular deposed object of the specified resource instance 362 // to be the "current" object if and only if the instance doesn't currently 363 // have a current object. 364 // 365 // This is intended for use when the create leg of a create before destroy 366 // fails with no partial new object: if we didn't take any action, the user 367 // would be left in the unfortunate situation of having no current object 368 // and the previously-workign object now deposed. This EvalNode causes a 369 // better outcome by restoring things to how they were before the replace 370 // operation began. 371 // 372 // The create operation may have produced a partial result even though it 373 // failed and it's important that we don't "forget" that state, so in that 374 // situation the prior object remains deposed and the partial new object 375 // remains the current object, allowing the situation to hopefully be 376 // improved in a subsequent run. 377 type EvalMaybeRestoreDeposedObject struct { 378 Addr addrs.ResourceInstance 379 380 // Key is a pointer to the deposed object key that should be forgotten 381 // from the state, which must be non-nil. 382 Key *states.DeposedKey 383 } 384 385 // TODO: test 386 func (n *EvalMaybeRestoreDeposedObject) Eval(ctx EvalContext) (interface{}, error) { 387 absAddr := n.Addr.Absolute(ctx.Path()) 388 dk := *n.Key 389 state := ctx.State() 390 391 restored := state.MaybeRestoreResourceInstanceDeposed(absAddr, dk) 392 if restored { 393 log.Printf("[TRACE] EvalMaybeRestoreDeposedObject: %s deposed object %s was restored as the current object", absAddr, dk) 394 } else { 395 log.Printf("[TRACE] EvalMaybeRestoreDeposedObject: %s deposed object %s remains deposed", absAddr, dk) 396 } 397 398 return nil, nil 399 } 400 401 // EvalWriteResourceState is an EvalNode implementation that ensures that 402 // a suitable resource-level state record is present in the state, if that's 403 // required for the "each mode" of that resource. 404 // 405 // This is important primarily for the situation where count = 0, since this 406 // eval is the only change we get to set the resource "each mode" to list 407 // in that case, allowing expression evaluation to see it as a zero-element 408 // list rather than as not set at all. 409 type EvalWriteResourceState struct { 410 Addr addrs.Resource 411 Config *configs.Resource 412 ProviderAddr addrs.AbsProviderConfig 413 } 414 415 // TODO: test 416 func (n *EvalWriteResourceState) Eval(ctx EvalContext) (interface{}, error) { 417 var diags tfdiags.Diagnostics 418 absAddr := n.Addr.Absolute(ctx.Path()) 419 state := ctx.State() 420 421 count, countDiags := evaluateResourceCountExpression(n.Config.Count, ctx) 422 diags = diags.Append(countDiags) 423 if countDiags.HasErrors() { 424 return nil, diags.Err() 425 } 426 427 eachMode := states.NoEach 428 if count >= 0 { // -1 signals "count not set" 429 eachMode = states.EachList 430 } 431 432 forEach, forEachDiags := evaluateResourceForEachExpression(n.Config.ForEach, ctx) 433 diags = diags.Append(forEachDiags) 434 if forEachDiags.HasErrors() { 435 return nil, diags.Err() 436 } 437 438 if forEach != nil { 439 eachMode = states.EachMap 440 } 441 442 // This method takes care of all of the business logic of updating this 443 // while ensuring that any existing instances are preserved, etc. 444 state.SetResourceMeta(absAddr, eachMode, n.ProviderAddr) 445 446 return nil, nil 447 } 448 449 // EvalForgetResourceState is an EvalNode implementation that prunes out an 450 // empty resource-level state for a given resource address, or produces an 451 // error if it isn't empty after all. 452 // 453 // This should be the last action taken for a resource that has been removed 454 // from the configuration altogether, to clean up the leftover husk of the 455 // resource in the state after other EvalNodes have destroyed and removed 456 // all of the instances and instance objects beneath it. 457 type EvalForgetResourceState struct { 458 Addr addrs.Resource 459 } 460 461 func (n *EvalForgetResourceState) Eval(ctx EvalContext) (interface{}, error) { 462 absAddr := n.Addr.Absolute(ctx.Path()) 463 state := ctx.State() 464 465 pruned := state.RemoveResourceIfEmpty(absAddr) 466 if !pruned { 467 // If this produces an error, it indicates a bug elsewhere in Terraform 468 // -- probably missing graph nodes, graph edges, or 469 // incorrectly-implemented evaluation steps. 470 return nil, fmt.Errorf("orphan resource %s still has a non-empty state after apply; this is a bug in Terraform", absAddr) 471 } 472 log.Printf("[TRACE] EvalForgetResourceState: Pruned husk of %s from state", absAddr) 473 474 return nil, nil 475 }