mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 01:41:48 -06:00
a14fd0344c
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.
220 lines
5.4 KiB
Go
220 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{}
|
|
}
|
|
|
|
// 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: rs.Provider,
|
|
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: rs.Provider,
|
|
Dependencies: rs.Dependencies,
|
|
State: &state,
|
|
},
|
|
|
|
&EvalUpdateStateHook{},
|
|
},
|
|
}
|
|
}
|