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