terraform/terraform/node_resource_refresh.go

270 lines
8.1 KiB
Go

package terraform
import (
"fmt"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/dag"
"github.com/hashicorp/terraform/tfdiags"
)
// NodeRefreshableManagedResource represents a resource that is expanabled into
// NodeRefreshableManagedResourceInstance. Resource count orphans are also added.
type NodeRefreshableManagedResource struct {
*NodeAbstractResource
}
var (
_ GraphNodeSubPath = (*NodeRefreshableManagedResource)(nil)
_ GraphNodeDynamicExpandable = (*NodeRefreshableManagedResource)(nil)
_ GraphNodeReferenceable = (*NodeRefreshableManagedResource)(nil)
_ GraphNodeReferencer = (*NodeRefreshableManagedResource)(nil)
_ GraphNodeResource = (*NodeRefreshableManagedResource)(nil)
_ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResource)(nil)
)
// GraphNodeDynamicExpandable
func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
var diags tfdiags.Diagnostics
count, countDiags := evaluateResourceCountExpression(n.Config.Count, ctx)
diags = diags.Append(countDiags)
if countDiags.HasErrors() {
return nil, diags.Err()
}
// 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().Resource, count != -1)
// Grab the state which we read
state, lock := ctx.State()
lock.RLock()
defer lock.RUnlock()
// 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
return &NodeRefreshableManagedResourceInstance{
NodeAbstractResourceInstance: a,
}
}
// Start creating the steps
steps := []GraphTransformer{
// Expand the count.
&ResourceCountTransformer{
Concrete: concreteResource,
Schema: n.Schema,
Count: count,
Addr: n.ResourceAddr(),
},
// Add the count orphans to make sure these resources are accounted for
// during a scale in.
&OrphanResourceCountTransformer{
Concrete: concreteResource,
Count: count,
Addr: n.ResourceAddr(),
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: "NodeRefreshableManagedResource",
}
graph, diags := b.Build(ctx.Path())
return graph, diags.ErrWithWarnings()
}
// NodeRefreshableManagedResourceInstance represents a resource that is "applyable":
// it is ready to be applied and is represented by a diff.
type NodeRefreshableManagedResourceInstance struct {
*NodeAbstractResourceInstance
}
var (
_ GraphNodeSubPath = (*NodeRefreshableManagedResourceInstance)(nil)
_ GraphNodeReferenceable = (*NodeRefreshableManagedResourceInstance)(nil)
_ GraphNodeReferencer = (*NodeRefreshableManagedResourceInstance)(nil)
_ GraphNodeDestroyer = (*NodeRefreshableManagedResourceInstance)(nil)
_ GraphNodeResource = (*NodeRefreshableManagedResourceInstance)(nil)
_ GraphNodeResourceInstance = (*NodeRefreshableManagedResourceInstance)(nil)
_ GraphNodeAttachResourceConfig = (*NodeRefreshableManagedResourceInstance)(nil)
_ GraphNodeAttachResourceState = (*NodeRefreshableManagedResourceInstance)(nil)
_ GraphNodeEvalable = (*NodeRefreshableManagedResourceInstance)(nil)
)
// GraphNodeDestroyer
func (n *NodeRefreshableManagedResourceInstance) DestroyAddr() *addrs.AbsResourceInstance {
addr := n.ResourceInstanceAddr()
return &addr
}
// GraphNodeEvalable
func (n *NodeRefreshableManagedResourceInstance) EvalTree() EvalNode {
addr := n.ResourceInstanceAddr()
// Eval info is different depending on what kind of resource this is
switch addr.Resource.Resource.Mode {
case addrs.ManagedResourceMode:
if n.ResourceState == nil {
return n.evalTreeManagedResourceNoState()
}
return n.evalTreeManagedResource()
case addrs.DataResourceMode:
// Get the data source node. If we don't have a configuration
// then it is an orphan so we destroy it (remove it from the state).
var dn GraphNodeEvalable
if n.Config != nil {
dn = &NodeRefreshableDataResourceInstance{
NodeAbstractResourceInstance: n.NodeAbstractResourceInstance,
}
} else {
dn = &NodeDestroyableDataResource{
NodeAbstractResourceInstance: n.NodeAbstractResourceInstance,
}
}
return dn.EvalTree()
default:
panic(fmt.Errorf("unsupported resource mode %s", addr.Resource.Resource.Mode))
}
}
func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResource() EvalNode {
addr := n.ResourceInstanceAddr()
// State still uses legacy-style internal ids, so we need to shim to get
// a suitable key to use.
stateId := NewLegacyResourceInstanceAddress(addr).stateId()
// 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 state *InstanceState
// This happened during initial development. All known cases were
// fixed and tested but as a sanity check let's assert here.
if n.ResourceState == nil {
err := fmt.Errorf(
"No resource state attached for addr: %s\n\n"+
"This is a bug. Please report this to Terraform with your configuration\n"+
"and state attached. Please be careful to scrub any sensitive information.",
addr)
return &EvalReturnError{Error: &err}
}
return &EvalSequence{
Nodes: []EvalNode{
&EvalReadState{
Name: stateId,
Output: &state,
},
&EvalGetProvider{
Addr: n.ResolvedProvider,
Output: &provider,
},
&EvalRefresh{
Addr: addr.Resource,
Provider: &provider,
State: &state,
Output: &state,
},
&EvalWriteState{
Name: stateId,
ResourceType: n.ResourceState.Type,
Provider: n.ResolvedProvider,
Dependencies: n.ResourceState.Dependencies,
State: &state,
},
},
}
}
// evalTreeManagedResourceNoState produces an EvalSequence for refresh resource
// nodes that don't have state attached. An example of where this functionality
// is useful is when a resource that already exists in state is being scaled
// out, ie: has its resource count increased. In this case, the scaled out node
// needs to be available to other nodes (namely data sources) that may depend
// on it for proper interpolation, or confusing "index out of range" errors can
// occur.
//
// The steps in this sequence are very similar to the steps carried out in
// plan, but nothing is done with the diff after it is created - it is dropped,
// and its changes are not counted in the UI.
func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResourceNoState() EvalNode {
addr := n.ResourceInstanceAddr()
// 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 providerSchema *ProviderSchema
var diff *InstanceDiff
var state *InstanceState
// State still uses legacy-style internal ids, so we need to shim to get
// a suitable key to use.
stateID := NewLegacyResourceInstanceAddress(addr).stateId()
// Determine the dependencies for the state.
stateDeps := n.StateReferences()
return &EvalSequence{
Nodes: []EvalNode{
&EvalReadState{
Name: stateID,
Output: &state,
},
&EvalGetProvider{
Addr: n.ResolvedProvider,
Output: &provider,
Schema: &providerSchema,
},
&EvalDiff{
Addr: addr.Resource,
Config: n.Config,
Provider: &provider,
ProviderSchema: &providerSchema,
State: &state,
OutputDiff: &diff,
OutputState: &state,
Stub: true,
},
&EvalWriteState{
Name: stateID,
ResourceType: n.Config.Type,
Provider: n.ResolvedProvider,
Dependencies: stateDeps,
State: &state,
},
},
}
}