Merge pull request #28275 from hashicorp/jbardin/diagnostic-addresses
Add addresses to diagnostics
This commit is contained in:
commit
b7fb533bd2
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -25,6 +25,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Description struct {
|
type Description struct {
|
||||||
|
Address string
|
||||||
Summary string
|
Summary string
|
||||||
Detail string
|
Detail string
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue