1package terraform 2 3import ( 4 "fmt" 5 "log" 6 7 "github.com/hashicorp/terraform/addrs" 8 "github.com/hashicorp/terraform/configs" 9 "github.com/hashicorp/terraform/providers" 10 "github.com/hashicorp/terraform/states" 11 "github.com/hashicorp/terraform/tfdiags" 12) 13 14// EvalReadState is an EvalNode implementation that reads the 15// current object for a specific instance in the state. 16type 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 32func (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 78type 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 97func (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. 146type EvalRequireState struct { 147 State **states.ResourceInstanceObject 148} 149 150func (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. 165type EvalUpdateStateHook struct{} 166 167func (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. 190type 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 205func (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. 255type 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 273func (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. 324type 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 340func (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. 377type 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 386func (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. 409type EvalWriteResourceState struct { 410 Addr addrs.Resource 411 Config *configs.Resource 412 ProviderAddr addrs.AbsProviderConfig 413} 414 415// TODO: test 416func (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 // Currently we ony support NoEach and EachList, because for_each support 428 // is not fully wired up across Terraform. Once for_each support is added, 429 // we'll need to handle that here too, setting states.EachMap if the 430 // assigned expression is a map. 431 eachMode := states.NoEach 432 if count >= 0 { // -1 signals "count not set" 433 eachMode = states.EachList 434 } 435 436 // This method takes care of all of the business logic of updating this 437 // while ensuring that any existing instances are preserved, etc. 438 state.SetResourceMeta(absAddr, eachMode, n.ProviderAddr) 439 440 return nil, nil 441} 442 443// EvalForgetResourceState is an EvalNode implementation that prunes out an 444// empty resource-level state for a given resource address, or produces an 445// error if it isn't empty after all. 446// 447// This should be the last action taken for a resource that has been removed 448// from the configuration altogether, to clean up the leftover husk of the 449// resource in the state after other EvalNodes have destroyed and removed 450// all of the instances and instance objects beneath it. 451type EvalForgetResourceState struct { 452 Addr addrs.Resource 453} 454 455func (n *EvalForgetResourceState) Eval(ctx EvalContext) (interface{}, error) { 456 absAddr := n.Addr.Absolute(ctx.Path()) 457 state := ctx.State() 458 459 pruned := state.RemoveResourceIfEmpty(absAddr) 460 if !pruned { 461 // If this produces an error, it indicates a bug elsewhere in Terraform 462 // -- probably missing graph nodes, graph edges, or 463 // incorrectly-implemented evaluation steps. 464 return nil, fmt.Errorf("orphan resource %s still has a non-empty state after apply; this is a bug in Terraform", absAddr) 465 } 466 log.Printf("[TRACE] EvalForgetResourceState: Pruned husk of %s from state", absAddr) 467 468 return nil, nil 469} 470