diff --git a/backend/cli.go b/backend/cli.go index cd29e3862..80313c39c 100644 --- a/backend/cli.go +++ b/backend/cli.go @@ -1,9 +1,11 @@ package backend import ( - "github.com/hashicorp/terraform/terraform" "github.com/mitchellh/cli" "github.com/mitchellh/colorstring" + + "github.com/hashicorp/terraform/internal/terminal" + "github.com/hashicorp/terraform/terraform" ) // CLI is an optional interface that can be implemented to be initialized @@ -48,6 +50,12 @@ type CLIOpts struct { CLI cli.Ui CLIColor *colorstring.Colorize + // Streams describes the low-level streams for Stdout, Stderr and Stdin, + // including some metadata about whether they are terminals. Most output + // should go via the object in field CLI above, but Streams can be useful + // for tailoring the output to fit the attached terminal, for example. + Streams *terminal.Streams + // ShowDiagnostics is a function that will format and print diagnostic // messages to the UI. ShowDiagnostics func(vals ...interface{}) diff --git a/backend/local/backend.go b/backend/local/backend.go index 866c4899a..8acbb0054 100644 --- a/backend/local/backend.go +++ b/backend/local/backend.go @@ -14,6 +14,7 @@ import ( "github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/command/clistate" "github.com/hashicorp/terraform/configs/configschema" + "github.com/hashicorp/terraform/internal/terminal" "github.com/hashicorp/terraform/states/statemgr" "github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/tfdiags" @@ -38,6 +39,10 @@ type Local struct { CLI cli.Ui CLIColor *colorstring.Colorize + // If CLI is set then Streams might also be set, to describe the physical + // input/output handles that CLI is connected to. + Streams *terminal.Streams + // ShowDiagnostics prints diagnostic messages to the UI. ShowDiagnostics func(vals ...interface{}) diff --git a/backend/local/cli.go b/backend/local/cli.go index c3d7a65ac..f395fbff6 100644 --- a/backend/local/cli.go +++ b/backend/local/cli.go @@ -10,6 +10,7 @@ import ( func (b *Local) CLIInit(opts *backend.CLIOpts) error { b.CLI = opts.CLI b.CLIColor = opts.CLIColor + b.Streams = opts.Streams b.ShowDiagnostics = opts.ShowDiagnostics b.ContextOpts = opts.ContextOpts b.OpInput = opts.Input @@ -34,3 +35,31 @@ func (b *Local) CLIInit(opts *backend.CLIOpts) error { return nil } + +// 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 b.CLI.Output or +// b.CLI.Info. +func (b *Local) outputColumns() int { + if b.Streams == nil { + // We can potentially get here in tests, if they don't populate the + // CLIOpts fully. + return 78 // placeholder just so we don't panic + } + return b.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 b.CLI.Error or +// b.CLI.Warn. +func (b *Local) errorColumns() int { + if b.Streams == nil { + // We can potentially get here in tests, if they don't populate the + // CLIOpts fully. + return 78 // placeholder just so we don't panic + } + return b.Streams.Stderr.Columns() +} diff --git a/command/meta.go b/command/meta.go index 74cdd73f4..6b77c3a68 100644 --- a/command/meta.go +++ b/command/meta.go @@ -22,8 +22,8 @@ import ( "github.com/hashicorp/terraform/command/format" "github.com/hashicorp/terraform/command/webbrowser" "github.com/hashicorp/terraform/configs/configload" - "github.com/hashicorp/terraform/helper/wrappedstreams" "github.com/hashicorp/terraform/internal/getproviders" + "github.com/hashicorp/terraform/internal/terminal" "github.com/hashicorp/terraform/providers" "github.com/hashicorp/terraform/provisioners" "github.com/hashicorp/terraform/terraform" @@ -51,6 +51,15 @@ type Meta struct { // for some reason. OriginalWorkingDir string + // Streams tracks the raw Stdout, Stderr, and Stdin handles along with + // some basic metadata about them, such as whether each is connected to + // a terminal, how wide the possible terminal is, etc. + // + // For historical reasons this might not be set in unit test code, and + // so functions working with this field must check if it's nil and + // do some default behavior instead if so, rather than panicking. + Streams *terminal.Streams + Color bool // True if output should be colored GlobalPluginDirs []string // Additional paths to search for plugins Ui cli.Ui // Ui for output @@ -288,15 +297,42 @@ func (m *Meta) UIInput() terraform.UIInput { } } +// OutputColumns returns the number of columns that normal (non-error) UI +// output should be wrapped to fill. +// +// This is the column count to use if you'll be printing your message via +// the Output or Info methods of m.Ui. +func (m *Meta) OutputColumns() int { + if m.Streams == nil { + // A default for unit tests that don't populate Meta fully. + return 78 + } + return m.Streams.Stdout.Columns() +} + +// ErrorColumns returns the number of columns that error UI output should be +// wrapped to fill. +// +// This is the column count to use if you'll be printing your message via +// the Error or Warn methods of m.Ui. +func (m *Meta) ErrorColumns() int { + if m.Streams == nil { + // A default for unit tests that don't populate Meta fully. + return 78 + } + return m.Streams.Stderr.Columns() +} + // StdinPiped returns true if the input is piped. func (m *Meta) StdinPiped() bool { - fi, err := wrappedstreams.Stdin().Stat() - if err != nil { - // If there is an error, let's just say its not piped + if m.Streams == nil { + // If we don't have m.Streams populated then we're presumably in a unit + // test that doesn't properly populate Meta, so we'll just say the + // output _isn't_ piped because that's the common case and so most likely + // to be useful to a unit test. return false } - - return fi.Mode()&os.ModeNamedPipe != 0 + return !m.Streams.Stdin.IsTerminal() } // InterruptibleContext returns a context.Context that will be cancelled diff --git a/command/meta_backend.go b/command/meta_backend.go index be7e37fdb..d129546a5 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -308,6 +308,7 @@ func (m *Meta) backendCLIOpts() (*backend.CLIOpts, error) { return &backend.CLIOpts{ CLI: m.Ui, CLIColor: m.Colorize(), + Streams: m.Streams, ShowDiagnostics: m.showDiagnostics, StatePath: m.statePath, StateOutPath: m.stateOutPath, diff --git a/commands.go b/commands.go index d63b6d2b1..569c3e8ed 100644 --- a/commands.go +++ b/commands.go @@ -80,6 +80,7 @@ func initCommands( meta := command.Meta{ OriginalWorkingDir: originalWorkingDir, + Streams: streams, Color: true, GlobalPluginDirs: globalPluginDirs(), diff --git a/go.mod b/go.mod index 0a6bfbca0..7476bfbdc 100644 --- a/go.mod +++ b/go.mod @@ -80,7 +80,6 @@ require ( github.com/lusis/go-artifactory v0.0.0-20160115162124-7e4ce345df82 github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786 // indirect github.com/masterzen/winrm v0.0.0-20200615185753-c42b5136ff88 - github.com/mattn/go-colorable v0.1.8 // indirect github.com/mattn/go-isatty v0.0.12 github.com/mattn/go-shellwords v1.0.4 github.com/miekg/dns v1.0.8 // indirect diff --git a/go.sum b/go.sum index 650efa501..e073cd43f 100644 --- a/go.sum +++ b/go.sum @@ -420,9 +420,8 @@ github.com/masterzen/simplexml v0.0.0-20190410153822-31eea3082786/go.mod h1:kCEb github.com/masterzen/winrm v0.0.0-20200615185753-c42b5136ff88 h1:cxuVcCvCLD9yYDbRCWw0jSgh1oT6P6mv3aJDKK5o7X4= github.com/masterzen/winrm v0.0.0-20200615185753-c42b5136ff88/go.mod h1:a2HXwefeat3evJHxFXSayvRHpYEPJYtErl4uIzfaUqY= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=