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:
Martin Atkins 2017-10-05 11:59:08 -07:00
parent 780e758f1e
commit ea81e75a4e
2 changed files with 97 additions and 0 deletions

View File

@ -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()
}

View File

@ -19,11 +19,13 @@ import (
"github.com/hashicorp/go-getter"
"github.com/hashicorp/terraform/backend"
"github.com/hashicorp/terraform/backend/local"
"github.com/hashicorp/terraform/command/format"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/helper/experiment"
"github.com/hashicorp/terraform/helper/variables"
"github.com/hashicorp/terraform/helper/wrappedstreams"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
"github.com/mitchellh/cli"
"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 (
// ModuleDepthDefault is the default value for
// module depth, which can be overridden by flag