122 lines
4.8 KiB
Go
122 lines
4.8 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/hashicorp/hcl2/hcl"
|
|
"github.com/hashicorp/terraform/addrs"
|
|
"github.com/hashicorp/terraform/tfdiags"
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/gocty"
|
|
)
|
|
|
|
// evaluateResourceCountExpression is our standard mechanism for interpreting an
|
|
// expression given for a "count" argument on a resource. This should be called
|
|
// from the DynamicExpand of a node representing a resource in order to
|
|
// determine the final count value.
|
|
//
|
|
// If the result is zero or positive and no error diagnostics are returned, then
|
|
// the result is the literal count value to use.
|
|
//
|
|
// 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 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
|
|
}
|
|
|
|
var diags tfdiags.Diagnostics
|
|
var count int
|
|
|
|
countVal, countDiags := ctx.EvaluateExpr(expr, cty.Number, nil)
|
|
diags = diags.Append(countDiags)
|
|
if diags.HasErrors() {
|
|
return -1, diags
|
|
}
|
|
|
|
switch {
|
|
case countVal.IsNull():
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid count argument",
|
|
Detail: `The given "count" argument value is null. An integer is required.`,
|
|
Subject: expr.Range().Ptr(),
|
|
})
|
|
return -1, diags
|
|
case !countVal.IsKnown():
|
|
// Currently this is a rather bad outcome from a UX standpoint, since we have
|
|
// no real mechanism to deal with this situation and all we can do is produce
|
|
// an error message.
|
|
// FIXME: In future, implement a built-in mechanism for deferring changes that
|
|
// can't yet be predicted, and use it to guide the user through several
|
|
// plan/apply steps until the desired configuration is eventually reached.
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid count argument",
|
|
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(),
|
|
})
|
|
// 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)
|
|
if err != nil {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid count argument",
|
|
Detail: fmt.Sprintf(`The given "count" argument value is unsuitable: %s.`, err),
|
|
Subject: expr.Range().Ptr(),
|
|
})
|
|
return -1, diags
|
|
}
|
|
if count < 0 {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid count argument",
|
|
Detail: `The given "count" argument value is unsuitable: negative numbers are not supported.`,
|
|
Subject: expr.Range().Ptr(),
|
|
})
|
|
return -1, diags
|
|
}
|
|
|
|
return count, diags
|
|
}
|
|
|
|
// fixResourceCountSetTransition is a helper function to fix up the state when a
|
|
// resource transitions its "count" from being set to unset or vice-versa,
|
|
// treating a 0-key and a no-key instance as aliases for one another across
|
|
// the transition.
|
|
//
|
|
// The correct time to call this function is in the DynamicExpand method for
|
|
// a node representing a resource, just after evaluating the count with
|
|
// evaluateResourceCountExpression, and before any other analysis of the
|
|
// state such as orphan detection.
|
|
//
|
|
// This function calls methods on the given EvalContext to update the current
|
|
// state in-place, if necessary. It is a no-op if there is no count transition
|
|
// taking place.
|
|
//
|
|
// Since the state is modified in-place, this function must take a writer lock
|
|
// on the state. The caller must therefore not also be holding a state lock,
|
|
// or this function will block forever awaiting the lock.
|
|
func fixResourceCountSetTransition(ctx EvalContext, addr addrs.AbsResource, countEnabled bool) {
|
|
state := ctx.State()
|
|
changed := state.MaybeFixUpResourceInstanceAddressForCount(addr, countEnabled)
|
|
if changed {
|
|
log.Printf("[TRACE] renamed first %s instance in transient state due to count argument change", addr)
|
|
}
|
|
}
|