terraform: handle count fields for data sources
This commit is contained in:
parent
38286fe491
commit
7c014b84b6
|
@ -56,7 +56,9 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
|||
|
||||
concreteResource := func(a *NodeAbstractResource) dag.Vertex {
|
||||
return &NodePlannableResource{
|
||||
NodeAbstractCountResource: &NodeAbstractCountResource{
|
||||
NodeAbstractResource: a,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -62,6 +62,14 @@ func (b *RefreshGraphBuilder) Steps() []GraphTransformer {
|
|||
}
|
||||
}
|
||||
|
||||
concreteDataResource := func(a *NodeAbstractResource) dag.Vertex {
|
||||
return &NodeRefreshableDataResource{
|
||||
NodeAbstractCountResource: &NodeAbstractCountResource{
|
||||
NodeAbstractResource: a,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
steps := []GraphTransformer{
|
||||
// Creates all the resources represented in the state
|
||||
&StateTransformer{
|
||||
|
@ -71,7 +79,7 @@ func (b *RefreshGraphBuilder) Steps() []GraphTransformer {
|
|||
|
||||
// Creates all the data resources that aren't in the state
|
||||
&ConfigTransformer{
|
||||
Concrete: concreteResource,
|
||||
Concrete: concreteDataResource,
|
||||
Module: b.Module,
|
||||
Unique: true,
|
||||
ModeFilter: true,
|
||||
|
|
|
@ -0,0 +1,209 @@
|
|||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// Start creating the steps
|
||||
steps := []GraphTransformer{
|
||||
// Expand the count.
|
||||
&ResourceCountTransformer{
|
||||
Concrete: concreteResource,
|
||||
Count: count,
|
||||
Addr: n.ResourceAddr(),
|
||||
},
|
||||
|
||||
// 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 {
|
||||
// Determine the dependencies for the state. We use some older
|
||||
// code for this that we've used for a long time.
|
||||
var stateDeps []string
|
||||
{
|
||||
oldN := &graphNodeExpandedResource{
|
||||
Resource: n.Config,
|
||||
Index: addr.Index,
|
||||
}
|
||||
stateDeps = oldN.StateDependencies()
|
||||
}
|
||||
|
||||
rs = &ResourceState{
|
||||
Type: n.Config.Type,
|
||||
Provider: n.Config.Provider,
|
||||
Dependencies: stateDeps,
|
||||
}
|
||||
}
|
||||
|
||||
// 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{},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
package terraform
|
||||
|
||||
// NodeAbstractCountResource should be embedded instead of NodeAbstractResource
|
||||
// if the resource has a `count` value that needs to be expanded.
|
||||
//
|
||||
// The embedder should implement `DynamicExpand` to process the count.
|
||||
type NodeAbstractCountResource struct {
|
||||
*NodeAbstractResource
|
||||
}
|
||||
|
||||
// GraphNodeEvalable
|
||||
func (n *NodeAbstractCountResource) EvalTree() EvalNode {
|
||||
return &EvalSequence{
|
||||
Nodes: []EvalNode{
|
||||
// The EvalTree for a plannable resource primarily involves
|
||||
// interpolating the count since it can contain variables
|
||||
// we only just received access to.
|
||||
//
|
||||
// With the interpolated count, we can then DynamicExpand
|
||||
// into the proper number of instances.
|
||||
&EvalInterpolate{Config: n.Config.RawCount},
|
||||
|
||||
&EvalCountCheckComputed{Resource: n.Config},
|
||||
&EvalCountFixZeroOneBoundary{Resource: n.Config},
|
||||
},
|
||||
}
|
||||
}
|
|
@ -7,34 +7,7 @@ import (
|
|||
// NodePlannableResource represents a resource that is "plannable":
|
||||
// it is ready to be planned in order to create a diff.
|
||||
type NodePlannableResource struct {
|
||||
*NodeAbstractResource
|
||||
|
||||
// Set by GraphNodeTargetable and used during DynamicExpand to
|
||||
// forward targets downwards.
|
||||
targets []ResourceAddress
|
||||
}
|
||||
|
||||
// GraphNodeTargetable
|
||||
func (n *NodePlannableResource) SetTargets(targets []ResourceAddress) {
|
||||
n.targets = targets
|
||||
}
|
||||
|
||||
// GraphNodeEvalable
|
||||
func (n *NodePlannableResource) EvalTree() EvalNode {
|
||||
return &EvalSequence{
|
||||
Nodes: []EvalNode{
|
||||
// The EvalTree for a plannable resource primarily involves
|
||||
// interpolating the count since it can contain variables
|
||||
// we only just received access to.
|
||||
//
|
||||
// With the interpolated count, we can then DynamicExpand
|
||||
// into the proper number of instances.
|
||||
&EvalInterpolate{Config: n.Config.RawCount},
|
||||
|
||||
&EvalCountCheckComputed{Resource: n.Config},
|
||||
&EvalCountFixZeroOneBoundary{Resource: n.Config},
|
||||
},
|
||||
}
|
||||
*NodeAbstractCountResource
|
||||
}
|
||||
|
||||
// GraphNodeDynamicExpandable
|
||||
|
@ -91,7 +64,7 @@ func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
|||
&AttachStateTransformer{State: state},
|
||||
|
||||
// Targeting
|
||||
&TargetsTransformer{ParsedTargets: n.targets},
|
||||
&TargetsTransformer{ParsedTargets: n.Targets},
|
||||
|
||||
// Connect references so ordering is correct
|
||||
&ReferenceTransformer{},
|
||||
|
|
|
@ -24,7 +24,11 @@ func (n *NodeRefreshableResource) EvalTree() EvalNode {
|
|||
case config.ManagedResourceMode:
|
||||
return n.evalTreeManagedResource()
|
||||
case config.DataResourceMode:
|
||||
return n.evalTreeDataResource()
|
||||
dn := &NodeRefreshableDataResourceInstance{
|
||||
NodeAbstractResource: n.NodeAbstractResource,
|
||||
}
|
||||
|
||||
return dn.EvalTree()
|
||||
default:
|
||||
panic(fmt.Errorf("unsupported resource mode %s", mode))
|
||||
}
|
||||
|
@ -84,139 +88,3 @@ func (n *NodeRefreshableResource) evalTreeManagedResource() EvalNode {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (n *NodeRefreshableResource) evalTreeDataResource() 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 {
|
||||
// Determine the dependencies for the state. We use some older
|
||||
// code for this that we've used for a long time.
|
||||
var stateDeps []string
|
||||
{
|
||||
oldN := &graphNodeExpandedResource{
|
||||
Resource: n.Config,
|
||||
Index: addr.Index,
|
||||
}
|
||||
stateDeps = oldN.StateDependencies()
|
||||
}
|
||||
|
||||
rs = &ResourceState{
|
||||
Type: n.Config.Type,
|
||||
Provider: n.Config.Provider,
|
||||
Dependencies: stateDeps,
|
||||
}
|
||||
}
|
||||
|
||||
// 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{},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue