From 62d338c12eb92af762deca216412097e6324c5f5 Mon Sep 17 00:00:00 2001 From: Jonathan Lima Date: Tue, 29 Nov 2016 15:53:47 -0200 Subject: [PATCH] Fix count interpolation with computed values This commit enables the use computed values when interpolating the count special variable. This is achieved by defering the expansion of `count` during walkInput and walkValidate as the real value isn't necessary at this point. The value will be resolved during walkRefresh and DynamicExpand will work as expected. Partially fixes #3888. --- config/config.go | 6 ++++++ terraform/eval_context.go | 7 +++++++ terraform/eval_context_builtin.go | 5 +++++ terraform/eval_context_mock.go | 4 ++++ terraform/eval_count_computed.go | 25 +++++++++++++++-------- terraform/graph_walk_context.go | 6 ++++++ terraform/node_resource_abstract_count.go | 2 +- 7 files changed, 45 insertions(+), 10 deletions(-) diff --git a/config/config.go b/config/config.go index 5210e49376d4..aed1854c6e6d 100644 --- a/config/config.go +++ b/config/config.go @@ -82,6 +82,8 @@ type Resource struct { Provider string DependsOn []string Lifecycle ResourceLifecycle + + DeferCountComputation bool } // Copy returns a copy of this Resource. Helpful for avoiding shared @@ -212,6 +214,10 @@ func (r *Module) Id() string { // Count returns the count of this resource. func (r *Resource) Count() (int, error) { + if r.DeferCountComputation { + return 1, nil + } + raw := r.RawCount.Value() count, ok := r.RawCount.Value().(string) if !ok { diff --git a/terraform/eval_context.go b/terraform/eval_context.go index a1f815b7d460..a4056ee12e88 100644 --- a/terraform/eval_context.go +++ b/terraform/eval_context.go @@ -81,4 +81,11 @@ type EvalContext interface { // State returns the global state as well as the lock that should // be used to modify that state. State() (*State, *sync.RWMutex) + + // True if we can ignore missing computed values when interpolating + // the count variable + // + // This is useful as we don't have the full tree during walkInput + // or walkValidate + CanIgnoreMissingCountExpansion() bool } diff --git a/terraform/eval_context_builtin.go b/terraform/eval_context_builtin.go index 3dcfb2275bb9..c9a9f30b5e52 100644 --- a/terraform/eval_context_builtin.go +++ b/terraform/eval_context_builtin.go @@ -43,6 +43,7 @@ type BuiltinEvalContext struct { DiffLock *sync.RWMutex StateValue *State StateLock *sync.RWMutex + IgnoreMissingCount bool once sync.Once } @@ -56,6 +57,10 @@ func (ctx *BuiltinEvalContext) Stopped() <-chan struct{} { return ctx.StopContext.Done() } +func (ctx *BuiltinEvalContext) CanIgnoreMissingCountExpansion() bool { + return ctx.IgnoreMissingCount +} + func (ctx *BuiltinEvalContext) Hook(fn func(Hook) (HookAction, error)) error { for _, h := range ctx.Hooks { action, err := fn(h) diff --git a/terraform/eval_context_mock.go b/terraform/eval_context_mock.go index 4f90d5b129dc..034bf9c09ca4 100644 --- a/terraform/eval_context_mock.go +++ b/terraform/eval_context_mock.go @@ -206,3 +206,7 @@ func (c *MockEvalContext) State() (*State, *sync.RWMutex) { c.StateCalled = true return c.StateState, c.StateLock } + +func (c *MockEvalContext) CanIgnoreMissingCountExpansion() bool { + return false +} diff --git a/terraform/eval_count_computed.go b/terraform/eval_count_computed.go index 54a8333e0fc4..b7e941fb55c9 100644 --- a/terraform/eval_count_computed.go +++ b/terraform/eval_count_computed.go @@ -6,19 +6,26 @@ import ( "github.com/hashicorp/terraform/config" ) -// EvalCountCheckComputed is an EvalNode that checks if a resource count -// is computed and errors if so. This can possibly happen across a -// module boundary and we don't yet support this. -type EvalCountCheckComputed struct { +// EvalCountFixComputed is an EvalNode that checks if a resource count +// is computed and try to fix the value. +// It can only be fixed if the walk operation is either walkInput or walkValidate +// Otherwise it errors +type EvalCountFixComputed struct { Resource *config.Resource } // TODO: test -func (n *EvalCountCheckComputed) Eval(ctx EvalContext) (interface{}, error) { - if n.Resource.RawCount.Value() == unknownValue() { - return nil, fmt.Errorf( - "%s: value of 'count' cannot be computed", - n.Resource.Id()) +func (n *EvalCountFixComputed) Eval(ctx EvalContext) (interface{}, error) { + if ctx.CanIgnoreMissingCountExpansion() { + n.Resource.DeferCountComputation = true + } else { + n.Resource.DeferCountComputation = false + + if n.Resource.RawCount.Value() == unknownValue() { + return nil, fmt.Errorf( + "%s: value of 'count' cannot be computed", + n.Resource.Id()) + } } return nil, nil diff --git a/terraform/graph_walk_context.go b/terraform/graph_walk_context.go index 19fd47ceb6b2..b654d7555102 100644 --- a/terraform/graph_walk_context.go +++ b/terraform/graph_walk_context.go @@ -66,6 +66,11 @@ func (w *ContextGraphWalker) EnterPath(path []string) EvalContext { w.interpolaterVars[key] = variables w.interpolaterVarLock.Unlock() + ignoreMissingCount := false + if w.Operation == walkInput || w.Operation == walkValidate { + ignoreMissingCount = true + } + ctx := &BuiltinEvalContext{ StopContext: w.StopContext, PathValue: path, @@ -92,6 +97,7 @@ func (w *ContextGraphWalker) EnterPath(path []string) EvalContext { }, InterpolaterVars: w.interpolaterVars, InterpolaterVarLock: &w.interpolaterVarLock, + IgnoreMissingCount: ignoreMissingCount, } w.contexts[key] = ctx diff --git a/terraform/node_resource_abstract_count.go b/terraform/node_resource_abstract_count.go index 573570d8e2ca..ea3b05557c49 100644 --- a/terraform/node_resource_abstract_count.go +++ b/terraform/node_resource_abstract_count.go @@ -19,7 +19,7 @@ func (n *NodeAbstractCountResource) EvalTree() EvalNode { // into more computed values. var evalCountCheckComputed EvalNode if !n.Validate { - evalCountCheckComputed = &EvalCountCheckComputed{Resource: n.Config} + evalCountCheckComputed = &EvalCountFixComputed{Resource: n.Config} } return &EvalSequence{