Unify some CLI error reporting

This unifies some of the CLI error reporting logic.  It's still
not perfect, but this tidies up some minor issues that were starting
to annoy me (e.g., inconsistencies in message formatting, message
colorization, and exit code handling).
This commit is contained in:
joeduffy 2017-03-01 10:09:27 -08:00
parent 49f5f3debc
commit f93e093ab3
10 changed files with 46 additions and 52 deletions

View file

@ -4,10 +4,15 @@ package cmd
import (
"flag"
"fmt"
"os"
"strconv"
"github.com/golang/glog"
"github.com/spf13/cobra"
"github.com/pulumi/coconut/pkg/compiler/core"
"github.com/pulumi/coconut/pkg/diag"
)
func NewCoconutCmd() *cobra.Command {
@ -45,3 +50,27 @@ func NewCoconutCmd() *cobra.Command {
return cmd
}
var snk diag.Sink
// sink lazily allocates a sink to be used if we can't create a compiler.
func sink() diag.Sink {
if snk == nil {
snk = core.DefaultSink("")
}
return snk
}
// exitErrorPrefix is auto-appended to any abrupt command exit.
const exitErrorPrefix = "fatal: "
// exitError issues an error and exits with a standard error exit code.
func exitError(msg string, args ...interface{}) {
exitErrorCode(-1, msg, args...)
}
// exitErrorCode issues an error and exists with the given error exit code.
func exitErrorCode(code int, msg string, args ...interface{}) {
sink().Errorf(diag.Message(exitErrorPrefix + fmt.Sprintf(msg, args...)))
os.Exit(code)
}

View file

@ -42,8 +42,7 @@ func newDescribeCmd() *cobra.Command {
pwd, _ := os.Getwd()
pkgpath, err := workspace.DetectPackage(pwd, sink())
if err != nil {
fmt.Fprintf(os.Stderr, "fatal: could not find a nut: %v", err)
os.Exit(-1)
exitError("could not locate a nut to load: %v", err)
}
if pkg := cmdutil.ReadPackage(pkgpath); pkg != nil {

View file

@ -38,8 +38,7 @@ func newEvalCmd() *cobra.Command {
if dotOutput {
// Convert the output to a DOT file.
if err := dotconv.Print(g, os.Stdout); err != nil {
fmt.Fprintf(os.Stderr, "error: failed to write DOT file to output: %v\n", err)
os.Exit(-1)
exitError("failed to write DOT file to output: %v", err)
}
} else {
// Just print a very basic, yet (hopefully) aesthetically pleasinge, ascii-ization of the graph.

View file

@ -53,23 +53,13 @@ func newHuskCmd() *cobra.Command {
return cmd
}
var snk diag.Sink
// sink lazily allocates a sink to be used if we can't create a compiler.
func sink() diag.Sink {
if snk == nil {
snk = core.DefaultSink("")
}
return snk
}
func initHuskCmd(cmd *cobra.Command, args []string) (*huskCmdInfo, error) {
func initHuskCmd(cmd *cobra.Command, args []string) *huskCmdInfo {
// Create a new context for the plan operations.
ctx := resource.NewContext(sink())
// Read in the name of the husk to use.
if len(args) == 0 {
return nil, fmt.Errorf("missing required husk name")
exitError("missing required husk name")
}
// Read in the deployment information, bailing if an IO error occurs.
@ -77,7 +67,7 @@ func initHuskCmd(cmd *cobra.Command, args []string) (*huskCmdInfo, error) {
huskfile, husk, old := readHusk(ctx, name)
if husk == nil {
contract.Assert(!ctx.Diag.Success())
return nil, fmt.Errorf("failed to read huskfile") // failure reading the husk information.
exitError("could not read huskfile required to proceed") // failure reading the husk information.
}
return &huskCmdInfo{
Ctx: ctx,
@ -86,7 +76,7 @@ func initHuskCmd(cmd *cobra.Command, args []string) (*huskCmdInfo, error) {
Old: old,
Args: args[1:],
Orig: args,
}, nil
}
}
type huskCmdInfo struct {

View file

@ -26,11 +26,8 @@ func newHuskDeployCmd() *cobra.Command {
"\n" +
"By default, the Nut to execute is loaded from the current directory. Optionally, an\n" +
"explicit path can be provided using the [nut-file] argument.",
RunE: func(cmd *cobra.Command, args []string) error {
info, err := initHuskCmd(cmd, args)
if err != nil {
return err
}
Run: func(cmd *cobra.Command, args []string) {
info := initHuskCmd(cmd, args)
apply(cmd, info, applyOptions{
Delete: false,
DryRun: dryRun,
@ -39,7 +36,6 @@ func newHuskDeployCmd() *cobra.Command {
Summary: summary,
Output: output,
})
return nil
},
}

View file

@ -21,11 +21,8 @@ func newHuskDestroyCmd() *cobra.Command {
"\n" +
"Warning: although old snapshots can be used to recreate an environment, this command\n" +
"is generally irreversable and should be used with great care.",
RunE: func(cmd *cobra.Command, args []string) error {
info, err := initHuskCmd(cmd, args)
if err != nil {
return err
}
Run: func(cmd *cobra.Command, args []string) {
info := initHuskCmd(cmd, args)
if dryRun || yes ||
confirmPrompt("This will permanently destroy all resources in the '%v' husk!", info.Husk.Name) {
apply(cmd, info, applyOptions{
@ -34,7 +31,6 @@ func newHuskDestroyCmd() *cobra.Command {
Summary: summary,
})
}
return nil
},
}

View file

@ -3,9 +3,6 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/pulumi/coconut/pkg/tokens"
@ -22,8 +19,7 @@ func newHuskInitCmd() *cobra.Command {
Run: func(cmd *cobra.Command, args []string) {
// Read in the name of the husk to use.
if len(args) == 0 {
fmt.Fprintf(os.Stderr, "fatal: missing required husk name\n")
os.Exit(-1)
exitError("missing required husk name")
}
husk := tokens.QName(args[0])

View file

@ -27,8 +27,7 @@ func newHuskLsCmd() *cobra.Command {
path := workspace.HuskPath("")
files, err := ioutil.ReadDir(path)
if err != nil && !os.IsNotExist(err) {
fmt.Fprintf(os.Stderr, "fatal: could not read husks: %v\n", err)
os.Exit(-1)
exitError("could not read husks: %v", err)
}
fmt.Printf("%-20s %-48s %-12s\n", "NAME", "LAST DEPLOYMENT", "RESOURCE COUNT")

View file

@ -3,8 +3,6 @@
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
@ -20,20 +18,16 @@ func newHuskRmCmd() *cobra.Command {
"`destroy` command for removing a husk's resources, as it is a distinct operation.\n" +
"\n" +
"After this command completes, the husk will no longer be available for deployments.",
RunE: func(cmd *cobra.Command, args []string) error {
info, err := initHuskCmd(cmd, args)
if err != nil {
return err
}
Run: func(cmd *cobra.Command, args []string) {
info := initHuskCmd(cmd, args)
if !force && info.Old != nil && len(info.Old.Resources()) > 0 {
return fmt.Errorf(
"Husk '%v' still has resources; removal rejected; pass --force to override\n", info.Husk.Name)
exitError(
"'%v' still has resources; removal rejected; pass --force to override", info.Husk.Name)
}
if yes ||
confirmPrompt("This will permanently remove the '%v' husk!", info.Husk.Name) {
remove(info.Husk)
}
return nil
},
}

View file

@ -3,9 +3,6 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
@ -25,8 +22,7 @@ func newVerifyCmd() *cobra.Command {
Run: func(cmd *cobra.Command, args []string) {
// Create a compiler object and perform the verification.
if !verify(cmd, args) {
fmt.Printf("fatal: Nut verification failed\n")
os.Exit(-1)
exitError("Nut verification failed")
}
},
}