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.
This commit is contained in:
Martin Atkins 2021-01-11 18:15:04 -08:00
parent 17728c8fe8
commit 15c0645bd5
2 changed files with 59 additions and 1 deletions

View File

@ -15,6 +15,7 @@ import (
"github.com/hashicorp/terraform/command/cliconfig" "github.com/hashicorp/terraform/command/cliconfig"
"github.com/hashicorp/terraform/command/webbrowser" "github.com/hashicorp/terraform/command/webbrowser"
"github.com/hashicorp/terraform/internal/getproviders" "github.com/hashicorp/terraform/internal/getproviders"
"github.com/hashicorp/terraform/internal/terminal"
pluginDiscovery "github.com/hashicorp/terraform/plugin/discovery" pluginDiscovery "github.com/hashicorp/terraform/plugin/discovery"
) )
@ -48,6 +49,7 @@ var Ui cli.Ui
func initCommands( func initCommands(
originalWorkingDir string, originalWorkingDir string,
streams *terminal.Streams,
config *cliconfig.Config, config *cliconfig.Config,
services *disco.Disco, services *disco.Disco,
providerSrc getproviders.Source, providerSrc getproviders.Source,

58
main.go
View File

@ -19,6 +19,7 @@ import (
"github.com/hashicorp/terraform/httpclient" "github.com/hashicorp/terraform/httpclient"
"github.com/hashicorp/terraform/internal/didyoumean" "github.com/hashicorp/terraform/internal/didyoumean"
"github.com/hashicorp/terraform/internal/logging" "github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/terminal"
"github.com/hashicorp/terraform/version" "github.com/hashicorp/terraform/version"
"github.com/mattn/go-shellwords" "github.com/mattn/go-shellwords"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
@ -34,6 +35,12 @@ const (
// The parent process will create a file to collect crash logs // The parent process will create a file to collect crash logs
envTmpLogPath = "TF_TEMP_LOG_PATH" 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 // 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 // store the path in the environment for the wrapped executable
os.Setenv(envTmpLogPath, logTempFile.Name()) 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 // Create the configuration for panicwrap and wrap our executable
wrapConfig.Handler = logging.PanicHandler(logTempFile.Name()) wrapConfig.Handler = logging.PanicHandler(logTempFile.Name())
wrapConfig.IgnoreSignals = ignoreSignals wrapConfig.IgnoreSignals = ignoreSignals
@ -122,6 +145,39 @@ func wrappedMain() int {
log.Printf("[INFO] Go runtime version: %s", runtime.Version()) log.Printf("[INFO] Go runtime version: %s", runtime.Version())
log.Printf("[INFO] CLI args: %#v", os.Args) 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 // NOTE: We're intentionally calling LoadConfig _before_ handling a possible
// -chdir=... option on the command line, so that a possible relative // -chdir=... option on the command line, so that a possible relative
// path in the TERRAFORM_CONFIG_FILE environment variable (though probably // 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 // in case they need to refer back to it for any special reason, though
// they should primarily be working with the override working directory // they should primarily be working with the override working directory
// that we've now switched to above. // that we've now switched to above.
initCommands(originalWd, config, services, providerSrc, providerDevOverrides, unmanagedProviders) initCommands(originalWd, streams, config, services, providerSrc, providerDevOverrides, unmanagedProviders)
} }
// Run checkpoint // Run checkpoint