command: utility for rendering tfdiag diagnostics
This new method showDiagnostics takes any value that would be accepted by tfdiags.Append and renders it to the UI. This is intended to encourage consistent handling of the different kinds of errors and diagnostics that can be produced, and allow richer error objects like the HCL2 diagnostics to be easily unwrapped and shown in their full-fidelity.
This commit is contained in:
parent
780e758f1e
commit
ea81e75a4e
|
@ -0,0 +1,65 @@
|
||||||
|
package format
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
|
"github.com/mitchellh/colorstring"
|
||||||
|
wordwrap "github.com/mitchellh/go-wordwrap"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Diagnostic formats a single diagnostic message.
|
||||||
|
//
|
||||||
|
// The width argument specifies at what column the diagnostic messages will
|
||||||
|
// be wrapped. If set to zero, messages will not be wrapped by this function
|
||||||
|
// at all. Although the long-form text parts of the message are wrapped,
|
||||||
|
// not all aspects of the message are guaranteed to fit within the specified
|
||||||
|
// terminal width.
|
||||||
|
func Diagnostic(diag tfdiags.Diagnostic, color *colorstring.Colorize, width int) string {
|
||||||
|
if diag == nil {
|
||||||
|
// No good reason to pass a nil diagnostic in here...
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
switch diag.Severity() {
|
||||||
|
case tfdiags.Error:
|
||||||
|
buf.WriteString(color.Color("\n[bold][red]Error: [reset]"))
|
||||||
|
case tfdiags.Warning:
|
||||||
|
buf.WriteString(color.Color("\n[bold][yellow]Warning: [reset]"))
|
||||||
|
default:
|
||||||
|
// Clear out any coloring that might be applied by Terraform's UI helper,
|
||||||
|
// so our result is not context-sensitive.
|
||||||
|
buf.WriteString(color.Color("\n[reset]"))
|
||||||
|
}
|
||||||
|
|
||||||
|
desc := diag.Description()
|
||||||
|
sourceRefs := diag.Source()
|
||||||
|
|
||||||
|
// We don't wrap the summary, since we expect it to be terse, and since
|
||||||
|
// this is where we put the text of a native Go error it may not always
|
||||||
|
// be pure text that lends itself well to word-wrapping.
|
||||||
|
if sourceRefs.Subject != nil {
|
||||||
|
fmt.Fprintf(&buf, color.Color("[bold]%s[reset] at %s\n\n"), desc.Summary, sourceRefs.Subject.StartString())
|
||||||
|
} else {
|
||||||
|
fmt.Fprintf(&buf, color.Color("[bold]%s[reset]\n\n"), desc.Summary)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: also print out the relevant snippet of config source with the
|
||||||
|
// relevant section highlighted, so the user doesn't need to manually
|
||||||
|
// correlate back to config. Before we can do this, the HCL2 parser
|
||||||
|
// needs to be more deeply integrated so that we can use it to obtain
|
||||||
|
// the parsed source code and AST.
|
||||||
|
|
||||||
|
if desc.Detail != "" {
|
||||||
|
detail := desc.Detail
|
||||||
|
if width != 0 {
|
||||||
|
detail = wordwrap.WrapString(detail, uint(width))
|
||||||
|
}
|
||||||
|
fmt.Fprintf(&buf, "%s\n", detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf.String()
|
||||||
|
}
|
|
@ -19,11 +19,13 @@ import (
|
||||||
"github.com/hashicorp/go-getter"
|
"github.com/hashicorp/go-getter"
|
||||||
"github.com/hashicorp/terraform/backend"
|
"github.com/hashicorp/terraform/backend"
|
||||||
"github.com/hashicorp/terraform/backend/local"
|
"github.com/hashicorp/terraform/backend/local"
|
||||||
|
"github.com/hashicorp/terraform/command/format"
|
||||||
"github.com/hashicorp/terraform/config"
|
"github.com/hashicorp/terraform/config"
|
||||||
"github.com/hashicorp/terraform/helper/experiment"
|
"github.com/hashicorp/terraform/helper/experiment"
|
||||||
"github.com/hashicorp/terraform/helper/variables"
|
"github.com/hashicorp/terraform/helper/variables"
|
||||||
"github.com/hashicorp/terraform/helper/wrappedstreams"
|
"github.com/hashicorp/terraform/helper/wrappedstreams"
|
||||||
"github.com/hashicorp/terraform/terraform"
|
"github.com/hashicorp/terraform/terraform"
|
||||||
|
"github.com/hashicorp/terraform/tfdiags"
|
||||||
"github.com/mitchellh/cli"
|
"github.com/mitchellh/cli"
|
||||||
"github.com/mitchellh/colorstring"
|
"github.com/mitchellh/colorstring"
|
||||||
)
|
)
|
||||||
|
@ -476,6 +478,36 @@ func (m *Meta) confirm(opts *terraform.InputOpts) (bool, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// showDiagnostics displays error and warning messages in the UI.
|
||||||
|
//
|
||||||
|
// "Diagnostics" here means the Diagnostics type from the tfdiag package,
|
||||||
|
// though as a convenience this function accepts anything that could be
|
||||||
|
// passed to the "Append" method on that type, converting it to Diagnostics
|
||||||
|
// before displaying it.
|
||||||
|
//
|
||||||
|
// Internally this function uses Diagnostics.Append, and so it will panic
|
||||||
|
// if given unsupported value types, just as Append does.
|
||||||
|
func (m *Meta) showDiagnostics(vals ...interface{}) {
|
||||||
|
var diags tfdiags.Diagnostics
|
||||||
|
diags = diags.Append(vals...)
|
||||||
|
|
||||||
|
for _, diag := range diags {
|
||||||
|
// TODO: Actually measure the terminal width and pass it here.
|
||||||
|
// For now, we don't have easy access to the writer that
|
||||||
|
// ui.Error (etc) are writing to and thus can't interrogate
|
||||||
|
// to see if it's a terminal and what size it is.
|
||||||
|
msg := format.Diagnostic(diag, m.Colorize(), 78)
|
||||||
|
switch diag.Severity() {
|
||||||
|
case tfdiags.Error:
|
||||||
|
m.Ui.Error(msg)
|
||||||
|
case tfdiags.Warning:
|
||||||
|
m.Ui.Warn(msg)
|
||||||
|
default:
|
||||||
|
m.Ui.Output(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ModuleDepthDefault is the default value for
|
// ModuleDepthDefault is the default value for
|
||||||
// module depth, which can be overridden by flag
|
// module depth, which can be overridden by flag
|
||||||
|
|
Loading…
Reference in New Issue