浏览代码

cscli refact / encapsulate methods for capi, hubtest, dashboard, alerts, decisions, simulation (#2650)

mmetc 1 年之前
父节点
当前提交
c10aad79d9

+ 57 - 51
cmd/crowdsec-cli/alerts.go

@@ -21,12 +21,11 @@ import (
 
 	"github.com/crowdsecurity/go-cs-lib/version"
 
+	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
 	"github.com/crowdsecurity/crowdsec/pkg/apiclient"
 	"github.com/crowdsecurity/crowdsec/pkg/database"
 	"github.com/crowdsecurity/crowdsec/pkg/models"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
-
-	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
 )
 
 func DecisionsFromAlert(alert *models.Alert) string {
@@ -208,8 +207,14 @@ func DisplayOneAlert(alert *models.Alert, withDetail bool) error {
 	return nil
 }
 
-func NewAlertsCmd() *cobra.Command {
-	var cmdAlerts = &cobra.Command{
+type cliAlerts struct{}
+
+func NewCLIAlerts() *cliAlerts {
+	return &cliAlerts{}
+}
+
+func (cli cliAlerts) NewCommand() *cobra.Command {
+	cmd := &cobra.Command{
 		Use:               "alerts [action]",
 		Short:             "Manage alerts",
 		Args:              cobra.MinimumNArgs(1),
@@ -239,15 +244,15 @@ func NewAlertsCmd() *cobra.Command {
 		},
 	}
 
-	cmdAlerts.AddCommand(NewAlertsListCmd())
-	cmdAlerts.AddCommand(NewAlertsInspectCmd())
-	cmdAlerts.AddCommand(NewAlertsFlushCmd())
-	cmdAlerts.AddCommand(NewAlertsDeleteCmd())
+	cmd.AddCommand(cli.NewListCmd())
+	cmd.AddCommand(cli.NewInspectCmd())
+	cmd.AddCommand(cli.NewFlushCmd())
+	cmd.AddCommand(cli.NewDeleteCmd())
 
-	return cmdAlerts
+	return cmd
 }
 
-func NewAlertsListCmd() *cobra.Command {
+func (cli cliAlerts) NewListCmd() *cobra.Command {
 	var alertListFilter = apiclient.AlertsListOpts{
 		ScopeEquals:    new(string),
 		ValueEquals:    new(string),
@@ -260,10 +265,11 @@ func NewAlertsListCmd() *cobra.Command {
 		IncludeCAPI:    new(bool),
 		OriginEquals:   new(string),
 	}
-	var limit = new(int)
+	limit := new(int)
 	contained := new(bool)
 	var printMachine bool
-	var cmdAlertsList = &cobra.Command{
+
+	cmd := &cobra.Command{
 		Use:   "list [filters]",
 		Short: "List alerts",
 		Example: `cscli alerts list
@@ -353,25 +359,25 @@ cscli alerts list --type ban`,
 			return nil
 		},
 	}
-	cmdAlertsList.Flags().SortFlags = false
-	cmdAlertsList.Flags().BoolVarP(alertListFilter.IncludeCAPI, "all", "a", false, "Include decisions from Central API")
-	cmdAlertsList.Flags().StringVar(alertListFilter.Until, "until", "", "restrict to alerts older than until (ie. 4h, 30d)")
-	cmdAlertsList.Flags().StringVar(alertListFilter.Since, "since", "", "restrict to alerts newer than since (ie. 4h, 30d)")
-	cmdAlertsList.Flags().StringVarP(alertListFilter.IPEquals, "ip", "i", "", "restrict to alerts from this source ip (shorthand for --scope ip --value <IP>)")
-	cmdAlertsList.Flags().StringVarP(alertListFilter.ScenarioEquals, "scenario", "s", "", "the scenario (ie. crowdsecurity/ssh-bf)")
-	cmdAlertsList.Flags().StringVarP(alertListFilter.RangeEquals, "range", "r", "", "restrict to alerts from this range (shorthand for --scope range --value <RANGE/X>)")
-	cmdAlertsList.Flags().StringVar(alertListFilter.TypeEquals, "type", "", "restrict to alerts with given decision type (ie. ban, captcha)")
-	cmdAlertsList.Flags().StringVar(alertListFilter.ScopeEquals, "scope", "", "restrict to alerts of this scope (ie. ip,range)")
-	cmdAlertsList.Flags().StringVarP(alertListFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
-	cmdAlertsList.Flags().StringVar(alertListFilter.OriginEquals, "origin", "", fmt.Sprintf("the value to match for the specified origin (%s ...)", strings.Join(types.GetOrigins(), ",")))
-	cmdAlertsList.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
-	cmdAlertsList.Flags().BoolVarP(&printMachine, "machine", "m", false, "print machines that sent alerts")
-	cmdAlertsList.Flags().IntVarP(limit, "limit", "l", 50, "limit size of alerts list table (0 to view all alerts)")
-
-	return cmdAlertsList
+	cmd.Flags().SortFlags = false
+	cmd.Flags().BoolVarP(alertListFilter.IncludeCAPI, "all", "a", false, "Include decisions from Central API")
+	cmd.Flags().StringVar(alertListFilter.Until, "until", "", "restrict to alerts older than until (ie. 4h, 30d)")
+	cmd.Flags().StringVar(alertListFilter.Since, "since", "", "restrict to alerts newer than since (ie. 4h, 30d)")
+	cmd.Flags().StringVarP(alertListFilter.IPEquals, "ip", "i", "", "restrict to alerts from this source ip (shorthand for --scope ip --value <IP>)")
+	cmd.Flags().StringVarP(alertListFilter.ScenarioEquals, "scenario", "s", "", "the scenario (ie. crowdsecurity/ssh-bf)")
+	cmd.Flags().StringVarP(alertListFilter.RangeEquals, "range", "r", "", "restrict to alerts from this range (shorthand for --scope range --value <RANGE/X>)")
+	cmd.Flags().StringVar(alertListFilter.TypeEquals, "type", "", "restrict to alerts with given decision type (ie. ban, captcha)")
+	cmd.Flags().StringVar(alertListFilter.ScopeEquals, "scope", "", "restrict to alerts of this scope (ie. ip,range)")
+	cmd.Flags().StringVarP(alertListFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
+	cmd.Flags().StringVar(alertListFilter.OriginEquals, "origin", "", fmt.Sprintf("the value to match for the specified origin (%s ...)", strings.Join(types.GetOrigins(), ",")))
+	cmd.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
+	cmd.Flags().BoolVarP(&printMachine, "machine", "m", false, "print machines that sent alerts")
+	cmd.Flags().IntVarP(limit, "limit", "l", 50, "limit size of alerts list table (0 to view all alerts)")
+
+	return cmd
 }
 
-func NewAlertsDeleteCmd() *cobra.Command {
+func (cli cliAlerts) NewDeleteCmd() *cobra.Command {
 	var ActiveDecision *bool
 	var AlertDeleteAll bool
 	var delAlertByID string
@@ -383,7 +389,7 @@ func NewAlertsDeleteCmd() *cobra.Command {
 		IPEquals:       new(string),
 		RangeEquals:    new(string),
 	}
-	var cmdAlertsDelete = &cobra.Command{
+	cmd := &cobra.Command{
 		Use: "delete [filters] [--all]",
 		Short: `Delete alerts
 /!\ This command can be use only on the same machine than the local API.`,
@@ -461,21 +467,21 @@ cscli alerts delete -s crowdsecurity/ssh-bf"`,
 			return nil
 		},
 	}
-	cmdAlertsDelete.Flags().SortFlags = false
-	cmdAlertsDelete.Flags().StringVar(alertDeleteFilter.ScopeEquals, "scope", "", "the scope (ie. ip,range)")
-	cmdAlertsDelete.Flags().StringVarP(alertDeleteFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
-	cmdAlertsDelete.Flags().StringVarP(alertDeleteFilter.ScenarioEquals, "scenario", "s", "", "the scenario (ie. crowdsecurity/ssh-bf)")
-	cmdAlertsDelete.Flags().StringVarP(alertDeleteFilter.IPEquals, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
-	cmdAlertsDelete.Flags().StringVarP(alertDeleteFilter.RangeEquals, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
-	cmdAlertsDelete.Flags().StringVar(&delAlertByID, "id", "", "alert ID")
-	cmdAlertsDelete.Flags().BoolVarP(&AlertDeleteAll, "all", "a", false, "delete all alerts")
-	cmdAlertsDelete.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
-	return cmdAlertsDelete
+	cmd.Flags().SortFlags = false
+	cmd.Flags().StringVar(alertDeleteFilter.ScopeEquals, "scope", "", "the scope (ie. ip,range)")
+	cmd.Flags().StringVarP(alertDeleteFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
+	cmd.Flags().StringVarP(alertDeleteFilter.ScenarioEquals, "scenario", "s", "", "the scenario (ie. crowdsecurity/ssh-bf)")
+	cmd.Flags().StringVarP(alertDeleteFilter.IPEquals, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
+	cmd.Flags().StringVarP(alertDeleteFilter.RangeEquals, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
+	cmd.Flags().StringVar(&delAlertByID, "id", "", "alert ID")
+	cmd.Flags().BoolVarP(&AlertDeleteAll, "all", "a", false, "delete all alerts")
+	cmd.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
+	return cmd
 }
 
-func NewAlertsInspectCmd() *cobra.Command {
+func (cli cliAlerts) NewInspectCmd() *cobra.Command {
 	var details bool
-	var cmdAlertsInspect = &cobra.Command{
+	cmd := &cobra.Command{
 		Use:               `inspect "alert_id"`,
 		Short:             `Show info about an alert`,
 		Example:           `cscli alerts inspect 123`,
@@ -517,16 +523,16 @@ func NewAlertsInspectCmd() *cobra.Command {
 			return nil
 		},
 	}
-	cmdAlertsInspect.Flags().SortFlags = false
-	cmdAlertsInspect.Flags().BoolVarP(&details, "details", "d", false, "show alerts with events")
+	cmd.Flags().SortFlags = false
+	cmd.Flags().BoolVarP(&details, "details", "d", false, "show alerts with events")
 
-	return cmdAlertsInspect
+	return cmd
 }
 
-func NewAlertsFlushCmd() *cobra.Command {
+func (cli cliAlerts) NewFlushCmd() *cobra.Command {
 	var maxItems int
 	var maxAge string
-	var cmdAlertsFlush = &cobra.Command{
+	cmd := &cobra.Command{
 		Use: `flush`,
 		Short: `Flush alerts
 /!\ This command can be used only on the same machine than the local API`,
@@ -552,9 +558,9 @@ func NewAlertsFlushCmd() *cobra.Command {
 		},
 	}
 
-	cmdAlertsFlush.Flags().SortFlags = false
-	cmdAlertsFlush.Flags().IntVar(&maxItems, "max-items", 5000, "Maximum number of alert items to keep in the database")
-	cmdAlertsFlush.Flags().StringVar(&maxAge, "max-age", "7d", "Maximum age of alert items to keep in the database")
+	cmd.Flags().SortFlags = false
+	cmd.Flags().IntVar(&maxItems, "max-items", 5000, "Maximum number of alert items to keep in the database")
+	cmd.Flags().StringVar(&maxAge, "max-age", "7d", "Maximum age of alert items to keep in the database")
 
-	return cmdAlertsFlush
+	return cmd
 }

+ 25 - 17
cmd/crowdsec-cli/capi.go

@@ -13,20 +13,27 @@ import (
 
 	"github.com/crowdsecurity/go-cs-lib/version"
 
+	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
 	"github.com/crowdsecurity/crowdsec/pkg/apiclient"
 	"github.com/crowdsecurity/crowdsec/pkg/csconfig"
 	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
 	"github.com/crowdsecurity/crowdsec/pkg/models"
 	"github.com/crowdsecurity/crowdsec/pkg/types"
+)
 
-	"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require"
+const (
+	CAPIBaseURL   = "https://api.crowdsec.net/"
+	CAPIURLPrefix = "v3"
 )
 
-const CAPIBaseURL string = "https://api.crowdsec.net/"
-const CAPIURLPrefix = "v3"
+type cliCapi struct{}
 
-func NewCapiCmd() *cobra.Command {
-	var cmdCapi = &cobra.Command{
+func NewCLICapi() *cliCapi {
+	return &cliCapi{}
+}
+
+func (cli cliCapi) NewCommand() *cobra.Command {
+	var cmd = &cobra.Command{
 		Use:               "capi [action]",
 		Short:             "Manage interaction with Central API (CAPI)",
 		Args:              cobra.MinimumNArgs(1),
@@ -44,17 +51,17 @@ func NewCapiCmd() *cobra.Command {
 		},
 	}
 
-	cmdCapi.AddCommand(NewCapiRegisterCmd())
-	cmdCapi.AddCommand(NewCapiStatusCmd())
+	cmd.AddCommand(cli.NewRegisterCmd())
+	cmd.AddCommand(cli.NewStatusCmd())
 
-	return cmdCapi
+	return cmd
 }
 
-func NewCapiRegisterCmd() *cobra.Command {
+func (cli cliCapi) NewRegisterCmd() *cobra.Command {
 	var capiUserPrefix string
 	var outputFile string
 
-	var cmdCapiRegister = &cobra.Command{
+	var cmd = &cobra.Command{
 		Use:               "register",
 		Short:             "Register to Central API (CAPI)",
 		Args:              cobra.MinimumNArgs(0),
@@ -116,17 +123,18 @@ func NewCapiRegisterCmd() *cobra.Command {
 			return nil
 		},
 	}
-	cmdCapiRegister.Flags().StringVarP(&outputFile, "file", "f", "", "output file destination")
-	cmdCapiRegister.Flags().StringVar(&capiUserPrefix, "schmilblick", "", "set a schmilblick (use in tests only)")
-	if err := cmdCapiRegister.Flags().MarkHidden("schmilblick"); err != nil {
+
+	cmd.Flags().StringVarP(&outputFile, "file", "f", "", "output file destination")
+	cmd.Flags().StringVar(&capiUserPrefix, "schmilblick", "", "set a schmilblick (use in tests only)")
+	if err := cmd.Flags().MarkHidden("schmilblick"); err != nil {
 		log.Fatalf("failed to hide flag: %s", err)
 	}
 
-	return cmdCapiRegister
+	return cmd
 }
 
-func NewCapiStatusCmd() *cobra.Command {
-	var cmdCapiStatus = &cobra.Command{
+func (cli cliCapi) NewStatusCmd() *cobra.Command {
+	var cmd = &cobra.Command{
 		Use:               "status",
 		Short:             "Check status with the Central API (CAPI)",
 		Args:              cobra.MinimumNArgs(0),
@@ -185,5 +193,5 @@ func NewCapiStatusCmd() *cobra.Command {
 		},
 	}
 
-	return cmdCapiStatus
+	return cmd
 }

+ 43 - 36
cmd/crowdsec-cli/dashboard.go

@@ -43,9 +43,15 @@ var (
 	// information needed to set up a random password on user's behalf
 )
 
-func NewDashboardCmd() *cobra.Command {
+type cliDashboard struct{}
+
+func NewCLIDashboard() *cliDashboard {
+	return &cliDashboard{}
+}
+
+func (cli cliDashboard) NewCommand() *cobra.Command {
 	/* ---- UPDATE COMMAND */
-	var cmdDashboard = &cobra.Command{
+	cmd := &cobra.Command{
 		Use:   "dashboard [command]",
 		Short: "Manage your metabase dashboard container [requires local API]",
 		Long: `Install/Start/Stop/Remove a metabase container exposing dashboard and metrics.
@@ -93,19 +99,19 @@ cscli dashboard remove
 		},
 	}
 
-	cmdDashboard.AddCommand(NewDashboardSetupCmd())
-	cmdDashboard.AddCommand(NewDashboardStartCmd())
-	cmdDashboard.AddCommand(NewDashboardStopCmd())
-	cmdDashboard.AddCommand(NewDashboardShowPasswordCmd())
-	cmdDashboard.AddCommand(NewDashboardRemoveCmd())
+	cmd.AddCommand(cli.NewSetupCmd())
+	cmd.AddCommand(cli.NewStartCmd())
+	cmd.AddCommand(cli.NewStopCmd())
+	cmd.AddCommand(cli.NewShowPasswordCmd())
+	cmd.AddCommand(cli.NewRemoveCmd())
 
-	return cmdDashboard
+	return cmd
 }
 
-func NewDashboardSetupCmd() *cobra.Command {
+func (cli cliDashboard) NewSetupCmd() *cobra.Command {
 	var force bool
 
-	var cmdDashSetup = &cobra.Command{
+	cmd := &cobra.Command{
 		Use:               "setup",
 		Short:             "Setup a metabase container.",
 		Long:              `Perform a metabase docker setup, download standard dashboards, create a fresh user and start the container`,
@@ -158,20 +164,20 @@ cscli dashboard setup -l 0.0.0.0 -p 443 --password <password>
 			return nil
 		},
 	}
-	cmdDashSetup.Flags().BoolVarP(&force, "force", "f", false, "Force setup : override existing files")
-	cmdDashSetup.Flags().StringVarP(&metabaseDbPath, "dir", "d", "", "Shared directory with metabase container")
-	cmdDashSetup.Flags().StringVarP(&metabaseListenAddress, "listen", "l", metabaseListenAddress, "Listen address of container")
-	cmdDashSetup.Flags().StringVar(&metabaseImage, "metabase-image", metabaseImage, "Metabase image to use")
-	cmdDashSetup.Flags().StringVarP(&metabaseListenPort, "port", "p", metabaseListenPort, "Listen port of container")
-	cmdDashSetup.Flags().BoolVarP(&forceYes, "yes", "y", false, "force  yes")
-	//cmdDashSetup.Flags().StringVarP(&metabaseUser, "user", "u", "crowdsec@crowdsec.net", "metabase user")
-	cmdDashSetup.Flags().StringVar(&metabasePassword, "password", "", "metabase password")
-
-	return cmdDashSetup
+	cmd.Flags().BoolVarP(&force, "force", "f", false, "Force setup : override existing files")
+	cmd.Flags().StringVarP(&metabaseDbPath, "dir", "d", "", "Shared directory with metabase container")
+	cmd.Flags().StringVarP(&metabaseListenAddress, "listen", "l", metabaseListenAddress, "Listen address of container")
+	cmd.Flags().StringVar(&metabaseImage, "metabase-image", metabaseImage, "Metabase image to use")
+	cmd.Flags().StringVarP(&metabaseListenPort, "port", "p", metabaseListenPort, "Listen port of container")
+	cmd.Flags().BoolVarP(&forceYes, "yes", "y", false, "force  yes")
+	//cmd.Flags().StringVarP(&metabaseUser, "user", "u", "crowdsec@crowdsec.net", "metabase user")
+	cmd.Flags().StringVar(&metabasePassword, "password", "", "metabase password")
+
+	return cmd
 }
 
-func NewDashboardStartCmd() *cobra.Command {
-	var cmdDashStart = &cobra.Command{
+func (cli cliDashboard) NewStartCmd() *cobra.Command {
+	cmd := &cobra.Command{
 		Use:               "start",
 		Short:             "Start the metabase container.",
 		Long:              `Stats the metabase container using docker.`,
@@ -194,12 +200,12 @@ func NewDashboardStartCmd() *cobra.Command {
 			return nil
 		},
 	}
-	cmdDashStart.Flags().BoolVarP(&forceYes, "yes", "y", false, "force  yes")
-	return cmdDashStart
+	cmd.Flags().BoolVarP(&forceYes, "yes", "y", false, "force  yes")
+	return cmd
 }
 
-func NewDashboardStopCmd() *cobra.Command {
-	var cmdDashStop = &cobra.Command{
+func (cli cliDashboard) NewStopCmd() *cobra.Command {
+	cmd := &cobra.Command{
 		Use:               "stop",
 		Short:             "Stops the metabase container.",
 		Long:              `Stops the metabase container using docker.`,
@@ -212,11 +218,11 @@ func NewDashboardStopCmd() *cobra.Command {
 			return nil
 		},
 	}
-	return cmdDashStop
+	return cmd
 }
 
-func NewDashboardShowPasswordCmd() *cobra.Command {
-	var cmdDashShowPassword = &cobra.Command{Use: "show-password",
+func (cli cliDashboard) NewShowPasswordCmd() *cobra.Command {
+	cmd := &cobra.Command{Use: "show-password",
 		Short:             "displays password of metabase.",
 		Args:              cobra.ExactArgs(0),
 		DisableAutoGenTag: true,
@@ -229,13 +235,13 @@ func NewDashboardShowPasswordCmd() *cobra.Command {
 			return nil
 		},
 	}
-	return cmdDashShowPassword
+	return cmd
 }
 
-func NewDashboardRemoveCmd() *cobra.Command {
+func (cli cliDashboard) NewRemoveCmd() *cobra.Command {
 	var force bool
 
-	var cmdDashRemove = &cobra.Command{
+	cmd := &cobra.Command{
 		Use:               "remove",
 		Short:             "removes the metabase container.",
 		Long:              `removes the metabase container using docker.`,
@@ -300,17 +306,19 @@ cscli dashboard remove --force
 			return nil
 		},
 	}
-	cmdDashRemove.Flags().BoolVarP(&force, "force", "f", false, "Remove also the metabase image")
-	cmdDashRemove.Flags().BoolVarP(&forceYes, "yes", "y", false, "force  yes")
+	cmd.Flags().BoolVarP(&force, "force", "f", false, "Remove also the metabase image")
+	cmd.Flags().BoolVarP(&forceYes, "yes", "y", false, "force  yes")
 
-	return cmdDashRemove
+	return cmd
 }
 
 func passwordIsValid(password string) bool {
 	hasDigit := false
+
 	for _, j := range password {
 		if unicode.IsDigit(j) {
 			hasDigit = true
+
 			break
 		}
 	}
@@ -319,7 +327,6 @@ func passwordIsValid(password string) bool {
 		return false
 	}
 	return true
-
 }
 
 func checkSystemMemory(forceYes *bool) error {

+ 9 - 3
cmd/crowdsec-cli/dashboard_unsupported.go

@@ -9,8 +9,14 @@ import (
 	"github.com/spf13/cobra"
 )
 
-func NewDashboardCmd() *cobra.Command {
-	var cmdDashboard = &cobra.Command{
+type cliDashboard struct{}
+
+func NewCLIDashboard() *cliDashboard {
+	return &cliDashboard{}
+}
+
+func (cli cliDashboard) NewCommand() *cobra.Command {
+	cmd := &cobra.Command{
 		Use:               "dashboard",
 		DisableAutoGenTag: true,
 		Run: func(cmd *cobra.Command, args []string) {
@@ -18,5 +24,5 @@ func NewDashboardCmd() *cobra.Command {
 		},
 	}
 
-	return cmdDashboard
+	return cmd
 }

+ 57 - 50
cmd/crowdsec-cli/decisions.go

@@ -102,8 +102,15 @@ func DecisionsToTable(alerts *models.GetAlertsResponse, printMachine bool) error
 	return nil
 }
 
-func NewDecisionsCmd() *cobra.Command {
-	var cmdDecisions = &cobra.Command{
+
+type cliDecisions struct {}
+
+func NewCLIDecisions() *cliDecisions {
+	return &cliDecisions{}
+}
+
+func (cli cliDecisions) NewCommand() *cobra.Command {
+	cmd := &cobra.Command{
 		Use:     "decisions [action]",
 		Short:   "Manage decisions",
 		Long:    `Add/List/Delete/Import decisions from LAPI`,
@@ -135,15 +142,15 @@ func NewDecisionsCmd() *cobra.Command {
 		},
 	}
 
-	cmdDecisions.AddCommand(NewDecisionsListCmd())
-	cmdDecisions.AddCommand(NewDecisionsAddCmd())
-	cmdDecisions.AddCommand(NewDecisionsDeleteCmd())
-	cmdDecisions.AddCommand(NewDecisionsImportCmd())
+	cmd.AddCommand(cli.NewListCmd())
+	cmd.AddCommand(cli.NewAddCmd())
+	cmd.AddCommand(cli.NewDeleteCmd())
+	cmd.AddCommand(cli.NewImportCmd())
 
-	return cmdDecisions
+	return cmd
 }
 
-func NewDecisionsListCmd() *cobra.Command {
+func (cli cliDecisions) NewListCmd() *cobra.Command {
 	var filter = apiclient.AlertsListOpts{
 		ValueEquals:    new(string),
 		ScopeEquals:    new(string),
@@ -161,7 +168,7 @@ func NewDecisionsListCmd() *cobra.Command {
 	contained := new(bool)
 	var printMachine bool
 
-	var cmdDecisionsList = &cobra.Command{
+	cmd := &cobra.Command{
 		Use:   "list [options]",
 		Short: "List decisions from LAPI",
 		Example: `cscli decisions list -i 1.2.3.4
@@ -251,26 +258,26 @@ cscli decisions list -t ban
 			return nil
 		},
 	}
-	cmdDecisionsList.Flags().SortFlags = false
-	cmdDecisionsList.Flags().BoolVarP(filter.IncludeCAPI, "all", "a", false, "Include decisions from Central API")
-	cmdDecisionsList.Flags().StringVar(filter.Since, "since", "", "restrict to alerts newer than since (ie. 4h, 30d)")
-	cmdDecisionsList.Flags().StringVar(filter.Until, "until", "", "restrict to alerts older than until (ie. 4h, 30d)")
-	cmdDecisionsList.Flags().StringVarP(filter.TypeEquals, "type", "t", "", "restrict to this decision type (ie. ban,captcha)")
-	cmdDecisionsList.Flags().StringVar(filter.ScopeEquals, "scope", "", "restrict to this scope (ie. ip,range,session)")
-	cmdDecisionsList.Flags().StringVar(filter.OriginEquals, "origin", "", fmt.Sprintf("the value to match for the specified origin (%s ...)", strings.Join(types.GetOrigins(), ",")))
-	cmdDecisionsList.Flags().StringVarP(filter.ValueEquals, "value", "v", "", "restrict to this value (ie. 1.2.3.4,userName)")
-	cmdDecisionsList.Flags().StringVarP(filter.ScenarioEquals, "scenario", "s", "", "restrict to this scenario (ie. crowdsecurity/ssh-bf)")
-	cmdDecisionsList.Flags().StringVarP(filter.IPEquals, "ip", "i", "", "restrict to alerts from this source ip (shorthand for --scope ip --value <IP>)")
-	cmdDecisionsList.Flags().StringVarP(filter.RangeEquals, "range", "r", "", "restrict to alerts from this source range (shorthand for --scope range --value <RANGE>)")
-	cmdDecisionsList.Flags().IntVarP(filter.Limit, "limit", "l", 100, "number of alerts to get (use 0 to remove the limit)")
-	cmdDecisionsList.Flags().BoolVar(NoSimu, "no-simu", false, "exclude decisions in simulation mode")
-	cmdDecisionsList.Flags().BoolVarP(&printMachine, "machine", "m", false, "print machines that triggered decisions")
-	cmdDecisionsList.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
-
-	return cmdDecisionsList
+	cmd.Flags().SortFlags = false
+	cmd.Flags().BoolVarP(filter.IncludeCAPI, "all", "a", false, "Include decisions from Central API")
+	cmd.Flags().StringVar(filter.Since, "since", "", "restrict to alerts newer than since (ie. 4h, 30d)")
+	cmd.Flags().StringVar(filter.Until, "until", "", "restrict to alerts older than until (ie. 4h, 30d)")
+	cmd.Flags().StringVarP(filter.TypeEquals, "type", "t", "", "restrict to this decision type (ie. ban,captcha)")
+	cmd.Flags().StringVar(filter.ScopeEquals, "scope", "", "restrict to this scope (ie. ip,range,session)")
+	cmd.Flags().StringVar(filter.OriginEquals, "origin", "", fmt.Sprintf("the value to match for the specified origin (%s ...)", strings.Join(types.GetOrigins(), ",")))
+	cmd.Flags().StringVarP(filter.ValueEquals, "value", "v", "", "restrict to this value (ie. 1.2.3.4,userName)")
+	cmd.Flags().StringVarP(filter.ScenarioEquals, "scenario", "s", "", "restrict to this scenario (ie. crowdsecurity/ssh-bf)")
+	cmd.Flags().StringVarP(filter.IPEquals, "ip", "i", "", "restrict to alerts from this source ip (shorthand for --scope ip --value <IP>)")
+	cmd.Flags().StringVarP(filter.RangeEquals, "range", "r", "", "restrict to alerts from this source range (shorthand for --scope range --value <RANGE>)")
+	cmd.Flags().IntVarP(filter.Limit, "limit", "l", 100, "number of alerts to get (use 0 to remove the limit)")
+	cmd.Flags().BoolVar(NoSimu, "no-simu", false, "exclude decisions in simulation mode")
+	cmd.Flags().BoolVarP(&printMachine, "machine", "m", false, "print machines that triggered decisions")
+	cmd.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
+
+	return cmd
 }
 
-func NewDecisionsAddCmd() *cobra.Command {
+func (cli cliDecisions) NewAddCmd() *cobra.Command {
 	var (
 		addIP       string
 		addRange    string
@@ -281,7 +288,7 @@ func NewDecisionsAddCmd() *cobra.Command {
 		addType     string
 	)
 
-	var cmdDecisionsAdd = &cobra.Command{
+	cmd := &cobra.Command{
 		Use:   "add [options]",
 		Short: "Add decision to LAPI",
 		Example: `cscli decisions add --ip 1.2.3.4
@@ -369,19 +376,19 @@ cscli decisions add --scope username --value foobar
 		},
 	}
 
-	cmdDecisionsAdd.Flags().SortFlags = false
-	cmdDecisionsAdd.Flags().StringVarP(&addIP, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
-	cmdDecisionsAdd.Flags().StringVarP(&addRange, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
-	cmdDecisionsAdd.Flags().StringVarP(&addDuration, "duration", "d", "4h", "Decision duration (ie. 1h,4h,30m)")
-	cmdDecisionsAdd.Flags().StringVarP(&addValue, "value", "v", "", "The value (ie. --scope username --value foobar)")
-	cmdDecisionsAdd.Flags().StringVar(&addScope, "scope", types.Ip, "Decision scope (ie. ip,range,username)")
-	cmdDecisionsAdd.Flags().StringVarP(&addReason, "reason", "R", "", "Decision reason (ie. scenario-name)")
-	cmdDecisionsAdd.Flags().StringVarP(&addType, "type", "t", "ban", "Decision type (ie. ban,captcha,throttle)")
+	cmd.Flags().SortFlags = false
+	cmd.Flags().StringVarP(&addIP, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
+	cmd.Flags().StringVarP(&addRange, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
+	cmd.Flags().StringVarP(&addDuration, "duration", "d", "4h", "Decision duration (ie. 1h,4h,30m)")
+	cmd.Flags().StringVarP(&addValue, "value", "v", "", "The value (ie. --scope username --value foobar)")
+	cmd.Flags().StringVar(&addScope, "scope", types.Ip, "Decision scope (ie. ip,range,username)")
+	cmd.Flags().StringVarP(&addReason, "reason", "R", "", "Decision reason (ie. scenario-name)")
+	cmd.Flags().StringVarP(&addType, "type", "t", "ban", "Decision type (ie. ban,captcha,throttle)")
 
-	return cmdDecisionsAdd
+	return cmd
 }
 
-func NewDecisionsDeleteCmd() *cobra.Command {
+func (cli cliDecisions) NewDeleteCmd() *cobra.Command {
 	var delFilter = apiclient.DecisionsDeleteOpts{
 		ScopeEquals:    new(string),
 		ValueEquals:    new(string),
@@ -395,7 +402,7 @@ func NewDecisionsDeleteCmd() *cobra.Command {
 	var delDecisionAll bool
 	contained := new(bool)
 
-	var cmdDecisionsDelete = &cobra.Command{
+	cmd := &cobra.Command{
 		Use:               "delete [options]",
 		Short:             "Delete decisions",
 		DisableAutoGenTag: true,
@@ -472,17 +479,17 @@ cscli decisions delete --type captcha
 		},
 	}
 
-	cmdDecisionsDelete.Flags().SortFlags = false
-	cmdDecisionsDelete.Flags().StringVarP(delFilter.IPEquals, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
-	cmdDecisionsDelete.Flags().StringVarP(delFilter.RangeEquals, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
-	cmdDecisionsDelete.Flags().StringVarP(delFilter.TypeEquals, "type", "t", "", "the decision type (ie. ban,captcha)")
-	cmdDecisionsDelete.Flags().StringVarP(delFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
-	cmdDecisionsDelete.Flags().StringVarP(delFilter.ScenarioEquals, "scenario", "s", "", "the scenario name (ie. crowdsecurity/ssh-bf)")
-	cmdDecisionsDelete.Flags().StringVar(delFilter.OriginEquals, "origin", "", fmt.Sprintf("the value to match for the specified origin (%s ...)", strings.Join(types.GetOrigins(), ",")))
+	cmd.Flags().SortFlags = false
+	cmd.Flags().StringVarP(delFilter.IPEquals, "ip", "i", "", "Source ip (shorthand for --scope ip --value <IP>)")
+	cmd.Flags().StringVarP(delFilter.RangeEquals, "range", "r", "", "Range source ip (shorthand for --scope range --value <RANGE>)")
+	cmd.Flags().StringVarP(delFilter.TypeEquals, "type", "t", "", "the decision type (ie. ban,captcha)")
+	cmd.Flags().StringVarP(delFilter.ValueEquals, "value", "v", "", "the value to match for in the specified scope")
+	cmd.Flags().StringVarP(delFilter.ScenarioEquals, "scenario", "s", "", "the scenario name (ie. crowdsecurity/ssh-bf)")
+	cmd.Flags().StringVar(delFilter.OriginEquals, "origin", "", fmt.Sprintf("the value to match for the specified origin (%s ...)", strings.Join(types.GetOrigins(), ",")))
 
-	cmdDecisionsDelete.Flags().StringVar(&delDecisionId, "id", "", "decision id")
-	cmdDecisionsDelete.Flags().BoolVar(&delDecisionAll, "all", false, "delete all decisions")
-	cmdDecisionsDelete.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
+	cmd.Flags().StringVar(&delDecisionId, "id", "", "decision id")
+	cmd.Flags().BoolVar(&delDecisionAll, "all", false, "delete all decisions")
+	cmd.Flags().BoolVar(contained, "contained", false, "query decisions contained by range")
 
-	return cmdDecisionsDelete
+	return cmd
 }

+ 7 - 7
cmd/crowdsec-cli/decisions_import.go

@@ -63,7 +63,7 @@ func parseDecisionList(content []byte, format string) ([]decisionRaw, error) {
 }
 
 
-func runDecisionsImport(cmd *cobra.Command, args []string) error  {
+func (cli cliDecisions) runImport(cmd *cobra.Command, args []string) error  {
 	flags := cmd.Flags()
 
 	input, err := flags.GetString("input")
@@ -226,8 +226,8 @@ func runDecisionsImport(cmd *cobra.Command, args []string) error  {
 }
 
 
-func NewDecisionsImportCmd() *cobra.Command {
-	var cmdDecisionsImport = &cobra.Command{
+func (cli cliDecisions) NewImportCmd() *cobra.Command {
+	cmd := &cobra.Command{
 		Use:   "import [options]",
 		Short: "Import decisions from a file or pipe",
 		Long: "expected format:\n" +
@@ -250,10 +250,10 @@ Raw values, standard input:
 
 $ echo "1.2.3.4" | cscli decisions import -i - --format values
 `,
-		RunE: runDecisionsImport,
+		RunE: cli.runImport,
 	}
 
-	flags := cmdDecisionsImport.Flags()
+	flags := cmd.Flags()
 	flags.SortFlags = false
 	flags.StringP("input", "i", "", "Input file")
 	flags.StringP("duration", "d", "4h", "Decision duration: 1h,4h,30m")
@@ -263,7 +263,7 @@ $ echo "1.2.3.4" | cscli decisions import -i - --format values
 	flags.Int("batch", 0, "Split import in batches of N decisions")
 	flags.String("format", "", "Input format: 'json', 'csv' or 'values' (each line is a value, no headers)")
 
-	cmdDecisionsImport.MarkFlagRequired("input")
+	cmd.MarkFlagRequired("input")
 
-	return cmdDecisionsImport
+	return cmd
 }

+ 4 - 4
cmd/crowdsec-cli/hubappsec.go

@@ -13,8 +13,8 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
 )
 
-func NewAppsecConfigCLI() *itemCLI {
-	return &itemCLI{
+func NewCLIAppsecConfig() *cliItem {
+	return &cliItem{
 		name:      cwhub.APPSEC_CONFIGS,
 		singular:  "appsec-config",
 		oneOrMore: "appsec-config(s)",
@@ -46,7 +46,7 @@ cscli appsec-configs list crowdsecurity/vpatch`,
 	}
 }
 
-func NewAppsecRuleCLI() *itemCLI {
+func NewCLIAppsecRule() *cliItem {
 	inspectDetail := func(item *cwhub.Item) error {
 		appsecRule := appsec.AppsecCollectionConfig{}
 		yamlContent, err := os.ReadFile(item.State.LocalPath)
@@ -71,7 +71,7 @@ func NewAppsecRuleCLI() *itemCLI {
 		return nil
 	}
 
-	return &itemCLI{
+	return &cliItem{
 		name:      "appsec-rules",
 		singular:  "appsec-rule",
 		oneOrMore: "appsec-rule(s)",

+ 2 - 2
cmd/crowdsec-cli/hubcollection.go

@@ -4,8 +4,8 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
 )
 
-func NewCollectionCLI() *itemCLI {
-	return &itemCLI{
+func NewCLICollection() *cliItem {
+	return &cliItem{
 		name:      cwhub.COLLECTIONS,
 		singular:  "collection",
 		oneOrMore: "collection(s)",

+ 2 - 2
cmd/crowdsec-cli/hubcontext.go

@@ -4,8 +4,8 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
 )
 
-func NewContextCLI() *itemCLI {
-	return &itemCLI{
+func NewCLIContext() *cliItem {
+	return &cliItem{
 		name:      cwhub.CONTEXTS,
 		singular:  "context",
 		oneOrMore: "context(s)",

+ 2 - 2
cmd/crowdsec-cli/hubparser.go

@@ -4,8 +4,8 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
 )
 
-func NewParserCLI() *itemCLI {
-	return &itemCLI{
+func NewCLIParser() *cliItem {
+	return &cliItem{
 		name:      cwhub.PARSERS,
 		singular:  "parser",
 		oneOrMore: "parser(s)",

+ 2 - 2
cmd/crowdsec-cli/hubpostoverflow.go

@@ -4,8 +4,8 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
 )
 
-func NewPostOverflowCLI() *itemCLI {
-	return &itemCLI{
+func NewCLIPostOverflow() *cliItem {
+	return &cliItem{
 		name:      cwhub.POSTOVERFLOWS,
 		singular:  "postoverflow",
 		oneOrMore: "postoverflow(s)",

+ 2 - 2
cmd/crowdsec-cli/hubscenario.go

@@ -4,8 +4,8 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
 )
 
-func NewScenarioCLI() *itemCLI {
-	return &itemCLI{
+func NewCLIScenario() *cliItem {
+	return &cliItem{
 		name:      cwhub.SCENARIOS,
 		singular:  "scenario",
 		oneOrMore: "scenario(s)",

+ 61 - 55
cmd/crowdsec-cli/hubtest.go

@@ -23,12 +23,18 @@ var HubAppsecTests hubtest.HubTest
 var hubPtr *hubtest.HubTest
 var isAppsecTest bool
 
-func NewHubTestCmd() *cobra.Command {
+type cliHubTest struct{}
+
+func NewCLIHubTest() *cliHubTest {
+	return &cliHubTest{}
+}
+
+func (cli cliHubTest) NewCommand() *cobra.Command {
 	var hubPath string
 	var crowdsecPath string
 	var cscliPath string
 
-	var cmdHubTest = &cobra.Command{
+	cmd := &cobra.Command{
 		Use:               "hubtest",
 		Short:             "Run functional tests on hub configurations",
 		Long:              "Run functional tests on hub configurations (parsers, scenarios, collections...)",
@@ -54,24 +60,24 @@ func NewHubTestCmd() *cobra.Command {
 		},
 	}
 
-	cmdHubTest.PersistentFlags().StringVar(&hubPath, "hub", ".", "Path to hub folder")
-	cmdHubTest.PersistentFlags().StringVar(&crowdsecPath, "crowdsec", "crowdsec", "Path to crowdsec")
-	cmdHubTest.PersistentFlags().StringVar(&cscliPath, "cscli", "cscli", "Path to cscli")
-	cmdHubTest.PersistentFlags().BoolVar(&isAppsecTest, "appsec", false, "Command relates to appsec tests")
-
-	cmdHubTest.AddCommand(NewHubTestCreateCmd())
-	cmdHubTest.AddCommand(NewHubTestRunCmd())
-	cmdHubTest.AddCommand(NewHubTestCleanCmd())
-	cmdHubTest.AddCommand(NewHubTestInfoCmd())
-	cmdHubTest.AddCommand(NewHubTestListCmd())
-	cmdHubTest.AddCommand(NewHubTestCoverageCmd())
-	cmdHubTest.AddCommand(NewHubTestEvalCmd())
-	cmdHubTest.AddCommand(NewHubTestExplainCmd())
-
-	return cmdHubTest
+	cmd.PersistentFlags().StringVar(&hubPath, "hub", ".", "Path to hub folder")
+	cmd.PersistentFlags().StringVar(&crowdsecPath, "crowdsec", "crowdsec", "Path to crowdsec")
+	cmd.PersistentFlags().StringVar(&cscliPath, "cscli", "cscli", "Path to cscli")
+	cmd.PersistentFlags().BoolVar(&isAppsecTest, "appsec", false, "Command relates to appsec tests")
+
+	cmd.AddCommand(cli.NewCreateCmd())
+	cmd.AddCommand(cli.NewRunCmd())
+	cmd.AddCommand(cli.NewCleanCmd())
+	cmd.AddCommand(cli.NewInfoCmd())
+	cmd.AddCommand(cli.NewListCmd())
+	cmd.AddCommand(cli.NewCoverageCmd())
+	cmd.AddCommand(cli.NewEvalCmd())
+	cmd.AddCommand(cli.NewExplainCmd())
+
+	return cmd
 }
 
-func NewHubTestCreateCmd() *cobra.Command {
+func (cli cliHubTest) NewCreateCmd() *cobra.Command {
 	parsers := []string{}
 	postoverflows := []string{}
 	scenarios := []string{}
@@ -79,7 +85,7 @@ func NewHubTestCreateCmd() *cobra.Command {
 	var labels map[string]string
 	var logType string
 
-	var cmdHubTestCreate = &cobra.Command{
+	cmd := &cobra.Command{
 		Use:   "create",
 		Short: "create [test_name]",
 		Example: `cscli hubtest create my-awesome-test --type syslog
@@ -191,21 +197,21 @@ cscli hubtest create my-scenario-test --parsers crowdsecurity/nginx --scenarios
 		},
 	}
 
-	cmdHubTestCreate.PersistentFlags().StringVarP(&logType, "type", "t", "", "Log type of the test")
-	cmdHubTestCreate.Flags().StringSliceVarP(&parsers, "parsers", "p", parsers, "Parsers to add to test")
-	cmdHubTestCreate.Flags().StringSliceVar(&postoverflows, "postoverflows", postoverflows, "Postoverflows to add to test")
-	cmdHubTestCreate.Flags().StringSliceVarP(&scenarios, "scenarios", "s", scenarios, "Scenarios to add to test")
-	cmdHubTestCreate.PersistentFlags().BoolVar(&ignoreParsers, "ignore-parsers", false, "Don't run test on parsers")
+	cmd.PersistentFlags().StringVarP(&logType, "type", "t", "", "Log type of the test")
+	cmd.Flags().StringSliceVarP(&parsers, "parsers", "p", parsers, "Parsers to add to test")
+	cmd.Flags().StringSliceVar(&postoverflows, "postoverflows", postoverflows, "Postoverflows to add to test")
+	cmd.Flags().StringSliceVarP(&scenarios, "scenarios", "s", scenarios, "Scenarios to add to test")
+	cmd.PersistentFlags().BoolVar(&ignoreParsers, "ignore-parsers", false, "Don't run test on parsers")
 
-	return cmdHubTestCreate
+	return cmd
 }
 
-func NewHubTestRunCmd() *cobra.Command {
+func (cli cliHubTest) NewRunCmd() *cobra.Command {
 	var noClean bool
 	var runAll bool
 	var forceClean bool
 
-	var cmdHubTestRun = &cobra.Command{
+	var cmd = &cobra.Command{
 		Use:               "run",
 		Short:             "run [test_name]",
 		DisableAutoGenTag: true,
@@ -353,15 +359,15 @@ func NewHubTestRunCmd() *cobra.Command {
 		},
 	}
 
-	cmdHubTestRun.Flags().BoolVar(&noClean, "no-clean", false, "Don't clean runtime environment if test succeed")
-	cmdHubTestRun.Flags().BoolVar(&forceClean, "clean", false, "Clean runtime environment if test fail")
-	cmdHubTestRun.Flags().BoolVar(&runAll, "all", false, "Run all tests")
+	cmd.Flags().BoolVar(&noClean, "no-clean", false, "Don't clean runtime environment if test succeed")
+	cmd.Flags().BoolVar(&forceClean, "clean", false, "Clean runtime environment if test fail")
+	cmd.Flags().BoolVar(&runAll, "all", false, "Run all tests")
 
-	return cmdHubTestRun
+	return cmd
 }
 
-func NewHubTestCleanCmd() *cobra.Command {
-	var cmdHubTestClean = &cobra.Command{
+func (cli cliHubTest) NewCleanCmd() *cobra.Command {
+	var cmd = &cobra.Command{
 		Use:               "clean",
 		Short:             "clean [test_name]",
 		Args:              cobra.MinimumNArgs(1),
@@ -381,17 +387,16 @@ func NewHubTestCleanCmd() *cobra.Command {
 		},
 	}
 
-	return cmdHubTestClean
+	return cmd
 }
 
-func NewHubTestInfoCmd() *cobra.Command {
-	var cmdHubTestInfo = &cobra.Command{
+func (cli cliHubTest) NewInfoCmd() *cobra.Command {
+	cmd := &cobra.Command{
 		Use:               "info",
 		Short:             "info [test_name]",
 		Args:              cobra.MinimumNArgs(1),
 		DisableAutoGenTag: true,
 		RunE: func(cmd *cobra.Command, args []string) error {
-
 			for _, testName := range args {
 				test, err := hubPtr.LoadTestItem(testName)
 				if err != nil {
@@ -415,11 +420,11 @@ func NewHubTestInfoCmd() *cobra.Command {
 		},
 	}
 
-	return cmdHubTestInfo
+	return cmd
 }
 
-func NewHubTestListCmd() *cobra.Command {
-	var cmdHubTestList = &cobra.Command{
+func (cli cliHubTest) NewListCmd() *cobra.Command {
+	cmd := &cobra.Command{
 		Use:               "list",
 		Short:             "list",
 		DisableAutoGenTag: true,
@@ -445,16 +450,16 @@ func NewHubTestListCmd() *cobra.Command {
 		},
 	}
 
-	return cmdHubTestList
+	return cmd
 }
 
-func NewHubTestCoverageCmd() *cobra.Command {
+func (cli cliHubTest) NewCoverageCmd() *cobra.Command {
 	var showParserCov bool
 	var showScenarioCov bool
 	var showOnlyPercent bool
 	var showAppsecCov bool
 
-	var cmdHubTestCoverage = &cobra.Command{
+	cmd := &cobra.Command{
 		Use:               "coverage",
 		Short:             "coverage",
 		DisableAutoGenTag: true,
@@ -580,17 +585,18 @@ func NewHubTestCoverageCmd() *cobra.Command {
 		},
 	}
 
-	cmdHubTestCoverage.PersistentFlags().BoolVar(&showOnlyPercent, "percent", false, "Show only percentages of coverage")
-	cmdHubTestCoverage.PersistentFlags().BoolVar(&showParserCov, "parsers", false, "Show only parsers coverage")
-	cmdHubTestCoverage.PersistentFlags().BoolVar(&showScenarioCov, "scenarios", false, "Show only scenarios coverage")
-	cmdHubTestCoverage.PersistentFlags().BoolVar(&showAppsecCov, "appsec", false, "Show only appsec coverage")
+	cmd.PersistentFlags().BoolVar(&showOnlyPercent, "percent", false, "Show only percentages of coverage")
+	cmd.PersistentFlags().BoolVar(&showParserCov, "parsers", false, "Show only parsers coverage")
+	cmd.PersistentFlags().BoolVar(&showScenarioCov, "scenarios", false, "Show only scenarios coverage")
+	cmd.PersistentFlags().BoolVar(&showAppsecCov, "appsec", false, "Show only appsec coverage")
 
-	return cmdHubTestCoverage
+	return cmd
 }
 
-func NewHubTestEvalCmd() *cobra.Command {
+func (cli cliHubTest) NewEvalCmd() *cobra.Command {
 	var evalExpression string
-	var cmdHubTestEval = &cobra.Command{
+
+	cmd := &cobra.Command{
 		Use:               "eval",
 		Short:             "eval [test_name]",
 		Args:              cobra.ExactArgs(1),
@@ -619,13 +625,13 @@ func NewHubTestEvalCmd() *cobra.Command {
 		},
 	}
 
-	cmdHubTestEval.PersistentFlags().StringVarP(&evalExpression, "expr", "e", "", "Expression to eval")
+	cmd.PersistentFlags().StringVarP(&evalExpression, "expr", "e", "", "Expression to eval")
 
-	return cmdHubTestEval
+	return cmd
 }
 
-func NewHubTestExplainCmd() *cobra.Command {
-	var cmdHubTestExplain = &cobra.Command{
+func (cli cliHubTest) NewExplainCmd() *cobra.Command {
+	cmd := &cobra.Command{
 		Use:               "explain",
 		Short:             "explain [test_name]",
 		Args:              cobra.ExactArgs(1),
@@ -665,5 +671,5 @@ func NewHubTestExplainCmd() *cobra.Command {
 		},
 	}
 
-	return cmdHubTestExplain
+	return cmd
 }

+ 12 - 12
cmd/crowdsec-cli/itemcli.go

@@ -22,7 +22,7 @@ type cliHelp struct {
 	example string
 }
 
-type itemCLI struct {
+type cliItem struct {
 	name          string // plural, as used in the hub index
 	singular      string
 	oneOrMore     string // parenthetical pluralizaion: "parser(s)"
@@ -35,7 +35,7 @@ type itemCLI struct {
 	listHelp      cliHelp
 }
 
-func (it itemCLI) NewCommand() *cobra.Command {
+func (it cliItem) NewCommand() *cobra.Command {
 	cmd := &cobra.Command{
 		Use:               coalesce.String(it.help.use, fmt.Sprintf("%s <action> [item]...", it.name)),
 		Short:             coalesce.String(it.help.short, fmt.Sprintf("Manage hub %s", it.name)),
@@ -55,7 +55,7 @@ func (it itemCLI) NewCommand() *cobra.Command {
 	return cmd
 }
 
-func (it itemCLI) Install(cmd *cobra.Command, args []string) error {
+func (it cliItem) Install(cmd *cobra.Command, args []string) error {
 	flags := cmd.Flags()
 
 	downloadOnly, err := flags.GetBool("download-only")
@@ -103,7 +103,7 @@ func (it itemCLI) Install(cmd *cobra.Command, args []string) error {
 	return nil
 }
 
-func (it itemCLI) NewInstallCmd() *cobra.Command {
+func (it cliItem) NewInstallCmd() *cobra.Command {
 	cmd := &cobra.Command{
 		Use:               coalesce.String(it.installHelp.use, "install [item]..."),
 		Short:             coalesce.String(it.installHelp.short, fmt.Sprintf("Install given %s", it.oneOrMore)),
@@ -138,7 +138,7 @@ func istalledParentNames(item *cwhub.Item) []string {
 	return ret
 }
 
-func (it itemCLI) Remove(cmd *cobra.Command, args []string) error {
+func (it cliItem) Remove(cmd *cobra.Command, args []string) error {
 	flags := cmd.Flags()
 
 	purge, err := flags.GetBool("purge")
@@ -232,7 +232,7 @@ func (it itemCLI) Remove(cmd *cobra.Command, args []string) error {
 	return nil
 }
 
-func (it itemCLI) NewRemoveCmd() *cobra.Command {
+func (it cliItem) NewRemoveCmd() *cobra.Command {
 	cmd := &cobra.Command{
 		Use:               coalesce.String(it.removeHelp.use, "remove [item]..."),
 		Short:             coalesce.String(it.removeHelp.short, fmt.Sprintf("Remove given %s", it.oneOrMore)),
@@ -254,7 +254,7 @@ func (it itemCLI) NewRemoveCmd() *cobra.Command {
 	return cmd
 }
 
-func (it itemCLI) Upgrade(cmd *cobra.Command, args []string) error {
+func (it cliItem) Upgrade(cmd *cobra.Command, args []string) error {
 	flags := cmd.Flags()
 
 	force, err := flags.GetBool("force")
@@ -328,7 +328,7 @@ func (it itemCLI) Upgrade(cmd *cobra.Command, args []string) error {
 	return nil
 }
 
-func (it itemCLI) NewUpgradeCmd() *cobra.Command {
+func (it cliItem) NewUpgradeCmd() *cobra.Command {
 	cmd := &cobra.Command{
 		Use:               coalesce.String(it.upgradeHelp.use, "upgrade [item]..."),
 		Short:             coalesce.String(it.upgradeHelp.short, fmt.Sprintf("Upgrade given %s", it.oneOrMore)),
@@ -348,7 +348,7 @@ func (it itemCLI) NewUpgradeCmd() *cobra.Command {
 	return cmd
 }
 
-func (it itemCLI) Inspect(cmd *cobra.Command, args []string) error {
+func (it cliItem) Inspect(cmd *cobra.Command, args []string) error {
 	flags := cmd.Flags()
 
 	url, err := flags.GetString("url")
@@ -389,7 +389,7 @@ func (it itemCLI) Inspect(cmd *cobra.Command, args []string) error {
 	return nil
 }
 
-func (it itemCLI) NewInspectCmd() *cobra.Command {
+func (it cliItem) NewInspectCmd() *cobra.Command {
 	cmd := &cobra.Command{
 		Use:               coalesce.String(it.inspectHelp.use, "inspect [item]..."),
 		Short:             coalesce.String(it.inspectHelp.short, fmt.Sprintf("Inspect given %s", it.oneOrMore)),
@@ -410,7 +410,7 @@ func (it itemCLI) NewInspectCmd() *cobra.Command {
 	return cmd
 }
 
-func (it itemCLI) List(cmd *cobra.Command, args []string) error {
+func (it cliItem) List(cmd *cobra.Command, args []string) error {
 	flags := cmd.Flags()
 
 	all, err := flags.GetBool("all")
@@ -437,7 +437,7 @@ func (it itemCLI) List(cmd *cobra.Command, args []string) error {
 	return nil
 }
 
-func (it itemCLI) NewListCmd() *cobra.Command {
+func (it cliItem) NewListCmd() *cobra.Command {
 	cmd := &cobra.Command{
 		Use:               coalesce.String(it.listHelp.use, "list [item... | -a]"),
 		Short:             coalesce.String(it.listHelp.short, fmt.Sprintf("List %s", it.oneOrMore)),

+ 13 - 13
cmd/crowdsec-cli/main.go

@@ -228,28 +228,28 @@ It is meant to allow you to manage bans, parsers/scenarios/etc, api and generall
 	rootCmd.AddCommand(NewConfigCmd())
 	rootCmd.AddCommand(NewCLIHub().NewCommand())
 	rootCmd.AddCommand(NewMetricsCmd())
-	rootCmd.AddCommand(NewDashboardCmd())
-	rootCmd.AddCommand(NewDecisionsCmd())
-	rootCmd.AddCommand(NewAlertsCmd())
-	rootCmd.AddCommand(NewSimulationCmds())
+	rootCmd.AddCommand(NewCLIDashboard().NewCommand())
+	rootCmd.AddCommand(NewCLIDecisions().NewCommand())
+	rootCmd.AddCommand(NewCLIAlerts().NewCommand())
+	rootCmd.AddCommand(NewCLISimulation().NewCommand())
 	rootCmd.AddCommand(NewCLIBouncers().NewCommand())
 	rootCmd.AddCommand(NewCLIMachines().NewCommand())
-	rootCmd.AddCommand(NewCapiCmd())
+	rootCmd.AddCommand(NewCLICapi().NewCommand())
 	rootCmd.AddCommand(NewLapiCmd())
 	rootCmd.AddCommand(NewCompletionCmd())
 	rootCmd.AddCommand(NewConsoleCmd())
 	rootCmd.AddCommand(NewCLIExplain().NewCommand())
-	rootCmd.AddCommand(NewHubTestCmd())
+	rootCmd.AddCommand(NewCLIHubTest().NewCommand())
 	rootCmd.AddCommand(NewCLINotifications().NewCommand())
 	rootCmd.AddCommand(NewCLISupport().NewCommand())
 	rootCmd.AddCommand(NewCLIPapi().NewCommand())
-	rootCmd.AddCommand(NewCollectionCLI().NewCommand())
-	rootCmd.AddCommand(NewParserCLI().NewCommand())
-	rootCmd.AddCommand(NewScenarioCLI().NewCommand())
-	rootCmd.AddCommand(NewPostOverflowCLI().NewCommand())
-	rootCmd.AddCommand(NewContextCLI().NewCommand())
-	rootCmd.AddCommand(NewAppsecConfigCLI().NewCommand())
-	rootCmd.AddCommand(NewAppsecRuleCLI().NewCommand())
+	rootCmd.AddCommand(NewCLICollection().NewCommand())
+	rootCmd.AddCommand(NewCLIParser().NewCommand())
+	rootCmd.AddCommand(NewCLIScenario().NewCommand())
+	rootCmd.AddCommand(NewCLIPostOverflow().NewCommand())
+	rootCmd.AddCommand(NewCLIContext().NewCommand())
+	rootCmd.AddCommand(NewCLIAppsecConfig().NewCommand())
+	rootCmd.AddCommand(NewCLIAppsecRule().NewCommand())
 
 	if fflag.CscliSetup.IsEnabled() {
 		rootCmd.AddCommand(NewSetupCmd())

+ 110 - 103
cmd/crowdsec-cli/simulation.go

@@ -13,95 +13,14 @@ import (
 	"github.com/crowdsecurity/crowdsec/pkg/cwhub"
 )
 
-func addToExclusion(name string) error {
-	csConfig.Cscli.SimulationConfig.Exclusions = append(csConfig.Cscli.SimulationConfig.Exclusions, name)
-	return nil
-}
-
-func removeFromExclusion(name string) error {
-	index := slices.Index(csConfig.Cscli.SimulationConfig.Exclusions, name)
-
-	// Remove element from the slice
-	csConfig.Cscli.SimulationConfig.Exclusions[index] = csConfig.Cscli.SimulationConfig.Exclusions[len(csConfig.Cscli.SimulationConfig.Exclusions)-1]
-	csConfig.Cscli.SimulationConfig.Exclusions[len(csConfig.Cscli.SimulationConfig.Exclusions)-1] = ""
-	csConfig.Cscli.SimulationConfig.Exclusions = csConfig.Cscli.SimulationConfig.Exclusions[:len(csConfig.Cscli.SimulationConfig.Exclusions)-1]
-
-	return nil
-}
-
-func enableGlobalSimulation() error {
-	csConfig.Cscli.SimulationConfig.Simulation = new(bool)
-	*csConfig.Cscli.SimulationConfig.Simulation = true
-	csConfig.Cscli.SimulationConfig.Exclusions = []string{}
-
-	if err := dumpSimulationFile(); err != nil {
-		log.Fatalf("unable to dump simulation file: %s", err)
-	}
-
-	log.Printf("global simulation: enabled")
-
-	return nil
-}
-
-func dumpSimulationFile() error {
-	newConfigSim, err := yaml.Marshal(csConfig.Cscli.SimulationConfig)
-	if err != nil {
-		return fmt.Errorf("unable to marshal simulation configuration: %s", err)
-	}
-	err = os.WriteFile(csConfig.ConfigPaths.SimulationFilePath, newConfigSim, 0o644)
-	if err != nil {
-		return fmt.Errorf("write simulation config in '%s' failed: %s", csConfig.ConfigPaths.SimulationFilePath, err)
-	}
-	log.Debugf("updated simulation file %s", csConfig.ConfigPaths.SimulationFilePath)
-
-	return nil
-}
+type cliSimulation struct {}
 
-func disableGlobalSimulation() error {
-	csConfig.Cscli.SimulationConfig.Simulation = new(bool)
-	*csConfig.Cscli.SimulationConfig.Simulation = false
-
-	csConfig.Cscli.SimulationConfig.Exclusions = []string{}
-	newConfigSim, err := yaml.Marshal(csConfig.Cscli.SimulationConfig)
-	if err != nil {
-		return fmt.Errorf("unable to marshal new simulation configuration: %s", err)
-	}
-	err = os.WriteFile(csConfig.ConfigPaths.SimulationFilePath, newConfigSim, 0o644)
-	if err != nil {
-		return fmt.Errorf("unable to write new simulation config in '%s' : %s", csConfig.ConfigPaths.SimulationFilePath, err)
-	}
-
-	log.Printf("global simulation: disabled")
-	return nil
+func NewCLISimulation() *cliSimulation {
+	return &cliSimulation{}
 }
 
-func simulationStatus() error {
-	if csConfig.Cscli.SimulationConfig == nil {
-		log.Printf("global simulation: disabled (configuration file is missing)")
-		return nil
-	}
-	if *csConfig.Cscli.SimulationConfig.Simulation {
-		log.Println("global simulation: enabled")
-		if len(csConfig.Cscli.SimulationConfig.Exclusions) > 0 {
-			log.Println("Scenarios not in simulation mode :")
-			for _, scenario := range csConfig.Cscli.SimulationConfig.Exclusions {
-				log.Printf("  - %s", scenario)
-			}
-		}
-	} else {
-		log.Println("global simulation: disabled")
-		if len(csConfig.Cscli.SimulationConfig.Exclusions) > 0 {
-			log.Println("Scenarios in simulation mode :")
-			for _, scenario := range csConfig.Cscli.SimulationConfig.Exclusions {
-				log.Printf("  - %s", scenario)
-			}
-		}
-	}
-	return nil
-}
-
-func NewSimulationCmds() *cobra.Command {
-	var cmdSimulation = &cobra.Command{
+func (cli cliSimulation) NewCommand() *cobra.Command {
+	cmd := &cobra.Command{
 		Use:   "simulation [command]",
 		Short: "Manage simulation status of scenarios",
 		Example: `cscli simulation status
@@ -123,20 +42,20 @@ cscli simulation disable crowdsecurity/ssh-bf`,
 			}
 		},
 	}
-	cmdSimulation.Flags().SortFlags = false
-	cmdSimulation.PersistentFlags().SortFlags = false
+	cmd.Flags().SortFlags = false
+	cmd.PersistentFlags().SortFlags = false
 
-	cmdSimulation.AddCommand(NewSimulationEnableCmd())
-	cmdSimulation.AddCommand(NewSimulationDisableCmd())
-	cmdSimulation.AddCommand(NewSimulationStatusCmd())
+	cmd.AddCommand(cli.NewEnableCmd())
+	cmd.AddCommand(cli.NewDisableCmd())
+	cmd.AddCommand(cli.NewStatusCmd())
 
-	return cmdSimulation
+	return cmd
 }
 
-func NewSimulationEnableCmd() *cobra.Command {
+func (cli cliSimulation) NewEnableCmd() *cobra.Command {
 	var forceGlobalSimulation bool
 
-	var cmdSimulationEnable = &cobra.Command{
+	cmd := &cobra.Command{
 		Use:               "enable [scenario] [-global]",
 		Short:             "Enable the simulation, globally or on specified scenarios",
 		Example:           `cscli simulation enable`,
@@ -190,15 +109,15 @@ func NewSimulationEnableCmd() *cobra.Command {
 			}
 		},
 	}
-	cmdSimulationEnable.Flags().BoolVarP(&forceGlobalSimulation, "global", "g", false, "Enable global simulation (reverse mode)")
+	cmd.Flags().BoolVarP(&forceGlobalSimulation, "global", "g", false, "Enable global simulation (reverse mode)")
 
-	return cmdSimulationEnable
+	return cmd
 }
 
-func NewSimulationDisableCmd() *cobra.Command {
+func (cli cliSimulation) NewDisableCmd() *cobra.Command {
 	var forceGlobalSimulation bool
 
-	var cmdSimulationDisable = &cobra.Command{
+	cmd := &cobra.Command{
 		Use:               "disable [scenario]",
 		Short:             "Disable the simulation mode. Disable only specified scenarios",
 		Example:           `cscli simulation disable`,
@@ -239,13 +158,13 @@ func NewSimulationDisableCmd() *cobra.Command {
 			}
 		},
 	}
-	cmdSimulationDisable.Flags().BoolVarP(&forceGlobalSimulation, "global", "g", false, "Disable global simulation (reverse mode)")
+	cmd.Flags().BoolVarP(&forceGlobalSimulation, "global", "g", false, "Disable global simulation (reverse mode)")
 
-	return cmdSimulationDisable
+	return cmd
 }
 
-func NewSimulationStatusCmd() *cobra.Command {
-	var cmdSimulationStatus = &cobra.Command{
+func (cli cliSimulation) NewStatusCmd() *cobra.Command {
+	cmd := &cobra.Command{
 		Use:               "status",
 		Short:             "Show simulation mode status",
 		Example:           `cscli simulation status`,
@@ -259,5 +178,93 @@ func NewSimulationStatusCmd() *cobra.Command {
 		},
 	}
 
-	return cmdSimulationStatus
+	return cmd
+}
+
+func addToExclusion(name string) error {
+	csConfig.Cscli.SimulationConfig.Exclusions = append(csConfig.Cscli.SimulationConfig.Exclusions, name)
+	return nil
+}
+
+func removeFromExclusion(name string) error {
+	index := slices.Index(csConfig.Cscli.SimulationConfig.Exclusions, name)
+
+	// Remove element from the slice
+	csConfig.Cscli.SimulationConfig.Exclusions[index] = csConfig.Cscli.SimulationConfig.Exclusions[len(csConfig.Cscli.SimulationConfig.Exclusions)-1]
+	csConfig.Cscli.SimulationConfig.Exclusions[len(csConfig.Cscli.SimulationConfig.Exclusions)-1] = ""
+	csConfig.Cscli.SimulationConfig.Exclusions = csConfig.Cscli.SimulationConfig.Exclusions[:len(csConfig.Cscli.SimulationConfig.Exclusions)-1]
+
+	return nil
+}
+
+func enableGlobalSimulation() error {
+	csConfig.Cscli.SimulationConfig.Simulation = new(bool)
+	*csConfig.Cscli.SimulationConfig.Simulation = true
+	csConfig.Cscli.SimulationConfig.Exclusions = []string{}
+
+	if err := dumpSimulationFile(); err != nil {
+		log.Fatalf("unable to dump simulation file: %s", err)
+	}
+
+	log.Printf("global simulation: enabled")
+
+	return nil
+}
+
+func dumpSimulationFile() error {
+	newConfigSim, err := yaml.Marshal(csConfig.Cscli.SimulationConfig)
+	if err != nil {
+		return fmt.Errorf("unable to marshal simulation configuration: %s", err)
+	}
+	err = os.WriteFile(csConfig.ConfigPaths.SimulationFilePath, newConfigSim, 0o644)
+	if err != nil {
+		return fmt.Errorf("write simulation config in '%s' failed: %s", csConfig.ConfigPaths.SimulationFilePath, err)
+	}
+	log.Debugf("updated simulation file %s", csConfig.ConfigPaths.SimulationFilePath)
+
+	return nil
+}
+
+func disableGlobalSimulation() error {
+	csConfig.Cscli.SimulationConfig.Simulation = new(bool)
+	*csConfig.Cscli.SimulationConfig.Simulation = false
+
+	csConfig.Cscli.SimulationConfig.Exclusions = []string{}
+	newConfigSim, err := yaml.Marshal(csConfig.Cscli.SimulationConfig)
+	if err != nil {
+		return fmt.Errorf("unable to marshal new simulation configuration: %s", err)
+	}
+	err = os.WriteFile(csConfig.ConfigPaths.SimulationFilePath, newConfigSim, 0o644)
+	if err != nil {
+		return fmt.Errorf("unable to write new simulation config in '%s' : %s", csConfig.ConfigPaths.SimulationFilePath, err)
+	}
+
+	log.Printf("global simulation: disabled")
+	return nil
 }
+
+func simulationStatus() error {
+	if csConfig.Cscli.SimulationConfig == nil {
+		log.Printf("global simulation: disabled (configuration file is missing)")
+		return nil
+	}
+	if *csConfig.Cscli.SimulationConfig.Simulation {
+		log.Println("global simulation: enabled")
+		if len(csConfig.Cscli.SimulationConfig.Exclusions) > 0 {
+			log.Println("Scenarios not in simulation mode :")
+			for _, scenario := range csConfig.Cscli.SimulationConfig.Exclusions {
+				log.Printf("  - %s", scenario)
+			}
+		}
+	} else {
+		log.Println("global simulation: disabled")
+		if len(csConfig.Cscli.SimulationConfig.Exclusions) > 0 {
+			log.Println("Scenarios in simulation mode :")
+			for _, scenario := range csConfig.Cscli.SimulationConfig.Exclusions {
+				log.Printf("  - %s", scenario)
+			}
+		}
+	}
+	return nil
+}
+