Merge pull request #28275 from hashicorp/jbardin/diagnostic-addresses

Add addresses to diagnostics
This commit is contained in:
James Bardin 2021-04-06 16:09:39 -04:00 committed by GitHub
commit b7fb533bd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 66 additions and 29 deletions

View File

@ -39,7 +39,7 @@ func TestBackendConfig(t *testing.T, b Backend, c hcl.Body) Backend {
diags = diags.Append(decDiags) diags = diags.Append(decDiags)
newObj, valDiags := b.PrepareConfig(obj) newObj, valDiags := b.PrepareConfig(obj)
diags = diags.Append(valDiags.InConfigBody(c)) diags = diags.Append(valDiags.InConfigBody(c, ""))
if len(diags) != 0 { if len(diags) != 0 {
t.Fatal(diags.ErrWithWarnings()) t.Fatal(diags.ErrWithWarnings())
@ -49,7 +49,7 @@ func TestBackendConfig(t *testing.T, b Backend, c hcl.Body) Backend {
confDiags := b.Configure(obj) confDiags := b.Configure(obj)
if len(confDiags) != 0 { if len(confDiags) != 0 {
confDiags = confDiags.InConfigBody(c) confDiags = confDiags.InConfigBody(c, "")
t.Fatal(confDiags.ErrWithWarnings()) t.Fatal(confDiags.ErrWithWarnings())
} }

View File

@ -213,6 +213,10 @@ func DiagnosticWarningsCompact(diags tfdiags.Diagnostics, color *colorstring.Col
} }
func appendSourceSnippets(buf *bytes.Buffer, diag *viewsjson.Diagnostic, color *colorstring.Colorize) { func appendSourceSnippets(buf *bytes.Buffer, diag *viewsjson.Diagnostic, color *colorstring.Colorize) {
if diag.Address != "" {
fmt.Fprintf(buf, " with %s,\n", diag.Address)
}
if diag.Range == nil { if diag.Range == nil {
return return
} }

View File

@ -1086,13 +1086,13 @@ func (m *Meta) backendInitFromConfig(c *configs.Backend) (backend.Backend, cty.V
} }
newVal, validateDiags := b.PrepareConfig(configVal) newVal, validateDiags := b.PrepareConfig(configVal)
diags = diags.Append(validateDiags.InConfigBody(c.Config)) diags = diags.Append(validateDiags.InConfigBody(c.Config, ""))
if validateDiags.HasErrors() { if validateDiags.HasErrors() {
return nil, cty.NilVal, diags return nil, cty.NilVal, diags
} }
configureDiags := b.Configure(newVal) configureDiags := b.Configure(newVal)
diags = diags.Append(configureDiags.InConfigBody(c.Config)) diags = diags.Append(configureDiags.InConfigBody(c.Config, ""))
return b, configVal, diags return b, configVal, diags
} }

View File

@ -30,6 +30,7 @@ type Diagnostic struct {
Severity string `json:"severity"` Severity string `json:"severity"`
Summary string `json:"summary"` Summary string `json:"summary"`
Detail string `json:"detail"` Detail string `json:"detail"`
Address string `json:"address,omitempty"`
Range *DiagnosticRange `json:"range,omitempty"` Range *DiagnosticRange `json:"range,omitempty"`
Snippet *DiagnosticSnippet `json:"snippet,omitempty"` Snippet *DiagnosticSnippet `json:"snippet,omitempty"`
} }
@ -124,6 +125,7 @@ func NewDiagnostic(diag tfdiags.Diagnostic, sources map[string][]byte) *Diagnost
Severity: sev, Severity: sev,
Summary: desc.Summary, Summary: desc.Summary,
Detail: desc.Detail, Detail: desc.Detail,
Address: desc.Address,
} }
sourceRefs := diag.Source() sourceRefs := diag.Source()

View File

