expand planned resources

While the Expander itself now handles the recursive expansion of
modules, Resources themselves still need to be expanded twice, because
the evaluation of the Resource, which entails evaluating the for_each or
count expressions, is separate from the ResourceInstance expansion.

Add a nodeExpandPlannableResource to do handle this expansion to allow
all NodePlannableResources to call EvalWriteResourceState with an
absolute address.
This commit is contained in:
James Bardin 2020-03-20 15:19:01 -04:00
parent 0b025d74e5
commit 40f09027f0
9 changed files with 101 additions and 33 deletions

View File

@ -15,12 +15,12 @@ func TestBuiltinEvalContextProviderInput(t *testing.T) {
cache := make(map[string]map[string]cty.Value) cache := make(map[string]map[string]cty.Value)
ctx1 := testBuiltinEvalContext(t) ctx1 := testBuiltinEvalContext(t)
ctx1.PathValue = addrs.RootModuleInstance ctx1 = ctx1.WithPath(addrs.RootModuleInstance).(*BuiltinEvalContext)
ctx1.ProviderInputConfig = cache ctx1.ProviderInputConfig = cache
ctx1.ProviderLock = &lock ctx1.ProviderLock = &lock
ctx2 := testBuiltinEvalContext(t) ctx2 := testBuiltinEvalContext(t)
ctx2.PathValue = addrs.RootModuleInstance.Child("child", addrs.NoKey) ctx2 = ctx2.WithPath(addrs.RootModuleInstance.Child("child", addrs.NoKey)).(*BuiltinEvalContext)
ctx2.ProviderInputConfig = cache ctx2.ProviderInputConfig = cache
ctx2.ProviderLock = &lock ctx2.ProviderLock = &lock
@ -56,6 +56,7 @@ func TestBuildingEvalContextInitProvider(t *testing.T) {
testP := &MockProvider{} testP := &MockProvider{}
ctx := testBuiltinEvalContext(t) ctx := testBuiltinEvalContext(t)
ctx = ctx.WithPath(addrs.RootModuleInstance).(*BuiltinEvalContext)
ctx.ProviderLock = &lock ctx.ProviderLock = &lock
ctx.ProviderCache = make(map[string]providers.Interface) ctx.ProviderCache = make(map[string]providers.Interface)
ctx.Components = &basicComponentFactory{ ctx.Components = &basicComponentFactory{

View File

@ -15,7 +15,7 @@ import (
// EvalDeleteOutput is an EvalNode implementation that deletes an output // EvalDeleteOutput is an EvalNode implementation that deletes an output
// from the state. // from the state.
type EvalDeleteOutput struct { type EvalDeleteOutput struct {
Addr addrs.OutputValue Addr addrs.AbsOutputValue
} }
// TODO: test // TODO: test
@ -25,7 +25,7 @@ func (n *EvalDeleteOutput) Eval(ctx EvalContext) (interface{}, error) {
return nil, nil return nil, nil
} }
state.RemoveOutputValue(n.Addr.Absolute(ctx.Path())) state.RemoveOutputValue(n.Addr)
return nil, nil return nil, nil
} }

View File

@ -452,16 +452,20 @@ func (n *EvalMaybeRestoreDeposedObject) Eval(ctx EvalContext) (interface{}, erro
// in that case, allowing expression evaluation to see it as a zero-element // in that case, allowing expression evaluation to see it as a zero-element
// list rather than as not set at all. // list rather than as not set at all.
type EvalWriteResourceState struct { type EvalWriteResourceState struct {
Addr addrs.ConfigResource Addr addrs.AbsResource
Config *configs.Resource Config *configs.Resource
ProviderAddr addrs.AbsProviderConfig ProviderAddr addrs.AbsProviderConfig
} }
// TODO: test
func (n *EvalWriteResourceState) Eval(ctx EvalContext) (interface{}, error) { func (n *EvalWriteResourceState) Eval(ctx EvalContext) (interface{}, error) {
var diags tfdiags.Diagnostics var diags tfdiags.Diagnostics
state := ctx.State() state := ctx.State()
// 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. Since this node represents the abstract module, we need
// to expand the module here to create all resources.
expander := ctx.InstanceExpander()
count, countDiags := evaluateResourceCountExpression(n.Config.Count, ctx) count, countDiags := evaluateResourceCountExpression(n.Config.Count, ctx)
diags = diags.Append(countDiags) diags = diags.Append(countDiags)
if countDiags.HasErrors() { if countDiags.HasErrors() {
@ -482,25 +486,17 @@ func (n *EvalWriteResourceState) Eval(ctx EvalContext) (interface{}, error) {
if forEach != nil { if forEach != nil {
eachMode = states.EachMap eachMode = states.EachMap
} }
// This method takes care of all of the business logic of updating this
// while ensuring that any existing instances are preserved, etc.
state.SetResourceMeta(n.Addr, eachMode, n.ProviderAddr)
// We'll record our expansion decision in the shared "expander" object switch eachMode {
// so that later operations (i.e. DynamicExpand and expression evaluation) case states.EachList:
// can refer to it. Since this node represents the abstract module, we need expander.SetResourceCount(n.Addr.Module, n.Addr.Resource, count)
// to expand the module here to create all resources. case states.EachMap:
expander := ctx.InstanceExpander() expander.SetResourceForEach(n.Addr.Module, n.Addr.Resource, forEach)
for _, module := range expander.ExpandModule(n.Addr.Module) { default:
// This method takes care of all of the business logic of updating this expander.SetResourceSingle(n.Addr.Module, n.Addr.Resource)
// while ensuring that any existing instances are preserved, etc.
state.SetResourceMeta(n.Addr.Absolute(module), eachMode, n.ProviderAddr)
switch eachMode {
case states.EachList:
expander.SetResourceCount(module, n.Addr.Resource, count)
case states.EachMap:
expander.SetResourceForEach(module, n.Addr.Resource, forEach)
default:
expander.SetResourceSingle(module, n.Addr.Resource)
}
} }
return nil, nil return nil, nil

View File

@ -196,7 +196,7 @@ func (b *PlanGraphBuilder) init() {
} }
b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex { b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
return &NodePlannableResource{ return &nodeExpandPlannableResource{
NodeAbstractResource: a, NodeAbstractResource: a,
} }
} }

View File

@ -235,8 +235,7 @@ func (n *NodeApplyableOutput) DotNode(name string, opts *dag.DotOpts) *dag.DotNo
// NodeDestroyableOutput represents an output that is "destroybale": // NodeDestroyableOutput represents an output that is "destroybale":
// its application will remove the output from the state. // its application will remove the output from the state.
type NodeDestroyableOutput struct { type NodeDestroyableOutput struct {
Addr addrs.OutputValue Addr addrs.AbsOutputValue
Module addrs.Module
Config *configs.Output // Config is the output in the config Config *configs.Output // Config is the output in the config
} }
@ -254,7 +253,7 @@ func (n *NodeDestroyableOutput) Name() string {
// GraphNodeModulePath // GraphNodeModulePath
func (n *NodeDestroyableOutput) ModulePath() addrs.Module { func (n *NodeDestroyableOutput) ModulePath() addrs.Module {
return n.Module return n.Addr.Module.Module()
} }
// RemovableIfNotTargeted // RemovableIfNotTargeted

View File

@ -47,7 +47,7 @@ func (n *NodeOutputOrphan) EvalTree() EvalNode {
return &EvalOpFilter{ return &EvalOpFilter{
Ops: []walkOperation{walkRefresh, walkApply, walkDestroy}, Ops: []walkOperation{walkRefresh, walkApply, walkDestroy},
Node: &EvalDeleteOutput{ Node: &EvalDeleteOutput{
Addr: n.Addr.OutputValue, Addr: n.Addr,
}, },
} }
} }

View File

@ -60,7 +60,7 @@ func (n *NodeApplyableResource) EvalTree() EvalNode {
} }
return &EvalWriteResourceState{ return &EvalWriteResourceState{
Addr: n.Addr, Addr: n.Addr.Resource.Absolute(n.Addr.Module.UnkeyedInstanceShim()),
Config: n.Config, Config: n.Config,
ProviderAddr: n.ResolvedProvider, ProviderAddr: n.ResolvedProvider,
} }

View File

@ -3,15 +3,82 @@ package terraform
import ( import (
"log" "log"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/dag"
"github.com/hashicorp/terraform/tfdiags" "github.com/hashicorp/terraform/tfdiags"
) )
// nodeExpandPlannableResource handles the first layer of resource
// expansion. We need this extra layer so DynamicExpand is called twice for
// the resource, the first to expand the Resource for each module instance, and
// the second to expand each ResourceInstnace for the expanded Resources.
//
// Even though the Expander can handle this recursive expansion, the
// EvalWriteState nodes need to be expanded and Evaluated first, and our
// current graph doesn't allow evaluation within DynamicExpand, and doesn't
// call it recursively.
type nodeExpandPlannableResource struct {
*NodeAbstractResource
// TODO: can we eliminate the need for this flag and combine this with the
// apply expander?
// ForceCreateBeforeDestroy might be set via our GraphNodeDestroyerCBD
// during graph construction, if dependencies require us to force this
// on regardless of what the configuration says.
ForceCreateBeforeDestroy *bool
}
var (
_ GraphNodeDestroyerCBD = (*nodeExpandPlannableResource)(nil)
_ GraphNodeDynamicExpandable = (*nodeExpandPlannableResource)(nil)
_ GraphNodeReferenceable = (*nodeExpandPlannableResource)(nil)
_ GraphNodeReferencer = (*nodeExpandPlannableResource)(nil)
_ GraphNodeConfigResource = (*nodeExpandPlannableResource)(nil)
_ GraphNodeAttachResourceConfig = (*nodeExpandPlannableResource)(nil)
)
// GraphNodeDestroyerCBD
func (n *nodeExpandPlannableResource) CreateBeforeDestroy() bool {
if n.ForceCreateBeforeDestroy != nil {
return *n.ForceCreateBeforeDestroy
}
// If we have no config, we just assume no
if n.Config == nil || n.Config.Managed == nil {
return false
}
return n.Config.Managed.CreateBeforeDestroy
}
// GraphNodeDestroyerCBD
func (n *nodeExpandPlannableResource) ModifyCreateBeforeDestroy(v bool) error {
n.ForceCreateBeforeDestroy = &v
return nil
}
func (n *nodeExpandPlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
var g Graph
expander := ctx.InstanceExpander()
for _, module := range expander.ExpandModule(n.Addr.Module) {
g.Add(&NodePlannableResource{
NodeAbstractResource: n.NodeAbstractResource,
Addr: n.Addr.Resource.Absolute(module),
ForceCreateBeforeDestroy: n.ForceCreateBeforeDestroy,
})
}
return &g, nil
}
// NodePlannableResource represents a resource that is "plannable": // NodePlannableResource represents a resource that is "plannable":
// it is ready to be planned in order to create a diff. // it is ready to be planned in order to create a diff.
type NodePlannableResource struct { type NodePlannableResource struct {
*NodeAbstractResource *NodeAbstractResource
Addr addrs.AbsResource
// ForceCreateBeforeDestroy might be set via our GraphNodeDestroyerCBD // ForceCreateBeforeDestroy might be set via our GraphNodeDestroyerCBD
// during graph construction, if dependencies require us to force this // during graph construction, if dependencies require us to force this
// on regardless of what the configuration says. // on regardless of what the configuration says.
@ -19,6 +86,7 @@ type NodePlannableResource struct {
} }
var ( var (
_ GraphNodeModuleInstance = (*NodePlannableResource)(nil)
_ GraphNodeDestroyerCBD = (*NodePlannableResource)(nil) _ GraphNodeDestroyerCBD = (*NodePlannableResource)(nil)
_ GraphNodeDynamicExpandable = (*NodePlannableResource)(nil) _ GraphNodeDynamicExpandable = (*NodePlannableResource)(nil)
_ GraphNodeReferenceable = (*NodePlannableResource)(nil) _ GraphNodeReferenceable = (*NodePlannableResource)(nil)
@ -27,6 +95,11 @@ var (
_ GraphNodeAttachResourceConfig = (*NodePlannableResource)(nil) _ GraphNodeAttachResourceConfig = (*NodePlannableResource)(nil)
) )
// GraphNodeModuleInstance
func (n *NodePlannableResource) ModuleInstance() addrs.ModuleInstance {
return n.Addr.Module
}
// GraphNodeEvalable // GraphNodeEvalable
func (n *NodePlannableResource) EvalTree() EvalNode { func (n *NodePlannableResource) EvalTree() EvalNode {
if n.Config == nil { if n.Config == nil {
@ -85,7 +158,7 @@ func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
if countDiags.HasErrors() { if countDiags.HasErrors() {
return nil, diags.Err() return nil, diags.Err()
} }
fixResourceCountSetTransition(ctx, n.ResourceAddr(), count != -1) fixResourceCountSetTransition(ctx, n.Addr.Config(), count != -1)
// Our graph transformers require access to the full state, so we'll // Our graph transformers require access to the full state, so we'll
// temporarily lock it while we work on this. // temporarily lock it while we work on this.

View File

@ -79,8 +79,7 @@ func (t *DestroyOutputTransformer) Transform(g *Graph) error {
// create the destroy node for this output // create the destroy node for this output
node := &NodeDestroyableOutput{ node := &NodeDestroyableOutput{
Addr: output.Addr, Addr: output.Addr.Absolute(addrs.RootModuleInstance),
Module: output.Module,
Config: output.Config, Config: output.Config,
} }