blob: 206ead7fd5c18624d9d9cf212427180d029d2c8c [file] [log] [blame]
package views
import (
"github.com/hashicorp/terraform/internal/command/arguments"
"github.com/hashicorp/terraform/internal/command/format"
"github.com/hashicorp/terraform/internal/terminal"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/mitchellh/colorstring"
)
// View is the base layer for command views, encapsulating a set of I/O
// streams, a colorize implementation, and implementing a human friendly view
// for diagnostics.
type View struct {
streams *terminal.Streams
colorize *colorstring.Colorize
compactWarnings bool
// When this is true it's a hint that Terraform is being run indirectly
// via a wrapper script or other automation and so we may wish to replace
// direct examples of commands to run with more conceptual directions.
// However, we only do this on a best-effort basis, typically prioritizing
// the messages that users are most likely to see.
runningInAutomation bool
// This unfortunate wart is required to enable rendering of diagnostics which
// have associated source code in the configuration. This function pointer
// will be dereferenced as late as possible when rendering diagnostics in
// order to access the config loader cache.
configSources func() map[string][]byte
}
// Initialize a View with the given streams, a disabled colorize object, and a
// no-op configSources callback.
func NewView(streams *terminal.Streams) *View {
return &View{
streams: streams,
colorize: &colorstring.Colorize{
Colors: colorstring.DefaultColors,
Disable: true,
Reset: true,
},
configSources: func() map[string][]byte { return nil },
}
}
// SetRunningInAutomation modifies the view's "running in automation" flag,
// which causes some slight adjustments to certain messages that would normally
// suggest specific Terraform commands to run, to make more conceptual gestures
// instead for situations where the user isn't running Terraform directly.
//
// For convenient use during initialization (in conjunction with NewView),
// SetRunningInAutomation returns the reciever after modifying it.
func (v *View) SetRunningInAutomation(new bool) *View {
v.runningInAutomation = new
return v
}
func (v *View) RunningInAutomation() bool {
return v.runningInAutomation
}
// Configure applies the global view configuration flags.
func (v *View) Configure(view *arguments.View) {
v.colorize.Disable = view.NoColor
v.compactWarnings = view.CompactWarnings
}
// SetConfigSources overrides the default no-op callback with a new function
// pointer, and should be called when the config loader is initialized.
func (v *View) SetConfigSources(cb func() map[string][]byte) {
v.configSources = cb
}
// Diagnostics renders a set of warnings and errors in human-readable form.
// Warnings are printed to stdout, and errors to stderr.
func (v *View) Diagnostics(diags tfdiags.Diagnostics) {
diags.Sort()
if len(diags) == 0 {
return
}
diags = diags.ConsolidateWarnings(1)
// Since warning messages are generally competing
if v.compactWarnings {
// If the user selected compact warnings and all of the diagnostics are
// warnings then we'll use a more compact representation of the warnings
// that only includes their summaries.
// We show full warnings if there are also errors, because a warning
// can sometimes serve as good context for a subsequent error.
useCompact := true
for _, diag := range diags {
if diag.Severity() != tfdiags.Warning {
useCompact = false
break
}
}
if useCompact {
msg := format.DiagnosticWarningsCompact(diags, v.colorize)
msg = "\n" + msg + "\nTo see the full warning notes, run Terraform without -compact-warnings.\n"
v.streams.Print(msg)
return
}
}
for _, diag := range diags {
var msg string
if v.colorize.Disable {
msg = format.DiagnosticPlain(diag, v.configSources(), v.streams.Stderr.Columns())
} else {
msg = format.Diagnostic(diag, v.configSources(), v.colorize, v.streams.Stderr.Columns())
}
if diag.Severity() == tfdiags.Error {
v.streams.Eprint(msg)
} else {
v.streams.Print(msg)
}
}
}
// HelpPrompt is intended to be called from commands which fail to parse all
// of their CLI arguments successfully. It refers users to the full help output
// rather than rendering it directly, which can be overwhelming and confusing.
func (v *View) HelpPrompt(command string) {
v.streams.Eprintf(helpPrompt, command)
}
const helpPrompt = `
For more help on using this command, run:
terraform %s -help
`
// outputColumns returns the number of text character cells any non-error
// output should be wrapped to.
//
// This is the number of columns to use if you are calling v.streams.Print or
// related functions.
func (v *View) outputColumns() int {
return v.streams.Stdout.Columns()
}
// errorColumns returns the number of text character cells any error
// output should be wrapped to.
//
// This is the number of columns to use if you are calling v.streams.Eprint
// or related functions.
func (v *View) errorColumns() int {
return v.streams.Stderr.Columns()
}
// outputHorizRule will call v.streams.Println with enough horizontal line
// characters to fill an entire row of output.
//
// If UI color is enabled, the rule will get a dark grey coloring to try to
// visually de-emphasize it.
func (v *View) outputHorizRule() {
v.streams.Println(format.HorizontalRule(v.colorize, v.outputColumns()))
}