@ -393,7 +393,7 @@ func TestProtoDiagnostics_emptyAttributePath(t *testing.T) {
if parseDiags.HasErrors() { if parseDiags.HasErrors() {
t.Fatal(parseDiags) t.Fatal(parseDiags)
} }
diags := tfDiags.InConfigBody(f.Body) diags := tfDiags.InConfigBody(f.Body, "")
if len(tfDiags) != 1 { if len(tfDiags) != 1 {
t.Fatalf("expected 1 diag, got %d", len(tfDiags)) t.Fatalf("expected 1 diag, got %d", len(tfDiags))

View File

@ -45,26 +45,26 @@ func grpcErr(err error) (diags tfdiags.Diagnostics) {
case codes.Unavailable: case codes.Unavailable:
// This case is when the plugin has stopped running for some reason, // This case is when the plugin has stopped running for some reason,
// and is usually the result of a crash. // and is usually the result of a crash.
diags = diags.Append(tfdiags.Sourceless( diags = diags.Append(tfdiags.WholeContainingBody(
tfdiags.Error, tfdiags.Error,
"Plugin did not respond", "Plugin did not respond",
fmt.Sprintf("The plugin encountered an error, and failed to respond to the %s call. "+ fmt.Sprintf("The plugin encountered an error, and failed to respond to the %s call. "+
"The plugin logs may contain more details.", requestName), "The plugin logs may contain more details.", requestName),
)) ))
case codes.Canceled: case codes.Canceled:
diags = diags.Append(tfdiags.Sourceless( diags = diags.Append(tfdiags.WholeContainingBody(
tfdiags.Error, tfdiags.Error,
"Request cancelled", "Request cancelled",
fmt.Sprintf("The %s request was cancelled.", requestName), fmt.Sprintf("The %s request was cancelled.", requestName),
)) ))
case codes.Unimplemented: case codes.Unimplemented:
diags = diags.Append(tfdiags.Sourceless( diags = diags.Append(tfdiags.WholeContainingBody(
tfdiags.Error, tfdiags.Error,
"Unsupported plugin method", "Unsupported plugin method",
fmt.Sprintf("The %s method is not supported by this plugin.", requestName), fmt.Sprintf("The %s method is not supported by this plugin.", requestName),
)) ))
default: default:
diags = diags.Append(tfdiags.Sourceless( diags = diags.Append(tfdiags.WholeContainingBody(
tfdiags.Error, tfdiags.Error,
"Plugin error", "Plugin error",
fmt.Sprintf("The plugin returned an unexpected error from %s: %v", requestName, err), fmt.Sprintf("The plugin returned an unexpected error from %s: %v", requestName, err),

View File

@ -167,14 +167,14 @@ func (n *NodeApplyableProvider) ConfigureProvider(ctx EvalContext, provider prov
diags = diags.Append(tfdiags.Sourceless( diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error, tfdiags.Error,
"Invalid provider configuration", "Invalid provider configuration",
fmt.Sprintf(providerConfigErr, configDiags.InConfigBody(configBody).Err(), n.Addr.Provider), fmt.Sprintf(providerConfigErr, configDiags.InConfigBody(configBody, n.Addr.String()).Err(), n.Addr.Provider),
)) ))
return diags return diags
} else { } else {
return diags.Append(configDiags.InConfigBody(configBody)) return diags.Append(configDiags.InConfigBody(configBody, n.Addr.String()))
} }
} }
diags = diags.Append(configDiags.InConfigBody(configBody)) diags = diags.Append(configDiags.InConfigBody(configBody, n.Addr.String()))
return diags return diags
} }

View File

@ -625,8 +625,9 @@ func (n *NodeAbstractResourceInstance) plan(
Config: unmarkedConfigVal, Config: unmarkedConfigVal,
}, },
) )
if validateResp.Diagnostics.HasErrors() { if validateResp.Diagnostics.HasErrors() {
diags = diags.Append(validateResp.Diagnostics.InConfigBody(config.Config)) diags = diags.Append(validateResp.Diagnostics.InConfigBody(config.Config, n.Addr.String()))
return plan, state, diags return plan, state, diags
} }
@ -659,7 +660,7 @@ func (n *NodeAbstractResourceInstance) plan(
PriorPrivate: priorPrivate, PriorPrivate: priorPrivate,
ProviderMeta: metaConfigVal, ProviderMeta: metaConfigVal,
}) })
diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config)) diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config, n.Addr.String()))
if diags.HasErrors() { if diags.HasErrors() {
return plan, state, diags return plan, state, diags
} }
@ -869,7 +870,7 @@ func (n *NodeAbstractResourceInstance) plan(
// Consequently, we break from the usual pattern here and only // Consequently, we break from the usual pattern here and only
// append these new diagnostics if there's at least one error inside. // append these new diagnostics if there's at least one error inside.
if resp.Diagnostics.HasErrors() { if resp.Diagnostics.HasErrors() {
diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config)) diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config, n.Addr.String()))
return plan, state, diags return plan, state, diags
} }
plannedNewVal = resp.PlannedState plannedNewVal = resp.PlannedState
@ -1195,7 +1196,7 @@ func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal
}, },
) )
if validateResp.Diagnostics.HasErrors() { if validateResp.Diagnostics.HasErrors() {
return newVal, validateResp.Diagnostics.InConfigBody(config.Config) return newVal, validateResp.Diagnostics.InConfigBody(config.Config, n.Addr.String())
} }
// If we get down here then our configuration is complete and we're read // If we get down here then our configuration is complete and we're read
@ -1207,7 +1208,7 @@ func (n *NodeAbstractResourceInstance) readDataSource(ctx EvalContext, configVal
Config: configVal, Config: configVal,
ProviderMeta: metaConfigVal, ProviderMeta: metaConfigVal,
}) })
diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config)) diags = diags.Append(resp.Diagnostics.InConfigBody(config.Config, n.Addr.String()))
if diags.HasErrors() { if diags.HasErrors() {
return newVal, diags return newVal, diags
} }
@ -1741,7 +1742,7 @@ func (n *NodeAbstractResourceInstance) applyProvisioners(ctx EvalContext, state
Connection: unmarkedConnInfo, Connection: unmarkedConnInfo,
UIOutput: &output, UIOutput: &output,
}) })
applyDiags := resp.Diagnostics.InConfigBody(prov.Config) applyDiags := resp.Diagnostics.InConfigBody(prov.Config, n.Addr.String())
// Call post hook // Call post hook
hookErr := ctx.Hook(func(h Hook) (HookAction, error) { hookErr := ctx.Hook(func(h Hook) (HookAction, error) {
@ -1907,7 +1908,7 @@ func (n *NodeAbstractResourceInstance) apply(
}) })
applyDiags := resp.Diagnostics applyDiags := resp.Diagnostics
if applyConfig != nil { if applyConfig != nil {
applyDiags = applyDiags.InConfigBody(applyConfig.Config) applyDiags = applyDiags.InConfigBody(applyConfig.Config, n.Addr.String())
} }
diags = diags.Append(applyDiags) diags = diags.Append(applyDiags)

View File

@ -381,7 +381,7 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
} }
resp := provider.ValidateResourceConfig(req) resp := provider.ValidateResourceConfig(req)
diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config)) diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String()))
case addrs.DataResourceMode: case addrs.DataResourceMode:
schema, _ := providerSchema.SchemaForResourceType(n.Config.Mode, n.Config.Type) schema, _ := providerSchema.SchemaForResourceType(n.Config.Mode, n.Config.Type)
@ -409,7 +409,7 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
} }
resp := provider.ValidateDataResourceConfig(req) resp := provider.ValidateDataResourceConfig(req)
diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config)) diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String()))
} }
return diags return diags

