mirror of
https://github.com/opentffoundation/opentf.git
synced 2026-02-17 10:01:05 -05:00
Right now, there's a bug that if a diagnostic comes back from the provider with an AttributePath set, but no steps in the AttributePath, Terraform _thinks_ it's an attribute-specific diagnostic and not a whole-resource diagnostic, but then doesn't associate it with any specific attribute, meaning the diagnostic doesn't get associated with the config at all. This PR changes things to check if there are any steps in the AttributePath before deciding this isn't a whole-resource diagnostic, and if there aren't, treats it as a whole-resource diagnostic, instead. See hashicorp/terraform-plugin-sdk#561 for more details on how this surfaces in the wild.
133 lines
3.6 KiB
Go
133 lines
3.6 KiB
Go
package convert
|
|
|
|
import (
|
|
proto "github.com/hashicorp/terraform/internal/tfplugin5"
|
|
"github.com/hashicorp/terraform/tfdiags"
|
|
"github.com/zclconf/go-cty/cty"
|
|
)
|
|
|
|
// WarnsAndErrorsToProto converts the warnings and errors return by the legacy
|
|
// provider to protobuf diagnostics.
|
|
func WarnsAndErrsToProto(warns []string, errs []error) (diags []*proto.Diagnostic) {
|
|
for _, w := range warns {
|
|
diags = AppendProtoDiag(diags, w)
|
|
}
|
|
|
|
for _, e := range errs {
|
|
diags = AppendProtoDiag(diags, e)
|
|
}
|
|
|
|
return diags
|
|
}
|
|
|
|
// AppendProtoDiag appends a new diagnostic from a warning string or an error.
|
|
// This panics if d is not a string or error.
|
|
func AppendProtoDiag(diags []*proto.Diagnostic, d interface{}) []*proto.Diagnostic {
|
|
switch d := d.(type) {
|
|
case cty.PathError:
|
|
ap := PathToAttributePath(d.Path)
|
|
diags = append(diags, &proto.Diagnostic{
|
|
Severity: proto.Diagnostic_ERROR,
|
|
Summary: d.Error(),
|
|
Attribute: ap,
|
|
})
|
|
case error:
|
|
diags = append(diags, &proto.Diagnostic{
|
|
Severity: proto.Diagnostic_ERROR,
|
|
Summary: d.Error(),
|
|
})
|
|
case string:
|
|
diags = append(diags, &proto.Diagnostic{
|
|
Severity: proto.Diagnostic_WARNING,
|
|
Summary: d,
|
|
})
|
|
case *proto.Diagnostic:
|
|
diags = append(diags, d)
|
|
case []*proto.Diagnostic:
|
|
diags = append(diags, d...)
|
|
}
|
|
return diags
|
|
}
|
|
|
|
// ProtoToDiagnostics converts a list of proto.Diagnostics to a tf.Diagnostics.
|
|
func ProtoToDiagnostics(ds []*proto.Diagnostic) tfdiags.Diagnostics {
|
|
var diags tfdiags.Diagnostics
|
|
for _, d := range ds {
|
|
var severity tfdiags.Severity
|
|
|
|
switch d.Severity {
|
|
case proto.Diagnostic_ERROR:
|
|
severity = tfdiags.Error
|
|
case proto.Diagnostic_WARNING:
|
|
severity = tfdiags.Warning
|
|
}
|
|
|
|
var newDiag tfdiags.Diagnostic
|
|
|
|
// if there's an attribute path, we need to create a AttributeValue diagnostic
|
|
if d.Attribute != nil && len(d.Attribute.Steps) > 0 {
|
|
path := AttributePathToPath(d.Attribute)
|
|
newDiag = tfdiags.AttributeValue(severity, d.Summary, d.Detail, path)
|
|
} else {
|
|
newDiag = tfdiags.WholeContainingBody(severity, d.Summary, d.Detail)
|
|
}
|
|
|
|
diags = diags.Append(newDiag)
|
|
}
|
|
|
|
return diags
|
|
}
|
|
|
|
// AttributePathToPath takes the proto encoded path and converts it to a cty.Path
|
|
func AttributePathToPath(ap *proto.AttributePath) cty.Path {
|
|
var p cty.Path
|
|
for _, step := range ap.Steps {
|
|
switch selector := step.Selector.(type) {
|
|
case *proto.AttributePath_Step_AttributeName:
|
|
p = p.GetAttr(selector.AttributeName)
|
|
case *proto.AttributePath_Step_ElementKeyString:
|
|
p = p.Index(cty.StringVal(selector.ElementKeyString))
|
|
case *proto.AttributePath_Step_ElementKeyInt:
|
|
p = p.Index(cty.NumberIntVal(selector.ElementKeyInt))
|
|
}
|
|
}
|
|
return p
|
|
}
|
|
|
|
// AttributePathToPath takes a cty.Path and converts it to a proto-encoded path.
|
|
func PathToAttributePath(p cty.Path) *proto.AttributePath {
|
|
ap := &proto.AttributePath{}
|
|
for _, step := range p {
|
|
switch selector := step.(type) {
|
|
case cty.GetAttrStep:
|
|
ap.Steps = append(ap.Steps, &proto.AttributePath_Step{
|
|
Selector: &proto.AttributePath_Step_AttributeName{
|
|
AttributeName: selector.Name,
|
|
},
|
|
})
|
|
case cty.IndexStep:
|
|
key := selector.Key
|
|
switch key.Type() {
|
|
case cty.String:
|
|
ap.Steps = append(ap.Steps, &proto.AttributePath_Step{
|
|
Selector: &proto.AttributePath_Step_ElementKeyString{
|
|
ElementKeyString: key.AsString(),
|
|
},
|
|
})
|
|
case cty.Number:
|
|
v, _ := key.AsBigFloat().Int64()
|
|
ap.Steps = append(ap.Steps, &proto.AttributePath_Step{
|
|
Selector: &proto.AttributePath_Step_ElementKeyInt{
|
|
ElementKeyInt: v,
|
|
},
|
|
})
|
|
default:
|
|
// We'll bail early if we encounter anything else, and just
|
|
// return the valid prefix.
|
|
return ap
|
|
}
|
|
}
|
|
}
|
|
return ap
|
|
}
|