Skip to content

Commit

Permalink
cmd/analyze: use exit status 1 and 2 for errors, improve error messag…
Browse files Browse the repository at this point in the history
…es for invalid cli arguments (#967)

* cli usability improvements for cmd/analyze (print errors for missing flags, exit codes)

Signed-off-by: Max Fisher <[email protected]>

* use exit code 2 for user errors and store exit codes in variables

Signed-off-by: Max Fisher <[email protected]>

* introduce usage error type and function and simplify exit codes to 0 or 1

Signed-off-by: Max Fisher <[email protected]>

* print different message depending on whether the error was a usage error or not, streamline error message for invalid feature flag

Signed-off-by: Max Fisher <[email protected]>

* exit(2) on user error, use %q to quote strings, improve formatting of available ecosystems in usage text

Signed-off-by: Max Fisher <[email protected]>

---------

Signed-off-by: Max Fisher <[email protected]>
  • Loading branch information
maxfisher-g authored Nov 15, 2023
1 parent f14994f commit 1740a95
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 18 deletions.
58 changes: 42 additions & 16 deletions cmd/analyze/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package main

import (
"context"
"errors"
"flag"
"fmt"
"log/slog"
Expand Down Expand Up @@ -46,6 +47,15 @@ var (
"list of analysis modes to run, separated by commas. Use -list-modes to see available options")
)

// usageError wraps an error, to signal that the error arises from incorrect user input.
type usageError struct {
error
}

func usagef(format string, args ...any) error {
return usageError{fmt.Errorf(format, args...)}
}

func makeResultStores() worker.ResultStores {
rs := worker.ResultStores{}

Expand Down Expand Up @@ -167,59 +177,61 @@ func staticAnalysis(ctx context.Context, pkg *pkgmanager.Pkg, resultStores *work
}
}

func main() {
func run() error {
log.Initialize(os.Getenv("LOGGER_ENV"))

flag.TextVar(&ecosystem, "ecosystem", pkgecosystem.None, fmt.Sprintf("package ecosystem. Can be %s", pkgecosystem.SupportedEcosystemsStrings))
flag.TextVar(&ecosystem, "ecosystem", pkgecosystem.None, "package ecosystem. Available: "+
strings.Join(pkgecosystem.SupportedEcosystemsStrings, ", "))

analysisMode.InitFlag()
flag.Parse()

if err := featureflags.Update(*features); err != nil {
slog.Error("Failed to parse flags", "error", err)
return
return usageError{err}
}

if *help {
flag.Usage()
return
return nil
}

if *listModes {
printAnalysisModes()
return
return nil
}

if *listFeatures {
printFeatureFlags()
return
return nil
}

if ecosystem == pkgecosystem.None {
flag.Usage()
return
return usagef("missing ecosystem")
}
ctx := log.ContextWithAttrs(context.Background(), slog.Any("ecosystem", ecosystem))

manager := pkgmanager.Manager(ecosystem)
if manager == nil {
slog.ErrorContext(ctx, "Unsupported pkg manager")
os.Exit(1)
return usagef("unsupported package ecosystem %q", ecosystem)
}

if *pkgName == "" {
flag.Usage()
return
return usagef("missing package name")
}
ctx = log.ContextWithAttrs(ctx, slog.String("name", *pkgName), slog.String("version", *version))

ctx := log.ContextWithAttrs(context.Background(),
slog.Any("ecosystem", ecosystem),
slog.String("name", *pkgName),
slog.String("version", *version),
)

runMode := make(map[analysis.Mode]bool)
for _, analysisName := range analysisMode.Values {
mode, ok := analysis.ModeFromString(strings.ToLower(analysisName))
if !ok {
slog.ErrorContext(ctx, "Unknown analysis mode: "+analysisName)
printAnalysisModes()
return
return usagef("unknown analysis mode %q", analysisName)
}
runMode[mode] = true
}
Expand All @@ -229,7 +241,7 @@ func main() {
pkg, err := worker.ResolvePkg(manager, *pkgName, *version, *localPkg)
if err != nil {
slog.ErrorContext(ctx, "Error resolving package", "error", err)
os.Exit(1)
return err
}

resultStores := makeResultStores()
Expand All @@ -244,4 +256,18 @@ func main() {
slog.InfoContext(ctx, "Starting dynamic analysis")
dynamicAnalysis(ctx, pkg, &resultStores)
}

return nil
}

func main() {
if err := run(); err != nil {
if errors.As(err, &usageError{}) {
fmt.Fprintf(os.Stderr, "Usage error: %v\n", err)
os.Exit(2)
} else {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
}
4 changes: 2 additions & 2 deletions internal/featureflags/featureflags.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"strings"
)

var ErrUndefinedFlag = errors.New("flag is undefined")
var ErrUndefinedFlag = errors.New("undefined feature flag")

var flagRegistry = make(map[string]*FeatureFlag)

Expand Down Expand Up @@ -54,7 +54,7 @@ func Update(flags string) error {
if ff, ok := flagRegistry[n]; ok {
ff.isEnabled = isEnabled
} else {
return fmt.Errorf("%w: %s", ErrUndefinedFlag, n)
return fmt.Errorf("%w %q", ErrUndefinedFlag, n)
}
}
return nil
Expand Down

0 comments on commit 1740a95

Please sign in to comment.