219 lines
5.3 KiB
Go
219 lines
5.3 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
|
|
|
|
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.ProvidedBy()[0],
|
|
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{},
|
|
},
|
|
}
|
|
}
|