From 15c0645bd5d849663196417f01cafec904480b44 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 11 Jan 2021 18:15:04 -0800 Subject: [PATCH] main: initialize the terminal (if any) using internal/terminal We need to call into terminal.Init in early startup to make sure that we either have a suitable Terminal or that we disable attempts to use virtual terminal escape sequences. This commit gets the terminal initialized but doesn't do much with it after that. Subsequent commits will make more use of this. --- commands.go | 2 ++ main.go | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 5b627e2dc..d63b6d2b1 100644 --- a/commands.go +++ b/commands.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform/command/cliconfig" "github.com/hashicorp/terraform/command/webbrowser" "github.com/hashicorp/terraform/internal/getproviders" + "github.com/hashicorp/terraform/internal/terminal" pluginDiscovery "github.com/hashicorp/terraform/plugin/discovery" ) @@ -48,6 +49,7 @@ var Ui cli.Ui func initCommands( originalWorkingDir string, + streams *terminal.Streams, config *cliconfig.Config, services *disco.Disco, providerSrc getproviders.Source, diff --git a/main.go b/main.go index 03a7ff663..5fd393b07 100644 --- a/main.go +++ b/main.go @@ -19,6 +19,7 @@ import ( "github.com/hashicorp/terraform/httpclient" "github.com/hashicorp/terraform/internal/didyoumean" "github.com/hashicorp/terraform/internal/logging" + "github.com/hashicorp/terraform/internal/terminal" "github.com/hashicorp/terraform/version" "github.com/mattn/go-shellwords" "github.com/mitchellh/cli" @@ -34,6 +35,12 @@ const ( // The parent process will create a file to collect crash logs envTmpLogPath = "TF_TEMP_LOG_PATH" + + // Environment variable name used for smuggling true stderr terminal + // settings into a panicwrap child process. This is an implementation + // detail, subject to change in future, and should not ever be directly + // set by an end-user. + envTerminalPanicwrapWorkaround = "TF_PANICWRAP_STDERR" ) // ui wraps the primary output cli.Ui, and redirects Warn calls to Output @@ -75,6 +82,22 @@ func realMain() int { // store the path in the environment for the wrapped executable os.Setenv(envTmpLogPath, logTempFile.Name()) + // We also need to do our terminal initialization before we fork, + // because the child process doesn't necessarily have access to + // the true stderr in order to initialize it. + streams, err := terminal.Init() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to initialize terminal: %s", err) + return 1 + } + + // We need the child process to behave _as if_ connected to the real + // stderr, even though panicwrap is about to add a pipe in the way, + // so we'll smuggle the true stderr information in an environment + // varible. + streamState := streams.StateForAfterPanicWrap() + os.Setenv(envTerminalPanicwrapWorkaround, fmt.Sprintf("%t:%d", streamState.StderrIsTerminal, streamState.StderrWidth)) + // Create the configuration for panicwrap and wrap our executable wrapConfig.Handler = logging.PanicHandler(logTempFile.Name()) wrapConfig.IgnoreSignals = ignoreSignals @@ -122,6 +145,39 @@ func wrappedMain() int { log.Printf("[INFO] Go runtime version: %s", runtime.Version()) log.Printf("[INFO] CLI args: %#v", os.Args) + // This is the recieving end of our workaround to retain the metadata + // about the real stderr even though we're talking to it via the panicwrap + // pipe. See the call to StateForAfterPanicWrap above for the producer + // part of this. + var streamState *terminal.PrePanicwrapState + if raw := os.Getenv(envTerminalPanicwrapWorkaround); raw != "" { + streamState = &terminal.PrePanicwrapState{} + if _, err := fmt.Sscanf(raw, "%t:%d", &streamState.StderrIsTerminal, &streamState.StderrWidth); err != nil { + log.Printf("[WARN] %s is set but is incorrectly-formatted: %s", envTerminalPanicwrapWorkaround, err) + streamState = nil // leave it unset for a normal init, then + } + } + streams, err := terminal.ReinitInsidePanicwrap(streamState) + if err != nil { + Ui.Error(fmt.Sprintf("Failed to configure the terminal: %s", err)) + return 1 + } + if streams.Stdout.IsTerminal() { + log.Printf("[TRACE] Stdout is a terminal of width %d", streams.Stdout.Columns()) + } else { + log.Printf("[TRACE] Stdout is not a terminal") + } + if streams.Stderr.IsTerminal() { + log.Printf("[TRACE] Stderr is a terminal of width %d", streams.Stderr.Columns()) + } else { + log.Printf("[TRACE] Stderr is not a terminal") + } + if streams.Stdin.IsTerminal() { + log.Printf("[TRACE] Stdin is a terminal") + } else { + log.Printf("[TRACE] Stdin is not a terminal") + } + // NOTE: We're intentionally calling LoadConfig _before_ handling a possible // -chdir=... option on the command line, so that a possible relative // path in the TERRAFORM_CONFIG_FILE environment variable (though probably @@ -235,7 +291,7 @@ func wrappedMain() int { // in case they need to refer back to it for any special reason, though // they should primarily be working with the override working directory // that we've now switched to above. - initCommands(originalWd, config, services, providerSrc, providerDevOverrides, unmanagedProviders) + initCommands(originalWd, streams, config, services, providerSrc, providerDevOverrides, unmanagedProviders) } // Run checkpoint