mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-30 10:47:14 -06:00
4637be4377
Resources that are not yet created will not be in the graph during refresh, and therefore cannot be attached to the data source nodes. In this case we still need to indicate if there are depends_on entries inherited from the module call, which we can do with the forceDependsOn field.
281 lines
8.3 KiB
Go
281 lines
8.3 KiB
Go
package terraform
|
|
|
|
import (
|
|
"github.com/hashicorp/terraform/addrs"
|
|
"github.com/hashicorp/terraform/dag"
|
|
"github.com/hashicorp/terraform/plans"
|
|
"github.com/hashicorp/terraform/providers"
|
|
"github.com/hashicorp/terraform/states"
|
|
"github.com/hashicorp/terraform/tfdiags"
|
|
)
|
|
|
|
type nodeExpandRefreshableDataResource struct {
|
|
*NodeAbstractResource
|
|
}
|
|
|
|
var (
|
|
_ GraphNodeDynamicExpandable = (*nodeExpandRefreshableDataResource)(nil)
|
|
_ GraphNodeReferenceable = (*nodeExpandRefreshableDataResource)(nil)
|
|
_ GraphNodeReferencer = (*nodeExpandRefreshableDataResource)(nil)
|
|
_ GraphNodeConfigResource = (*nodeExpandRefreshableDataResource)(nil)
|
|
_ GraphNodeAttachResourceConfig = (*nodeExpandRefreshableDataResource)(nil)
|
|
)
|
|
|
|
func (n *nodeExpandRefreshableDataResource) Name() string {
|
|
return n.NodeAbstractResource.Name() + " (expand)"
|
|
}
|
|
|
|
func (n *nodeExpandRefreshableDataResource) References() []*addrs.Reference {
|
|
return (&NodeRefreshableManagedResource{NodeAbstractResource: n.NodeAbstractResource}).References()
|
|
}
|
|
|
|
func (n *nodeExpandRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
|
var g Graph
|
|
|
|
expander := ctx.InstanceExpander()
|
|
for _, module := range expander.ExpandModule(n.Addr.Module) {
|
|
g.Add(&NodeRefreshableDataResource{
|
|
NodeAbstractResource: n.NodeAbstractResource,
|
|
Addr: n.Addr.Resource.Absolute(module),
|
|
})
|
|
}
|
|
|
|
return &g, nil
|
|
}
|
|
|
|
// NodeRefreshableDataResource represents a resource that is "refreshable".
|
|
type NodeRefreshableDataResource struct {
|
|
*NodeAbstractResource
|
|
|
|
Addr addrs.AbsResource
|
|
}
|
|
|
|
var (
|
|
_ GraphNodeModuleInstance = (*NodeRefreshableDataResource)(nil)
|
|
_ GraphNodeDynamicExpandable = (*NodeRefreshableDataResource)(nil)
|
|
_ GraphNodeReferenceable = (*NodeRefreshableDataResource)(nil)
|
|
_ GraphNodeReferencer = (*NodeRefreshableDataResource)(nil)
|
|
_ GraphNodeConfigResource = (*NodeRefreshableDataResource)(nil)
|
|
_ GraphNodeAttachResourceConfig = (*NodeRefreshableDataResource)(nil)
|
|
_ GraphNodeAttachProviderMetaConfigs = (*NodeAbstractResource)(nil)
|
|
)
|
|
|
|
func (n *NodeRefreshableDataResource) Path() addrs.ModuleInstance {
|
|
return n.Addr.Module
|
|
}
|
|
|
|
// GraphNodeDynamicExpandable
|
|
func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
|
var diags tfdiags.Diagnostics
|
|
|
|
expander := ctx.InstanceExpander()
|
|
|
|
switch {
|
|
case n.Config.Count != nil:
|
|
count, countDiags := evaluateCountExpressionValue(n.Config.Count, ctx)
|
|
diags = diags.Append(countDiags)
|
|
if countDiags.HasErrors() {
|
|
return nil, diags.Err()
|
|
}
|
|
if !count.IsKnown() {
|
|
// If the count isn't known yet, we'll skip refreshing and try expansion
|
|
// again during the plan walk.
|
|
return nil, nil
|
|
}
|
|
|
|
c, _ := count.AsBigFloat().Int64()
|
|
expander.SetResourceCount(n.Addr.Module, n.Addr.Resource, int(c))
|
|
|
|
case n.Config.ForEach != nil:
|
|
forEachVal, forEachDiags := evaluateForEachExpressionValue(n.Config.ForEach, ctx)
|
|
diags = diags.Append(forEachDiags)
|
|
if forEachDiags.HasErrors() {
|
|
return nil, diags.Err()
|
|
}
|
|
if !forEachVal.IsKnown() {
|
|
// If the for_each isn't known yet, we'll skip refreshing and try expansion
|
|
// again during the plan walk.
|
|
return nil, nil
|
|
}
|
|
|
|
expander.SetResourceForEach(n.Addr.Module, n.Addr.Resource, forEachVal.AsValueMap())
|
|
|
|
default:
|
|
expander.SetResourceSingle(n.Addr.Module, n.Addr.Resource)
|
|
}
|
|
|
|
// Next we need to potentially rename an instance address in the state
|
|
// if we're transitioning whether "count" is set at all.
|
|
fixResourceCountSetTransition(ctx, n.ResourceAddr(), n.Config.Count != nil)
|
|
|
|
instanceAddrs := expander.ExpandResource(n.Addr)
|
|
|
|
// Our graph transformers require access to the full state, so we'll
|
|
// temporarily lock it while we work on this.
|
|
state := ctx.State().Lock()
|
|
defer ctx.State().Unlock()
|
|
|
|
// The concrete resource factory we'll use
|
|
concreteResource := func(a *NodeAbstractResourceInstance) dag.Vertex {
|
|
// Add the config and state since we don't do that via transforms
|
|
a.Config = n.Config
|
|
a.ResolvedProvider = n.ResolvedProvider
|
|
a.ProviderMetas = n.ProviderMetas
|
|
a.dependsOn = n.dependsOn
|
|
a.forceDependsOn = n.forceDependsOn
|
|
|
|
return &NodeRefreshableDataResourceInstance{
|
|
NodeAbstractResourceInstance: a,
|
|
}
|
|
}
|
|
|
|
// We also need a destroyable resource for orphans that are a result of a
|
|
// scaled-in count.
|
|
concreteResourceDestroyable := func(a *NodeAbstractResourceInstance) dag.Vertex {
|
|
// Add the config and provider since we don't do that via transforms
|
|
a.Config = n.Config
|
|
a.ResolvedProvider = n.ResolvedProvider
|
|
|
|
return &NodeDestroyableDataResourceInstance{
|
|
NodeAbstractResourceInstance: a,
|
|
}
|
|
}
|
|
|
|
// Start creating the steps
|
|
steps := []GraphTransformer{
|
|
// Expand the count.
|
|
&ResourceCountTransformer{
|
|
Concrete: concreteResource,
|
|
Schema: n.Schema,
|
|
Addr: n.ResourceAddr(),
|
|
InstanceAddrs: instanceAddrs,
|
|
},
|
|
|
|
// Add the count orphans. As these are orphaned refresh nodes, we add them
|
|
// directly as NodeDestroyableDataResource.
|
|
&OrphanResourceInstanceCountTransformer{
|
|
Concrete: concreteResourceDestroyable,
|
|
Addr: n.Addr,
|
|
InstanceAddrs: instanceAddrs,
|
|
State: state,
|
|
},
|
|
|
|
// Attach the state
|
|
&AttachStateTransformer{State: state},
|
|
|
|
// Targeting
|
|
&TargetsTransformer{Targets: 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",
|
|
}
|
|
|
|
graph, diags := b.Build(nil)
|
|
return graph, diags.ErrWithWarnings()
|
|
}
|
|
|
|
// NodeRefreshableDataResourceInstance represents a single resource instance
|
|
// that is refreshable.
|
|
type NodeRefreshableDataResourceInstance struct {
|
|
*NodeAbstractResourceInstance
|
|
}
|
|
|
|
// GraphNodeEvalable
|
|
func (n *NodeRefreshableDataResourceInstance) EvalTree() EvalNode {
|
|
addr := n.ResourceInstanceAddr()
|
|
|
|
// These variables are the state for the eval sequence below, and are
|
|
// updated through pointers.
|
|
var provider providers.Interface
|
|
var providerSchema *ProviderSchema
|
|
var change *plans.ResourceInstanceChange
|
|
var state *states.ResourceInstanceObject
|
|
|
|
return &EvalSequence{
|
|
Nodes: []EvalNode{
|
|
&EvalGetProvider{
|
|
Addr: n.ResolvedProvider,
|
|
Output: &provider,
|
|
Schema: &providerSchema,
|
|
},
|
|
|
|
&EvalReadState{
|
|
Addr: addr.Resource,
|
|
Provider: &provider,
|
|
ProviderSchema: &providerSchema,
|
|
Output: &state,
|
|
},
|
|
|
|
// EvalReadDataRefresh will _attempt_ to read the data source, but
|
|
// may generate an incomplete planned object if the configuration
|
|
// includes values that won't be known until apply.
|
|
&evalReadDataRefresh{
|
|
evalReadData{
|
|
Addr: addr.Resource,
|
|
Config: n.Config,
|
|
Provider: &provider,
|
|
ProviderAddr: n.ResolvedProvider,
|
|
ProviderMetas: n.ProviderMetas,
|
|
ProviderSchema: &providerSchema,
|
|
OutputChange: &change,
|
|
State: &state,
|
|
dependsOn: n.dependsOn,
|
|
forceDependsOn: n.forceDependsOn,
|
|
},
|
|
},
|
|
|
|
&EvalIf{
|
|
If: func(ctx EvalContext) (bool, error) {
|
|
return change == nil, nil
|
|
|
|
},
|
|
Then: &EvalSequence{
|
|
Nodes: []EvalNode{
|
|
&EvalWriteState{
|
|
Addr: addr.Resource,
|
|
ProviderAddr: n.ResolvedProvider,
|
|
State: &state,
|
|
ProviderSchema: &providerSchema,
|
|
},
|
|
&EvalUpdateStateHook{},
|
|
},
|
|
},
|
|
Else: &EvalSequence{
|
|
// We can't deal with this yet, so we'll repeat this step
|
|
// during the plan walk to produce a planned change to read
|
|
// this during the apply walk. However, we do still need to
|
|
// save the generated change and partial state so that
|
|
// results from it can be included in other data resources
|
|
// or provider configurations during the refresh walk.
|
|
// (The planned object we save in the state here will be
|
|
// pruned out at the end of the refresh walk, returning
|
|
// it back to being unset again for subsequent walks.)
|
|
Nodes: []EvalNode{
|
|
&EvalWriteDiff{
|
|
Addr: addr.Resource,
|
|
Change: &change,
|
|
ProviderSchema: &providerSchema,
|
|
},
|
|
&EvalWriteState{
|
|
Addr: addr.Resource,
|
|
ProviderAddr: n.ResolvedProvider,
|
|
State: &state,
|
|
ProviderSchema: &providerSchema,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
}
|