mirror of
https://github.com/opentofu/opentofu.git
synced 2025-02-25 18:45:20 -06:00
By observing the sorts of questions people ask in the community, and the ways they ask them, we've inferred that various different people have been confused by Terraform reporting that a value won't be known until apply or that a value is sensitive as part of an error message when that message doesn't actually relate to the known-ness and sensitivity of any value. Quite reasonably, someone who sees Terraform discussing an unfamiliar concept like unknown values can assume that it must be somehow relevant to the problem being discussed, and so in that sense Terraform's current error messages are giving "too much information": information that isn't actually helpful in understanding the problem being described, and in the worst case is a distraction from understanding the problem being described. With that in mind then, here we introduce an explicit annotation on diagnostic objects that are directly talking about unknown values or sensitive values, and then the diagnostic renderer will react to that to avoid using the terminology "known only after apply" or "sensitive" in the generated diagnostic annotations unless we're rendering a message that is explicitly related to one of those topics. This ends up being a bit of a cross-cutting concern because the code that generates these diagnostics and the code that renders them are in separate packages and are not directly aware of each other. With that in mind, the logic for actually deciding for a particular diagnostic whether it's flagged in one of these special ways lives inside the tfdiags package as an intermediation point, which both the diagnostic generator (in the core package) and the diagnostic renderer can both depend on.
108 lines
3.7 KiB
Go
108 lines
3.7 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/gocty"
|
|
)
|
|
|
|
// evaluateCountExpression is our standard mechanism for interpreting an
|
|
// expression given for a "count" argument on a resource or a module. This
|
|
// should be called during expansion in order to determine the final count
|
|
// value.
|
|
//
|
|
// evaluateCountExpression differs from evaluateCountExpressionValue by
|
|
// returning an error if the count value is not known, and converting the
|
|
// cty.Value to an integer.
|
|
func evaluateCountExpression(expr hcl.Expression, ctx EvalContext) (int, tfdiags.Diagnostics) {
|
|
countVal, diags := evaluateCountExpressionValue(expr, ctx)
|
|
if !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(),
|
|
|
|
// TODO: Also populate Expression and EvalContext in here, but
|
|
// we can't easily do that right now because the hcl.EvalContext
|
|
// (which is not the same as the ctx we have in scope here) is
|
|
// hidden away inside evaluateCountExpressionValue.
|
|
Extra: diagnosticCausedByUnknown(true),
|
|
})
|
|
}
|
|
|
|
if countVal.IsNull() || !countVal.IsKnown() {
|
|
return -1, diags
|
|
}
|
|
|
|
count, _ := countVal.AsBigFloat().Int64()
|
|
return int(count), diags
|
|
}
|
|
|
|
// evaluateCountExpressionValue is like evaluateCountExpression
|
|
// except that it returns a cty.Value which must be a cty.Number and can be
|
|
// unknown.
|
|
func evaluateCountExpressionValue(expr hcl.Expression, ctx EvalContext) (cty.Value, tfdiags.Diagnostics) {
|
|
var diags tfdiags.Diagnostics
|
|
nullCount := cty.NullVal(cty.Number)
|
|
if expr == nil {
|
|
return nullCount, nil
|
|
}
|
|
|
|
countVal, countDiags := ctx.EvaluateExpr(expr, cty.Number, nil)
|
|
diags = diags.Append(countDiags)
|
|
if diags.HasErrors() {
|
|
return nullCount, diags
|
|
}
|
|
|
|
// Unmark the count value, sensitive values are allowed in count but not for_each,
|
|
// as using it here will not disclose the sensitive value
|
|
countVal, _ = countVal.Unmark()
|
|
|
|
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 nullCount, diags
|
|
|
|
case !countVal.IsKnown():
|
|
return cty.UnknownVal(cty.Number), diags
|
|
}
|
|
|
|
var count int
|
|
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 nullCount, diags
|
|
}
|
|
if count < 0 {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Invalid count argument",
|
|
Detail: `The given "count" argument value is unsuitable: must be greater than or equal to zero.`,
|
|
Subject: expr.Range().Ptr(),
|
|
})
|
|
return nullCount, diags
|
|
}
|
|
|
|
return countVal, diags
|
|
}
|