diff --git a/config/config.go b/config/config.go index e1543a1afc3f..e7c99a3b6bae 100644 --- a/config/config.go +++ b/config/config.go @@ -88,6 +88,8 @@ type Resource struct { Provider string DependsOn []string Lifecycle ResourceLifecycle + + DeferCountComputation bool } // Copy returns a copy of this Resource. Helpful for avoiding shared @@ -213,6 +215,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 + } + v, err := strconv.ParseInt(r.RawCount.Value().(string), 0, 0) if err != nil { return 0, err diff --git a/terraform/eval_context.go b/terraform/eval_context.go index f2867511d7ad..321c32a35f11 100644 --- a/terraform/eval_context.go +++ b/terraform/eval_context.go @@ -77,4 +77,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 032f79f9d92c..559af40f6d2c 100644 --- a/terraform/eval_context_builtin.go +++ b/terraform/eval_context_builtin.go @@ -39,10 +39,15 @@ type BuiltinEvalContext struct { DiffLock *sync.RWMutex StateValue *State StateLock *sync.RWMutex + IgnoreMissingCount bool once sync.Once } +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 4f5c23bc49b1..c73096a1499e 100644 --- a/terraform/eval_context_mock.go +++ b/terraform/eval_context_mock.go @@ -198,3 +198,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_config_node_resource.go b/terraform/graph_config_node_resource.go index cecedb6ec6a5..16765206e9c2 100644 --- a/terraform/graph_config_node_resource.go +++ b/terraform/graph_config_node_resource.go @@ -221,7 +221,7 @@ func (n *GraphNodeConfigResource) EvalTree() EvalNode { return &EvalSequence{ Nodes: []EvalNode{ &EvalInterpolate{Config: n.Resource.RawCount}, - &EvalCountCheckComputed{Resource: n.Resource}, + &EvalCountFixComputed{Resource: n.Resource}, &EvalOpFilter{ Ops: []walkOperation{walkValidate}, Node: &EvalValidateCount{Resource: n.Resource}, diff --git a/terraform/graph_walk_context.go b/terraform/graph_walk_context.go index 459fcdec99f3..a06445c772bc 100644 --- a/terraform/graph_walk_context.go +++ b/terraform/graph_walk_context.go @@ -64,6 +64,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{ PathValue: path, Hooks: w.Context.hooks, @@ -89,6 +94,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_plan.go b/terraform/node_resource_plan.go index c4694d4930fe..02b5cd663685 100644 --- a/terraform/node_resource_plan.go +++ b/terraform/node_resource_plan.go @@ -31,7 +31,7 @@ func (n *NodePlannableResource) EvalTree() EvalNode { // into the proper number of instances. &EvalInterpolate{Config: n.Config.RawCount}, - &EvalCountCheckComputed{Resource: n.Config}, + &EvalCountFixComputed{Resource: n.Config}, &EvalCountFixZeroOneBoundary{Resource: n.Config}, }, }