Bläddra i källkod

replace log.Fatal -> fmt.Errorf (#2058)

mmetc 2 år sedan
förälder
incheckning
b7d1e2c483

+ 33 - 24
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 -s crowdsecurity/ssh-bf
 cscli alerts list --type ban`,
 cscli alerts list --type ban`,
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			var err error
 			var err error
 
 
 			if err := manageCliDecisionAlerts(alertListFilter.IPEquals, alertListFilter.RangeEquals,
 			if err := manageCliDecisionAlerts(alertListFilter.IPEquals, alertListFilter.RangeEquals,
 				alertListFilter.ScopeEquals, alertListFilter.ValueEquals); err != nil {
 				alertListFilter.ScopeEquals, alertListFilter.ValueEquals); err != nil {
 				printHelp(cmd)
 				printHelp(cmd)
-				log.Fatalf("%s", err)
+				return err
 			}
 			}
 			if limit != nil {
 			if limit != nil {
 				alertListFilter.Limit = limit
 				alertListFilter.Limit = limit
@@ -273,7 +273,7 @@ cscli alerts list --type ban`,
 				days, err := strconv.Atoi(realDuration)
 				days, err := strconv.Atoi(realDuration)
 				if err != nil {
 				if err != nil {
 					printHelp(cmd)
 					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")
 				*alertListFilter.Until = fmt.Sprintf("%d%s", days*24, "h")
 			}
 			}
@@ -285,7 +285,7 @@ cscli alerts list --type ban`,
 				days, err := strconv.Atoi(realDuration)
 				days, err := strconv.Atoi(realDuration)
 				if err != nil {
 				if err != nil {
 					printHelp(cmd)
 					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")
 				*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)
 			alerts, _, err := Client.Alerts.List(context.Background(), alertListFilter)
 			if err != nil {
 			if err != nil {
-				log.Fatalf("Unable to list alerts : %v", err)
+				return fmt.Errorf("unable to list alerts: %v", err)
 			}
 			}
 
 
 			err = AlertsToTable(alerts, printMachine)
 			err = AlertsToTable(alerts, printMachine)
 			if err != nil {
 			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
 	cmdAlertsList.Flags().SortFlags = false
@@ -372,25 +374,27 @@ cscli alerts delete -s crowdsecurity/ssh-bf"`,
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
 		Aliases:           []string{"remove"},
 		Aliases:           []string{"remove"},
 		Args:              cobra.ExactArgs(0),
 		Args:              cobra.ExactArgs(0),
-		PreRun: func(cmd *cobra.Command, args []string) {
+		PreRunE: func(cmd *cobra.Command, args []string) error {
 			if AlertDeleteAll {
 			if AlertDeleteAll {
-				return
+				return nil
 			}
 			}
 			if *alertDeleteFilter.ScopeEquals == "" && *alertDeleteFilter.ValueEquals == "" &&
 			if *alertDeleteFilter.ScopeEquals == "" && *alertDeleteFilter.ValueEquals == "" &&
 				*alertDeleteFilter.ScenarioEquals == "" && *alertDeleteFilter.IPEquals == "" &&
 				*alertDeleteFilter.ScenarioEquals == "" && *alertDeleteFilter.IPEquals == "" &&
 				*alertDeleteFilter.RangeEquals == "" && delAlertByID == "" {
 				*alertDeleteFilter.RangeEquals == "" && delAlertByID == "" {
 				_ = cmd.Usage()
 				_ = 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
 			var err error
 
 
 			if !AlertDeleteAll {
 			if !AlertDeleteAll {
 				if err := manageCliDecisionAlerts(alertDeleteFilter.IPEquals, alertDeleteFilter.RangeEquals,
 				if err := manageCliDecisionAlerts(alertDeleteFilter.IPEquals, alertDeleteFilter.RangeEquals,
 					alertDeleteFilter.ScopeEquals, alertDeleteFilter.ValueEquals); err != nil {
 					alertDeleteFilter.ScopeEquals, alertDeleteFilter.ValueEquals); err != nil {
 					printHelp(cmd)
 					printHelp(cmd)
-					log.Fatalf("%s", err)
+					return err
 				}
 				}
 				if ActiveDecision != nil {
 				if ActiveDecision != nil {
 					alertDeleteFilter.ActiveDecisionEquals = ActiveDecision
 					alertDeleteFilter.ActiveDecisionEquals = ActiveDecision
@@ -425,15 +429,17 @@ cscli alerts delete -s crowdsecurity/ssh-bf"`,
 			if delAlertByID == "" {
 			if delAlertByID == "" {
 				alerts, _, err = Client.Alerts.Delete(context.Background(), alertDeleteFilter)
 				alerts, _, err = Client.Alerts.Delete(context.Background(), alertDeleteFilter)
 				if err != nil {
 				if err != nil {
-					log.Fatalf("Unable to delete alerts : %v", err)
+					return fmt.Errorf("unable to delete alerts : %v", err)
 				}
 				}
 			} else {
 			} else {
 				alerts, _, err = Client.Alerts.DeleteOne(context.Background(), delAlertByID)
 				alerts, _, err = Client.Alerts.DeleteOne(context.Background(), delAlertByID)
 				if err != nil {
 				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)
 			log.Infof("%s alert(s) deleted", alerts.NbDeleted)
+
+			return nil
 		},
 		},
 	}
 	}
 	cmdAlertsDelete.Flags().SortFlags = false
 	cmdAlertsDelete.Flags().SortFlags = false
@@ -455,20 +461,19 @@ func NewAlertsInspectCmd() *cobra.Command {
 		Short:             `Show info about an alert`,
 		Short:             `Show info about an alert`,
 		Example:           `cscli alerts inspect 123`,
 		Example:           `cscli alerts inspect 123`,
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			if len(args) == 0 {
 			if len(args) == 0 {
 				printHelp(cmd)
 				printHelp(cmd)
-				return
+				return fmt.Errorf("missing alert_id")
 			}
 			}
 			for _, alertID := range args {
 			for _, alertID := range args {
 				id, err := strconv.Atoi(alertID)
 				id, err := strconv.Atoi(alertID)
 				if err != nil {
 				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)
 				alert, _, err := Client.Alerts.GetByID(context.Background(), id)
 				if err != nil {
 				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 {
 				switch csConfig.Cscli.Output {
 				case "human":
 				case "human":
@@ -478,17 +483,19 @@ func NewAlertsInspectCmd() *cobra.Command {
 				case "json":
 				case "json":
 					data, err := json.MarshalIndent(alert, "", "  ")
 					data, err := json.MarshalIndent(alert, "", "  ")
 					if err != nil {
 					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))
 					fmt.Printf("%s\n", string(data))
 				case "raw":
 				case "raw":
 					data, err := yaml.Marshal(alert)
 					data, err := yaml.Marshal(alert)
 					if err != nil {
 					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))
 					fmt.Printf("%s\n", string(data))
 				}
 				}
 			}
 			}
+
+			return nil
 		},
 		},
 	}
 	}
 	cmdAlertsInspect.Flags().SortFlags = false
 	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`,
 /!\ This command can be used only on the same machine than the local API`,
 		Example:           `cscli alerts flush --max-items 1000 --max-age 7d`,
 		Example:           `cscli alerts flush --max-items 1000 --max-age 7d`,
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			var err error
 			var err error
 			if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI {
 			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)
 			dbClient, err = database.NewClient(csConfig.DbConfig)
 			if err != nil {
 			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 !!")
 			log.Info("Flushing alerts. !! This may take a long time !!")
 			err = dbClient.FlushAlerts(maxAge, maxItems)
 			err = dbClient.FlushAlerts(maxAge, maxItems)
 			if err != nil {
 			if err != nil {
-				log.Fatalf("unable to flush alerts: %s", err)
+				return fmt.Errorf("unable to flush alerts: %s", err)
 			}
 			}
 			log.Info("Alerts flushed")
 			log.Info("Alerts flushed")
+
+			return nil
 		},
 		},
 	}
 	}
 
 

+ 15 - 14
cmd/crowdsec-cli/bouncers.go

@@ -9,7 +9,6 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/fatih/color"
 	"github.com/fatih/color"
-	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 
 
@@ -29,14 +28,14 @@ func getBouncers(out io.Writer, dbClient *database.Client) error {
 		enc := json.NewEncoder(out)
 		enc := json.NewEncoder(out)
 		enc.SetIndent("", "  ")
 		enc.SetIndent("", "  ")
 		if err := enc.Encode(bouncers); err != nil {
 		if err := enc.Encode(bouncers); err != nil {
-			return errors.Wrap(err, "failed to unmarshal")
+			return fmt.Errorf("failed to unmarshal: %w", err)
 		}
 		}
 		return nil
 		return nil
 	} else if csConfig.Cscli.Output == "raw" {
 	} else if csConfig.Cscli.Output == "raw" {
 		csvwriter := csv.NewWriter(out)
 		csvwriter := csv.NewWriter(out)
 		err := csvwriter.Write([]string{"name", "ip", "revoked", "last_pull", "type", "version", "auth_type"})
 		err := csvwriter.Write([]string{"name", "ip", "revoked", "last_pull", "type", "version", "auth_type"})
 		if err != nil {
 		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 {
 		for _, b := range bouncers {
 			var revoked string
 			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})
 			err := csvwriter.Write([]string{b.Name, b.IPAddress, revoked, b.LastPull.Format(time.RFC3339), b.Type, b.Version, b.AuthType})
 			if err != nil {
 			if err != nil {
-				return errors.Wrap(err, "failed to write raw")
+				return fmt.Errorf("failed to write raw: %w", err)
 			}
 			}
 		}
 		}
 		csvwriter.Flush()
 		csvwriter.Flush()
@@ -63,11 +62,12 @@ func NewBouncersListCmd() *cobra.Command {
 		Example:           `cscli bouncers list`,
 		Example:           `cscli bouncers list`,
 		Args:              cobra.ExactArgs(0),
 		Args:              cobra.ExactArgs(0),
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		Run: func(cmd *cobra.Command, arg []string) {
+		RunE: func(cmd *cobra.Command, arg []string) error {
 			err := getBouncers(color.Output, dbClient)
 			err := getBouncers(color.Output, dbClient)
 			if err != nil {
 			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
 	var apiKey string
 
 
 	if keyName == "" {
 	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
 	apiKey = key
 	if key == "" {
 	if key == "" {
 		apiKey, err = middlewares.GenerateAPIKey(keyLength)
 		apiKey, err = middlewares.GenerateAPIKey(keyLength)
 	}
 	}
 	if err != nil {
 	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)
 	_, err = dbClient.CreateBouncer(keyName, "", middlewares.HashSHA512(apiKey), types.ApiKeyAuthType)
 	if err != nil {
 	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" {
 	if csConfig.Cscli.Output == "human" {
@@ -114,7 +114,7 @@ func runBouncersAdd(cmd *cobra.Command, args []string) error {
 	} else if csConfig.Cscli.Output == "json" {
 	} else if csConfig.Cscli.Output == "json" {
 		j, err := json.Marshal(apiKey)
 		j, err := json.Marshal(apiKey)
 		if err != nil {
 		if err != nil {
-			log.Fatalf("unable to marshal api key")
+			return fmt.Errorf("unable to marshal api key")
 		}
 		}
 		fmt.Printf("%s", string(j))
 		fmt.Printf("%s", string(j))
 	}
 	}
@@ -149,7 +149,7 @@ func runBouncersDelete(cmd *cobra.Command, args []string) error {
 	for _, bouncerID := range args {
 	for _, bouncerID := range args {
 		err := dbClient.DeleteBouncer(bouncerID)
 		err := dbClient.DeleteBouncer(bouncerID)
 		if err != nil {
 		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)
 		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),
 		Args:              cobra.MinimumNArgs(1),
 		Aliases:           []string{"bouncer"},
 		Aliases:           []string{"bouncer"},
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		PersistentPreRun: func(cmd *cobra.Command, args []string) {
+		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
 			var err error
 			var err error
 			if err := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI {
 			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)
 			dbClient, err = database.NewClient(csConfig.DbConfig)
 			if err != nil {
 			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
 		},
 		},
 	}
 	}
 
 

+ 9 - 9
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) {
 	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
 	var f *os.File
@@ -77,13 +77,13 @@ func runExplain(cmd *cobra.Command, args []string) error {
 		tmpFile = filepath.Join(dir, "cscli_test_tmp.log")
 		tmpFile = filepath.Join(dir, "cscli_test_tmp.log")
 		f, err = os.Create(tmpFile)
 		f, err = os.Create(tmpFile)
 		if err != nil {
 		if err != nil {
-			log.Fatal(err)
+			return err
 		}
 		}
 
 
 		if logLine != "" {
 		if logLine != "" {
 			_, err = f.WriteString(logLine)
 			_, err = f.WriteString(logLine)
 			if err != nil {
 			if err != nil {
-				log.Fatal(err)
+				return err
 			}
 			}
 		} else if logFile == "-" {
 		} else if logFile == "-" {
 			reader := bufio.NewReader(os.Stdin)
 			reader := bufio.NewReader(os.Stdin)
@@ -110,7 +110,7 @@ func runExplain(cmd *cobra.Command, args []string) error {
 	if logFile != "" {
 	if logFile != "" {
 		absolutePath, err := filepath.Abs(logFile)
 		absolutePath, err := filepath.Abs(logFile)
 		if err != nil {
 		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)
 		dsn = fmt.Sprintf("file://%s", absolutePath)
 		lineCount := types.GetLineCountForFile(absolutePath)
 		lineCount := types.GetLineCountForFile(absolutePath)
@@ -120,7 +120,7 @@ func runExplain(cmd *cobra.Command, args []string) error {
 	}
 	}
 
 
 	if dsn == "" {
 	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"}
 	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()
 	output, err := crowdsecCmd.CombinedOutput()
 	if err != nil {
 	if err != nil {
 		fmt.Println(string(output))
 		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
 	// rm the temporary log file if only a log line/stdin was provided
 	if tmpFile != "" {
 	if tmpFile != "" {
 		if err := os.Remove(tmpFile); err != nil {
 		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)
 	parserDumpFile := filepath.Join(dir, hubtest.ParserResultFileName)
@@ -143,12 +143,12 @@ func runExplain(cmd *cobra.Command, args []string) error {
 
 
 	parserDump, err := hubtest.LoadParserDump(parserDumpFile)
 	parserDump, err := hubtest.LoadParserDump(parserDumpFile)
 	if err != nil {
 	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)
 	bucketStateDump, err := hubtest.LoadBucketPourDump(bucketStateDumpFile)
 	if err != nil {
 	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)
 	hubtest.DumpTree(*parserDump, *bucketStateDump, opts)

+ 66 - 50
cmd/crowdsec-cli/hubtest.go

@@ -33,12 +33,14 @@ func NewHubTestCmd() *cobra.Command {
 		Long:              "Run functional tests on hub configurations (parsers, scenarios, collections...)",
 		Long:              "Run functional tests on hub configurations (parsers, scenarios, collections...)",
 		Args:              cobra.ExactArgs(0),
 		Args:              cobra.ExactArgs(0),
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		PersistentPreRun: func(cmd *cobra.Command, args []string) {
+		PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
 			var err error
 			var err error
 			HubTest, err = hubtest.NewHubTest(hubPath, crowdsecPath, cscliPath)
 			HubTest, err = hubtest.NewHubTest(hubPath, crowdsecPath, cscliPath)
 			if err != nil {
 			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")
 	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`,
 cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios crowdsecurity/http-probing`,
 		Args:              cobra.ExactArgs(1),
 		Args:              cobra.ExactArgs(1),
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			testName := args[0]
 			testName := args[0]
 			testPath := filepath.Join(HubTest.HubTestPath, testName)
 			testPath := filepath.Join(HubTest.HubTestPath, testName)
 			if _, err := os.Stat(testPath); os.IsExist(err) {
 			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 == "" {
 			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 {
 			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
 			// create empty log file
@@ -94,7 +96,7 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
 			logFilePath := filepath.Join(testPath, logFileName)
 			logFilePath := filepath.Join(testPath, logFileName)
 			logFile, err := os.Create(logFilePath)
 			logFile, err := os.Create(logFilePath)
 			if err != nil {
 			if err != nil {
-				log.Fatal(err)
+				return err
 			}
 			}
 			logFile.Close()
 			logFile.Close()
 
 
@@ -102,7 +104,7 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
 			parserAssertFilePath := filepath.Join(testPath, hubtest.ParserAssertFileName)
 			parserAssertFilePath := filepath.Join(testPath, hubtest.ParserAssertFileName)
 			parserAssertFile, err := os.Create(parserAssertFilePath)
 			parserAssertFile, err := os.Create(parserAssertFilePath)
 			if err != nil {
 			if err != nil {
-				log.Fatal(err)
+				return err
 			}
 			}
 			parserAssertFile.Close()
 			parserAssertFile.Close()
 
 
@@ -110,7 +112,7 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
 			scenarioAssertFilePath := filepath.Join(testPath, hubtest.ScenarioAssertFileName)
 			scenarioAssertFilePath := filepath.Join(testPath, hubtest.ScenarioAssertFileName)
 			scenarioAssertFile, err := os.Create(scenarioAssertFilePath)
 			scenarioAssertFile, err := os.Create(scenarioAssertFilePath)
 			if err != nil {
 			if err != nil {
-				log.Fatal(err)
+				return err
 			}
 			}
 			scenarioAssertFile.Close()
 			scenarioAssertFile.Close()
 
 
@@ -138,18 +140,18 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
 			configFilePath := filepath.Join(testPath, "config.yaml")
 			configFilePath := filepath.Join(testPath, "config.yaml")
 			fd, err := os.OpenFile(configFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
 			fd, err := os.OpenFile(configFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0666)
 			if err != nil {
 			if err != nil {
-				log.Fatalf("open: %s", err)
+				return fmt.Errorf("open: %s", err)
 			}
 			}
 			data, err := yaml.Marshal(configFileData)
 			data, err := yaml.Marshal(configFileData)
 			if err != nil {
 			if err != nil {
-				log.Fatalf("marshal: %s", err)
+				return fmt.Errorf("marshal: %s", err)
 			}
 			}
 			_, err = fd.Write(data)
 			_, err = fd.Write(data)
 			if err != nil {
 			if err != nil {
-				log.Fatalf("write: %s", err)
+				return fmt.Errorf("write: %s", err)
 			}
 			}
 			if err := fd.Close(); err != nil {
 			if err := fd.Close(); err != nil {
-				log.Fatalf(" close: %s", err)
+				return fmt.Errorf("close: %s", err)
 			}
 			}
 			fmt.Println()
 			fmt.Println()
 			fmt.Printf("  Test name                   :  %s\n", testName)
 			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("  Scenario assertion file     :  %s (please fill it with assertion)\n", scenarioAssertFilePath)
 			fmt.Printf("  Configuration File          :  %s (please fill it with parsers, scenarios...)\n", configFilePath)
 			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")
 	cmdHubTestCreate.PersistentFlags().StringVarP(&logType, "type", "t", "", "Log type of the test")
@@ -180,22 +183,21 @@ func NewHubTestRunCmd() *cobra.Command {
 		Use:               "run",
 		Use:               "run",
 		Short:             "run [test_name]",
 		Short:             "run [test_name]",
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			if !runAll && len(args) == 0 {
 			if !runAll && len(args) == 0 {
 				printHelp(cmd)
 				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 runAll {
 				if err := HubTest.LoadAllTests(); err != nil {
 				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 {
 			} else {
 				for _, testName := range args {
 				for _, testName := range args {
 					_, err := HubTest.LoadTestItem(testName)
 					_, err := HubTest.LoadTestItem(testName)
 					if err != nil {
 					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
 			success := true
 			testResult := make(map[string]bool)
 			testResult := make(map[string]bool)
 			for _, test := range HubTest.Tests {
 			for _, test := range HubTest.Tests {
@@ -228,7 +231,7 @@ func NewHubTestRunCmd() *cobra.Command {
 					}
 					}
 					if !noClean {
 					if !noClean {
 						if err := test.Clean(); err != nil {
 						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)
 					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 !noClean {
 						if err := test.Clean(); err != nil {
 						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 {
 				} else {
@@ -278,14 +281,14 @@ func NewHubTestRunCmd() *cobra.Command {
 								Default: true,
 								Default: true,
 							}
 							}
 							if err := survey.AskOne(prompt, &cleanTestEnv); err != nil {
 							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 cleanTestEnv || forceClean {
 						if err := test.Clean(); err != nil {
 						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)
 				jsonStr, err := json.Marshal(jsonResult)
 				if err != nil {
 				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))
 				fmt.Println(string(jsonStr))
 			}
 			}
@@ -313,6 +316,8 @@ func NewHubTestRunCmd() *cobra.Command {
 			if !success {
 			if !success {
 				os.Exit(1)
 				os.Exit(1)
 			}
 			}
+
+			return nil
 		},
 		},
 	}
 	}
 	cmdHubTestRun.Flags().BoolVar(&noClean, "no-clean", false, "Don't clean runtime environment if test succeed")
 	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]",
 		Short:             "clean [test_name]",
 		Args:              cobra.MinimumNArgs(1),
 		Args:              cobra.MinimumNArgs(1),
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			for _, testName := range args {
 			for _, testName := range args {
 				test, err := HubTest.LoadTestItem(testName)
 				test, err := HubTest.LoadTestItem(testName)
 				if err != nil {
 				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 {
 				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]",
 		Short:             "info [test_name]",
 		Args:              cobra.MinimumNArgs(1),
 		Args:              cobra.MinimumNArgs(1),
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			for _, testName := range args {
 			for _, testName := range args {
 				test, err := HubTest.LoadTestItem(testName)
 				test, err := HubTest.LoadTestItem(testName)
 				if err != nil {
 				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.Println()
 				fmt.Printf("  Test name                   :  %s\n", test.Name)
 				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("  Scenario assertion file     :  %s\n", filepath.Join(test.Path, hubtest.ScenarioAssertFileName))
 				fmt.Printf("  Configuration File          :  %s\n", filepath.Join(test.Path, "config.yaml"))
 				fmt.Printf("  Configuration File          :  %s\n", filepath.Join(test.Path, "config.yaml"))
 			}
 			}
+
+			return nil
 		},
 		},
 	}
 	}
 
 
@@ -378,9 +387,9 @@ func NewHubTestListCmd() *cobra.Command {
 		Use:               "list",
 		Use:               "list",
 		Short:             "list",
 		Short:             "list",
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			if err := HubTest.LoadAllTests(); err != nil {
 			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 {
 			switch csConfig.Cscli.Output {
@@ -389,12 +398,14 @@ func NewHubTestListCmd() *cobra.Command {
 			case "json":
 			case "json":
 				j, err := json.MarshalIndent(HubTest.Tests, " ", "  ")
 				j, err := json.MarshalIndent(HubTest.Tests, " ", "  ")
 				if err != nil {
 				if err != nil {
-					log.Fatal(err)
+					return err
 				}
 				}
 				fmt.Println(string(j))
 				fmt.Println(string(j))
 			default:
 			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",
 		Use:               "coverage",
 		Short:             "coverage",
 		Short:             "coverage",
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			if err := HubTest.LoadAllTests(); err != nil {
 			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
 			var err error
 			scenarioCoverage := []hubtest.ScenarioCoverage{}
 			scenarioCoverage := []hubtest.ScenarioCoverage{}
@@ -427,7 +438,7 @@ func NewHubTestCoverageCmd() *cobra.Command {
 			if showParserCov || showAll {
 			if showParserCov || showAll {
 				parserCoverage, err = HubTest.GetParsersCoverage()
 				parserCoverage, err = HubTest.GetParsersCoverage()
 				if err != nil {
 				if err != nil {
-					log.Fatalf("while getting parser coverage : %s", err)
+					return fmt.Errorf("while getting parser coverage: %s", err)
 				}
 				}
 				parserTested := 0
 				parserTested := 0
 				for _, test := range parserCoverage {
 				for _, test := range parserCoverage {
@@ -441,7 +452,7 @@ func NewHubTestCoverageCmd() *cobra.Command {
 			if showScenarioCov || showAll {
 			if showScenarioCov || showAll {
 				scenarioCoverage, err = HubTest.GetScenariosCoverage()
 				scenarioCoverage, err = HubTest.GetScenariosCoverage()
 				if err != nil {
 				if err != nil {
-					log.Fatalf("while getting scenario coverage: %s", err)
+					return fmt.Errorf("while getting scenario coverage: %s", err)
 				}
 				}
 				scenarioTested := 0
 				scenarioTested := 0
 				for _, test := range scenarioCoverage {
 				for _, test := range scenarioCoverage {
@@ -481,18 +492,19 @@ func NewHubTestCoverageCmd() *cobra.Command {
 			} else if csConfig.Cscli.Output == "json" {
 			} else if csConfig.Cscli.Output == "json" {
 				dump, err := json.MarshalIndent(parserCoverage, "", " ")
 				dump, err := json.MarshalIndent(parserCoverage, "", " ")
 				if err != nil {
 				if err != nil {
-					log.Fatal(err)
+					return err
 				}
 				}
 				fmt.Printf("%s", dump)
 				fmt.Printf("%s", dump)
 				dump, err = json.MarshalIndent(scenarioCoverage, "", " ")
 				dump, err = json.MarshalIndent(scenarioCoverage, "", " ")
 				if err != nil {
 				if err != nil {
-					log.Fatal(err)
+					return err
 				}
 				}
 				fmt.Printf("%s", dump)
 				fmt.Printf("%s", dump)
 			} else {
 			} 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")
 	cmdHubTestCoverage.PersistentFlags().BoolVar(&showOnlyPercent, "percent", false, "Show only percentages of coverage")
@@ -510,22 +522,24 @@ func NewHubTestEvalCmd() *cobra.Command {
 		Short:             "eval [test_name]",
 		Short:             "eval [test_name]",
 		Args:              cobra.ExactArgs(1),
 		Args:              cobra.ExactArgs(1),
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			for _, testName := range args {
 			for _, testName := range args {
 				test, err := HubTest.LoadTestItem(testName)
 				test, err := HubTest.LoadTestItem(testName)
 				if err != nil {
 				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)
 				err = test.ParserAssert.LoadTest(test.ParserResultFile)
 				if err != nil {
 				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)
 				output, err := test.ParserAssert.EvalExpression(evalExpression)
 				if err != nil {
 				if err != nil {
-					log.Fatal(err)
+					return err
 				}
 				}
 				fmt.Print(output)
 				fmt.Print(output)
 			}
 			}
+
+			return nil
 		},
 		},
 	}
 	}
 	cmdHubTestEval.PersistentFlags().StringVarP(&evalExpression, "expr", "e", "", "Expression to eval")
 	cmdHubTestEval.PersistentFlags().StringVarP(&evalExpression, "expr", "e", "", "Expression to eval")
@@ -540,21 +554,21 @@ func NewHubTestExplainCmd() *cobra.Command {
 		Short:             "explain [test_name]",
 		Short:             "explain [test_name]",
 		Args:              cobra.ExactArgs(1),
 		Args:              cobra.ExactArgs(1),
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		Run: func(cmd *cobra.Command, args []string) {
+		RunE: func(cmd *cobra.Command, args []string) error {
 			for _, testName := range args {
 			for _, testName := range args {
 				test, err := HubTest.LoadTestItem(testName)
 				test, err := HubTest.LoadTestItem(testName)
 				if err != nil {
 				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)
 				err = test.ParserAssert.LoadTest(test.ParserResultFile)
 				if err != nil {
 				if err != nil {
 					err := test.Run()
 					err := test.Run()
 					if err != nil {
 					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)
 					err = test.ParserAssert.LoadTest(test.ParserResultFile)
 					if err != nil {
 					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 {
 				if err != nil {
 					err := test.Run()
 					err := test.Run()
 					if err != nil {
 					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)
 					err = test.ScenarioAssert.LoadTest(test.ScenarioResultFile, test.BucketPourResultFile)
 					if err != nil {
 					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{}
 				opts := hubtest.DumpOpts{}
 				hubtest.DumpTree(*test.ParserAssert.TestData, *test.ScenarioAssert.PourData, opts)
 				hubtest.DumpTree(*test.ParserAssert.TestData, *test.ScenarioAssert.PourData, opts)
 			}
 			}
+
+			return nil
 		},
 		},
 	}
 	}
 
 

+ 36 - 24
cmd/crowdsec-cli/machines.go

@@ -16,7 +16,6 @@ import (
 	"github.com/fatih/color"
 	"github.com/fatih/color"
 	"github.com/go-openapi/strfmt"
 	"github.com/go-openapi/strfmt"
 	"github.com/google/uuid"
 	"github.com/google/uuid"
-	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
 	log "github.com/sirupsen/logrus"
 	"github.com/spf13/cobra"
 	"github.com/spf13/cobra"
 	"gopkg.in/yaml.v2"
 	"gopkg.in/yaml.v2"
@@ -67,7 +66,7 @@ func generateIDPrefix() (string, error) {
 	if err == nil {
 	if err == nil {
 		return bId.String(), 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.
 // 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 := json.NewEncoder(out)
 		enc.SetIndent("", "  ")
 		enc.SetIndent("", "  ")
 		if err := enc.Encode(machines); err != nil {
 		if err := enc.Encode(machines); err != nil {
-			log.Fatalf("failed to unmarshal")
+			return fmt.Errorf("failed to marshal")
 		}
 		}
 		return nil
 		return nil
 	} else if csConfig.Cscli.Output == "raw" {
 	} else if csConfig.Cscli.Output == "raw" {
 		csvwriter := csv.NewWriter(out)
 		csvwriter := csv.NewWriter(out)
 		err := csvwriter.Write([]string{"machine_id", "ip_address", "updated_at", "validated", "version", "auth_type", "last_heartbeat"})
 		err := csvwriter.Write([]string{"machine_id", "ip_address", "updated_at", "validated", "version", "auth_type", "last_heartbeat"})
 		if err != nil {
 		if err != nil {
-			log.Fatalf("failed to write header: %s", err)
+			return fmt.Errorf("failed to write header: %s", err)
 		}
 		}
 		for _, m := range machines {
 		for _, m := range machines {
 			var validated string
 			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)})
 			err := csvwriter.Write([]string{m.MachineId, m.IpAddress, m.UpdatedAt.Format(time.RFC3339), validated, m.Version, m.AuthType, displayLastHeartBeat(m, false)})
 			if err != nil {
 			if err != nil {
-				log.Fatalf("failed to write raw output : %s", err)
+				return fmt.Errorf("failed to write raw output : %s", err)
 			}
 			}
 		}
 		}
 		csvwriter.Flush()
 		csvwriter.Flush()
@@ -150,18 +149,22 @@ func NewMachinesListCmd() *cobra.Command {
 		Example:           `cscli machines list`,
 		Example:           `cscli machines list`,
 		Args:              cobra.MaximumNArgs(1),
 		Args:              cobra.MaximumNArgs(1),
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		PreRun: func(cmd *cobra.Command, args []string) {
+		PreRunE: func(cmd *cobra.Command, args []string) error {
 			var err error
 			var err error
 			dbClient, err = database.NewClient(csConfig.DbConfig)
 			dbClient, err = database.NewClient(csConfig.DbConfig)
 			if err != nil {
 			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)
 			err := getAgents(color.Output, dbClient)
 			if err != nil {
 			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 --auto
 cscli machines add MyTestMachine --password MyPassword
 cscli machines add MyTestMachine --password MyPassword
 `,
 `,
-		PreRun: func(cmd *cobra.Command, args []string) {
+		PreRunE: func(cmd *cobra.Command, args []string) error {
 			var err error
 			var err error
 			dbClient, err = database.NewClient(csConfig.DbConfig)
 			dbClient, err = database.NewClient(csConfig.DbConfig)
 			if err != nil {
 			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,
 		RunE: runMachinesAdd,
 	}
 	}
@@ -246,7 +251,7 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error {
 		}
 		}
 		machineID, err = generateID("")
 		machineID, err = generateID("")
 		if err != nil {
 		if err != nil {
-			log.Fatalf("unable to generate machine id : %s", err)
+			return fmt.Errorf("unable to generate machine id: %s", err)
 		}
 		}
 	} else {
 	} else {
 		machineID = args[0]
 		machineID = args[0]
@@ -275,7 +280,7 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error {
 	password := strfmt.Password(machinePassword)
 	password := strfmt.Password(machinePassword)
 	_, err = dbClient.CreateMachine(&machineID, &password, "", true, forceAdd, types.PasswordAuthType)
 	_, err = dbClient.CreateMachine(&machineID, &password, "", true, forceAdd, types.PasswordAuthType)
 	if err != nil {
 	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)
 	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 != "" {
 		} else if csConfig.API.Server != nil && csConfig.API.Server.ListenURI != "" {
 			apiURL = "http://" + csConfig.API.Server.ListenURI
 			apiURL = "http://" + csConfig.API.Server.ListenURI
 		} else {
 		} 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{
 	apiCfg := csconfig.ApiCredentialsCfg{
@@ -295,12 +300,12 @@ func runMachinesAdd(cmd *cobra.Command, args []string) error {
 	}
 	}
 	apiConfigDump, err := yaml.Marshal(apiCfg)
 	apiConfigDump, err := yaml.Marshal(apiCfg)
 	if err != nil {
 	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 != "-" {
 	if dumpFile != "" && dumpFile != "-" {
 		err = os.WriteFile(dumpFile, apiConfigDump, 0644)
 		err = os.WriteFile(dumpFile, apiConfigDump, 0644)
 		if err != nil {
 		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)
 		log.Printf("API credentials dumped to '%s'", dumpFile)
 	} else {
 	} else {
@@ -318,12 +323,13 @@ func NewMachinesDeleteCmd() *cobra.Command {
 		Args:              cobra.MinimumNArgs(1),
 		Args:              cobra.MinimumNArgs(1),
 		Aliases:           []string{"remove"},
 		Aliases:           []string{"remove"},
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		PreRun: func(cmd *cobra.Command, args []string) {
+		PreRunE: func(cmd *cobra.Command, args []string) error {
 			var err error
 			var err error
 			dbClient, err = database.NewClient(csConfig.DbConfig)
 			dbClient, err = database.NewClient(csConfig.DbConfig)
 			if err != nil {
 			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) {
 		ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
 			var err error
 			var err error
@@ -371,19 +377,23 @@ func NewMachinesValidateCmd() *cobra.Command {
 		Example:           `cscli machines validate "machine_name"`,
 		Example:           `cscli machines validate "machine_name"`,
 		Args:              cobra.ExactArgs(1),
 		Args:              cobra.ExactArgs(1),
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
-		PreRun: func(cmd *cobra.Command, args []string) {
+		PreRunE: func(cmd *cobra.Command, args []string) error {
 			var err error
 			var err error
 			dbClient, err = database.NewClient(csConfig.DbConfig)
 			dbClient, err = database.NewClient(csConfig.DbConfig)
 			if err != nil {
 			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]
 			machineID := args[0]
 			if err := dbClient.ValidateMachine(machineID); err != nil {
 			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)
 			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]`,
 		Example:           `cscli machines [action]`,
 		DisableAutoGenTag: true,
 		DisableAutoGenTag: true,
 		Aliases:           []string{"machine"},
 		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 := csConfig.LoadAPIServer(); err != nil || csConfig.DisableAPI {
 				if err != nil {
 				if err != nil {
 					log.Errorf("local api : %s", err)
 					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
 		},
 		},
 	}
 	}
 
 

+ 3 - 2
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),
 		Args:              cobra.ExactArgs(0),
 		Hidden:            true,
 		Hidden:            true,
 		DisableAutoGenTag: 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 {
 			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)
 	rootCmd.AddCommand(cmdDocGen)

+ 23 - 0
tests/bats/01_base.bats

@@ -19,6 +19,7 @@ setup() {
 }
 }
 
 
 teardown() {
 teardown() {
+    cd "$TEST_DIR" || exit 1
     ./instance-crowdsec stop
     ./instance-crowdsec stop
 }
 }
 
 
@@ -297,3 +298,25 @@ declare stderr
     rune -0 cscli config show --key Config.DbConfig.Password
     rune -0 cscli config show --key Config.DbConfig.Password
     assert_output 'P@ssw0rd$'
     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"
+}

+ 2 - 2
tests/bats/02_nolapi.bats

@@ -75,7 +75,7 @@ teardown() {
     config_disable_lapi
     config_disable_lapi
     ./instance-crowdsec start || true
     ./instance-crowdsec start || true
     run -1 --separate-stderr cscli machines list
     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" {
 @test "cscli metrics" {
@@ -87,5 +87,5 @@ teardown() {
     assert_output --partial "/v1/watchers/login"
     assert_output --partial "/v1/watchers/login"
 
 
     assert_stderr --partial "crowdsec local API is disabled"
     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"
 }
 }

+ 4 - 1
tests/bats/80_alerts.bats

@@ -66,6 +66,9 @@ teardown() {
 }
 }
 
 
 @test "cscli alerts inspect" {
 @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 decisions add -i 10.20.30.40 -t ban
     run -0 cscli alerts list -o raw <(output)
     run -0 cscli alerts list -o raw <(output)
     run -0 grep 10.20.30.40 <(output)
     run -0 grep 10.20.30.40 <(output)
@@ -143,7 +146,7 @@ teardown() {
     # can't delete twice
     # can't delete twice
     run -1 --separate-stderr cscli alerts delete --id "$ALERT_ID"
     run -1 --separate-stderr cscli alerts delete --id "$ALERT_ID"
     refute_output
     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"
     assert_stderr --partial "API error: ent: alert not found"
 }
 }