mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-27 09:21:14 -06:00
c2f3522f7d
And update the test state strings Destroying with no config is no longer allowed, run an exlpicit destroy for the destroyOrder test.
222 lines
5.4 KiB
Go
222 lines
5.4 KiB
Go
package terraform
|
|
|
|
import (
|
|
"github.com/hashicorp/terraform/dag"
|
|
)
|
|
|
|
// NodeRefreshableDataResource represents a resource that is "plannable":
|
|
// it is ready to be planned in order to create a diff.
|
|
type NodeRefreshableDataResource struct {
|
|
*NodeAbstractCountResource
|
|
}
|
|
|
|
// GraphNodeDynamicExpandable
|
|
func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
|
// Grab the state which we read
|
|
state, lock := ctx.State()
|
|
lock.RLock()
|
|
defer lock.RUnlock()
|
|
|
|
// Expand the resource count which must be available by now from EvalTree
|
|
count, err := n.Config.Count()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// The concrete resource factory we'll use
|
|
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
|
|
// Add the config and state since we don't do that via transforms
|
|
a.Config = n.Config
|
|
a.ResolvedProvider = n.ResolvedProvider
|
|
|
|
return &NodeRefreshableDataResourceInstance{
|
|
NodeAbstractResource: a,
|
|
}
|
|
}
|
|
|
|
// We also need a destroyable resource for orphans that are a result of a
|
|
// scaled-in count.
|
|
concreteResourceDestroyable := func(a *NodeAbstractResource) dag.Vertex {
|
|
// Add the config since we don't do that via transforms
|
|
a.Config = n.Config
|
|
|
|
return &NodeDestroyableDataResource{
|
|
NodeAbstractResource: a,
|
|
}
|
|
}
|
|
|
|
// Start creating the steps
|
|
steps := []GraphTransformer{
|
|
// Expand the count.
|
|
&ResourceCountTransformer{
|
|
Concrete: concreteResource,
|
|
Count: count,
|
|
Addr: n.ResourceAddr(),
|
|
},
|
|
|
|
// Add the count orphans. As these are orphaned refresh nodes, we add them
|
|
// directly as NodeDestroyableDataResource.
|
|
&OrphanResourceCountTransformer{
|
|
Concrete: concreteResourceDestroyable,
|
|
Count: count,
|
|
Addr: n.ResourceAddr(),
|
|
State: state,
|
|
},
|
|
|
|
// Attach the state
|
|
&AttachStateTransformer{State: state},
|
|
|
|
// Targeting
|
|
&TargetsTransformer{ParsedTargets: n.Targets},
|
|
|
|
// Connect references so ordering is correct
|
|
&ReferenceTransformer{},
|
|
|
|
// Make sure there is a single root
|
|
&RootTransformer{},
|
|
}
|
|
|
|
// Build the graph
|
|
b := &BasicGraphBuilder{
|
|
Steps: steps,
|
|
Validate: true,
|
|
Name: "NodeRefreshableDataResource",
|
|
}
|
|
|
|
return b.Build(ctx.Path())
|
|
}
|
|
|
|
// NodeRefreshableDataResourceInstance represents a _single_ resource instance
|
|
// that is refreshable.
|
|
type NodeRefreshableDataResourceInstance struct {
|
|
*NodeAbstractResource
|
|
}
|
|
|
|
// GraphNodeEvalable
|
|
func (n *NodeRefreshableDataResourceInstance) 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,
|
|
}
|
|
|
|
// Get the state if we have it, if not we build it
|
|
rs := n.ResourceState
|
|
if rs == nil {
|
|
rs = &ResourceState{
|
|
Provider: n.ResolvedProvider,
|
|
}
|
|
}
|
|
|
|
// If the config isn't empty we update the state
|
|
if n.Config != nil {
|
|
rs = &ResourceState{
|
|
Type: n.Config.Type,
|
|
Provider: n.Config.Provider,
|
|
Dependencies: n.StateReferences(),
|
|
}
|
|
}
|
|
|
|
// Build the resource for eval
|
|
resource := &Resource{
|
|
Name: addr.Name,
|
|
Type: addr.Type,
|
|
CountIndex: addr.Index,
|
|
}
|
|
if resource.CountIndex < 0 {
|
|
resource.CountIndex = 0
|
|
}
|
|
|
|
// Declare a bunch of variables that are used for state during
|
|
// evaluation. Most of this are written to by-address below.
|
|
var config *ResourceConfig
|
|
var diff *InstanceDiff
|
|
var provider ResourceProvider
|
|
var state *InstanceState
|
|
|
|
return &EvalSequence{
|
|
Nodes: []EvalNode{
|
|
// Always destroy the existing state first, since we must
|
|
// make sure that values from a previous read will not
|
|
// get interpolated if we end up needing to defer our
|
|
// loading until apply time.
|
|
&EvalWriteState{
|
|
Name: stateId,
|
|
ResourceType: rs.Type,
|
|
Provider: n.ResolvedProvider,
|
|
Dependencies: rs.Dependencies,
|
|
State: &state, // state is nil here
|
|
},
|
|
|
|
&EvalInterpolate{
|
|
Config: n.Config.RawConfig.Copy(),
|
|
Resource: resource,
|
|
Output: &config,
|
|
},
|
|
|
|
// The rest of this pass can proceed only if there are no
|
|
// computed values in our config.
|
|
// (If there are, we'll deal with this during the plan and
|
|
// apply phases.)
|
|
&EvalIf{
|
|
If: func(ctx EvalContext) (bool, error) {
|
|
if config.ComputedKeys != nil && len(config.ComputedKeys) > 0 {
|
|
return true, EvalEarlyExitError{}
|
|
}
|
|
|
|
// If the config explicitly has a depends_on for this
|
|
// data source, assume the intention is to prevent
|
|
// refreshing ahead of that dependency.
|
|
if len(n.Config.DependsOn) > 0 {
|
|
return true, EvalEarlyExitError{}
|
|
}
|
|
|
|
return true, nil
|
|
},
|
|
|
|
Then: EvalNoop{},
|
|
},
|
|
|
|
// The remainder of this pass is the same as running
|
|
// a "plan" pass immediately followed by an "apply" pass,
|
|
// populating the state early so it'll be available to
|
|
// provider configurations that need this data during
|
|
// refresh/plan.
|
|
&EvalGetProvider{
|
|
Name: n.ResolvedProvider,
|
|
Output: &provider,
|
|
},
|
|
|
|
&EvalReadDataDiff{
|
|
Info: info,
|
|
Config: &config,
|
|
Provider: &provider,
|
|
Output: &diff,
|
|
OutputState: &state,
|
|
},
|
|
|
|
&EvalReadDataApply{
|
|
Info: info,
|
|
Diff: &diff,
|
|
Provider: &provider,
|
|
Output: &state,
|
|
},
|
|
|
|
&EvalWriteState{
|
|
Name: stateId,
|
|
ResourceType: rs.Type,
|
|
Provider: n.ResolvedProvider,
|
|
Dependencies: rs.Dependencies,
|
|
State: &state,
|
|
},
|
|
|
|
&EvalUpdateStateHook{},
|
|
},
|
|
}
|
|
}
|