Recover from panic gracefully (#2353)

## Changes
This PR adds a recovery function for panics. This indicates to all users
running into a panic that it's a bug and they should report it to
Databricks.

## Tests
Manually and acceptance test.

Before:
```
.venv➜  cli git:(panic-r) ✗ ./cli selftest panic                                                
panic: the databricks selftest panic command always panics

goroutine 1 [running]:
github.com/databricks/cli/cmd/selftest.New.newPanic.func1(0x1400016f208?, {0x1016ca925?, 0x4?, 0x1016ca929?})
        /Users/shreyas.goenka/cli2/cli/cmd/selftest/panic.go:9 +0x2c
github.com/spf13/cobra.(*Command).execute(0x1400016f208, {0x10279bc40, 0x0, 0x0})
        /Users/shreyas.goenka/cli2/cli/vendor/github.com/spf13/cobra/command.go:989 +0x81c
github.com/spf13/cobra.(*Command).ExecuteC(0x14000428908)
        /Users/shreyas.goenka/cli2/cli/vendor/github.com/spf13/cobra/command.go:1117 +0x344
github.com/spf13/cobra.(*Command).ExecuteContextC(...)
        /Users/shreyas.goenka/cli2/cli/vendor/github.com/spf13/cobra/command.go:1050
github.com/databricks/cli/cmd/root.Execute({0x101d60440?, 0x10279bc40?}, 0x10266dd78?)
        /Users/shreyas.goenka/cli2/cli/cmd/root/root.go:101 +0x58
main.main()
        /Users/shreyas.goenka/cli2/cli/main.go:13 +0x44
```

After:
```
.venv➜  cli git:(panic-r) ./cli selftest panic
The Databricks CLI unexpectedly had a fatal error.
Please report this issue to Databricks in the form of a GitHub issue at:
https://github.com/databricks/cli

CLI Version: 0.0.0-dev+aae7ced52d36

Panic Payload: the databricks selftest panic command always panics

Stack Trace:
goroutine 1 [running]:
runtime/debug.Stack()
        /Users/shreyas.goenka/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.4.darwin-arm64/src/runtime/debug/stack.go:26 +0x64
github.com/databricks/cli/cmd/root.Execute.func1()
        /Users/shreyas.goenka/cli2/cli/cmd/root/root.go:110 +0xa4
panic({0x10368b5e0?, 0x1039d6d70?})
        /Users/shreyas.goenka/go/pkg/mod/golang.org/toolchain@v0.0.1-go1.23.4.darwin-arm64/src/runtime/panic.go:785 +0x124
github.com/databricks/cli/cmd/selftest.New.newPanic.func1(0x14000145208?, {0x103356be5?, 0x4?, 0x103356be9?})
        /Users/shreyas.goenka/cli2/cli/cmd/selftest/panic.go:9 +0x2c
github.com/spf13/cobra.(*Command).execute(0x14000145208, {0x104427c40, 0x0, 0x0})
        /Users/shreyas.goenka/cli2/cli/vendor/github.com/spf13/cobra/command.go:989 +0x81c
github.com/spf13/cobra.(*Command).ExecuteC(0x14000400c08)
        /Users/shreyas.goenka/cli2/cli/vendor/github.com/spf13/cobra/command.go:1117 +0x344
github.com/spf13/cobra.(*Command).ExecuteContextC(...)
        /Users/shreyas.goenka/cli2/cli/vendor/github.com/spf13/cobra/command.go:1050
github.com/databricks/cli/cmd/root.Execute({0x1039ec440?, 0x104427c40?}, 0x14000400c08)
        /Users/shreyas.goenka/cli2/cli/cmd/root/root.go:128 +0x94
main.main()
        /Users/shreyas.goenka/cli2/cli/main.go:13 +0x44
```
This commit is contained in:
shreyas-goenka 2025-02-27 18:57:36 +05:30 committed by GitHub
parent c1f835f951
commit bf2aded8e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 78 additions and 3 deletions

View File

@ -0,0 +1,15 @@
>>> [CLI] selftest panic
The Databricks CLI unexpectedly had a fatal error.
Please report this issue to Databricks in the form of a GitHub issue at:
https://github.com/databricks/cli
CLI Version: [DEV_VERSION]
Panic Payload: the databricks selftest panic command always panics
Stack Trace:
goroutine 1 [running]:
runtime/debug.Stack()
Exit code: 1

5
acceptance/panic/script Normal file
View File

@ -0,0 +1,5 @@
# We filter anything after runtime/debug.Stack() in the output because the stack
# trace itself is hard to perform replacements on, since it can depend upon the
# exact setup of where the modules are installed in your Go setup, memory addresses
# at runtime etc.
trace $CLI selftest panic 2>&1 | sed '/runtime\/debug\.Stack()/q'

View File

View File

@ -12,6 +12,7 @@ import (
"github.com/databricks/cli/cmd/fs" "github.com/databricks/cli/cmd/fs"
"github.com/databricks/cli/cmd/labs" "github.com/databricks/cli/cmd/labs"
"github.com/databricks/cli/cmd/root" "github.com/databricks/cli/cmd/root"
"github.com/databricks/cli/cmd/selftest"
"github.com/databricks/cli/cmd/sync" "github.com/databricks/cli/cmd/sync"
"github.com/databricks/cli/cmd/version" "github.com/databricks/cli/cmd/version"
"github.com/databricks/cli/cmd/workspace" "github.com/databricks/cli/cmd/workspace"
@ -74,6 +75,7 @@ func New(ctx context.Context) *cobra.Command {
cli.AddCommand(labs.New(ctx)) cli.AddCommand(labs.New(ctx))
cli.AddCommand(sync.New()) cli.AddCommand(sync.New())
cli.AddCommand(version.New()) cli.AddCommand(version.New())
cli.AddCommand(selftest.New())
return cli return cli
} }

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"log/slog" "log/slog"
"os" "os"
"runtime/debug"
"strings" "strings"
"github.com/databricks/cli/internal/build" "github.com/databricks/cli/internal/build"
@ -96,11 +97,35 @@ func flagErrorFunc(c *cobra.Command, err error) error {
// Execute adds all child commands to the root command and sets flags appropriately. // Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd. // This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute(ctx context.Context, cmd *cobra.Command) error { func Execute(ctx context.Context, cmd *cobra.Command) (err error) {
// TODO: deferred panic recovery defer func() {
r := recover()
// No panic. Return normally.
if r == nil {
return
}
version := build.GetInfo().Version
trace := debug.Stack()
// Set the error so that the CLI exits with a non-zero exit code.
err = fmt.Errorf("panic: %v", r)
fmt.Fprintf(cmd.ErrOrStderr(), `The Databricks CLI unexpectedly had a fatal error.
Please report this issue to Databricks in the form of a GitHub issue at:
https://github.com/databricks/cli
CLI Version: %s
Panic Payload: %v
Stack Trace:
%s`, version, r, string(trace))
}()
// Run the command // Run the command
cmd, err := cmd.ExecuteContextC(ctx) cmd, err = cmd.ExecuteContextC(ctx)
if err != nil && !errors.Is(err, ErrAlreadyPrinted) { if err != nil && !errors.Is(err, ErrAlreadyPrinted) {
// If cmdio logger initialization succeeds, then this function logs with the // If cmdio logger initialization succeeds, then this function logs with the
// initialized cmdio logger, otherwise with the default cmdio logger // initialized cmdio logger, otherwise with the default cmdio logger

12
cmd/selftest/panic.go Normal file
View File

@ -0,0 +1,12 @@
package selftest
import "github.com/spf13/cobra"
func newPanic() *cobra.Command {
return &cobra.Command{
Use: "panic",
Run: func(cmd *cobra.Command, args []string) {
panic("the databricks selftest panic command always panics")
},
}
}

16
cmd/selftest/selftest.go Normal file
View File

@ -0,0 +1,16 @@
package selftest
import (
"github.com/spf13/cobra"
)
func New() *cobra.Command {
cmd := &cobra.Command{
Use: "selftest",
Short: "Non functional CLI commands that are useful for testing",
Hidden: true,
}
cmd.AddCommand(newPanic())
return cmd
}