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:
parent
0b025d74e5
commit
40f09027f0
|
@ -15,12 +15,12 @@ func TestBuiltinEvalContextProviderInput(t *testing.T) {
|
|||
cache := make(map[string]map[string]cty.Value)
|
||||
|
||||
ctx1 := testBuiltinEvalContext(t)
|
||||
ctx1.PathValue = addrs.RootModuleInstance
|
||||
ctx1 = ctx1.WithPath(addrs.RootModuleInstance).(*BuiltinEvalContext)
|
||||
ctx1.ProviderInputConfig = cache
|
||||
ctx1.ProviderLock = &lock
|
||||
|
||||
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.ProviderLock = &lock
|
||||
|
||||
|
@ -56,6 +56,7 @@ func TestBuildingEvalContextInitProvider(t *testing.T) {
|
|||
testP := &MockProvider{}
|
||||
|
||||
ctx := testBuiltinEvalContext(t)
|
||||
ctx = ctx.WithPath(addrs.RootModuleInstance).(*BuiltinEvalContext)
|
||||
ctx.ProviderLock = &lock
|
||||
ctx.ProviderCache = make(map[string]providers.Interface)
|
||||
ctx.Components = &basicComponentFactory{
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
// EvalDeleteOutput is an EvalNode implementation that deletes an output
|
||||
// from the state.
|
||||
type EvalDeleteOutput struct {
|
||||
Addr addrs.OutputValue
|
||||
Addr addrs.AbsOutputValue
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
|
@ -25,7 +25,7 @@ func (n *EvalDeleteOutput) Eval(ctx EvalContext) (interface{}, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
state.RemoveOutputValue(n.Addr.Absolute(ctx.Path()))
|
||||
state.RemoveOutputValue(n.Addr)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
// list rather than as not set at all.
|
||||
type EvalWriteResourceState struct {
|
||||
Addr addrs.ConfigResource
|
||||
Addr addrs.AbsResource
|
||||
Config *configs.Resource
|
||||
ProviderAddr addrs.AbsProviderConfig
|
||||
}
|
||||
|
||||
// TODO: test
|
||||
func (n *EvalWriteResourceState) Eval(ctx EvalContext) (interface{}, error) {
|
||||
var diags tfdiags.Diagnostics
|
||||
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)
|
||||
diags = diags.Append(countDiags)
|
||||
if countDiags.HasErrors() {
|
||||
|
@ -482,25 +486,17 @@ func (n *EvalWriteResourceState) Eval(ctx EvalContext) (interface{}, error) {
|
|||
if forEach != nil {
|
||||
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
|
||||
// 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()
|
||||
for _, module := range expander.ExpandModule(n.Addr.Module) {
|
||||
// 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.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)
|
||||
}
|
||||
switch eachMode {
|
||||
case states.EachList:
|
||||
expander.SetResourceCount(n.Addr.Module, n.Addr.Resource, count)
|
||||
case states.EachMap:
|
||||
expander.SetResourceForEach(n.Addr.Module, n.Addr.Resource, forEach)
|
||||
default:
|
||||
expander.SetResourceSingle(n.Addr.Module, n.Addr.Resource)
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
|
|
|
@ -196,7 +196,7 @@ func (b *PlanGraphBuilder) init() {
|
|||
}
|
||||
|
||||
b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
|
||||
return &NodePlannableResource{
|
||||
return &nodeExpandPlannableResource{
|
||||
NodeAbstractResource: a,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -235,8 +235,7 @@ func (n *NodeApplyableOutput) DotNode(name string, opts *dag.DotOpts) *dag.DotNo
|
|||
// NodeDestroyableOutput represents an output that is "destroybale":
|
||||
// its application will remove the output from the state.
|
||||
type NodeDestroyableOutput struct {
|
||||
Addr addrs.OutputValue
|
||||
Module addrs.Module
|
||||
Addr addrs.AbsOutputValue
|
||||
Config *configs.Output // Config is the output in the config
|
||||
}
|
||||
|
||||
|
@ -254,7 +253,7 @@ func (n *NodeDestroyableOutput) Name() string {
|
|||
|
||||
// GraphNodeModulePath
|
||||
func (n *NodeDestroyableOutput) ModulePath() addrs.Module {
|
||||
return n.Module
|
||||
return n.Addr.Module.Module()
|
||||
}
|
||||
|
||||
// RemovableIfNotTargeted
|
||||
|
|
|
@ -47,7 +47,7 @@ func (n *NodeOutputOrphan) EvalTree() EvalNode {
|
|||
return &EvalOpFilter{
|
||||
Ops: []walkOperation{walkRefresh, walkApply, walkDestroy},
|
||||
Node: &EvalDeleteOutput{
|
||||
Addr: n.Addr.OutputValue,
|
||||
Addr: n.Addr,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -60,7 +60,7 @@ func (n *NodeApplyableResource) EvalTree() EvalNode {
|
|||
}
|
||||
|
||||
return &EvalWriteResourceState{
|
||||
Addr: n.Addr,
|
||||
Addr: n.Addr.Resource.Absolute(n.Addr.Module.UnkeyedInstanceShim()),
|
||||
Config: n.Config,
|
||||
ProviderAddr: n.ResolvedProvider,
|
||||
}
|
||||
|
|
|
@ -3,15 +3,82 @@ package terraform
|
|||
import (
|
||||
"log"
|
||||
|
||||
"github.com/hashicorp/terraform/addrs"
|
||||
"github.com/hashicorp/terraform/dag"
|
||||
"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":
|
||||
// it is ready to be planned in order to create a diff.
|
||||
type NodePlannableResource struct {
|
||||
*NodeAbstractResource
|
||||
|
||||
Addr addrs.AbsResource
|
||||
|
||||
// ForceCreateBeforeDestroy might be set via our GraphNodeDestroyerCBD
|
||||
// during graph construction, if dependencies require us to force this
|
||||
// on regardless of what the configuration says.
|
||||
|
@ -19,6 +86,7 @@ type NodePlannableResource struct {
|
|||
}
|
||||
|
||||
var (
|
||||
_ GraphNodeModuleInstance = (*NodePlannableResource)(nil)
|
||||
_ GraphNodeDestroyerCBD = (*NodePlannableResource)(nil)
|
||||
_ GraphNodeDynamicExpandable = (*NodePlannableResource)(nil)
|
||||
_ GraphNodeReferenceable = (*NodePlannableResource)(nil)
|
||||
|
@ -27,6 +95,11 @@ var (
|
|||
_ GraphNodeAttachResourceConfig = (*NodePlannableResource)(nil)
|
||||
)
|
||||
|
||||
// GraphNodeModuleInstance
|
||||
func (n *NodePlannableResource) ModuleInstance() addrs.ModuleInstance {
|
||||
return n.Addr.Module
|
||||
}
|
||||
|
||||
// GraphNodeEvalable
|
||||
func (n *NodePlannableResource) EvalTree() EvalNode {
|
||||
if n.Config == nil {
|
||||
|
@ -85,7 +158,7 @@ func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
|
|||
if countDiags.HasErrors() {
|
||||
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
|
||||
// temporarily lock it while we work on this.
|
||||
|
|
|
@ -79,8 +79,7 @@ func (t *DestroyOutputTransformer) Transform(g *Graph) error {
|
|||
|
||||
// create the destroy node for this output
|
||||
node := &NodeDestroyableOutput{
|
||||
Addr: output.Addr,
|
||||
Module: output.Module,
|
||||
Addr: output.Addr.Absolute(addrs.RootModuleInstance),
|
||||
Config: output.Config,
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue