Skip to content

Commit

Permalink
Add sentinel error that can set command exit code
Browse files Browse the repository at this point in the history
  • Loading branch information
dadgar committed Jun 3, 2024
1 parent ca9d8a2 commit e1fccb7
Show file tree
Hide file tree
Showing 3 changed files with 52 additions and 1 deletion.
27 changes: 27 additions & 0 deletions internal/pkg/cmd/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package cmd

import (
"errors"
"fmt"
"time"

"github.com/hashicorp/go-hclog"
Expand Down Expand Up @@ -290,3 +291,29 @@ func (c *Command) Logger() hclog.Logger {
c.logger = hclog.New(logOpt)
return c.logger
}

// ExitCodeError is an error that includes an exit code. If returned by a
// command run, the command will exit using the specified exit code.
type ExitCodeError struct {
Err error
Code int
}

// NewExitError returns an ExitCodError. This can be returned to have the
// command exit code be set to a specific value.
func NewExitError(code int, wrapErr error) error {
return &ExitCodeError{
Err: wrapErr,
Code: code,
}
}

func (e *ExitCodeError) Error() string {
if e.Err != nil {
return fmt.Sprintf("exit code %d: %v", e.Code, e.Err)
}

return fmt.Sprintf("exit code %d", e.Code)
}

func (e *ExitCodeError) Unwrap() error { return e.Err }
6 changes: 5 additions & 1 deletion internal/pkg/cmd/command_internal.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,9 @@ func (c *Command) Run(args []string) int {

// Run the command
if err := c.RunF(c, parsedArgs); err != nil {
exitCode := 1
var runtimeErr runtime.ClientResponseStatus
var exitCodeErr *ExitCodeError
if errors.Is(err, ErrDisplayHelp) {
return cli.RunResultHelp
} else if errors.Is(err, ErrDisplayUsage) {
Expand All @@ -110,10 +112,12 @@ func (c *Command) Run(args []string) int {
// Request failed because of authentication issues.
fmt.Fprintf(io.Err(), "%s %s\n\n", cs.ErrorLabel(), authErrorHelp(io, c.commandPath(), args))
return 1
} else if errors.As(err, &exitCodeErr) {
exitCode = exitCodeErr.Code
}

fmt.Fprintf(io.Err(), "%s %s\n", cs.ErrorLabel(), wordWrap(err.Error(), 120))
return 1
return exitCode
}

return 0
Expand Down
20 changes: 20 additions & 0 deletions internal/pkg/cmd/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package cmd

import (
"fmt"
"testing"

"github.com/hashicorp/hcp/internal/pkg/iostreams"
Expand Down Expand Up @@ -111,3 +112,22 @@ func TestCommand_Logger(t *testing.T) {
r.Zero(child.Run([]string{}))
r.Contains(io.Error.String(), "hcp.child: hello, world!")
}

func TestCommand_ExitCode(t *testing.T) {
t.Parallel()
r := require.New(t)

// Create the command tree
io := iostreams.Test()
code := 42
err := fmt.Errorf("bad bad bad")
root := &Command{
Name: "root",
io: io,
RunF: func(c *Command, args []string) error {
return NewExitError(code, err)
},
}
r.Equal(code, root.Run([]string{}))
r.Contains(io.Error.String(), err.Error())
}

0 comments on commit e1fccb7

Please sign in to comment.