Merge pull request #29825 from hashicorp/jbardin/no-panicwrap

Remove the use of panicwrap in the Terraform CLI
This commit is contained in:
James Bardin 2021-10-29 14:41:05 -04:00 committed by GitHub
commit 81e709d185
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 73 additions and 522 deletions

1
go.mod
View File

@ -64,7 +64,6 @@ require (
github.com/mitchellh/go-wordwrap v1.0.1
github.com/mitchellh/gox v1.0.1
github.com/mitchellh/mapstructure v1.1.2
github.com/mitchellh/panicwrap v1.0.0
github.com/mitchellh/reflectwalk v1.0.2
github.com/nishanths/exhaustive v0.2.3
github.com/packer-community/winrmcp v0.0.0-20180921211025-c76d91c1e7db

2
go.sum
View File

@ -521,8 +521,6 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/panicwrap v1.0.0 h1:67zIyVakCIvcs69A0FGfZjBdPleaonSgGlXRSRlb6fE=
github.com/mitchellh/panicwrap v1.0.0/go.mod h1:pKvZHwWrZowLUzftuFq7coarnxbBXU4aQh3N0BJOeeA=
github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=

View File

@ -14,6 +14,7 @@ import (
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/command/views"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/states/statemgr"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/hashicorp/terraform/internal/tfdiags"
@ -313,6 +314,7 @@ func (b *Local) Operation(ctx context.Context, op *backend.Operation) (*backend.
// Do it
go func() {
defer logging.PanicHandler()
defer done()
defer stop()
defer cancel()

View File

@ -7,6 +7,7 @@ import (
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/command/views"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/states/statefile"
@ -156,6 +157,7 @@ func (b *Local) opApply(
var applyDiags tfdiags.Diagnostics
doneCh := make(chan struct{})
go func() {
defer logging.PanicHandler()
defer close(doneCh)
log.Printf("[INFO] backend/local: apply calling Apply")
applyState, applyDiags = lr.Core.Apply(plan, lr.Config)

View File

@ -6,6 +6,7 @@ import (
"log"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/plans/planfile"
"github.com/hashicorp/terraform/internal/states/statefile"
@ -79,6 +80,7 @@ func (b *Local) opPlan(
var planDiags tfdiags.Diagnostics
doneCh := make(chan struct{})
go func() {
defer logging.PanicHandler()
defer close(doneCh)
log.Printf("[INFO] backend/local: plan calling Plan")
plan, planDiags = lr.Core.Plan(lr.Config, lr.InputState, lr.PlanOpts)

View File

@ -7,6 +7,7 @@ import (
"os"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/states/statemgr"
"github.com/hashicorp/terraform/internal/tfdiags"
@ -77,6 +78,7 @@ func (b *Local) opRefresh(
var refreshDiags tfdiags.Diagnostics
doneCh := make(chan struct{})
go func() {
defer logging.PanicHandler()
defer close(doneCh)
newState, refreshDiags = lr.Core.Refresh(lr.Config, lr.InputState, lr.PlanOpts)
log.Printf("[INFO] backend/local: refresh calling Refresh")

View File

@ -18,6 +18,7 @@ import (
"github.com/hashicorp/terraform-svchost/disco"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/states/remote"
"github.com/hashicorp/terraform/internal/states/statemgr"
"github.com/hashicorp/terraform/internal/terraform"
@ -755,6 +756,7 @@ func (b *Remote) Operation(ctx context.Context, op *backend.Operation) (*backend
// Do it.
go func() {
defer logging.PanicHandler()
defer done()
defer stop()
defer cancel()

View File

@ -13,6 +13,7 @@ import (
tfe "github.com/hashicorp/go-tfe"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/terraform"
)
@ -464,6 +465,8 @@ func (b *Remote) confirm(stopCtx context.Context, op *backend.Operation, opts *t
result := make(chan error, 2)
go func() {
defer logging.PanicHandler()
// Make sure we cancel doneCtx before we return
// so the input command is also canceled.
defer cancel()

View File

@ -17,6 +17,7 @@ import (
tfe "github.com/hashicorp/go-tfe"
version "github.com/hashicorp/go-version"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/plans"
"github.com/hashicorp/terraform/internal/tfdiags"
)
@ -319,6 +320,8 @@ in order to capture the filesystem context the remote workspace expects:
// cancellable after that period, we attempt to cancel it.
if lockTimeout := op.StateLocker.Timeout(); lockTimeout > 0 {
go func() {
defer logging.PanicHandler()
select {
case <-stopCtx.Done():
return

View File

@ -3,11 +3,11 @@ package command
import (
"bufio"
"fmt"
"os"
"strings"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/backend"
"github.com/hashicorp/terraform/internal/helper/wrappedstreams"
"github.com/hashicorp/terraform/internal/repl"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/hashicorp/terraform/internal/tfdiags"
@ -113,8 +113,8 @@ func (c *ConsoleCommand) Run(args []string) int {
// Set up the UI so we can output directly to stdout
ui := &cli.BasicUi{
Writer: wrappedstreams.Stdout(),
ErrorWriter: wrappedstreams.Stderr(),
Writer: os.Stdout,
ErrorWriter: os.Stderr,
}
evalOpts := &terraform.EvalOpts{}
@ -164,7 +164,7 @@ func (c *ConsoleCommand) Run(args []string) int {
func (c *ConsoleCommand) modePiped(session *repl.Session, ui cli.Ui) int {
var lastResult string
scanner := bufio.NewScanner(wrappedstreams.Stdin())
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
result, exit, diags := session.Handle(strings.TrimSpace(scanner.Text()))
if diags.HasErrors() {

View File

@ -9,8 +9,8 @@ package command
import (
"fmt"
"io"
"os"
"github.com/hashicorp/terraform/internal/helper/wrappedreadline"
"github.com/hashicorp/terraform/internal/repl"
"github.com/chzyer/readline"
@ -19,12 +19,15 @@ import (
func (c *ConsoleCommand) modeInteractive(session *repl.Session, ui cli.Ui) int {
// Configure input
l, err := readline.NewEx(wrappedreadline.Override(&readline.Config{
l, err := readline.NewEx(&readline.Config{
Prompt: "> ",
InterruptPrompt: "^C",
EOFPrompt: "exit",
HistorySearchFold: true,
}))
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
})
if err != nil {
c.Ui.Error(fmt.Sprintf(
"Error initializing console: %s",

View File

@ -22,6 +22,7 @@ import (
"github.com/hashicorp/terraform-svchost/disco"
"github.com/hashicorp/terraform/internal/command/cliconfig"
"github.com/hashicorp/terraform/internal/httpclient"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/terraform"
"github.com/hashicorp/terraform/internal/tfdiags"
@ -450,6 +451,7 @@ func (c *LoginCommand) interactiveGetTokenByCode(hostname svchost.Hostname, cred
}),
}
go func() {
defer logging.PanicHandler()
err := server.Serve(listener)
if err != nil && err != http.ErrServerClosed {
diags = diags.Append(tfdiags.Sourceless(

View File

@ -1,77 +0,0 @@
// wrappedreadline is a package that has helpers for interacting with
// readline from a panicwrap executable.
//
// panicwrap overrides the standard file descriptors so that the child process
// no longer looks like a TTY. The helpers here access the extra file descriptors
// passed by panicwrap to fix that.
//
// panicwrap should be checked for with panicwrap.Wrapped before using this
// librar, since this library won't adapt if the binary is not wrapped.
package wrappedreadline
import (
"runtime"
"github.com/chzyer/readline"
"github.com/hashicorp/terraform/internal/helper/wrappedstreams"
)
// Override overrides the values in readline.Config that need to be
// set with wrapped values.
func Override(cfg *readline.Config) *readline.Config {
cfg.Stdin = wrappedstreams.Stdin()
cfg.Stdout = wrappedstreams.Stdout()
cfg.Stderr = wrappedstreams.Stderr()
cfg.FuncGetWidth = TerminalWidth
cfg.FuncIsTerminal = IsTerminal
rm := RawMode{StdinFd: int(wrappedstreams.Stdin().Fd())}
cfg.FuncMakeRaw = rm.Enter
cfg.FuncExitRaw = rm.Exit
return cfg
}
// IsTerminal determines if this process is attached to a TTY.
func IsTerminal() bool {
// Windows is always a terminal
if runtime.GOOS == "windows" {
return true
}
// Same implementation as readline but with our custom fds
return readline.IsTerminal(int(wrappedstreams.Stdin().Fd())) &&
(readline.IsTerminal(int(wrappedstreams.Stdout().Fd())) ||
readline.IsTerminal(int(wrappedstreams.Stderr().Fd())))
}
// TerminalWidth gets the terminal width in characters.
func TerminalWidth() int {
if runtime.GOOS == "windows" {
return readline.GetScreenWidth()
}
return getWidth()
}
// RawMode is a helper for entering and exiting raw mode.
type RawMode struct {
StdinFd int
state *readline.State
}
func (r *RawMode) Enter() (err error) {
r.state, err = readline.MakeRaw(r.StdinFd)
return err
}
func (r *RawMode) Exit() error {
if r.state == nil {
return nil
}
return readline.Restore(r.StdinFd, r.state)
}

View File

@ -1,47 +0,0 @@
//go:build darwin || dragonfly || freebsd || (linux && !appengine) || netbsd || openbsd
// +build darwin dragonfly freebsd linux,!appengine netbsd openbsd
package wrappedreadline
import (
"syscall"
"unsafe"
"github.com/hashicorp/terraform/internal/helper/wrappedstreams"
)
// getWidth impl for Unix
func getWidth() int {
stdoutFd := int(wrappedstreams.Stdout().Fd())
stderrFd := int(wrappedstreams.Stderr().Fd())
w := getWidthFd(stdoutFd)
if w < 0 {
w = getWidthFd(stderrFd)
}
return w
}
type winsize struct {
Row uint16
Col uint16
Xpixel uint16
Ypixel uint16
}
// get width of the terminal
func getWidthFd(stdoutFd int) int {
ws := &winsize{}
retCode, _, errno := syscall.Syscall(syscall.SYS_IOCTL,
uintptr(stdoutFd),
uintptr(syscall.TIOCGWINSZ),
uintptr(unsafe.Pointer(ws)))
if int(retCode) == -1 {
_ = errno
return -1
}
return int(ws.Col)
}

View File

@ -1,9 +0,0 @@
//go:build windows
// +build windows
package wrappedreadline
// getWidth impl for other
func getWidth() int {
return 0
}

View File

@ -1,44 +0,0 @@
// Package wrappedstreams provides access to the standard OS streams
// (stdin, stdout, stderr) even if wrapped under panicwrap.
package wrappedstreams
import (
"os"
"github.com/mitchellh/panicwrap"
)
// Stdin returns the true stdin of the process.
func Stdin() *os.File {
stdin, _, _ := fds()
return stdin
}
// Stdout returns the true stdout of the process.
func Stdout() *os.File {
_, stdout, _ := fds()
return stdout
}
// Stderr returns the true stderr of the process.
func Stderr() *os.File {
_, _, stderr := fds()
return stderr
}
func fds() (stdin, stdout, stderr *os.File) {
stdin, stdout, stderr = os.Stdin, os.Stdout, os.Stderr
if panicwrap.Wrapped(nil) {
initPlatform()
stdin, stdout, stderr = wrappedStdin, wrappedStdout, wrappedStderr
}
return
}
// These are the wrapped standard streams. These are set up by the
// platform specific code in initPlatform.
var (
wrappedStdin *os.File
wrappedStdout *os.File
wrappedStderr *os.File
)

View File

@ -1,22 +0,0 @@
//go:build !windows
// +build !windows
package wrappedstreams
import (
"os"
"sync"
)
var initOnce sync.Once
func initPlatform() {
// These must be initialized lazily, once it's been determined that this is
// a wrapped process.
initOnce.Do(func() {
// The standard streams are passed in via extra file descriptors.
wrappedStdin = os.NewFile(uintptr(3), "stdin")
wrappedStdout = os.NewFile(uintptr(4), "stdout")
wrappedStderr = os.NewFile(uintptr(5), "stderr")
})
}

View File

@ -1,53 +0,0 @@
//go:build windows
// +build windows
package wrappedstreams
import (
"log"
"os"
"syscall"
)
func initPlatform() {
wrappedStdin = openConsole("CONIN$", os.Stdin)
wrappedStdout = openConsole("CONOUT$", os.Stdout)
wrappedStderr = wrappedStdout
}
// openConsole opens a console handle, using a backup if it fails.
// This is used to get the exact console handle instead of the redirected
// handles from panicwrap.
func openConsole(name string, backup *os.File) *os.File {
// Convert to UTF16
path, err := syscall.UTF16PtrFromString(name)
if err != nil {
log.Printf("[ERROR] wrappedstreams: %s", err)
return backup
}
// Determine the share mode
var shareMode uint32
switch name {
case "CONIN$":
shareMode = syscall.FILE_SHARE_READ
case "CONOUT$":
shareMode = syscall.FILE_SHARE_WRITE
}
// Get the file
h, err := syscall.CreateFile(
path,
syscall.GENERIC_READ|syscall.GENERIC_WRITE,
shareMode,
nil,
syscall.OPEN_EXISTING,
0, 0)
if err != nil {
log.Printf("[ERROR] wrappedstreams: %s", err)
return backup
}
// Create the Go file
return os.NewFile(uintptr(h), name)
}

View File

@ -2,75 +2,51 @@ package logging
import (
"fmt"
"io"
"io/ioutil"
"os"
"runtime/debug"
"strings"
"sync"
"github.com/hashicorp/go-hclog"
"github.com/mitchellh/panicwrap"
)
// This output is shown if a panic happens.
const panicOutput = `
!!!!!!!!!!!!!!!!!!!!!!!!!!! TERRAFORM CRASH !!!!!!!!!!!!!!!!!!!!!!!!!!!!
Terraform crashed! This is always indicative of a bug within Terraform.
A crash log has been placed at %[1]q relative to your current
working directory. It would be immensely helpful if you could please
report the crash with Terraform[1] so that we can fix this.
Please report the crash with Terraform[1] so that we can fix this.
When reporting bugs, please include your terraform version. That
information is available on the first line of crash.log. You can also
get it by running 'terraform --version' on the command line.
SECURITY WARNING: the %[1]q file that was created may contain
sensitive information that must be redacted before it is safe to share
on the issue tracker.
When reporting bugs, please include your terraform version, the stack trace
shown below, and any additional information which may help replicate the issue.
[1]: https://github.com/hashicorp/terraform/issues
!!!!!!!!!!!!!!!!!!!!!!!!!!! TERRAFORM CRASH !!!!!!!!!!!!!!!!!!!!!!!!!!!!
`
// panicHandler is what is called by panicwrap when a panic is encountered
// within Terraform. It is guaranteed to run after the resulting process has
// exited so we can take the log file, add in the panic, and store it
// somewhere locally.
func PanicHandler(tmpLogPath string) panicwrap.HandlerFunc {
return func(m string) {
// Create the crash log file where we'll write the logs
f, err := ioutil.TempFile(".", "crash.*.log")
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to create crash log file: %s", err)
return
}
defer f.Close()
tmpLog, err := os.Open(tmpLogPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to open log file %q: %v\n", tmpLogPath, err)
return
}
defer tmpLog.Close()
// Copy the contents to the crash file. This will include
// the panic that just happened.
if _, err = io.Copy(f, tmpLog); err != nil {
fmt.Fprintf(os.Stderr, "Failed to write crash log: %s", err)
return
}
// add the trace back to the log
f.WriteString("\n" + m)
// Tell the user a crash occurred in some helpful way that
// they'll hopefully notice.
fmt.Printf("\n\n")
fmt.Printf(panicOutput, f.Name())
// PanicHandler is called to recover from an internal panic in Terraform, and
// augments the standard stack trace with a more user friendly error message.
// PanicHandler must be called as a defered function, and must be the first
// defer called at the start of a new goroutine.
func PanicHandler() {
recovered := recover()
if recovered == nil {
return
}
fmt.Fprint(os.Stderr, panicOutput)
fmt.Fprint(os.Stderr, recovered)
// When called from a deferred function, debug.PrintStack will include the
// full stack from the point of the pending panic.
debug.PrintStack()
// An exit code of 11 keeps us out of the way of the detailed exitcodes
// from plan, and also happens to be the same code as SIGSEGV which is
// roughly the same type of condition that causes most panics.
os.Exit(11)
}
const pluginPanicOutput = `
@ -181,13 +157,5 @@ func (l *logPanicWrapper) Debug(msg string, args ...interface{}) {
l.panicRecorder(msg)
}
// If we have logging turned on, we need to prevent panicwrap from seeing
// this as a core panic. This can be done by obfuscating the panic error
// line.
if panicPrefix {
colon := strings.Index(msg, ":")
msg = strings.ToUpper(msg[:colon]) + msg[colon:]
}
l.Logger.Debug(msg, args...)
}

View File

@ -1,12 +1,9 @@
package logging
import (
"bytes"
"fmt"
"strings"
"testing"
"github.com/hashicorp/go-hclog"
)
func TestPanicRecorder(t *testing.T) {
@ -52,31 +49,3 @@ func TestPanicLimit(t *testing.T) {
}
}
}
func TestLogPanicWrapper(t *testing.T) {
var buf bytes.Buffer
logger := hclog.NewInterceptLogger(&hclog.LoggerOptions{
Name: "test",
Level: hclog.Debug,
Output: &buf,
DisableTime: true,
})
wrapped := (&logPanicWrapper{
Logger: logger,
}).Named("test")
wrapped.Debug("panic: invalid foo of bar")
wrapped.Debug("\tstack trace")
expected := `[DEBUG] test.test: PANIC: invalid foo of bar
[DEBUG] test.test: stack trace
`
got := buf.String()
if expected != got {
t.Fatalf("Expected:\n%q\nGot:\n%q", expected, got)
}
}

View File

@ -1,78 +0,0 @@
package terminal
import "os"
// This file has some annoying nonsense to, yet again, work around the
// panicwrap hack.
//
// Specifically, typically when we're running Terraform the stderr handle is
// not directly connected to the terminal but is instead a pipe into a parent
// process gathering up the output just in case a panic message appears.
// However, this package needs to know whether the _real_ stderr is connected
// to a terminal and what its width is.
//
// To work around that, we'll first initialize the terminal in the parent
// process, and then capture information about stderr into an environment
// variable so we can pass it down to the child process. The child process
// will then use the environment variable to pretend that the panicwrap pipe
// has the same characteristics as the terminal that it's indirectly writing
// to.
//
// This file has some helpers for implementing that awkward handshake, but the
// handshake itself is in package main, interspersed with all of the other
// panicwrap machinery.
//
// You might think that the code in helper/wrappedstreams could avoid this
// problem, but that package is broken on Windows: it always fails to recover
// the real stderr, and it also gets an incorrect result if the user was
// redirecting or piping stdout/stdin. So... we have this hack instead, which
// gets a correct result even on Windows and even with I/O redirection.
// StateForAfterPanicWrap is part of the workaround for panicwrap that
// captures some characteristics of stderr that the caller can pass to the
// panicwrap child process somehow and then use ReinitInsidePanicWrap.
func (s *Streams) StateForAfterPanicWrap() *PrePanicwrapState {
return &PrePanicwrapState{
StderrIsTerminal: s.Stderr.IsTerminal(),
StderrWidth: s.Stderr.Columns(),
}
}
// ReinitInsidePanicwrap is part of the workaround for panicwrap that
// produces a Streams containing a potentially-lying Stderr that might
// claim to be a terminal even if it's actually a pipe connected to the
// parent process.
//
// That's an okay lie in practice because the parent process will copy any
// data it recieves via that pipe verbatim to the real stderr anyway. (The
// original call to Init in the parent process should've already done any
// necessary modesetting on the Stderr terminal, if any.)
//
// The state argument can be nil if we're not running in panicwrap mode,
// in which case this function behaves exactly the same as Init.
func ReinitInsidePanicwrap(state *PrePanicwrapState) (*Streams, error) {
ret, err := Init()
if err != nil {
return ret, err
}
if state != nil {
// A lying stderr, then.
ret.Stderr = &OutputStream{
File: ret.Stderr.File,
isTerminal: func(f *os.File) bool {
return state.StderrIsTerminal
},
getColumns: func(f *os.File) int {
return state.StderrWidth
},
}
}
return ret, nil
}
// PrePanicwrapState is a horrible thing we use to work around panicwrap,
// related to both Streams.StateForAfterPanicWrap and ReinitInsidePanicwrap.
type PrePanicwrapState struct {
StderrIsTerminal bool
StderrWidth int
}

View File

@ -9,6 +9,7 @@ import (
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/provisioners"
"github.com/hashicorp/terraform/internal/states"
@ -264,6 +265,8 @@ func (c *Context) watchStop(walker *ContextGraphWalker) (chan struct{}, <-chan s
done := c.runContext.Done()
go func() {
defer logging.PanicHandler()
defer close(wait)
// Wait for a stop or completion
select {

View File

@ -4,6 +4,7 @@ import (
"log"
"strings"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/hashicorp/terraform/internal/addrs"
@ -39,6 +40,10 @@ func (g *Graph) walk(walker GraphWalker) tfdiags.Diagnostics {
// Walk the graph.
walkFn := func(v dag.Vertex) (diags tfdiags.Diagnostics) {
// the walkFn is called asynchronously, and needs to be recovered
// separately in the case of a panic.
defer logging.PanicHandler()
log.Printf("[TRACE] vertex %q: starting visit (%T)", dag.VertexName(v), v)
defer func() {

95
main.go
View File

@ -3,7 +3,6 @@ package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net"
"os"
@ -24,7 +23,6 @@ import (
"github.com/mattn/go-shellwords"
"github.com/mitchellh/cli"
"github.com/mitchellh/colorstring"
"github.com/mitchellh/panicwrap"
backendInit "github.com/hashicorp/terraform/internal/backend/init"
)
@ -35,12 +33,6 @@ 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
@ -54,67 +46,6 @@ func (u *ui) Warn(msg string) {
u.Ui.Output(msg)
}
func main() {
os.Exit(realMain())
}
func realMain() int {
var wrapConfig panicwrap.WrapConfig
// don't re-exec terraform as a child process for easier debugging
if os.Getenv("TF_FORK") == "0" {
return wrappedMain()
}
if !panicwrap.Wrapped(&wrapConfig) {
// We always send logs to a temporary file that we use in case
// there is a panic. Otherwise, we delete it.
logTempFile, err := ioutil.TempFile("", "terraform-log")
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't set up logging tempfile: %s", err)
return 1
}
// Now that we have the file, close it and leave it for the wrapped
// process to write to.
logTempFile.Close()
defer os.Remove(logTempFile.Name())
// 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
wrapConfig.ForwardSignals = forwardSignals
exitStatus, err := panicwrap.Wrap(&wrapConfig)
if err != nil {
fmt.Fprintf(os.Stderr, "Couldn't start Terraform: %s", err)
return 1
}
return exitStatus
}
// Call the real main
return wrappedMain()
}
func init() {
Ui = &ui{&cli.BasicUi{
Writer: os.Stdout,
@ -123,7 +54,13 @@ func init() {
}}
}
func wrappedMain() int {
func main() {
os.Exit(realMain())
}
func realMain() int {
defer logging.PanicHandler()
var err error
tmpLogPath := os.Getenv(envTmpLogPath)
@ -145,19 +82,7 @@ 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)
streams, err := terminal.Init()
if err != nil {
Ui.Error(fmt.Sprintf("Failed to configure the terminal: %s", err))
return 1
@ -391,9 +316,7 @@ func wrappedMain() int {
// plugins crashing
if exitCode != 0 {
for _, panicLog := range logging.PluginPanics() {
// we don't write this to Error, or else panicwrap will think this
// process panicked
Ui.Info(panicLog)
Ui.Error(panicLog)
}
}

View File

@ -130,7 +130,7 @@ func TestMain_cliArgsFromEnv(t *testing.T) {
// Run it!
os.Args = args
testCommand.Args = nil
exit := wrappedMain()
exit := realMain()
if (exit != 0) != tc.Err {
t.Fatalf("bad: %d", exit)
}
@ -237,7 +237,7 @@ func TestMain_cliArgsFromEnvAdvanced(t *testing.T) {
// Run it!
os.Args = args
testCommand.Args = nil
exit := wrappedMain()
exit := realMain()
if (exit != 0) != tc.Err {
t.Fatalf("unexpected exit status %d; want 0", exit)
}
@ -275,7 +275,7 @@ func TestMain_autoComplete(t *testing.T) {
// Run it!
os.Args = []string{"terraform", "terraform", "versio"}
exit := wrappedMain()
exit := realMain()
if exit != 0 {
t.Fatalf("unexpected exit status %d; want 0", exit)
}

View File

@ -18,11 +18,6 @@
set -eu
# Make sure we're debugging the process where the code is actually running.
# (This also, as a side effect, causes raw logs to go directly to stderr,
# and panics to be expressed directly, since we lose the log/panic wrapper.)
export TF_FORK=0
echo "Launching Terraform in a headless debug session"
echo "Connect to it using: dlv connect 127.0.0.1:2345"
echo "(Terraform takes a long time to build and launch in this mode; some logs will appear below)"