From b7d1e2c48354bd80444fd6da1063cf42628e8ce9 Mon Sep 17 00:00:00 2001 From: mmetc <92726601+mmetc@users.noreply.github.com> Date: Mon, 20 Feb 2023 15:05:42 +0100 Subject: [PATCH] replace log.Fatal -> fmt.Errorf (#2058) --- cmd/crowdsec-cli/alerts.go | 57 +++++++++-------- cmd/crowdsec-cli/bouncers.go | 29 ++++----- cmd/crowdsec-cli/explain.go | 18 +++--- cmd/crowdsec-cli/hubtest.go | 116 ++++++++++++++++++++--------------- cmd/crowdsec-cli/machines.go | 60 ++++++++++-------- cmd/crowdsec-cli/main.go | 5 +- tests/bats/01_base.bats | 23 +++++++ tests/bats/02_nolapi.bats | 4 +- tests/bats/80_alerts.bats | 5 +- 9 files changed, 191 insertions(+), 126 deletions(-) diff --git a/cmd/crowdsec-cli/alerts.go b/cmd/crowdsec-cli/alerts.go index 3d10efc14..f617f38bc 100644 --- a/cmd/crowdsec-cli/alerts.go +++ b/cmd/crowdsec-cli/alerts.go @@ -253,13 +253,13 @@ cscli alerts list --range 1.2.3.0/24 cscli alerts list -s crowdsecurity/ssh-bf cscli alerts list --type ban`, DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { var err error if err := manageCliDecisionAlerts(alertListFilter.IPEquals, alertListFilter.RangeEquals, alertListFilter.ScopeEquals, alertListFilter.ValueEquals); err != nil { printHelp(cmd) - log.Fatalf("%s", err) + return err } if limit != nil { alertListFilter.Limit = limit @@ -273,7 +273,7 @@ cscli alerts list --type ban`, days, err := strconv.Atoi(realDuration) if err != nil { printHelp(cmd) - log.Fatalf("Can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *alertListFilter.Until) + return fmt.Errorf("can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *alertListFilter.Until) } *alertListFilter.Until = fmt.Sprintf("%d%s", days*24, "h") } @@ -285,7 +285,7 @@ cscli alerts list --type ban`, days, err := strconv.Atoi(realDuration) if err != nil { printHelp(cmd) - log.Fatalf("Can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *alertListFilter.Since) + return fmt.Errorf("can't parse duration %s, valid durations format: 1d, 4h, 4h15m", *alertListFilter.Since) } *alertListFilter.Since = fmt.Sprintf("%d%s", days*24, "h") } @@ -323,13 +323,15 @@ cscli alerts list --type ban`, alerts, _, err := Client.Alerts.List(context.Background(), alertListFilter) if err != nil { - log.Fatalf("Unable to list alerts : %v", err) + return fmt.Errorf("unable to list alerts: %v", err) } err = AlertsToTable(alerts, printMachine) if err != nil { - log.Fatalf("unable to list alerts : %v", err) + return fmt.Errorf("unable to list alerts: %v", err) } + + return nil }, } cmdAlertsList.Flags().SortFlags = false @@ -372,25 +374,27 @@ cscli alerts delete -s crowdsecurity/ssh-bf"`, DisableAutoGenTag: true, Aliases: []string{"remove"}, Args: cobra.ExactArgs(0), - PreRun: func(cmd *cobra.Command, args []string) { + PreRunE: func(cmd *cobra.Command, args []string) error { if AlertDeleteAll { - return + return nil } if *alertDeleteFilter.ScopeEquals == "" && *alertDeleteFilter.ValueEquals == "" && *alertDeleteFilter.ScenarioEquals == "" && *alertDeleteFilter.IPEquals == "" && *alertDeleteFilter.RangeEquals == "" && delAlertByID == "" { _ = cmd.Usage() - log.Fatalln("At least one filter or --all must be specified") + return fmt.Errorf("at least one filter or --all must be specified") } + + return nil }, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { var err error if !AlertDeleteAll { if err := manageCliDecisionAlerts(alertDeleteFilter.IPEquals, alertDeleteFilter.RangeEquals, alertDeleteFilter.ScopeEquals, alertDeleteFilter.ValueEquals); err != nil { printHelp(cmd) - log.Fatalf("%s", err) + return err } if ActiveDecision != nil { alertDeleteFilter.ActiveDecisionEquals = ActiveDecision @@ -425,15 +429,17 @@ cscli alerts delete -s crowdsecurity/ssh-bf"`, if delAlertByID == "" { alerts, _, err = Client.Alerts.Delete(context.Background(), alertDeleteFilter) if err != nil { - log.Fatalf("Unable to delete alerts : %v", err) + return fmt.Errorf("unable to delete alerts : %v", err) } } else { alerts, _, err = Client.Alerts.DeleteOne(context.Background(), delAlertByID) if err != nil { - log.Fatalf("Unable to delete alert : %v", err) + return fmt.Errorf("unable to delete alert: %v", err) } } log.Infof("%s alert(s) deleted", alerts.NbDeleted) + + return nil }, } cmdAlertsDelete.Flags().SortFlags = false @@ -455,20 +461,19 @@ func NewAlertsInspectCmd() *cobra.Command { Short: `Show info about an alert`, Example: `cscli alerts inspect 123`, DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { if len(args) == 0 { printHelp(cmd) - return + return fmt.Errorf("missing alert_id") } for _, alertID := range args { id, err := strconv.Atoi(alertID) if err != nil { - log.Fatalf("bad alert id %s", alertID) - continue + return fmt.Errorf("bad alert id %s", alertID) } alert, _, err := Client.Alerts.GetByID(context.Background(), id) if err != nil { - log.Fatalf("can't find alert with id %s: %s", alertID, err) + return fmt.Errorf("can't find alert with id %s: %s", alertID, err) } switch csConfig.Cscli.Output { case "human": @@ -478,17 +483,19 @@ func NewAlertsInspectCmd() *cobra.Command { case "json": data, err := json.MarshalIndent(alert, "", " ") if err != nil { - log.Fatalf("unable to marshal alert with id %s: %s", alertID, err) + return fmt.Errorf("unable to marshal alert with id %s: %s", alertID, err) } fmt.Printf("%s\n", string(data)) case "raw": data, err := yaml.Marshal(alert) if err != nil { - log.Fatalf("unable to marshal alert with id %s: %s", alertID, err) + return fmt.Errorf("unable to marshal alert with id %s: %s", alertID, err) } fmt.Printf("%s\n", string(data)) } } + + return nil }, } cmdAlertsInspect.Flags().SortFlags = false @@ -506,21 +513,23 @@ func NewAlertsFlushCmd() *cobra.Command { /!\ This command can be used only on the same machine than the local API`, Example: `cscli alerts flush --max-items 1000 --max-age 7d`, DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { var err error if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI { - log.Fatal("Local API is disabled, please run this command on the local API machine") + return fmt.Errorf("local API is disabled, please run this command on the local API machine") } dbClient, err = database.NewClient(csConfig.DbConfig) if err != nil { - log.Fatalf("unable to create new database client: %s", err) + return fmt.Errorf("unable to create new database client: %s", err) } log.Info("Flushing alerts. !! This may take a long time !!") err = dbClient.FlushAlerts(maxAge, maxItems) if err != nil { - log.Fatalf("unable to flush alerts: %s", err) + return fmt.Errorf("unable to flush alerts: %s", err) } log.Info("Alerts flushed") + + return nil }, } diff --git a/cmd/crowdsec-cli/bouncers.go b/cmd/crowdsec-cli/bouncers.go index d16000119..f62b982f0 100644 --- a/cmd/crowdsec-cli/bouncers.go +++ b/cmd/crowdsec-cli/bouncers.go @@ -9,7 +9,6 @@ import ( "time" "github.com/fatih/color" - "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -29,14 +28,14 @@ func getBouncers(out io.Writer, dbClient *database.Client) error { enc := json.NewEncoder(out) enc.SetIndent("", " ") if err := enc.Encode(bouncers); err != nil { - return errors.Wrap(err, "failed to unmarshal") + return fmt.Errorf("failed to unmarshal: %w", err) } return nil } else if csConfig.Cscli.Output == "raw" { csvwriter := csv.NewWriter(out) err := csvwriter.Write([]string{"name", "ip", "revoked", "last_pull", "type", "version", "auth_type"}) if err != nil { - return errors.Wrap(err, "failed to write raw header") + return fmt.Errorf("failed to write raw header: %w", err) } for _, b := range bouncers { var revoked string @@ -47,7 +46,7 @@ func getBouncers(out io.Writer, dbClient *database.Client) error { } err := csvwriter.Write([]string{b.Name, b.IPAddress, revoked, b.LastPull.Format(time.RFC3339), b.Type, b.Version, b.AuthType}) if err != nil { - return errors.Wrap(err, "failed to write raw") + return fmt.Errorf("failed to write raw: %w", err) } } csvwriter.Flush() @@ -63,11 +62,12 @@ func NewBouncersListCmd() *cobra.Command { Example: `cscli bouncers list`, Args: cobra.ExactArgs(0), DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, arg []string) { + RunE: func(cmd *cobra.Command, arg []string) error { err := getBouncers(color.Output, dbClient) if err != nil { - log.Fatalf("unable to list bouncers: %s", err) + return fmt.Errorf("unable to list bouncers: %s", err) } + return nil }, } @@ -91,18 +91,18 @@ func runBouncersAdd(cmd *cobra.Command, args []string) error { var apiKey string if keyName == "" { - log.Fatalf("Please provide a name for the api key") + return fmt.Errorf("please provide a name for the api key") } apiKey = key if key == "" { apiKey, err = middlewares.GenerateAPIKey(keyLength) } if err != nil { - log.Fatalf("unable to generate api key: %s", err) + return fmt.Errorf("unable to generate api key: %s", err) } _, err = dbClient.CreateBouncer(keyName, "", middlewares.HashSHA512(apiKey), types.ApiKeyAuthType) if err != nil { - log.Fatalf("unable to create bouncer: %s", err) + return fmt.Errorf("unable to create bouncer: %s", err) } if csConfig.Cscli.Output == "human" { @@ -114,7 +114,7 @@ func runBouncersAdd(cmd *cobra.Command, args []string) error { } else if csConfig.Cscli.Output == "json" { j, err := json.Marshal(apiKey) if err != nil { - log.Fatalf("unable to marshal api key") + return fmt.Errorf("unable to marshal api key") } fmt.Printf("%s", string(j)) } @@ -149,7 +149,7 @@ func runBouncersDelete(cmd *cobra.Command, args []string) error { for _, bouncerID := range args { err := dbClient.DeleteBouncer(bouncerID) if err != nil { - log.Fatalf("unable to delete bouncer '%s': %s", bouncerID, err) + return fmt.Errorf("unable to delete bouncer '%s': %s", bouncerID, err) } log.Infof("bouncer '%s' deleted successfully", bouncerID) } @@ -200,15 +200,16 @@ Note: This command requires database direct access, so is intended to be run on Args: cobra.MinimumNArgs(1), Aliases: []string{"bouncer"}, DisableAutoGenTag: true, - PersistentPreRun: func(cmd *cobra.Command, args []string) { + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { var err error if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI { - log.Fatal("Local API is disabled, please run this command on the local API machine") + return fmt.Errorf("local API is disabled, please run this command on the local API machine") } dbClient, err = database.NewClient(csConfig.DbConfig) if err != nil { - log.Fatalf("unable to create new database client: %s", err) + return fmt.Errorf("unable to create new database client: %s", err) } + return nil }, } diff --git a/cmd/crowdsec-cli/explain.go b/cmd/crowdsec-cli/explain.go index abbcd66a4..2158c9979 100644 --- a/cmd/crowdsec-cli/explain.go +++ b/cmd/crowdsec-cli/explain.go @@ -65,7 +65,7 @@ func runExplain(cmd *cobra.Command, args []string) error { } if logFile == "-" && ((fileInfo.Mode() & os.ModeCharDevice) == os.ModeCharDevice) { - log.Fatal("-f - is intended to work with pipes.") + return fmt.Errorf("the option -f - is intended to work with pipes") } var f *os.File @@ -77,13 +77,13 @@ func runExplain(cmd *cobra.Command, args []string) error { tmpFile = filepath.Join(dir, "cscli_test_tmp.log") f, err = os.Create(tmpFile) if err != nil { - log.Fatal(err) + return err } if logLine != "" { _, err = f.WriteString(logLine) if err != nil { - log.Fatal(err) + return err } } else if logFile == "-" { reader := bufio.NewReader(os.Stdin) @@ -110,7 +110,7 @@ func runExplain(cmd *cobra.Command, args []string) error { if logFile != "" { absolutePath, err := filepath.Abs(logFile) if err != nil { - log.Fatalf("unable to get absolute path of '%s', exiting", logFile) + return fmt.Errorf("unable to get absolute path of '%s', exiting", logFile) } dsn = fmt.Sprintf("file://%s", absolutePath) lineCount := types.GetLineCountForFile(absolutePath) @@ -120,7 +120,7 @@ func runExplain(cmd *cobra.Command, args []string) error { } if dsn == "" { - log.Fatal("no acquisition (--file or --dsn) provided, can't run cscli test.") + return fmt.Errorf("no acquisition (--file or --dsn) provided, can't run cscli test") } cmdArgs := []string{"-c", ConfigFilePath, "-type", logType, "-dsn", dsn, "-dump-data", "./", "-no-api"} @@ -129,13 +129,13 @@ func runExplain(cmd *cobra.Command, args []string) error { output, err := crowdsecCmd.CombinedOutput() if err != nil { fmt.Println(string(output)) - log.Fatalf("fail to run crowdsec for test: %v", err) + return fmt.Errorf("fail to run crowdsec for test: %v", err) } // rm the temporary log file if only a log line/stdin was provided if tmpFile != "" { if err := os.Remove(tmpFile); err != nil { - log.Fatalf("unable to remove tmp log file '%s': %+v", tmpFile, err) + return fmt.Errorf("unable to remove tmp log file '%s': %+v", tmpFile, err) } } parserDumpFile := filepath.Join(dir, hubtest.ParserResultFileName) @@ -143,12 +143,12 @@ func runExplain(cmd *cobra.Command, args []string) error { parserDump, err := hubtest.LoadParserDump(parserDumpFile) if err != nil { - log.Fatalf("unable to load parser dump result: %s", err) + return fmt.Errorf("unable to load parser dump result: %s", err) } bucketStateDump, err := hubtest.LoadBucketPourDump(bucketStateDumpFile) if err != nil { - log.Fatalf("unable to load bucket dump result: %s", err) + return fmt.Errorf("unable to load bucket dump result: %s", err) } hubtest.DumpTree(*parserDump, *bucketStateDump, opts) diff --git a/cmd/crowdsec-cli/hubtest.go b/cmd/crowdsec-cli/hubtest.go index 7772df3eb..97bb8c8dd 100644 --- a/cmd/crowdsec-cli/hubtest.go +++ b/cmd/crowdsec-cli/hubtest.go @@ -33,12 +33,14 @@ func NewHubTestCmd() *cobra.Command { Long: "Run functional tests on hub configurations (parsers, scenarios, collections...)", Args: cobra.ExactArgs(0), DisableAutoGenTag: true, - PersistentPreRun: func(cmd *cobra.Command, args []string) { + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { var err error HubTest, err = hubtest.NewHubTest(hubPath, crowdsecPath, cscliPath) if err != nil { - log.Fatalf("unable to load hubtest: %+v", err) + return fmt.Errorf("unable to load hubtest: %+v", err) } + + return nil }, } cmdHubTest.PersistentFlags().StringVar(&hubPath, "hub", ".", "Path to hub folder") @@ -74,19 +76,19 @@ cscli hubtest create my-nginx-custom-test --type nginx cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios crowdsecurity/http-probing`, Args: cobra.ExactArgs(1), DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { testName := args[0] testPath := filepath.Join(HubTest.HubTestPath, testName) if _, err := os.Stat(testPath); os.IsExist(err) { - log.Fatalf("test '%s' already exists in '%s', exiting", testName, testPath) + return fmt.Errorf("test '%s' already exists in '%s', exiting", testName, testPath) } if logType == "" { - log.Fatalf("please provide a type (--type) for the test") + return fmt.Errorf("please provide a type (--type) for the test") } if err := os.MkdirAll(testPath, os.ModePerm); err != nil { - log.Fatalf("unable to create folder '%s': %+v", testPath, err) + return fmt.Errorf("unable to create folder '%s': %+v", testPath, err) } // create empty log file @@ -94,7 +96,7 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios logFilePath := filepath.Join(testPath, logFileName) logFile, err := os.Create(logFilePath) if err != nil { - log.Fatal(err) + return err } logFile.Close() @@ -102,7 +104,7 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios parserAssertFilePath := filepath.Join(testPath, hubtest.ParserAssertFileName) parserAssertFile, err := os.Create(parserAssertFilePath) if err != nil { - log.Fatal(err) + return err } parserAssertFile.Close() @@ -110,7 +112,7 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios scenarioAssertFilePath := filepath.Join(testPath, hubtest.ScenarioAssertFileName) scenarioAssertFile, err := os.Create(scenarioAssertFilePath) if err != nil { - log.Fatal(err) + return err } scenarioAssertFile.Close() @@ -138,18 +140,18 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios configFilePath := filepath.Join(testPath, "config.yaml") fd, err := os.OpenFile(configFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666) if err != nil { - log.Fatalf("open: %s", err) + return fmt.Errorf("open: %s", err) } data, err := yaml.Marshal(configFileData) if err != nil { - log.Fatalf("marshal: %s", err) + return fmt.Errorf("marshal: %s", err) } _, err = fd.Write(data) if err != nil { - log.Fatalf("write: %s", err) + return fmt.Errorf("write: %s", err) } if err := fd.Close(); err != nil { - log.Fatalf(" close: %s", err) + return fmt.Errorf("close: %s", err) } fmt.Println() fmt.Printf(" Test name : %s\n", testName) @@ -159,6 +161,7 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios fmt.Printf(" Scenario assertion file : %s (please fill it with assertion)\n", scenarioAssertFilePath) fmt.Printf(" Configuration File : %s (please fill it with parsers, scenarios...)\n", configFilePath) + return nil }, } cmdHubTestCreate.PersistentFlags().StringVarP(&logType, "type", "t", "", "Log type of the test") @@ -180,22 +183,21 @@ func NewHubTestRunCmd() *cobra.Command { Use: "run", Short: "run [test_name]", DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { if !runAll && len(args) == 0 { printHelp(cmd) - fmt.Println("Please provide test to run or --all flag") - os.Exit(1) + return fmt.Errorf("Please provide test to run or --all flag") } if runAll { if err := HubTest.LoadAllTests(); err != nil { - log.Fatalf("unable to load all tests: %+v", err) + return fmt.Errorf("unable to load all tests: %+v", err) } } else { for _, testName := range args { _, err := HubTest.LoadTestItem(testName) if err != nil { - log.Fatalf("unable to load test '%s': %s", testName, err) + return fmt.Errorf("unable to load test '%s': %s", testName, err) } } } @@ -210,8 +212,9 @@ func NewHubTestRunCmd() *cobra.Command { } } + return nil }, - PersistentPostRun: func(cmd *cobra.Command, args []string) { + PersistentPostRunE: func(cmd *cobra.Command, args []string) error { success := true testResult := make(map[string]bool) for _, test := range HubTest.Tests { @@ -228,7 +231,7 @@ func NewHubTestRunCmd() *cobra.Command { } if !noClean { if err := test.Clean(); err != nil { - log.Fatalf("unable to clean test '%s' env: %s", test.Name, err) + return fmt.Errorf("unable to clean test '%s' env: %s", test.Name, err) } } fmt.Printf("\nPlease fill your assert file(s) for test '%s', exiting\n", test.Name) @@ -241,7 +244,7 @@ func NewHubTestRunCmd() *cobra.Command { } if !noClean { if err := test.Clean(); err != nil { - log.Fatalf("unable to clean test '%s' env: %s", test.Name, err) + return fmt.Errorf("unable to clean test '%s' env: %s", test.Name, err) } } } else { @@ -278,14 +281,14 @@ func NewHubTestRunCmd() *cobra.Command { Default: true, } if err := survey.AskOne(prompt, &cleanTestEnv); err != nil { - log.Fatalf("unable to ask to remove runtime folder: %s", err) + return fmt.Errorf("unable to ask to remove runtime folder: %s", err) } } } if cleanTestEnv || forceClean { if err := test.Clean(); err != nil { - log.Fatalf("unable to clean test '%s' env: %s", test.Name, err) + return fmt.Errorf("unable to clean test '%s' env: %s", test.Name, err) } } } @@ -305,7 +308,7 @@ func NewHubTestRunCmd() *cobra.Command { } jsonStr, err := json.Marshal(jsonResult) if err != nil { - log.Fatalf("unable to json test result: %s", err) + return fmt.Errorf("unable to json test result: %s", err) } fmt.Println(string(jsonStr)) } @@ -313,6 +316,8 @@ func NewHubTestRunCmd() *cobra.Command { if !success { os.Exit(1) } + + return nil }, } cmdHubTestRun.Flags().BoolVar(&noClean, "no-clean", false, "Don't clean runtime environment if test succeed") @@ -329,16 +334,18 @@ func NewHubTestCleanCmd() *cobra.Command { Short: "clean [test_name]", Args: cobra.MinimumNArgs(1), DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { for _, testName := range args { test, err := HubTest.LoadTestItem(testName) if err != nil { - log.Fatalf("unable to load test '%s': %s", testName, err) + return fmt.Errorf("unable to load test '%s': %s", testName, err) } if err := test.Clean(); err != nil { - log.Fatalf("unable to clean test '%s' env: %s", test.Name, err) + return fmt.Errorf("unable to clean test '%s' env: %s", test.Name, err) } } + + return nil }, } @@ -352,11 +359,11 @@ func NewHubTestInfoCmd() *cobra.Command { Short: "info [test_name]", Args: cobra.MinimumNArgs(1), DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { for _, testName := range args { test, err := HubTest.LoadTestItem(testName) if err != nil { - log.Fatalf("unable to load test '%s': %s", testName, err) + return fmt.Errorf("unable to load test '%s': %s", testName, err) } fmt.Println() fmt.Printf(" Test name : %s\n", test.Name) @@ -366,6 +373,8 @@ func NewHubTestInfoCmd() *cobra.Command { fmt.Printf(" Scenario assertion file : %s\n", filepath.Join(test.Path, hubtest.ScenarioAssertFileName)) fmt.Printf(" Configuration File : %s\n", filepath.Join(test.Path, "config.yaml")) } + + return nil }, } @@ -378,9 +387,9 @@ func NewHubTestListCmd() *cobra.Command { Use: "list", Short: "list", DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { if err := HubTest.LoadAllTests(); err != nil { - log.Fatalf("unable to load all tests: %+v", err) + return fmt.Errorf("unable to load all tests: %s", err) } switch csConfig.Cscli.Output { @@ -389,12 +398,14 @@ func NewHubTestListCmd() *cobra.Command { case "json": j, err := json.MarshalIndent(HubTest.Tests, " ", " ") if err != nil { - log.Fatal(err) + return err } fmt.Println(string(j)) default: - log.Fatalf("only human/json output modes are supported") + return fmt.Errorf("only human/json output modes are supported") } + + return nil }, } @@ -411,9 +422,9 @@ func NewHubTestCoverageCmd() *cobra.Command { Use: "coverage", Short: "coverage", DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { if err := HubTest.LoadAllTests(); err != nil { - log.Fatalf("unable to load all tests: %+v", err) + return fmt.Errorf("unable to load all tests: %+v", err) } var err error scenarioCoverage := []hubtest.ScenarioCoverage{} @@ -427,7 +438,7 @@ func NewHubTestCoverageCmd() *cobra.Command { if showParserCov || showAll { parserCoverage, err = HubTest.GetParsersCoverage() if err != nil { - log.Fatalf("while getting parser coverage : %s", err) + return fmt.Errorf("while getting parser coverage: %s", err) } parserTested := 0 for _, test := range parserCoverage { @@ -441,7 +452,7 @@ func NewHubTestCoverageCmd() *cobra.Command { if showScenarioCov || showAll { scenarioCoverage, err = HubTest.GetScenariosCoverage() if err != nil { - log.Fatalf("while getting scenario coverage: %s", err) + return fmt.Errorf("while getting scenario coverage: %s", err) } scenarioTested := 0 for _, test := range scenarioCoverage { @@ -481,18 +492,19 @@ func NewHubTestCoverageCmd() *cobra.Command { } else if csConfig.Cscli.Output == "json" { dump, err := json.MarshalIndent(parserCoverage, "", " ") if err != nil { - log.Fatal(err) + return err } fmt.Printf("%s", dump) dump, err = json.MarshalIndent(scenarioCoverage, "", " ") if err != nil { - log.Fatal(err) + return err } fmt.Printf("%s", dump) } else { - log.Fatalf("only human/json output modes are supported") + return fmt.Errorf("only human/json output modes are supported") } + return nil }, } cmdHubTestCoverage.PersistentFlags().BoolVar(&showOnlyPercent, "percent", false, "Show only percentages of coverage") @@ -510,22 +522,24 @@ func NewHubTestEvalCmd() *cobra.Command { Short: "eval [test_name]", Args: cobra.ExactArgs(1), DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { for _, testName := range args { test, err := HubTest.LoadTestItem(testName) if err != nil { - log.Fatalf("can't load test: %+v", err) + return fmt.Errorf("can't load test: %+v", err) } err = test.ParserAssert.LoadTest(test.ParserResultFile) if err != nil { - log.Fatalf("can't load test results from '%s': %+v", test.ParserResultFile, err) + return fmt.Errorf("can't load test results from '%s': %+v", test.ParserResultFile, err) } output, err := test.ParserAssert.EvalExpression(evalExpression) if err != nil { - log.Fatal(err) + return err } fmt.Print(output) } + + return nil }, } cmdHubTestEval.PersistentFlags().StringVarP(&evalExpression, "expr", "e", "", "Expression to eval") @@ -540,21 +554,21 @@ func NewHubTestExplainCmd() *cobra.Command { Short: "explain [test_name]", Args: cobra.ExactArgs(1), DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { for _, testName := range args { test, err := HubTest.LoadTestItem(testName) if err != nil { - log.Fatalf("can't load test: %+v", err) + return fmt.Errorf("can't load test: %+v", err) } err = test.ParserAssert.LoadTest(test.ParserResultFile) if err != nil { err := test.Run() if err != nil { - log.Fatalf("running test '%s' failed: %+v", test.Name, err) + return fmt.Errorf("running test '%s' failed: %+v", test.Name, err) } err = test.ParserAssert.LoadTest(test.ParserResultFile) if err != nil { - log.Fatalf("unable to load parser result after run: %s", err) + return fmt.Errorf("unable to load parser result after run: %s", err) } } @@ -562,16 +576,18 @@ func NewHubTestExplainCmd() *cobra.Command { if err != nil { err := test.Run() if err != nil { - log.Fatalf("running test '%s' failed: %+v", test.Name, err) + return fmt.Errorf("running test '%s' failed: %+v", test.Name, err) } err = test.ScenarioAssert.LoadTest(test.ScenarioResultFile, test.BucketPourResultFile) if err != nil { - log.Fatalf("unable to load scenario result after run: %s", err) + return fmt.Errorf("unable to load scenario result after run: %s", err) } } opts := hubtest.DumpOpts{} hubtest.DumpTree(*test.ParserAssert.TestData, *test.ScenarioAssert.PourData, opts) } + + return nil }, } diff --git a/cmd/crowdsec-cli/machines.go b/cmd/crowdsec-cli/machines.go index 62f6ee124..09c01b964 100644 --- a/cmd/crowdsec-cli/machines.go +++ b/cmd/crowdsec-cli/machines.go @@ -16,7 +16,6 @@ import ( "github.com/fatih/color" "github.com/go-openapi/strfmt" "github.com/google/uuid" - "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "gopkg.in/yaml.v2" @@ -67,7 +66,7 @@ func generateIDPrefix() (string, error) { if err == nil { return bId.String(), nil } - return "", errors.Wrap(err, "generating machine id") + return "", fmt.Errorf("generating machine id: %w", err) } // Generate a unique identifier, composed by a prefix and a random suffix. @@ -114,14 +113,14 @@ func getAgents(out io.Writer, dbClient *database.Client) error { enc := json.NewEncoder(out) enc.SetIndent("", " ") if err := enc.Encode(machines); err != nil { - log.Fatalf("failed to unmarshal") + return fmt.Errorf("failed to marshal") } return nil } else if csConfig.Cscli.Output == "raw" { csvwriter := csv.NewWriter(out) err := csvwriter.Write([]string{"machine_id", "ip_address", "updated_at", "validated", "version", "auth_type", "last_heartbeat"}) if err != nil { - log.Fatalf("failed to write header: %s", err) + return fmt.Errorf("failed to write header: %s", err) } for _, m := range machines { var validated string @@ -132,7 +131,7 @@ func getAgents(out io.Writer, dbClient *database.Client) error { } err := csvwriter.Write([]string{m.MachineId, m.IpAddress, m.UpdatedAt.Format(time.RFC3339), validated, m.Version, m.AuthType, displayLastHeartBeat(m, false)}) if err != nil { - log.Fatalf("failed to write raw output : %s", err) + return fmt.Errorf("failed to write raw output : %s", err) } } csvwriter.Flush() @@ -150,18 +149,22 @@ func NewMachinesListCmd() *cobra.Command { Example: `cscli machines list`, Args: cobra.MaximumNArgs(1), DisableAutoGenTag: true, - PreRun: func(cmd *cobra.Command, args []string) { + PreRunE: func(cmd *cobra.Command, args []string) error { var err error dbClient, err = database.NewClient(csConfig.DbConfig) if err != nil { - log.Fatalf("unable to create new database client: %s", err) + return fmt.Errorf("unable to create new database client: %s", err) } + + return nil }, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { err := getAgents(color.Output, dbClient) if err != nil { - log.Fatalf("unable to list machines: %s", err) + return fmt.Errorf("unable to list machines: %s", err) } + + return nil }, } @@ -179,12 +182,14 @@ cscli machines add --auto cscli machines add MyTestMachine --auto cscli machines add MyTestMachine --password MyPassword `, - PreRun: func(cmd *cobra.Command, args []string) { + PreRunE: func(cmd *cobra.Command, args []string) error { var err error dbClient, err = database.NewClient(csConfig.DbConfig) if err != nil { - log.Fatalf("unable to create new database client: %s", err) + return fmt.Errorf("unable to create new database client: %s", err) } + + return nil }, RunE: runMachinesAdd, } @@ -246,7 +251,7 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error { } machineID, err = generateID("") if err != nil { - log.Fatalf("unable to generate machine id : %s", err) + return fmt.Errorf("unable to generate machine id: %s", err) } } else { machineID = args[0] @@ -275,7 +280,7 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error { password := strfmt.Password(machinePassword) _, err = dbClient.CreateMachine(&machineID, &password, "", true, forceAdd, types.PasswordAuthType) if err != nil { - log.Fatalf("unable to create machine: %s", err) + return fmt.Errorf("unable to create machine: %s", err) } log.Infof("Machine '%s' successfully added to the local API", machineID) @@ -285,7 +290,7 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error { } else if csConfig.API.Server != nil && csConfig.API.Server.ListenURI != "" { apiURL = "http://" + csConfig.API.Server.ListenURI } else { - log.Fatalf("unable to dump an api URL. Please provide it in your configuration or with the -u parameter") + return fmt.Errorf("unable to dump an api URL. Please provide it in your configuration or with the -u parameter") } } apiCfg := csconfig.ApiCredentialsCfg{ @@ -295,12 +300,12 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error { } apiConfigDump, err := yaml.Marshal(apiCfg) if err != nil { - log.Fatalf("unable to marshal api credentials: %s", err) + return fmt.Errorf("unable to marshal api credentials: %s", err) } if dumpFile != "" && dumpFile != "-" { err = os.WriteFile(dumpFile, apiConfigDump, 0644) if err != nil { - log.Fatalf("write api credentials in '%s' failed: %s", dumpFile, err) + return fmt.Errorf("write api credentials in '%s' failed: %s", dumpFile, err) } log.Printf("API credentials dumped to '%s'", dumpFile) } else { @@ -318,12 +323,13 @@ func NewMachinesDeleteCmd() *cobra.Command { Args: cobra.MinimumNArgs(1), Aliases: []string{"remove"}, DisableAutoGenTag: true, - PreRun: func(cmd *cobra.Command, args []string) { + PreRunE: func(cmd *cobra.Command, args []string) error { var err error dbClient, err = database.NewClient(csConfig.DbConfig) if err != nil { - log.Fatalf("unable to create new database client: %s", err) + return fmt.Errorf("unable to create new database client: %s", err) } + return nil }, ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { var err error @@ -371,19 +377,23 @@ func NewMachinesValidateCmd() *cobra.Command { Example: `cscli machines validate "machine_name"`, Args: cobra.ExactArgs(1), DisableAutoGenTag: true, - PreRun: func(cmd *cobra.Command, args []string) { + PreRunE: func(cmd *cobra.Command, args []string) error { var err error dbClient, err = database.NewClient(csConfig.DbConfig) if err != nil { - log.Fatalf("unable to create new database client: %s", err) + return fmt.Errorf("unable to create new database client: %s", err) } + + return nil }, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { machineID := args[0] if err := dbClient.ValidateMachine(machineID); err != nil { - log.Fatalf("unable to validate machine '%s': %s", machineID, err) + return fmt.Errorf("unable to validate machine '%s': %s", machineID, err) } log.Infof("machine '%s' validated successfully", machineID) + + return nil }, } @@ -400,13 +410,15 @@ Note: This command requires database direct access, so is intended to be run on Example: `cscli machines [action]`, DisableAutoGenTag: true, Aliases: []string{"machine"}, - PersistentPreRun: func(cmd *cobra.Command, args []string) { + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI { if err != nil { log.Errorf("local api : %s", err) } - log.Fatal("Local API is disabled, please run this command on the local API machine") + return fmt.Errorf("local API is disabled, please run this command on the local API machine") } + + return nil }, } diff --git a/cmd/crowdsec-cli/main.go b/cmd/crowdsec-cli/main.go index c1ce6adf3..ed4df0c1b 100644 --- a/cmd/crowdsec-cli/main.go +++ b/cmd/crowdsec-cli/main.go @@ -178,10 +178,11 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall Args: cobra.ExactArgs(0), Hidden: true, DisableAutoGenTag: true, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { if err := doc.GenMarkdownTreeCustom(rootCmd, "./doc/", prepender, linkHandler); err != nil { - log.Fatalf("Failed to generate cobra doc: %s", err) + return fmt.Errorf("Failed to generate cobra doc: %s", err) } + return nil }, } rootCmd.AddCommand(cmdDocGen) diff --git a/tests/bats/01_base.bats b/tests/bats/01_base.bats index 6f86f7b18..50af7cd99 100644 --- a/tests/bats/01_base.bats +++ b/tests/bats/01_base.bats @@ -19,6 +19,7 @@ setup() { } teardown() { + cd "$TEST_DIR" || exit 1 ./instance-crowdsec stop } @@ -297,3 +298,25 @@ declare stderr rune -0 cscli config show --key Config.DbConfig.Password assert_output 'P@ssw0rd$' } + +@test "cscli doc" { + # generating documentation requires a directory named "doc" + + cd "$BATS_TEST_TMPDIR" + rune -1 cscli doc + refute_output + assert_stderr --regexp 'Failed to generate cobra doc: open doc/.*: no such file or directory' + + mkdir -p doc + rune -0 cscli doc + refute_output + refute_stderr + assert_file_exist "doc/cscli.md" + assert_file_not_exist "doc/cscli_setup.md" + + # commands guarded by feature flags are not documented unless the feature flag is set + + export CROWDSEC_FEATURE_CSCLI_SETUP="true" + rune -0 cscli doc + assert_file_exist "doc/cscli_setup.md" +} diff --git a/tests/bats/02_nolapi.bats b/tests/bats/02_nolapi.bats index 21eb7f13e..2b8e1ef70 100644 --- a/tests/bats/02_nolapi.bats +++ b/tests/bats/02_nolapi.bats @@ -75,7 +75,7 @@ teardown() { config_disable_lapi ./instance-crowdsec start || true run -1 --separate-stderr cscli machines list - assert_stderr --partial "Local API is disabled, please run this command on the local API machine" + assert_stderr --partial "local API is disabled, please run this command on the local API machine" } @test "cscli metrics" { @@ -87,5 +87,5 @@ teardown() { assert_output --partial "/v1/watchers/login" assert_stderr --partial "crowdsec local API is disabled" - assert_stderr --partial "Local API is disabled, please run this command on the local API machine" + assert_stderr --partial "local API is disabled, please run this command on the local API machine" } diff --git a/tests/bats/80_alerts.bats b/tests/bats/80_alerts.bats index 87eccf45a..81c4800b7 100644 --- a/tests/bats/80_alerts.bats +++ b/tests/bats/80_alerts.bats @@ -66,6 +66,9 @@ teardown() { } @test "cscli alerts inspect" { + rune -1 cscli alerts inspect + assert_stderr --partial 'missing alert_id' + run -0 cscli decisions add -i 10.20.30.40 -t ban run -0 cscli alerts list -o raw <(output) run -0 grep 10.20.30.40 <(output) @@ -143,7 +146,7 @@ teardown() { # can't delete twice run -1 --separate-stderr cscli alerts delete --id "$ALERT_ID" refute_output - assert_stderr --partial "Unable to delete alert" + assert_stderr --partial "unable to delete alert" assert_stderr --partial "API error: ent: alert not found" }