mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-18 12:42:58 -06:00
5573868cd0
If the configuration contains preconditions and/or postconditions for any objects, we'll check them during evaluation of those objects and generate errors if any do not pass. The handling of post-conditions is particularly interesting here because we intentionally evaluate them _after_ we've committed our record of the resulting side-effects to the state/plan, with the intent that future plans against the same object will keep failing until the problem is addressed either by changing the object so it would pass the precondition or changing the precondition to accept the current object. That then avoids the need for us to proactively taint managed resources whose postconditions fail, as we would for provisioner failures: instead, we can leave the resolution approach up to the user to decide. Co-authored-by: Alisdair McDiarmid <alisdair@users.noreply.github.com>
116 lines
3.4 KiB
Go
116 lines
3.4 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
"github.com/zclconf/go-cty/cty"
|
|
"github.com/zclconf/go-cty/cty/convert"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/configs"
|
|
"github.com/hashicorp/terraform/internal/instances"
|
|
"github.com/hashicorp/terraform/internal/lang"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
)
|
|
|
|
type checkType int
|
|
|
|
const (
|
|
checkInvalid checkType = 0
|
|
checkResourcePrecondition checkType = 1
|
|
checkResourcePostcondition checkType = 2
|
|
checkOutputPrecondition checkType = 3
|
|
)
|
|
|
|
func (c checkType) FailureSummary() string {
|
|
switch c {
|
|
case checkResourcePrecondition:
|
|
return "Resource precondition failed"
|
|
case checkResourcePostcondition:
|
|
return "Resource postcondition failed"
|
|
case checkOutputPrecondition:
|
|
return "Module output value precondition failed"
|
|
default:
|
|
// This should not happen
|
|
return "Failed condition for invalid check type"
|
|
}
|
|
}
|
|
|
|
// evalCheckRules ensures that all of the given check rules pass against
|
|
// the given HCL evaluation context.
|
|
//
|
|
// If any check rules produce an unknown result then they will be silently
|
|
// ignored on the assumption that the same checks will be run again later
|
|
// with fewer unknown values in the EvalContext.
|
|
//
|
|
// If any of the rules do not pass, the returned diagnostics will contain
|
|
// errors. Otherwise, it will either be empty or contain only warnings.
|
|
func evalCheckRules(typ checkType, rules []*configs.CheckRule, ctx EvalContext, self addrs.Referenceable, keyData instances.RepetitionData) (diags tfdiags.Diagnostics) {
|
|
if len(rules) == 0 {
|
|
// Nothing to do
|
|
return nil
|
|
}
|
|
|
|
for _, rule := range rules {
|
|
const errInvalidCondition = "Invalid condition result"
|
|
var ruleDiags tfdiags.Diagnostics
|
|
|
|
refs, moreDiags := lang.ReferencesInExpr(rule.Condition)
|
|
ruleDiags = ruleDiags.Append(moreDiags)
|
|
scope := ctx.EvaluationScope(self, keyData)
|
|
hclCtx, moreDiags := scope.EvalContext(refs)
|
|
ruleDiags = ruleDiags.Append(moreDiags)
|
|
|
|
result, hclDiags := rule.Condition.Value(hclCtx)
|
|
ruleDiags = ruleDiags.Append(hclDiags)
|
|
diags = diags.Append(ruleDiags)
|
|
|
|
if ruleDiags.HasErrors() {
|
|
log.Printf("[TRACE] evalCheckRules: %s: %s", typ.FailureSummary(), ruleDiags.Err().Error())
|
|
}
|
|
|
|
if !result.IsKnown() {
|
|
continue // We'll wait until we've learned more, then.
|
|
}
|
|
if result.IsNull() {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: errInvalidCondition,
|
|
Detail: "Condition expression must return either true or false, not null.",
|
|
Subject: rule.Condition.Range().Ptr(),
|
|
Expression: rule.Condition,
|
|
EvalContext: hclCtx,
|
|
})
|
|
continue
|
|
}
|
|
var err error
|
|
result, err = convert.Convert(result, cty.Bool)
|
|
if err != nil {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: errInvalidCondition,
|
|
Detail: fmt.Sprintf("Invalid validation condition result value: %s.", tfdiags.FormatError(err)),
|
|
Subject: rule.Condition.Range().Ptr(),
|
|
Expression: rule.Condition,
|
|
EvalContext: hclCtx,
|
|
})
|
|
continue
|
|
}
|
|
|
|
if result.False() {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: typ.FailureSummary(),
|
|
Detail: rule.ErrorMessage,
|
|
Subject: rule.Condition.Range().Ptr(),
|
|
Expression: rule.Condition,
|
|
EvalContext: hclCtx,
|
|
})
|
|
}
|
|
}
|
|
|
|
return diags
|
|
}
|