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  }