"fmt"
"github.com/hashicorp/terraform/config"
+ "github.com/hashicorp/terraform/dag"
)
-// NodeRefreshableResource represents a resource that is "applyable":
+// NodeRefreshableManagedResource represents a resource that is expanabled into
+// NodeRefreshableManagedResourceInstance. Resource count orphans are also added.
+type NodeRefreshableManagedResource struct {
+ *NodeAbstractCountResource
+}
+
+// GraphNodeDynamicExpandable
+func (n *NodeRefreshableManagedResource) 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 &NodeRefreshableManagedResourceInstance{
+ NodeAbstractResource: a,
+ }
+ }
+
+ // Start creating the steps
+ steps := []GraphTransformer{
+ // Expand the count.
+ &ResourceCountTransformer{
+ Concrete: concreteResource,
+ 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{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: "NodeRefreshableManagedResource",
+ }
+
+ return b.Build(ctx.Path())
+}
+
+// NodeRefreshableManagedResourceInstance represents a resource that is "applyable":
// it is ready to be applied and is represented by a diff.
-type NodeRefreshableResource struct {
+type NodeRefreshableManagedResourceInstance struct {
*NodeAbstractResource
}
// GraphNodeDestroyer
-func (n *NodeRefreshableResource) DestroyAddr() *ResourceAddress {
+func (n *NodeRefreshableManagedResourceInstance) DestroyAddr() *ResourceAddress {
return n.Addr
}
// GraphNodeEvalable
-func (n *NodeRefreshableResource) EvalTree() EvalNode {
+func (n *NodeRefreshableManagedResourceInstance) EvalTree() EvalNode {
// Eval info is different depending on what kind of resource this is
switch mode := n.Addr.Mode; mode {
case config.ManagedResourceMode:
+ if n.ResourceState == nil {
+ return n.evalTreeManagedResourceNoState()
+ }
return n.evalTreeManagedResource()
case config.DataResourceMode:
}
}
-func (n *NodeRefreshableResource) evalTreeManagedResource() EvalNode {
+func (n *NodeRefreshableManagedResourceInstance) evalTreeManagedResource() EvalNode {
addr := n.NodeAbstractResource.Addr
// stateId is the ID to put into the 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 {
+ // 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
+ var resourceConfig *ResourceConfig
+
+ addr := n.NodeAbstractResource.Addr
+ stateID := addr.stateId()
+ info := &InstanceInfo{
+ Id: stateID,
+ Type: addr.Type,
+ ModulePath: normalizeModulePath(addr.Path),
+ }
+
+ // Build the resource for eval
+ resource := &Resource{
+ Name: addr.Name,
+ Type: addr.Type,
+ CountIndex: addr.Index,
+ }
+ if resource.CountIndex < 0 {
+ resource.CountIndex = 0
+ }
+
+ // Determine the dependencies for the state.
+ stateDeps := n.StateReferences()
+
+ return &EvalSequence{
+ Nodes: []EvalNode{
+ &EvalInterpolate{
+ Config: n.Config.RawConfig.Copy(),
+ Resource: resource,
+ Output: &resourceConfig,
+ },
+ &EvalGetProvider{
+ Name: n.ProvidedBy()[0],
+ Output: &provider,
+ },
+ // Re-run validation to catch any errors we missed, e.g. type
+ // mismatches on computed values.
+ &EvalValidateResource{
+ Provider: &provider,
+ Config: &resourceConfig,
+ ResourceName: n.Config.Name,
+ ResourceType: n.Config.Type,
+ ResourceMode: n.Config.Mode,
+ IgnoreWarnings: true,
+ },
+ &EvalReadState{
+ Name: stateID,
+ Output: &state,
+ },
+ &EvalDiff{
+ Name: stateID,
+ Info: info,
+ Config: &resourceConfig,
+ Resource: n.Config,
+ Provider: &provider,
+ State: &state,
+ OutputState: &state,
+ Stub: true,
+ },
+ &EvalWriteState{
+ Name: stateID,
+ ResourceType: n.Config.Type,
+ Provider: n.Config.Provider,
+ Dependencies: stateDeps,
+ State: &state,
+ },
+ },
+ }
+}