diff --git a/command/format/diagnostic.go b/command/format/diagnostic.go new file mode 100644 index 0000000000..4c5f7fff08 --- /dev/null +++ b/command/format/diagnostic.go @@ -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() +} diff --git a/command/meta.go b/command/meta.go index 4412929c78..806bd39615 100644 --- a/command/meta.go +++ b/command/meta.go @@ -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