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)
newObj, valDiags := b.PrepareConfig(obj)
diags = diags.Append(valDiags.InConfigBody(c))
diags = diags.Append(valDiags.InConfigBody(c, ""))
if len(diags) != 0 {
t.Fatal(diags.ErrWithWarnings())
@ -49,7 +49,7 @@ func TestBackendConfig(t *testing.T, b Backend, c hcl.Body) Backend {
confDiags := b.Configure(obj)
if len(confDiags) != 0 {
confDiags = confDiags.InConfigBody(c)
confDiags = confDiags.InConfigBody(c, "")
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) {
if diag.Address != "" {
fmt.Fprintf(buf, " with %s,\n", diag.Address)
}
if diag.Range == nil {
return
}

View File

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

View File

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

View File

@ -393,7 +393,7 @@ func TestProtoDiagnostics_emptyAttributePath(t *testing.T) {
if parseDiags.HasErrors() {
t.Fatal(parseDiags)
}
diags := tfDiags.InConfigBody(f.Body)
diags := tfDiags.InConfigBody(f.Body, "")
if len(tfDiags) != 1 {
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:
// This case is when the plugin has stopped running for some reason,
// and is usually the result of a crash.
diags = diags.Append(tfdiags.Sourceless(
diags = diags.Append(tfdiags.WholeContainingBody(
tfdiags.Error,
"Plugin did not respond",
fmt.Sprintf("The plugin encountered an error, and failed to respond to the %s call. "+
"The plugin logs may contain more details.", requestName),
))
case codes.Canceled:
diags = diags.Append(tfdiags.Sourceless(
diags = diags.Append(tfdiags.WholeContainingBody(
tfdiags.Error,
"Request cancelled",
fmt.Sprintf("The %s request was cancelled.", requestName),
))
case codes.Unimplemented:
diags = diags.Append(tfdiags.Sourceless(
diags = diags.Append(tfdiags.WholeContainingBody(
tfdiags.Error,
"Unsupported plugin method",
fmt.Sprintf("The %s method is not supported by this plugin.", requestName),
))
default:
diags = diags.Append(tfdiags.Sourceless(
diags = diags.Append(tfdiags.WholeContainingBody(
tfdiags.Error,
"Plugin error",
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(
tfdiags.Error,
"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
} 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
}

View File

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

View File

@ -381,7 +381,7 @@ func (n *NodeValidatableResource) validateResource(ctx EvalContext) tfdiags.Diag
}
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:
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)
diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config))
diags = diags.Append(resp.Diagnostics.InConfigBody(n.Config.Config, n.Addr.String()))
}
return diags

View File

@ -19,17 +19,20 @@ import (
// contextualFromConfig is an interface type implemented by diagnostic types
// 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
// populate the "Subject" range.
type contextualFromConfigBody interface {
ElaborateFromConfigBody(hcl.Body) Diagnostic
ElaborateFromConfigBody(hcl.Body, string) Diagnostic
}
// InConfigBody returns a copy of the receiver with any config-contextual
// diagnostics elaborated in the context of the given body.
func (diags Diagnostics) InConfigBody(body hcl.Body) Diagnostics {
// diagnostics elaborated in the context of the given body. An optional address
// 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 {
return nil
}
@ -37,7 +40,7 @@ func (diags Diagnostics) InConfigBody(body hcl.Body) Diagnostics {
ret := make(Diagnostics, len(diags))
for i, srcDiag := range diags {
if cd, isCD := srcDiag.(contextualFromConfigBody); isCD {
ret[i] = cd.ElaborateFromConfigBody(body)
ret[i] = cd.ElaborateFromConfigBody(body, addr)
} else {
ret[i] = srcDiag
}
@ -112,7 +115,12 @@ type attributeDiagnostic struct {
// source location information is still available, for more accuracy. This
// is not always possible due to system architecture, so this serves as a
// "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 {
// Should never happen, but we'll allow it rather than crashing.
return d
@ -353,7 +361,12 @@ type wholeBodyDiagnostic struct {
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 {
// Don't modify an already-elaborated diagnostic.
return d

View File

@ -185,6 +185,7 @@ simple_attr = "val"
diagnosticBase: diagnosticBase{
summary: "preexisting",
detail: "detail",
address: "original",
},
subject: &SourceRange{
Filename: "somewhere_else.tf",
@ -535,9 +536,22 @@ simple_attr = "val"
for i, tc := range testCases {
t.Run(fmt.Sprintf("%d:%s", i, tc.Diag.Description()), func(t *testing.T) {
var diags Diagnostics
origAddr := tc.Diag.Description().Address
diags = diags.Append(tc.Diag)
gotDiags := diags.InConfigBody(f.Body)
gotDiags := diags.InConfigBody(f.Body, "test.addr")
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) {
t.Error(problem)

View File

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

View File

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