mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
This turned out to be a big messy commit, since the way providers are referenced is tightly coupled throughout the code. That starts to unify how providers are referenced, using the format output node Name method. Add a new field to the internal resource data types called ResolvedProvider. This is set by a new setter method SetProvider when a resource is connected to a provider during graph creation. This allows us to later lookup the provider instance a resource is connected to, without requiring it to have the same module path. The InitProvider context method now takes 2 arguments, one if the provider type and the second is the full name of the provider. While the provider type could still be parsed from the full name, this makes it more explicit and, and changes to the name format won't effect this code.
358 lines
8.4 KiB
Go
358 lines
8.4 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/terraform/config"
|
|
)
|
|
|
|
// NodeApplyableResource represents a resource that is "applyable":
|
|
// it is ready to be applied and is represented by a diff.
|
|
type NodeApplyableResource struct {
|
|
*NodeAbstractResource
|
|
}
|
|
|
|
// GraphNodeCreator
|
|
func (n *NodeApplyableResource) CreateAddr() *ResourceAddress {
|
|
return n.NodeAbstractResource.Addr
|
|
}
|
|
|
|
// GraphNodeReferencer, overriding NodeAbstractResource
|
|
func (n *NodeApplyableResource) References() []string {
|
|
result := n.NodeAbstractResource.References()
|
|
|
|
// The "apply" side of a resource generally also depends on the
|
|
// destruction of its dependencies as well. For example, if a LB
|
|
// references a set of VMs with ${vm.foo.*.id}, then we must wait for
|
|
// the destruction so we get the newly updated list of VMs.
|
|
//
|
|
// The exception here is CBD. When CBD is set, we don't do this since
|
|
// it would create a cycle. By not creating a cycle, we require two
|
|
// applies since the first apply the creation step will use the OLD
|
|
// values (pre-destroy) and the second step will update.
|
|
//
|
|
// This is how Terraform behaved with "legacy" graphs (TF <= 0.7.x).
|
|
// We mimic that behavior here now and can improve upon it in the future.
|
|
//
|
|
// This behavior is tested in graph_build_apply_test.go to test ordering.
|
|
cbd := n.Config != nil && n.Config.Lifecycle.CreateBeforeDestroy
|
|
if !cbd {
|
|
// The "apply" side of a resource always depends on the destruction
|
|
// of all its dependencies in addition to the creation.
|
|
for _, v := range result {
|
|
result = append(result, v+".destroy")
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// GraphNodeEvalable
|
|
func (n *NodeApplyableResource) EvalTree() EvalNode {
|
|
addr := n.NodeAbstractResource.Addr
|
|
|
|
// stateId is the ID to put into the state
|
|
stateId := addr.stateId()
|
|
|
|
// Build the instance info. More of this will be populated during eval
|
|
info := &InstanceInfo{
|
|
Id: stateId,
|
|
Type: addr.Type,
|
|
}
|
|
|
|
// Build the resource for eval
|
|
resource := &Resource{
|
|
Name: addr.Name,
|
|
Type: addr.Type,
|
|
CountIndex: addr.Index,
|
|
}
|
|
if resource.CountIndex < 0 {
|
|
resource.CountIndex = 0
|
|
}
|
|
|
|
// Determine the dependencies for the state.
|
|
stateDeps := n.StateReferences()
|
|
|
|
// Eval info is different depending on what kind of resource this is
|
|
switch n.Config.Mode {
|
|
case config.ManagedResourceMode:
|
|
return n.evalTreeManagedResource(
|
|
stateId, info, resource, stateDeps,
|
|
)
|
|
case config.DataResourceMode:
|
|
return n.evalTreeDataResource(
|
|
stateId, info, resource, stateDeps)
|
|
default:
|
|
panic(fmt.Errorf("unsupported resource mode %s", n.Config.Mode))
|
|
}
|
|
}
|
|
|
|
func (n *NodeApplyableResource) evalTreeDataResource(
|
|
stateId string, info *InstanceInfo,
|
|
resource *Resource, stateDeps []string) EvalNode {
|
|
var provider ResourceProvider
|
|
var config *ResourceConfig
|
|
var diff *InstanceDiff
|
|
var state *InstanceState
|
|
|
|
return &EvalSequence{
|
|
Nodes: []EvalNode{
|
|
// Build the instance info
|
|
&EvalInstanceInfo{
|
|
Info: info,
|
|
},
|
|
|
|
// Get the saved diff for apply
|
|
&EvalReadDiff{
|
|
Name: stateId,
|
|
Diff: &diff,
|
|
},
|
|
|
|
// Stop here if we don't actually have a diff
|
|
&EvalIf{
|
|
If: func(ctx EvalContext) (bool, error) {
|
|
if diff == nil {
|
|
return true, EvalEarlyExitError{}
|
|
}
|
|
|
|
if diff.GetAttributesLen() == 0 {
|
|
return true, EvalEarlyExitError{}
|
|
}
|
|
|
|
return true, nil
|
|
},
|
|
Then: EvalNoop{},
|
|
},
|
|
|
|
// We need to re-interpolate the config here, rather than
|
|
// just using the diff's values directly, because we've
|
|
// potentially learned more variable values during the
|
|
// apply pass that weren't known when the diff was produced.
|
|
&EvalInterpolate{
|
|
Config: n.Config.RawConfig.Copy(),
|
|
Resource: resource,
|
|
Output: &config,
|
|
},
|
|
|
|
&EvalGetProvider{
|
|
Name: n.ResolvedProvider,
|
|
Output: &provider,
|
|
},
|
|
|
|
// Make a new diff with our newly-interpolated config.
|
|
&EvalReadDataDiff{
|
|
Info: info,
|
|
Config: &config,
|
|
Previous: &diff,
|
|
Provider: &provider,
|
|
Output: &diff,
|
|
},
|
|
|
|
&EvalReadDataApply{
|
|
Info: info,
|
|
Diff: &diff,
|
|
Provider: &provider,
|
|
Output: &state,
|
|
},
|
|
|
|
&EvalWriteState{
|
|
Name: stateId,
|
|
ResourceType: n.Config.Type,
|
|
Provider: n.Config.Provider,
|
|
Dependencies: stateDeps,
|
|
State: &state,
|
|
},
|
|
|
|
// Clear the diff now that we've applied it, so
|
|
// later nodes won't see a diff that's now a no-op.
|
|
&EvalWriteDiff{
|
|
Name: stateId,
|
|
Diff: nil,
|
|
},
|
|
|
|
&EvalUpdateStateHook{},
|
|
},
|
|
}
|
|
}
|
|
|
|
func (n *NodeApplyableResource) evalTreeManagedResource(
|
|
stateId string, info *InstanceInfo,
|
|
resource *Resource, stateDeps []string) EvalNode {
|
|
// Declare a bunch of variables that are used for state during
|
|
// evaluation. Most of this are written to by-address below.
|
|
var provider ResourceProvider
|
|
var diff, diffApply *InstanceDiff
|
|
var state *InstanceState
|
|
var resourceConfig *ResourceConfig
|
|
var err error
|
|
var createNew bool
|
|
var createBeforeDestroyEnabled bool
|
|
|
|
return &EvalSequence{
|
|
Nodes: []EvalNode{
|
|
// Build the instance info
|
|
&EvalInstanceInfo{
|
|
Info: info,
|
|
},
|
|
|
|
// Get the saved diff for apply
|
|
&EvalReadDiff{
|
|
Name: stateId,
|
|
Diff: &diffApply,
|
|
},
|
|
|
|
// We don't want to do any destroys
|
|
&EvalIf{
|
|
If: func(ctx EvalContext) (bool, error) {
|
|
if diffApply == nil {
|
|
return true, EvalEarlyExitError{}
|
|
}
|
|
|
|
if diffApply.GetDestroy() && diffApply.GetAttributesLen() == 0 {
|
|
return true, EvalEarlyExitError{}
|
|
}
|
|
|
|
diffApply.SetDestroy(false)
|
|
return true, nil
|
|
},
|
|
Then: EvalNoop{},
|
|
},
|
|
|
|
&EvalIf{
|
|
If: func(ctx EvalContext) (bool, error) {
|
|
destroy := false
|
|
if diffApply != nil {
|
|
destroy = diffApply.GetDestroy() || diffApply.RequiresNew()
|
|
}
|
|
|
|
createBeforeDestroyEnabled =
|
|
n.Config.Lifecycle.CreateBeforeDestroy &&
|
|
destroy
|
|
|
|
return createBeforeDestroyEnabled, nil
|
|
},
|
|
Then: &EvalDeposeState{
|
|
Name: stateId,
|
|
},
|
|
},
|
|
|
|
&EvalInterpolate{
|
|
Config: n.Config.RawConfig.Copy(),
|
|
Resource: resource,
|
|
Output: &resourceConfig,
|
|
},
|
|
&EvalGetProvider{
|
|
Name: n.ResolvedProvider,
|
|
Output: &provider,
|
|
},
|
|
&EvalReadState{
|
|
Name: stateId,
|
|
Output: &state,
|
|
},
|
|
// Re-run validation to catch any errors we missed, e.g. type
|
|
// mismatches on computed values.
|
|
&EvalValidateResource{
|
|
Provider: &provider,
|
|
Config: &resourceConfig,
|
|
ResourceName: n.Config.Name,
|
|
ResourceType: n.Config.Type,
|
|
ResourceMode: n.Config.Mode,
|
|
IgnoreWarnings: true,
|
|
},
|
|
&EvalDiff{
|
|
Info: info,
|
|
Config: &resourceConfig,
|
|
Resource: n.Config,
|
|
Provider: &provider,
|
|
Diff: &diffApply,
|
|
State: &state,
|
|
OutputDiff: &diffApply,
|
|
},
|
|
|
|
// Get the saved diff
|
|
&EvalReadDiff{
|
|
Name: stateId,
|
|
Diff: &diff,
|
|
},
|
|
|
|
// Compare the diffs
|
|
&EvalCompareDiff{
|
|
Info: info,
|
|
One: &diff,
|
|
Two: &diffApply,
|
|
},
|
|
|
|
&EvalGetProvider{
|
|
Name: n.ResolvedProvider,
|
|
Output: &provider,
|
|
},
|
|
&EvalReadState{
|
|
Name: stateId,
|
|
Output: &state,
|
|
},
|
|
// Call pre-apply hook
|
|
&EvalApplyPre{
|
|
Info: info,
|
|
State: &state,
|
|
Diff: &diffApply,
|
|
},
|
|
&EvalApply{
|
|
Info: info,
|
|
State: &state,
|
|
Diff: &diffApply,
|
|
Provider: &provider,
|
|
Output: &state,
|
|
Error: &err,
|
|
CreateNew: &createNew,
|
|
},
|
|
&EvalWriteState{
|
|
Name: stateId,
|
|
ResourceType: n.Config.Type,
|
|
Provider: n.Config.Provider,
|
|
Dependencies: stateDeps,
|
|
State: &state,
|
|
},
|
|
&EvalApplyProvisioners{
|
|
Info: info,
|
|
State: &state,
|
|
Resource: n.Config,
|
|
InterpResource: resource,
|
|
CreateNew: &createNew,
|
|
Error: &err,
|
|
When: config.ProvisionerWhenCreate,
|
|
},
|
|
&EvalIf{
|
|
If: func(ctx EvalContext) (bool, error) {
|
|
return createBeforeDestroyEnabled && err != nil, nil
|
|
},
|
|
Then: &EvalUndeposeState{
|
|
Name: stateId,
|
|
State: &state,
|
|
},
|
|
Else: &EvalWriteState{
|
|
Name: stateId,
|
|
ResourceType: n.Config.Type,
|
|
Provider: n.Config.Provider,
|
|
Dependencies: stateDeps,
|
|
State: &state,
|
|
},
|
|
},
|
|
|
|
// We clear the diff out here so that future nodes
|
|
// don't see a diff that is already complete. There
|
|
// is no longer a diff!
|
|
&EvalWriteDiff{
|
|
Name: stateId,
|
|
Diff: nil,
|
|
},
|
|
|
|
&EvalApplyPost{
|
|
Info: info,
|
|
State: &state,
|
|
Error: &err,
|
|
},
|
|
&EvalUpdateStateHook{},
|
|
},
|
|
}
|
|
}
|