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)
|
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{
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue