opentofu/internal/tfdiags/hcl.go
Alisdair McDiarmid a103c65140 core: Eval pre/postconditions in refresh-only mode
Evaluate precondition and postcondition blocks in refresh-only mode, but
report any failures as warnings instead of errors. This ensures that any
deviation from the contract defined by condition blocks is reported as
early as possible, without preventing the completion of a state refresh
operation.

Prior to this commit, Terraform evaluated output preconditions and data
source pre/postconditions as normal in refresh-only mode, while managed
resource pre/postconditions were not evaluated at all. This omission
could lead to confusing partial condition errors, or failure to detect
undesired changes which would otherwise cause resources to become
invalid.

Reporting the failures as errors also meant that changes retrieved
during refresh could cause the refresh operation to fail. This is also
undesirable, as the primary purpose of the operation is to update local
state. Precondition/postcondition checks are still valuable here, but
should be informative rather than blocking.
2022-03-11 13:32:40 -05:00

130 lines
3.1 KiB
Go

package tfdiags
import (
"github.com/hashicorp/hcl/v2"
)
// hclDiagnostic is a Diagnostic implementation that wraps a HCL Diagnostic
type hclDiagnostic struct {
diag *hcl.Diagnostic
}
var _ Diagnostic = hclDiagnostic{}
func (d hclDiagnostic) Severity() Severity {
switch d.diag.Severity {
case hcl.DiagWarning:
return Warning
default:
return Error
}
}
func (d hclDiagnostic) Description() Description {
return Description{
Summary: d.diag.Summary,
Detail: d.diag.Detail,
}
}
func (d hclDiagnostic) Source() Source {
var ret Source
if d.diag.Subject != nil {
rng := SourceRangeFromHCL(*d.diag.Subject)
ret.Subject = &rng
}
if d.diag.Context != nil {
rng := SourceRangeFromHCL(*d.diag.Context)
ret.Context = &rng
}
return ret
}
func (d hclDiagnostic) FromExpr() *FromExpr {
if d.diag.Expression == nil || d.diag.EvalContext == nil {
return nil
}
return &FromExpr{
Expression: d.diag.Expression,
EvalContext: d.diag.EvalContext,
}
}
// SourceRangeFromHCL constructs a SourceRange from the corresponding range
// type within the HCL package.
func SourceRangeFromHCL(hclRange hcl.Range) SourceRange {
return SourceRange{
Filename: hclRange.Filename,
Start: SourcePos{
Line: hclRange.Start.Line,
Column: hclRange.Start.Column,
Byte: hclRange.Start.Byte,
},
End: SourcePos{
Line: hclRange.End.Line,
Column: hclRange.End.Column,
Byte: hclRange.End.Byte,
},
}
}
// ToHCL constructs a HCL Range from the receiving SourceRange. This is the
// opposite of SourceRangeFromHCL.
func (r SourceRange) ToHCL() hcl.Range {
return hcl.Range{
Filename: r.Filename,
Start: hcl.Pos{
Line: r.Start.Line,
Column: r.Start.Column,
Byte: r.Start.Byte,
},
End: hcl.Pos{
Line: r.End.Line,
Column: r.End.Column,
Byte: r.End.Byte,
},
}
}
// ToHCL constructs a hcl.Diagnostics containing the same diagnostic messages
// as the receiving tfdiags.Diagnostics.
//
// This conversion preserves the data that HCL diagnostics are able to
// preserve but would be lossy in a round trip from tfdiags to HCL and then
// back to tfdiags, because it will lose the specific type information of
// the source diagnostics. In most cases this will not be a significant
// problem, but could produce an awkward result in some special cases such
// as converting the result of ConsolidateWarnings, which will force the
// resulting warning groups to be flattened early.
func (diags Diagnostics) ToHCL() hcl.Diagnostics {
if len(diags) == 0 {
return nil
}
ret := make(hcl.Diagnostics, len(diags))
for i, diag := range diags {
severity := diag.Severity()
desc := diag.Description()
source := diag.Source()
fromExpr := diag.FromExpr()
hclDiag := &hcl.Diagnostic{
Summary: desc.Summary,
Detail: desc.Detail,
Severity: severity.ToHCL(),
}
if source.Subject != nil {
hclDiag.Subject = source.Subject.ToHCL().Ptr()
}
if source.Context != nil {
hclDiag.Context = source.Context.ToHCL().Ptr()
}
if fromExpr != nil {
hclDiag.Expression = fromExpr.Expression
hclDiag.EvalContext = fromExpr.EvalContext
}
ret[i] = hclDiag
}
return ret
}