core: Use instances.Expander to handle resource count and for_each
This is a minimal integration of instances.Expander used just for resource count and for_each, for now just forcing modules to always be singletons because the rest of Terraform Core isn't ready to deal with expanding module calls yet. This doesn't integrate super cleanly yet because we still have some cleanup work to do in the design of the plan walk, to make it explicit that the nodes in the plan graph represent static configuration objects rather than expanded instances, including for modules. To make this work in the meantime, there is some shimming between addrs.Module and addrs.ModuleInstance to correct for the discontinuities that result from the fact that Terraform currently assumes that modules are always singletons.
This commit is contained in:
parent
8ea78dfe7d
commit
68b900928d
|
@ -488,6 +488,19 @@ func (n *EvalWriteResourceState) Eval(ctx EvalContext) (interface{}, error) {
|
|||
// while ensuring that any existing instances are preserved, etc.
|
||||
state.SetResourceMeta(absAddr, eachMode, n.ProviderAddr)
|
||||
|
||||
// We'll record our expansion decision in the shared "expander" object
|
||||
// so that later operations (i.e. DynamicExpand and expression evaluation)
|
||||
// can refer to it.
|
||||
expander := ctx.InstanceExpander()
|
||||
switch eachMode {
|
||||
case states.EachList:
|
||||
expander.SetResourceCount(ctx.Path(), n.Addr, count)
|
||||
case states.EachMap:
|
||||
expander.SetResourceForEach(ctx.Path(), n.Addr, forEach)
|
||||
default:
|
||||
expander.SetResourceSingle(ctx.Path(), n.Addr)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -153,6 +153,11 @@ func (b *ApplyGraphBuilder) Steps() []GraphTransformer {
|
|||
// analyze the configuration to find references.
|
||||
&AttachSchemaTransformer{Schemas: b.Schemas},
|
||||
|
||||
// Create expansion nodes for all of the module calls. This must
|
||||
// come after all other transformers that create nodes representing
|
||||
// objects that can belong to modules.
|
||||
&ModuleExpansionTransformer{Config: b.Config},
|
||||
|
||||
// Connect references so ordering is correct
|
||||
&ReferenceTransformer{},
|
||||
&AttachDependenciesTransformer{},
|
||||
|
|
|
@ -656,15 +656,18 @@ const testApplyGraphBuilderStr = `
|
|||
meta.count-boundary (EachMode fixup)
|
||||
module.child.test_object.other
|
||||
test_object.other
|
||||
module.child
|
||||
module.child.test_object.create
|
||||
module.child.test_object.create (prepare state)
|
||||
module.child.test_object.create (prepare state)
|
||||
module.child
|
||||
provider["registry.terraform.io/-/test"]
|
||||
provisioner.test
|
||||
module.child.test_object.other
|
||||
module.child.test_object.create
|
||||
module.child.test_object.other (prepare state)
|
||||
module.child.test_object.other (prepare state)
|
||||
module.child
|
||||
provider["registry.terraform.io/-/test"]
|
||||
provider["registry.terraform.io/-/test"]
|
||||
provider["registry.terraform.io/-/test"] (close)
|
||||
|
|
|
@ -137,6 +137,11 @@ func (b *PlanGraphBuilder) Steps() []GraphTransformer {
|
|||
// analyze the configuration to find references.
|
||||
&AttachSchemaTransformer{Schemas: b.Schemas},
|
||||
|
||||
// Create expansion nodes for all of the module calls. This must
|
||||
// come after all other transformers that create nodes representing
|
||||
// objects that can belong to modules.
|
||||
&ModuleExpansionTransformer{Config: b.Config},
|
||||
|
||||
// Connect so that the references are ready for targeting. We'll
|
||||
// have to connect again later for providers and so on.
|
||||
&ReferenceTransformer{},
|
||||
|
|
|
@ -162,6 +162,11 @@ func (b *RefreshGraphBuilder) Steps() []GraphTransformer {
|
|||
// analyze the configuration to find references.
|
||||
&AttachSchemaTransformer{Schemas: b.Schemas},
|
||||
|
||||
// Create expansion nodes for all of the module calls. This must
|
||||
// come after all other transformers that create nodes representing
|
||||
// objects that can belong to modules.
|
||||
&ModuleExpansionTransformer{Config: b.Config},
|
||||
|
||||
// Connect so that the references are ready for targeting. We'll
|
||||
// have to connect again later for providers and so on.
|
||||
&ReferenceTransformer{},
|
||||
|
|
|
@ -53,6 +53,19 @@ func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, er
|
|||
// if we're transitioning whether "count" is set at all.
|
||||
fixResourceCountSetTransition(ctx, n.ResourceAddr(), count != -1)
|
||||
|
||||
// Inform our instance expander about our expansion results above,
|
||||
// and then use it to calculate the instance addresses we'll expand for.
|
||||
expander := ctx.InstanceExpander()
|
||||
switch {
|
||||
case count >= 0:
|
||||
expander.SetResourceCount(ctx.Path(), n.ResourceAddr().Resource, count)
|
||||
case forEachMap != nil:
|
||||
expander.SetResourceForEach(ctx.Path(), n.ResourceAddr().Resource, forEachMap)
|
||||
default:
|
||||
expander.SetResourceSingle(ctx.Path(), n.ResourceAddr().Resource)
|
||||
}
|
||||
instanceAddrs := expander.ExpandResource(ctx.Path().Module(), n.ResourceAddr().Resource)
|
||||
|
||||
// Our graph transformers require access to the full state, so we'll
|
||||
// temporarily lock it while we work on this.
|
||||
state := ctx.State().Lock()
|
||||
|
@ -85,21 +98,19 @@ func (n *NodeRefreshableDataResource) DynamicExpand(ctx EvalContext) (*Graph, er
|
|||
steps := []GraphTransformer{
|
||||
// Expand the count.
|
||||
&ResourceCountTransformer{
|
||||
Concrete: concreteResource,
|
||||
Schema: n.Schema,
|
||||
Count: count,
|
||||
ForEach: forEachMap,
|
||||
Addr: n.ResourceAddr(),
|
||||
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.
|
||||
&OrphanResourceCountTransformer{
|
||||
Concrete: concreteResourceDestroyable,
|
||||
Count: count,
|
||||
ForEach: forEachMap,
|
||||
Addr: n.ResourceAddr(),
|
||||
State: state,
|
||||
Concrete: concreteResourceDestroyable,
|
||||
Addr: n.ResourceAddr(),
|
||||
InstanceAddrs: instanceAddrs,
|
||||
State: state,
|
||||
},
|
||||
|
||||
// Attach the state
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/instances"
|
||||
)
|
||||
|
||||
func TestNodeRefreshableDataResourceDynamicExpand_scaleOut(t *testing.T) {
|
||||
|
@ -49,8 +50,9 @@ func TestNodeRefreshableDataResourceDynamicExpand_scaleOut(t *testing.T) {
|
|||
}
|
||||
|
||||
g, err := n.DynamicExpand(&MockEvalContext{
|
||||
PathPath: addrs.RootModuleInstance,
|
||||
StateState: state.SyncWrapper(),
|
||||
PathPath: addrs.RootModuleInstance,
|
||||
StateState: state.SyncWrapper(),
|
||||
InstanceExpanderExpander: instances.NewExpander(),
|
||||
|
||||
// DynamicExpand will call EvaluateExpr to evaluate the "count"
|
||||
// expression, which is just a literal number 3 in the fixture config
|
||||
|
@ -136,8 +138,9 @@ func TestNodeRefreshableDataResourceDynamicExpand_scaleIn(t *testing.T) {
|
|||
}
|
||||
|
||||
g, err := n.DynamicExpand(&MockEvalContext{
|
||||
PathPath: addrs.RootModuleInstance,
|
||||
StateState: state.SyncWrapper(),
|
||||
PathPath: addrs.RootModuleInstance,
|
||||
StateState: state.SyncWrapper(),
|
||||
InstanceExpanderExpander: instances.NewExpander(),
|
||||
|
||||
// DynamicExpand will call EvaluateExpr to evaluate the "count"
|
||||
// expression, which is just a literal number 3 in the fixture config
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
)
|
||||
|
||||
// nodeExpandModule represents a module call in the configuration that
|
||||
// might expand into multiple module instances depending on how it is
|
||||
// configured.
|
||||
type nodeExpandModule struct {
|
||||
CallerAddr addrs.ModuleInstance
|
||||
Call addrs.ModuleCall
|
||||
Config *configs.Module
|
||||
}
|
||||
|
||||
var (
|
||||
_ GraphNodeSubPath = (*nodeExpandModule)(nil)
|
||||
_ RemovableIfNotTargeted = (*nodeExpandModule)(nil)
|
||||
_ GraphNodeEvalable = (*nodeExpandModule)(nil)
|
||||
_ GraphNodeReferencer = (*nodeExpandModule)(nil)
|
||||
)
|
||||
|
||||
func (n *nodeExpandModule) Name() string {
|
||||
return n.CallerAddr.Child(n.Call.Name, addrs.NoKey).String()
|
||||
}
|
||||
|
||||
// GraphNodeSubPath implementation
|
||||
func (n *nodeExpandModule) Path() addrs.ModuleInstance {
|
||||
// Notice that the node represents the module call and so we report
|
||||
// the parent module as the path. The module call we're representing
|
||||
// might expand into multiple child module instances during our work here.
|
||||
return n.CallerAddr
|
||||
}
|
||||
|
||||
// GraphNodeReferencer implementation
|
||||
func (n *nodeExpandModule) References() []*addrs.Reference {
|
||||
// Expansion only uses the count and for_each expressions, so this
|
||||
// particular graph node only refers to those.
|
||||
// Individual variable values in the module call definition might also
|
||||
// refer to other objects, but that's handled by
|
||||
// NodeApplyableModuleVariable.
|
||||
//
|
||||
// Because our Path method returns the module instance that contains
|
||||
// our call, these references will be correctly interpreted as being
|
||||
// in the calling module's namespace, not the namespaces of any of the
|
||||
// child module instances we might expand to during our evaluation.
|
||||
var ret []*addrs.Reference
|
||||
// TODO: Once count and for_each are actually supported, analyze their
|
||||
// expressions for references here.
|
||||
/*
|
||||
if n.Config.Count != nil {
|
||||
ret = append(ret, n.Config.Count.References()...)
|
||||
}
|
||||
if n.Config.ForEach != nil {
|
||||
ret = append(ret, n.Config.ForEach.References()...)
|
||||
}
|
||||
*/
|
||||
return ret
|
||||
}
|
||||
|
||||
// RemovableIfNotTargeted implementation
|
||||
func (n *nodeExpandModule) RemoveIfNotTargeted() bool {
|
||||
// We need to add this so that this node will be removed if
|
||||
// it isn't targeted or a dependency of a target.
|
||||
return true
|
||||
}
|
||||
|
||||
// GraphNodeEvalable
|
||||
func (n *nodeExpandModule) EvalTree() EvalNode {
|
||||
return &evalPrepareModuleExpansion{
|
||||
CallerAddr: n.CallerAddr,
|
||||
Call: n.Call,
|
||||
Config: n.Config,
|
||||
}
|
||||
}
|
||||
|
||||
type evalPrepareModuleExpansion struct {
|
||||
CallerAddr addrs.ModuleInstance
|
||||
Call addrs.ModuleCall
|
||||
Config *configs.Module
|
||||
}
|
||||
|
||||
func (n *evalPrepareModuleExpansion) Eval(ctx EvalContext) (interface{}, error) {
|
||||
// Modules don't support any of the repetition arguments yet, so their
|
||||
// expansion type is always "single". We just record this here to make
|
||||
// the expander data structure consistent for now.
|
||||
// FIXME: Once the rest of Terraform Core is ready to support expanding
|
||||
// modules, evaluate the "count" and "for_each" arguments here in a
|
||||
// similar way as in EvalWriteResourceState.
|
||||
log.Printf("[TRACE] evalPrepareModuleExpansion: %s is a singleton", n.CallerAddr.Child(n.Call.Name, addrs.NoKey))
|
||||
ctx.InstanceExpander().SetModuleSingle(n.CallerAddr, n.Call)
|
||||
return nil, nil
|
||||
}
|
|
@ -71,19 +71,25 @@ func (n *NodePlannableResource) ModifyCreateBeforeDestroy(v bool) error {
|
|||
func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
||||
var diags tfdiags.Diagnostics
|
||||
|
||||
// Our instance expander should already have been informed about the
|
||||
// expansion of this resource and of all of its containing modules, so
|
||||
// it can tell us which instance addresses we need to process.
|
||||
module := ctx.Path().Module()
|
||||
expander := ctx.InstanceExpander()
|
||||
instanceAddrs := expander.ExpandResource(module, n.ResourceAddr().Resource)
|
||||
|
||||
// We need to potentially rename an instance address in the state
|
||||
// if we're transitioning whether "count" is set at all.
|
||||
//
|
||||
// FIXME: We're re-evaluating count here, even though the InstanceExpander
|
||||
// has already dealt with our expansion above, because we need it to
|
||||
// call fixResourceCountSetTransition; the expander API and that function
|
||||
// are not compatible yet.
|
||||
count, countDiags := evaluateResourceCountExpression(n.Config.Count, ctx)
|
||||
diags = diags.Append(countDiags)
|
||||
if countDiags.HasErrors() {
|
||||
return nil, diags.Err()
|
||||
}
|
||||
|
||||
forEachMap, forEachDiags := evaluateResourceForEachExpression(n.Config.ForEach, ctx)
|
||||
if forEachDiags.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(), count != -1)
|
||||
|
||||
// Our graph transformers require access to the full state, so we'll
|
||||
|
@ -126,20 +132,18 @@ func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
|||
steps := []GraphTransformer{
|
||||
// Expand the count or for_each (if present)
|
||||
&ResourceCountTransformer{
|
||||
Concrete: concreteResource,
|
||||
Schema: n.Schema,
|
||||
Count: count,
|
||||
ForEach: forEachMap,
|
||||
Addr: n.ResourceAddr(),
|
||||
Concrete: concreteResource,
|
||||
Schema: n.Schema,
|
||||
Addr: n.ResourceAddr(),
|
||||
InstanceAddrs: instanceAddrs,
|
||||
},
|
||||
|
||||
// Add the count/for_each orphans
|
||||
&OrphanResourceCountTransformer{
|
||||
Concrete: concreteResourceOrphan,
|
||||
Count: count,
|
||||
ForEach: forEachMap,
|
||||
Addr: n.ResourceAddr(),
|
||||
State: state,
|
||||
Concrete: concreteResourceOrphan,
|
||||
Addr: n.ResourceAddr(),
|
||||
InstanceAddrs: instanceAddrs,
|
||||
State: state,
|
||||
},
|
||||
|
||||
// Attach the state
|
||||
|
|
|
@ -58,6 +58,19 @@ func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph,
|
|||
// if we're transitioning whether "count" is set at all.
|
||||
fixResourceCountSetTransition(ctx, n.ResourceAddr(), count != -1)
|
||||
|
||||
// Inform our instance expander about our expansion results above,
|
||||
// and then use it to calculate the instance addresses we'll expand for.
|
||||
expander := ctx.InstanceExpander()
|
||||
switch {
|
||||
case count >= 0:
|
||||
expander.SetResourceCount(ctx.Path(), n.ResourceAddr().Resource, count)
|
||||
case forEachMap != nil:
|
||||
expander.SetResourceForEach(ctx.Path(), n.ResourceAddr().Resource, forEachMap)
|
||||
default:
|
||||
expander.SetResourceSingle(ctx.Path(), n.ResourceAddr().Resource)
|
||||
}
|
||||
instanceAddrs := expander.ExpandResource(ctx.Path().Module(), n.ResourceAddr().Resource)
|
||||
|
||||
// Our graph transformers require access to the full state, so we'll
|
||||
// temporarily lock it while we work on this.
|
||||
state := ctx.State().Lock()
|
||||
|
@ -79,21 +92,19 @@ func (n *NodeRefreshableManagedResource) DynamicExpand(ctx EvalContext) (*Graph,
|
|||
steps := []GraphTransformer{
|
||||
// Expand the count.
|
||||
&ResourceCountTransformer{
|
||||
Concrete: concreteResource,
|
||||
Schema: n.Schema,
|
||||
Count: count,
|
||||
ForEach: forEachMap,
|
||||
Addr: n.ResourceAddr(),
|
||||
Concrete: concreteResource,
|
||||
Schema: n.Schema,
|
||||
Addr: n.ResourceAddr(),
|
||||
InstanceAddrs: instanceAddrs,
|
||||
},
|
||||
|
||||
// Add the count orphans to make sure these resources are accounted for
|
||||
// during a scale in.
|
||||
&OrphanResourceCountTransformer{
|
||||
Concrete: concreteResource,
|
||||
Count: count,
|
||||
ForEach: forEachMap,
|
||||
Addr: n.ResourceAddr(),
|
||||
State: state,
|
||||
Concrete: concreteResource,
|
||||
Addr: n.ResourceAddr(),
|
||||
InstanceAddrs: instanceAddrs,
|
||||
State: state,
|
||||
},
|
||||
|
||||
// Attach the state
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/zclconf/go-cty/cty"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/instances"
|
||||
)
|
||||
|
||||
func TestNodeRefreshableManagedResourceDynamicExpand_scaleOut(t *testing.T) {
|
||||
|
@ -49,8 +50,9 @@ func TestNodeRefreshableManagedResourceDynamicExpand_scaleOut(t *testing.T) {
|
|||
}
|
||||
|
||||
g, err := n.DynamicExpand(&MockEvalContext{
|
||||
PathPath: addrs.RootModuleInstance,
|
||||
StateState: state,
|
||||
PathPath: addrs.RootModuleInstance,
|
||||
StateState: state,
|
||||
InstanceExpanderExpander: instances.NewExpander(),
|
||||
|
||||
// DynamicExpand will call EvaluateExpr to evaluate the "count"
|
||||
// expression, which is just a literal number 3 in the fixture config
|
||||
|
@ -130,8 +132,9 @@ func TestNodeRefreshableManagedResourceDynamicExpand_scaleIn(t *testing.T) {
|
|||
}
|
||||
|
||||
g, err := n.DynamicExpand(&MockEvalContext{
|
||||
PathPath: addrs.RootModuleInstance,
|
||||
StateState: state,
|
||||
PathPath: addrs.RootModuleInstance,
|
||||
StateState: state,
|
||||
InstanceExpanderExpander: instances.NewExpander(),
|
||||
|
||||
// DynamicExpand will call EvaluateExpr to evaluate the "count"
|
||||
// expression, which is just a literal number 3 in the fixture config
|
||||
|
|
|
@ -0,0 +1,82 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/configs"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
)
|
||||
|
||||
// ModuleExpansionTransformer is a GraphTransformer that adds graph nodes
|
||||
// representing the possible expansion of each module call in the configuration,
|
||||
// and ensures that any nodes representing objects declared within a module
|
||||
// are dependent on the expansion node so that they will be visited only
|
||||
// after the module expansion has been decided.
|
||||
//
|
||||
// This transform must be applied only after all nodes representing objects
|
||||
// that can be contained within modules have already been added.
|
||||
type ModuleExpansionTransformer struct {
|
||||
Config *configs.Config
|
||||
}
|
||||
|
||||
func (t *ModuleExpansionTransformer) Transform(g *Graph) error {
|
||||
// The root module is always a singleton and so does not need expansion
|
||||
// processing, but any descendent modules do. We'll process them
|
||||
// recursively using t.transform.
|
||||
for _, cfg := range t.Config.Children {
|
||||
err := t.transform(g, cfg, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *ModuleExpansionTransformer) transform(g *Graph, c *configs.Config, parentNode dag.Vertex) error {
|
||||
// FIXME: We're using addrs.ModuleInstance to represent the paths here
|
||||
// because the rest of Terraform Core is expecting that, but in practice
|
||||
// thus is representing a path through the static module instances (not
|
||||
// expanded yet), and so as we weave in support for repetition of module
|
||||
// calls we'll need to make the plan processing actually use addrs.Module
|
||||
// to represent that our graph nodes are actually representing unexpanded
|
||||
// static configuration objects, not instances.
|
||||
fullAddr := c.Path.UnkeyedInstanceShim()
|
||||
callerAddr, callAddr := fullAddr.Call()
|
||||
|
||||
v := &nodeExpandModule{
|
||||
CallerAddr: callerAddr,
|
||||
Call: callAddr,
|
||||
Config: c.Module,
|
||||
}
|
||||
g.Add(v)
|
||||
log.Printf("[TRACE] ModuleExpansionTransformer: Added %s as %T", fullAddr, v)
|
||||
|
||||
if parentNode != nil {
|
||||
log.Printf("[TRACE] ModuleExpansionTransformer: %s must wait for expansion of %s", dag.VertexName(v), dag.VertexName(parentNode))
|
||||
g.Connect(dag.BasicEdge(v, parentNode))
|
||||
}
|
||||
|
||||
// Connect any node that reports this module as its Path to ensure that
|
||||
// the module expansion will be handled before that node.
|
||||
// FIXME: Again, there is some Module vs. ModuleInstance muddling here
|
||||
// for legacy reasons, which we'll need to clean up as part of further
|
||||
// work to properly support "count" and "for_each" for modules. Nodes
|
||||
// in the plan graph actually belong to modules, not to module instances.
|
||||
for _, childV := range g.Vertices() {
|
||||
pather, ok := childV.(GraphNodeSubPath)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if pather.Path().Equal(fullAddr) {
|
||||
log.Printf("[TRACE] ModuleExpansionTransformer: %s must wait for expansion of %s", dag.VertexName(childV), fullAddr)
|
||||
g.Connect(dag.BasicEdge(childV, v))
|
||||
}
|
||||
}
|
||||
|
||||
// Also visit child modules, recursively.
|
||||
for _, cc := range c.Children {
|
||||
return t.transform(g, cc, v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -6,7 +6,6 @@ import (
|
|||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
"github.com/hashicorp/terraform/states"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// OrphanResourceCountTransformer is a GraphTransformer that adds orphans
|
||||
|
@ -19,155 +18,43 @@ import (
|
|||
type OrphanResourceCountTransformer struct {
|
||||
Concrete ConcreteResourceInstanceNodeFunc
|
||||
|
||||
Count int // Actual count of the resource, or -1 if count is not set at all
|
||||
ForEach map[string]cty.Value // The ForEach map on the resource
|
||||
Addr addrs.AbsResource // Addr of the resource to look for orphans
|
||||
State *states.State // Full global state
|
||||
Addr addrs.AbsResource // Addr of the resource to look for orphans
|
||||
InstanceAddrs []addrs.AbsResourceInstance // Addresses that currently exist in config
|
||||
State *states.State // Full global state
|
||||
}
|
||||
|
||||
func (t *OrphanResourceCountTransformer) Transform(g *Graph) error {
|
||||
// FIXME: This is currently assuming that all of the instances of
|
||||
// this resource belong to a single module instance, which is true
|
||||
// at the time of writing this because Terraform Core doesn't support
|
||||
// repetition of module calls yet, but this will need to be corrected
|
||||
// in order to support count and for_each on module calls, where
|
||||
// our t.InstanceAddrs may contain resource instances from many different
|
||||
// module instances.
|
||||
rs := t.State.Resource(t.Addr)
|
||||
if rs == nil {
|
||||
return nil // Resource doesn't exist in state, so nothing to do!
|
||||
}
|
||||
|
||||
haveKeys := make(map[addrs.InstanceKey]struct{})
|
||||
// This is an O(n*m) analysis, which we accept for now because the
|
||||
// number of instances of a single resource ought to always be small in any
|
||||
// reasonable Terraform configuration.
|
||||
Have:
|
||||
for key := range rs.Instances {
|
||||
haveKeys[key] = struct{}{}
|
||||
}
|
||||
|
||||
// if for_each is set, use that transformer
|
||||
if t.ForEach != nil {
|
||||
return t.transformForEach(haveKeys, g)
|
||||
}
|
||||
if t.Count < 0 {
|
||||
return t.transformNoCount(haveKeys, g)
|
||||
}
|
||||
if t.Count == 0 {
|
||||
return t.transformZeroCount(haveKeys, g)
|
||||
}
|
||||
return t.transformCount(haveKeys, g)
|
||||
}
|
||||
|
||||
func (t *OrphanResourceCountTransformer) transformForEach(haveKeys map[addrs.InstanceKey]struct{}, g *Graph) error {
|
||||
// If there is a NoKey node, add this to the graph first,
|
||||
// so that we can create edges to it in subsequent (StringKey) nodes.
|
||||
// This is because the last item determines the resource mode for the whole resource,
|
||||
// (see SetResourceInstanceCurrent for more information) and we need to evaluate
|
||||
// an orphaned (NoKey) resource before the in-memory state is updated
|
||||
// to deal with a new for_each resource
|
||||
_, hasNoKeyNode := haveKeys[addrs.NoKey]
|
||||
var noKeyNode dag.Vertex
|
||||
if hasNoKeyNode {
|
||||
abstract := NewNodeAbstractResourceInstance(t.Addr.Instance(addrs.NoKey))
|
||||
noKeyNode = abstract
|
||||
if f := t.Concrete; f != nil {
|
||||
noKeyNode = f(abstract)
|
||||
thisAddr := t.Addr.Instance(key)
|
||||
for _, wantAddr := range t.InstanceAddrs {
|
||||
if wantAddr.Equal(thisAddr) {
|
||||
continue Have
|
||||
}
|
||||
}
|
||||
g.Add(noKeyNode)
|
||||
}
|
||||
// If thisAddr is not in t.InstanceAddrs then we've found an "orphan"
|
||||
|
||||
for key := range haveKeys {
|
||||
// If the key is no-key, we have already added it, so skip
|
||||
if key == addrs.NoKey {
|
||||
continue
|
||||
}
|
||||
|
||||
s, _ := key.(addrs.StringKey)
|
||||
// If the key is present in our current for_each, carry on
|
||||
if _, ok := t.ForEach[string(s)]; ok {
|
||||
continue
|
||||
}
|
||||
|
||||
abstract := NewNodeAbstractResourceInstance(t.Addr.Instance(key))
|
||||
abstract := NewNodeAbstractResourceInstance(thisAddr)
|
||||
var node dag.Vertex = abstract
|
||||
if f := t.Concrete; f != nil {
|
||||
node = f(abstract)
|
||||
}
|
||||
log.Printf("[TRACE] OrphanResourceCount(non-zero): adding %s as %T", t.Addr, node)
|
||||
g.Add(node)
|
||||
|
||||
// Add edge to noKeyNode if it exists
|
||||
if hasNoKeyNode {
|
||||
g.Connect(dag.BasicEdge(node, noKeyNode))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *OrphanResourceCountTransformer) transformCount(haveKeys map[addrs.InstanceKey]struct{}, g *Graph) error {
|
||||
// Due to the logic in Transform, we only get in here if our count is
|
||||
// at least one.
|
||||
|
||||
_, have0Key := haveKeys[addrs.IntKey(0)]
|
||||
|
||||
for key := range haveKeys {
|
||||
if key == addrs.NoKey && !have0Key {
|
||||
// If we have no 0-key then we will accept a no-key instance
|
||||
// as an alias for it.
|
||||
continue
|
||||
}
|
||||
|
||||
i, isInt := key.(addrs.IntKey)
|
||||
if isInt && int(i) < t.Count {
|
||||
continue
|
||||
}
|
||||
|
||||
abstract := NewNodeAbstractResourceInstance(t.Addr.Instance(key))
|
||||
var node dag.Vertex = abstract
|
||||
if f := t.Concrete; f != nil {
|
||||
node = f(abstract)
|
||||
}
|
||||
log.Printf("[TRACE] OrphanResourceCount(non-zero): adding %s as %T", t.Addr, node)
|
||||
g.Add(node)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *OrphanResourceCountTransformer) transformZeroCount(haveKeys map[addrs.InstanceKey]struct{}, g *Graph) error {
|
||||
// This case is easy: we need to orphan any keys we have at all.
|
||||
|
||||
for key := range haveKeys {
|
||||
abstract := NewNodeAbstractResourceInstance(t.Addr.Instance(key))
|
||||
var node dag.Vertex = abstract
|
||||
if f := t.Concrete; f != nil {
|
||||
node = f(abstract)
|
||||
}
|
||||
log.Printf("[TRACE] OrphanResourceCount(zero): adding %s as %T", t.Addr, node)
|
||||
g.Add(node)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *OrphanResourceCountTransformer) transformNoCount(haveKeys map[addrs.InstanceKey]struct{}, g *Graph) error {
|
||||
// Negative count indicates that count is not set at all, in which
|
||||
// case we expect to have a single instance with no key set at all.
|
||||
// However, we'll also accept an instance with key 0 set as an alias
|
||||
// for it, in case the user has just deleted the "count" argument and
|
||||
// so wants to keep the first instance in the set.
|
||||
|
||||
_, haveNoKey := haveKeys[addrs.NoKey]
|
||||
_, have0Key := haveKeys[addrs.IntKey(0)]
|
||||
keepKey := addrs.NoKey
|
||||
if have0Key && !haveNoKey {
|
||||
// If we don't have a no-key instance then we can use the 0-key instance
|
||||
// instead.
|
||||
keepKey = addrs.IntKey(0)
|
||||
}
|
||||
|
||||
for key := range haveKeys {
|
||||
if key == keepKey {
|
||||
continue
|
||||
}
|
||||
|
||||
abstract := NewNodeAbstractResourceInstance(t.Addr.Instance(key))
|
||||
var node dag.Vertex = abstract
|
||||
if f := t.Concrete; f != nil {
|
||||
node = f(abstract)
|
||||
}
|
||||
log.Printf("[TRACE] OrphanResourceCount(no-count): adding %s as %T", t.Addr, node)
|
||||
log.Printf("[TRACE] OrphanResourceCountTransformer: adding %s as %T", thisAddr, node)
|
||||
g.Add(node)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package terraform
|
||||
|
||||
// FIXME: Update these tests for the new OrphanResourceCountTransformer
|
||||
// interface that expects to be given a list of instance addresses that
|
||||
// exist in config.
|
||||
|
||||
/*
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -433,3 +438,4 @@ aws_instance.foo (orphan)
|
|||
aws_instance.foo["bar"] (orphan)
|
||||
aws_instance.foo (orphan)
|
||||
`
|
||||
*/
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
package terraform
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/configs/configschema"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
"github.com/zclconf/go-cty/cty"
|
||||
)
|
||||
|
||||
// ResourceCountTransformer is a GraphTransformer that expands the count
|
||||
|
@ -15,33 +16,12 @@ type ResourceCountTransformer struct {
|
|||
Concrete ConcreteResourceInstanceNodeFunc
|
||||
Schema *configschema.Block
|
||||
|
||||
// Count is either the number of indexed instances to create, or -1 to
|
||||
// indicate that count is not set at all and thus a no-key instance should
|
||||
// be created.
|
||||
Count int
|
||||
ForEach map[string]cty.Value
|
||||
Addr addrs.AbsResource
|
||||
Addr addrs.AbsResource
|
||||
InstanceAddrs []addrs.AbsResourceInstance
|
||||
}
|
||||
|
||||
func (t *ResourceCountTransformer) Transform(g *Graph) error {
|
||||
if t.Count < 0 && t.ForEach == nil {
|
||||
// Negative count indicates that count is not set at all.
|
||||
addr := t.Addr.Instance(addrs.NoKey)
|
||||
|
||||
abstract := NewNodeAbstractResourceInstance(addr)
|
||||
abstract.Schema = t.Schema
|
||||
var node dag.Vertex = abstract
|
||||
if f := t.Concrete; f != nil {
|
||||
node = f(abstract)
|
||||
}
|
||||
|
||||
g.Add(node)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add nodes related to the for_each expression
|
||||
for key := range t.ForEach {
|
||||
addr := t.Addr.Instance(addrs.StringKey(key))
|
||||
for _, addr := range t.InstanceAddrs {
|
||||
abstract := NewNodeAbstractResourceInstance(addr)
|
||||
abstract.Schema = t.Schema
|
||||
var node dag.Vertex = abstract
|
||||
|
@ -49,23 +29,8 @@ func (t *ResourceCountTransformer) Transform(g *Graph) error {
|
|||
node = f(abstract)
|
||||
}
|
||||
|
||||
log.Printf("[TRACE] ResourceCountTransformer: adding %s as %T", addr, node)
|
||||
g.Add(node)
|
||||
}
|
||||
|
||||
// For each count, build and add the node
|
||||
for i := 0; i < t.Count; i++ {
|
||||
key := addrs.IntKey(i)
|
||||
addr := t.Addr.Instance(key)
|
||||
|
||||
abstract := NewNodeAbstractResourceInstance(addr)
|
||||
abstract.Schema = t.Schema
|
||||
var node dag.Vertex = abstract
|
||||
if f := t.Concrete; f != nil {
|
||||
node = f(abstract)
|
||||
}
|
||||
|
||||
g.Add(node)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue