From d2c3403ab60e378f91ba49f70f08cbef720a5653 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 11 Jan 2021 18:20:58 -0800 Subject: [PATCH] command: Use the new terminal.Streams object Here we propagate in the initialized terminal.Streams from package main, and then onwards to backends running in CLI mode. This also replaces our use of helper/wrappedstreams to determine whether stdin is a terminal or a pipe. helper/wrappedstreams returns incorrect file descriptors on Windows, causing StdinPiped to always return false on that platform and thus causing one of the odd behaviors discussed in Finally, this includes some wrappers around the ability to look up the number of columns in the terminal in preparation for use elsewhere. These wrappers deal with the fact that our unit tests typically won't populate meta.Streams. --- backend/cli.go | 10 ++++++++- backend/local/backend.go | 5 +++++ backend/local/cli.go | 29 ++++++++++++++++++++++++ command/meta.go | 48 +++++++++++++++++++++++++++++++++++----- command/meta_backend.go | 1 + commands.go | 1 + go.mod | 1 - go.sum | 3 +-- 8 files changed, 88 insertions(+), 10 deletions(-) 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=