diff --git a/terraform/eval_count.go b/terraform/eval_count.go index ff943ed8a5..ea0f22d4f5 100644 --- a/terraform/eval_count.go +++ b/terraform/eval_count.go @@ -22,8 +22,13 @@ import ( // If the result is -1, this indicates that the given expression is nil and so // the "count" behavior should not be enabled for this resource at all. // -// If error diagnostics are returned then the result is undefined and must -// not be used. +// If error diagnostics are returned then the result is always the meaningless +// placeholder value -1, except in one case: if the count expression evaluates +// to an unknown number value then the result is zero, allowing this situation +// to be treated by the caller as special if needed. For example, an early +// graph walk may wish to just silently skip resources with unknown counts +// to allow them to be dealt with in a later graph walk where more information +// is available. func evaluateResourceCountExpression(expr hcl.Expression, ctx EvalContext) (int, tfdiags.Diagnostics) { if expr == nil { return -1, nil @@ -60,7 +65,11 @@ func evaluateResourceCountExpression(expr hcl.Expression, ctx EvalContext) (int, Detail: `The "count" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the count depends on.`, Subject: expr.Range().Ptr(), }) - return -1, diags + // We return zero+errors in this one case to allow callers to handle + // an unknown count as special. This is rarely necessary, but is used + // by the validate walk in particular so that it can just skip + // validation in this case, assuming a later walk will take care of it. + return 0, diags } err := gocty.FromCtyValue(countVal, &count) diff --git a/terraform/node_resource_validate.go b/terraform/node_resource_validate.go index 064e0cef90..414a72b662 100644 --- a/terraform/node_resource_validate.go +++ b/terraform/node_resource_validate.go @@ -31,10 +31,19 @@ func (n *NodeValidatableResource) DynamicExpand(ctx EvalContext) (*Graph, error) count, countDiags := evaluateResourceCountExpression(n.Config.Count, ctx) diags = diags.Append(countDiags) if countDiags.HasErrors() { - log.Printf("[TRACE] %T %s: count expression has errors", n, n.Name()) - return nil, diags.Err() - } - if count >= 0 { + if count != 0 { + log.Printf("[TRACE] %T %s: count expression has errors", n, n.Name()) + return nil, diags.Err() + } + + // evaluateResourceCountExpression returns zero+errors only in the + // case where the count value successfully evaluated to an unknown + // number. We don't treat this as an error here because counts that + // are computed during validate can become known during the plan + // walk, if they refer to data resources, and so we'll just defer + // our validation steps to the plan phase in that case. + log.Printf("[TRACE] %T %s: count expression value not yet known, so deferring validation until the plan walk", n, n.Name()) + } else if count >= 0 { log.Printf("[TRACE] %T %s: count expression evaluates to %d", n, n.Name(), count) } else { log.Printf("[TRACE] %T %s: no count argument present", n, n.Name())