View File

@ -19,17 +19,20 @@ import (
// contextualFromConfig is an interface type implemented by diagnostic types // contextualFromConfig is an interface type implemented by diagnostic types
// that can elaborate themselves when given information about the configuration // that can elaborate themselves when given information about the configuration
// body they are embedded in. // body they are embedded in, as well as the runtime address associated with
// that configuration.
// //
// Usually this entails extracting source location information in order to // Usually this entails extracting source location information in order to
// populate the "Subject" range. // populate the "Subject" range.
type contextualFromConfigBody interface { type contextualFromConfigBody interface {
ElaborateFromConfigBody(hcl.Body) Diagnostic ElaborateFromConfigBody(hcl.Body, string) Diagnostic
} }
// InConfigBody returns a copy of the receiver with any config-contextual // InConfigBody returns a copy of the receiver with any config-contextual
// diagnostics elaborated in the context of the given body. // diagnostics elaborated in the context of the given body. An optional address
func (diags Diagnostics) InConfigBody(body hcl.Body) Diagnostics { // argument may be added to indicate which instance of the configuration the
// error related to.
func (diags Diagnostics) InConfigBody(body hcl.Body, addr string) Diagnostics {
if len(diags) == 0 { if len(diags) == 0 {
return nil return nil
} }
@ -37,7 +40,7 @@ func (diags Diagnostics) InConfigBody(body hcl.Body) Diagnostics {
ret := make(Diagnostics, len(diags)) ret := make(Diagnostics, len(diags))
for i, srcDiag := range diags { for i, srcDiag := range diags {
if cd, isCD := srcDiag.(contextualFromConfigBody); isCD { if cd, isCD := srcDiag.(contextualFromConfigBody); isCD {
ret[i] = cd.ElaborateFromConfigBody(body) ret[i] = cd.ElaborateFromConfigBody(body, addr)
} else { } else {
ret[i] = srcDiag ret[i] = srcDiag
} }
@ -112,7 +115,12 @@ type attributeDiagnostic struct {
// source location information is still available, for more accuracy. This // source location information is still available, for more accuracy. This
// is not always possible due to system architecture, so this serves as a // is not always possible due to system architecture, so this serves as a
// "best effort" fallback behavior for such situations. // "best effort" fallback behavior for such situations.
func (d *attributeDiagnostic) ElaborateFromConfigBody(body hcl.Body) Diagnostic { func (d *attributeDiagnostic) ElaborateFromConfigBody(body hcl.Body, addr string) Diagnostic {
// don't change an existing address
if d.address == "" {
d.address = addr
}
if len(d.attrPath) < 1 { if len(d.attrPath) < 1 {
// Should never happen, but we'll allow it rather than crashing. // Should never happen, but we'll allow it rather than crashing.
return d return d
@ -353,7 +361,12 @@ type wholeBodyDiagnostic struct {
subject *SourceRange // populated only after ElaborateFromConfigBody subject *SourceRange // populated only after ElaborateFromConfigBody
} }
func (d *wholeBodyDiagnostic) ElaborateFromConfigBody(body hcl.Body) Diagnostic { func (d *wholeBodyDiagnostic) ElaborateFromConfigBody(body hcl.Body, addr string) Diagnostic {
// don't change an existing address
if d.address == "" {
d.address = addr
}
if d.subject != nil { if d.subject != nil {
// Don't modify an already-elaborated diagnostic. // Don't modify an already-elaborated diagnostic.
return d return d

View File

@ -185,6 +185,7 @@ simple_attr = "val"
diagnosticBase: diagnosticBase{ diagnosticBase: diagnosticBase{
summary: "preexisting", summary: "preexisting",
detail: "detail", detail: "detail",
address: "original",
}, },
subject: &SourceRange{ subject: &SourceRange{
Filename: "somewhere_else.tf", Filename: "somewhere_else.tf",
@ -535,9 +536,22 @@ simple_attr = "val"
for i, tc := range testCases { for i, tc := range testCases {
t.Run(fmt.Sprintf("%d:%s", i, tc.Diag.Description()), func(t *testing.T) { t.Run(fmt.Sprintf("%d:%s", i, tc.Diag.Description()), func(t *testing.T) {
var diags Diagnostics var diags Diagnostics
origAddr := tc.Diag.Description().Address
diags = diags.Append(tc.Diag) diags = diags.Append(tc.Diag)
gotDiags := diags.InConfigBody(f.Body)
gotDiags := diags.InConfigBody(f.Body, "test.addr")
gotRange := gotDiags[0].Source().Subject gotRange := gotDiags[0].Source().Subject
gotAddr := gotDiags[0].Description().Address
switch {
case origAddr != "":
if gotAddr != origAddr {
t.Errorf("original diagnostic address modified from %s to %s", origAddr, gotAddr)
}
case gotAddr != "test.addr":
t.Error("missing detail address")
}
for _, problem := range deep.Equal(gotRange, tc.ExpectedRange) { for _, problem := range deep.Equal(gotRange, tc.ExpectedRange) {
t.Error(problem) t.Error(problem)

View File

@ -25,6 +25,7 @@ const (
) )
type Description struct { type Description struct {
Address string
Summary string Summary string
Detail string Detail string
} }

View File

@ -9,6 +9,7 @@ type diagnosticBase struct {
severity Severity severity Severity
summary string summary string
detail string detail string
address string
} }
func (d diagnosticBase) Severity() Severity { func (d diagnosticBase) Severity() Severity {
@ -19,6 +20,7 @@ func (d diagnosticBase) Description() Description {
return Description{ return Description{
Summary: d.summary, Summary: d.summary,
Detail: d.detail, Detail: d.detail,
Address: d.address,
} }
} }