From b678f5f23754adb341d3e1db47ace02d34060e2c Mon Sep 17 00:00:00 2001 From: marco Date: Wed, 12 Jun 2024 08:21:51 +0200 Subject: [PATCH 1/8] Revert "enhancement: add deprecation notice to cscli dashboard prerun (#3079)" This reverts commit 24687e982a5c38dd91688057c09d4a0e1be6b0af. --- cmd/crowdsec-cli/dashboard.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/crowdsec-cli/dashboard.go b/cmd/crowdsec-cli/dashboard.go index beff06d478a..59b9e67cd94 100644 --- a/cmd/crowdsec-cli/dashboard.go +++ b/cmd/crowdsec-cli/dashboard.go @@ -99,8 +99,6 @@ cscli dashboard remove } } - log.Warn("cscli dashboard will be deprecated in version 1.7.0, read more at https://docs.crowdsec.net/blog/cscli_dashboard_deprecation/") - return nil }, } From d9cd495f0ac39ddcb8defcfdca7653719c9437a2 Mon Sep 17 00:00:00 2001 From: marco Date: Sun, 7 Apr 2024 23:31:32 +0200 Subject: [PATCH 2/8] remove "cscli dashboard" --- cmd/crowdsec-cli/dashboard.go | 452 +--------------------------------- pkg/metabase/api.go | 85 ------- pkg/metabase/container.go | 178 ------------- pkg/metabase/database.go | 100 -------- pkg/metabase/metabase.go | 387 ----------------------------- test/bats/01_cscli.bats | 5 + 6 files changed, 10 insertions(+), 1197 deletions(-) delete mode 100644 pkg/metabase/api.go delete mode 100644 pkg/metabase/container.go delete mode 100644 pkg/metabase/database.go delete mode 100644 pkg/metabase/metabase.go diff --git a/cmd/crowdsec-cli/dashboard.go b/cmd/crowdsec-cli/dashboard.go index 59b9e67cd94..532f2d2694c 100644 --- a/cmd/crowdsec-cli/dashboard.go +++ b/cmd/crowdsec-cli/dashboard.go @@ -3,43 +3,9 @@ package main import ( - "fmt" - "math" - "os" - "os/exec" - "os/user" - "path/filepath" - "strconv" - "strings" - "syscall" - "unicode" + "errors" - "github.com/AlecAivazis/survey/v2" - "github.com/pbnjay/memory" - log "github.com/sirupsen/logrus" "github.com/spf13/cobra" - - "github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/require" - "github.com/crowdsecurity/crowdsec/pkg/metabase" -) - -var ( - metabaseUser = "crowdsec@crowdsec.net" - metabasePassword string - metabaseDBPath string - metabaseConfigPath string - metabaseConfigFolder = "metabase/" - metabaseConfigFile = "metabase.yaml" - metabaseImage = "metabase/metabase:v0.46.6.1" - /**/ - metabaseListenAddress = "127.0.0.1" - metabaseListenPort = "3000" - metabaseContainerID = "crowdsec-metabase" - crowdsecGroup = "crowdsec" - - forceYes bool - - // information needed to set up a random password on user's behalf ) type cliDashboard struct { @@ -54,422 +20,14 @@ func NewCLIDashboard(cfg configGetter) *cliDashboard { func (cli *cliDashboard) NewCommand() *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. -Note: This command requires database direct access, so is intended to be run on Local API/master. - `, - Args: cobra.ExactArgs(1), - DisableAutoGenTag: true, - Example: ` -cscli dashboard setup -cscli dashboard start -cscli dashboard stop -cscli dashboard remove -`, - PersistentPreRunE: func(_ *cobra.Command, _ []string) error { - cfg := cli.cfg() - if err := require.LAPI(cfg); err != nil { - return err - } - - if err := metabase.TestAvailability(); err != nil { - return err - } - - metabaseConfigFolderPath := filepath.Join(cfg.ConfigPaths.ConfigDir, metabaseConfigFolder) - metabaseConfigPath = filepath.Join(metabaseConfigFolderPath, metabaseConfigFile) - if err := os.MkdirAll(metabaseConfigFolderPath, os.ModePerm); err != nil { - return err - } - - if err := require.DB(cfg); err != nil { - return err - } - - /* - Old container name was "/crowdsec-metabase" but podman doesn't - allow '/' in container name. We do this check to not break - existing dashboard setup. - */ - if !metabase.IsContainerExist(metabaseContainerID) { - oldContainerID := fmt.Sprintf("/%s", metabaseContainerID) - if metabase.IsContainerExist(oldContainerID) { - metabaseContainerID = oldContainerID - } - } - - return nil - }, - } - - cmd.AddCommand(cli.newSetupCmd()) - cmd.AddCommand(cli.newStartCmd()) - cmd.AddCommand(cli.newStopCmd()) - cmd.AddCommand(cli.newShowPasswordCmd()) - cmd.AddCommand(cli.newRemoveCmd()) - - return cmd -} - -func (cli *cliDashboard) newSetupCmd() *cobra.Command { - var force bool - - 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`, - Args: cobra.ExactArgs(0), - DisableAutoGenTag: true, - Example: ` -cscli dashboard setup -cscli dashboard setup --listen 0.0.0.0 -cscli dashboard setup -l 0.0.0.0 -p 443 --password - `, - RunE: func(_ *cobra.Command, _ []string) error { - if metabaseDBPath == "" { - metabaseDBPath = cli.cfg().ConfigPaths.DataDir - } - - if metabasePassword == "" { - isValid := passwordIsValid(metabasePassword) - for !isValid { - metabasePassword = generatePassword(16) - isValid = passwordIsValid(metabasePassword) - } - } - if err := checkSystemMemory(&forceYes); err != nil { - return err - } - warnIfNotLoopback(metabaseListenAddress) - if err := disclaimer(&forceYes); err != nil { - return err - } - dockerGroup, err := checkGroups(&forceYes) - if err != nil { - return err - } - if err = cli.chownDatabase(dockerGroup.Gid); err != nil { - return err - } - mb, err := metabase.SetupMetabase(cli.cfg().API.Server.DbConfig, metabaseListenAddress, metabaseListenPort, metabaseUser, metabasePassword, metabaseDBPath, dockerGroup.Gid, metabaseContainerID, metabaseImage) - if err != nil { - return err - } - if err := mb.DumpConfig(metabaseConfigPath); err != nil { - return err - } - - log.Infof("Metabase is ready") - fmt.Println() - fmt.Printf("\tURL : '%s'\n", mb.Config.ListenURL) - fmt.Printf("\tusername : '%s'\n", mb.Config.Username) - fmt.Printf("\tpassword : '%s'\n", mb.Config.Password) - - return nil - }, - } - - flags := cmd.Flags() - flags.BoolVarP(&force, "force", "f", false, "Force setup : override existing files") - flags.StringVarP(&metabaseDBPath, "dir", "d", "", "Shared directory with metabase container") - flags.StringVarP(&metabaseListenAddress, "listen", "l", metabaseListenAddress, "Listen address of container") - flags.StringVar(&metabaseImage, "metabase-image", metabaseImage, "Metabase image to use") - flags.StringVarP(&metabaseListenPort, "port", "p", metabaseListenPort, "Listen port of container") - flags.BoolVarP(&forceYes, "yes", "y", false, "force yes") - // flags.StringVarP(&metabaseUser, "user", "u", "crowdsec@crowdsec.net", "metabase user") - flags.StringVar(&metabasePassword, "password", "", "metabase password") - - return cmd -} - -func (cli *cliDashboard) newStartCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "start", - Short: "Start the metabase container.", - Long: `Stats the metabase container using docker.`, - Args: cobra.ExactArgs(0), - DisableAutoGenTag: true, - RunE: func(_ *cobra.Command, _ []string) error { - mb, err := metabase.NewMetabase(metabaseConfigPath, metabaseContainerID) - if err != nil { - return err - } - warnIfNotLoopback(mb.Config.ListenAddr) - if err := disclaimer(&forceYes); err != nil { - return err - } - if err := mb.Container.Start(); err != nil { - return fmt.Errorf("failed to start metabase container : %s", err) - } - log.Infof("Started metabase") - log.Infof("url : http://%s:%s", mb.Config.ListenAddr, mb.Config.ListenPort) - - return nil - }, - } - - cmd.Flags().BoolVarP(&forceYes, "yes", "y", false, "force yes") - - return cmd -} - -func (cli *cliDashboard) newStopCmd() *cobra.Command { - cmd := &cobra.Command{ - Use: "stop", - Short: "Stops the metabase container.", - Long: `Stops the metabase container using docker.`, - Args: cobra.ExactArgs(0), - DisableAutoGenTag: true, - RunE: func(_ *cobra.Command, _ []string) error { - if err := metabase.StopContainer(metabaseContainerID); err != nil { - return fmt.Errorf("unable to stop container '%s': %s", metabaseContainerID, err) - } - return nil - }, - } - - return cmd -} - -func (cli *cliDashboard) newShowPasswordCmd() *cobra.Command { - cmd := &cobra.Command{Use: "show-password", - Short: "displays password of metabase.", - Args: cobra.ExactArgs(0), + Use: "dashboard [command]", + Hidden: true, + Short: "Manage your metabase dashboard container [requires local API]", DisableAutoGenTag: true, RunE: func(_ *cobra.Command, _ []string) error { - m := metabase.Metabase{} - if err := m.LoadConfig(metabaseConfigPath); err != nil { - return err - } - log.Printf("'%s'", m.Config.Password) - - return nil + return errors.New("command 'dashboard' has been removed, please read https://...") }, } return cmd } - -func (cli *cliDashboard) newRemoveCmd() *cobra.Command { - var force bool - - cmd := &cobra.Command{ - Use: "remove", - Short: "removes the metabase container.", - Long: `removes the metabase container using docker.`, - Args: cobra.ExactArgs(0), - DisableAutoGenTag: true, - Example: ` -cscli dashboard remove -cscli dashboard remove --force - `, - RunE: func(_ *cobra.Command, _ []string) error { - if !forceYes { - var answer bool - prompt := &survey.Confirm{ - Message: "Do you really want to remove crowdsec dashboard? (all your changes will be lost)", - Default: true, - } - if err := survey.AskOne(prompt, &answer); err != nil { - return fmt.Errorf("unable to ask to force: %s", err) - } - if !answer { - return fmt.Errorf("user stated no to continue") - } - } - if metabase.IsContainerExist(metabaseContainerID) { - log.Debugf("Stopping container %s", metabaseContainerID) - if err := metabase.StopContainer(metabaseContainerID); err != nil { - log.Warningf("unable to stop container '%s': %s", metabaseContainerID, err) - } - dockerGroup, err := user.LookupGroup(crowdsecGroup) - if err == nil { // if group exist, remove it - groupDelCmd, err := exec.LookPath("groupdel") - if err != nil { - return fmt.Errorf("unable to find 'groupdel' command, can't continue") - } - - groupDel := &exec.Cmd{Path: groupDelCmd, Args: []string{groupDelCmd, crowdsecGroup}} - if err := groupDel.Run(); err != nil { - log.Warnf("unable to delete group '%s': %s", dockerGroup, err) - } - } - log.Debugf("Removing container %s", metabaseContainerID) - if err := metabase.RemoveContainer(metabaseContainerID); err != nil { - log.Warnf("unable to remove container '%s': %s", metabaseContainerID, err) - } - log.Infof("container %s stopped & removed", metabaseContainerID) - } - log.Debugf("Removing metabase db %s", cli.cfg().ConfigPaths.DataDir) - if err := metabase.RemoveDatabase(cli.cfg().ConfigPaths.DataDir); err != nil { - log.Warnf("failed to remove metabase internal db : %s", err) - } - if force { - m := metabase.Metabase{} - if err := m.LoadConfig(metabaseConfigPath); err != nil { - return err - } - if err := metabase.RemoveImageContainer(m.Config.Image); err != nil { - if !strings.Contains(err.Error(), "No such image") { - return fmt.Errorf("removing docker image: %s", err) - } - } - } - - return nil - }, - } - - flags := cmd.Flags() - flags.BoolVarP(&force, "force", "f", false, "Remove also the metabase image") - flags.BoolVarP(&forceYes, "yes", "y", false, "force yes") - - return cmd -} - -func passwordIsValid(password string) bool { - hasDigit := false - - for _, j := range password { - if unicode.IsDigit(j) { - hasDigit = true - - break - } - } - - if !hasDigit || len(password) < 6 { - return false - } - - return true -} - -func checkSystemMemory(forceYes *bool) error { - totMem := memory.TotalMemory() - if totMem >= uint64(math.Pow(2, 30)) { - return nil - } - - if !*forceYes { - var answer bool - - prompt := &survey.Confirm{ - Message: "Metabase requires 1-2GB of RAM, your system is below this requirement continue ?", - Default: true, - } - if err := survey.AskOne(prompt, &answer); err != nil { - return fmt.Errorf("unable to ask about RAM check: %s", err) - } - - if !answer { - return fmt.Errorf("user stated no to continue") - } - - return nil - } - - log.Warn("Metabase requires 1-2GB of RAM, your system is below this requirement") - - return nil -} - -func warnIfNotLoopback(addr string) { - if addr == "127.0.0.1" || addr == "::1" { - return - } - - log.Warnf("You are potentially exposing your metabase port to the internet (addr: %s), please consider using a reverse proxy", addr) -} - -func disclaimer(forceYes *bool) error { - if !*forceYes { - var answer bool - - prompt := &survey.Confirm{ - Message: "CrowdSec takes no responsibility for the security of your metabase instance. Do you accept these responsibilities ?", - Default: true, - } - - if err := survey.AskOne(prompt, &answer); err != nil { - return fmt.Errorf("unable to ask to question: %s", err) - } - - if !answer { - return fmt.Errorf("user stated no to responsibilities") - } - - return nil - } - - log.Warn("CrowdSec takes no responsibility for the security of your metabase instance. You used force yes, so you accept this disclaimer") - - return nil -} - -func checkGroups(forceYes *bool) (*user.Group, error) { - dockerGroup, err := user.LookupGroup(crowdsecGroup) - if err == nil { - return dockerGroup, nil - } - - if !*forceYes { - var answer bool - - prompt := &survey.Confirm{ - Message: fmt.Sprintf("For metabase docker to be able to access SQLite file we need to add a new group called '%s' to the system, is it ok for you ?", crowdsecGroup), - Default: true, - } - - if err := survey.AskOne(prompt, &answer); err != nil { - return dockerGroup, fmt.Errorf("unable to ask to question: %s", err) - } - - if !answer { - return dockerGroup, fmt.Errorf("unable to continue without creating '%s' group", crowdsecGroup) - } - } - - groupAddCmd, err := exec.LookPath("groupadd") - if err != nil { - return dockerGroup, fmt.Errorf("unable to find 'groupadd' command, can't continue") - } - - groupAdd := &exec.Cmd{Path: groupAddCmd, Args: []string{groupAddCmd, crowdsecGroup}} - if err := groupAdd.Run(); err != nil { - return dockerGroup, fmt.Errorf("unable to add group '%s': %s", dockerGroup, err) - } - - return user.LookupGroup(crowdsecGroup) -} - -func (cli *cliDashboard) chownDatabase(gid string) error { - cfg := cli.cfg() - intID, err := strconv.Atoi(gid) - - if err != nil { - return fmt.Errorf("unable to convert group ID to int: %s", err) - } - - if stat, err := os.Stat(cfg.DbConfig.DbPath); !os.IsNotExist(err) { - info := stat.Sys() - if err := os.Chown(cfg.DbConfig.DbPath, int(info.(*syscall.Stat_t).Uid), intID); err != nil { - return fmt.Errorf("unable to chown sqlite db file '%s': %s", cfg.DbConfig.DbPath, err) - } - } - - if cfg.DbConfig.Type == "sqlite" && cfg.DbConfig.UseWal != nil && *cfg.DbConfig.UseWal { - for _, ext := range []string{"-wal", "-shm"} { - file := cfg.DbConfig.DbPath + ext - if stat, err := os.Stat(file); !os.IsNotExist(err) { - info := stat.Sys() - if err := os.Chown(file, int(info.(*syscall.Stat_t).Uid), intID); err != nil { - return fmt.Errorf("unable to chown sqlite db file '%s': %s", file, err) - } - } - } - } - - return nil -} diff --git a/pkg/metabase/api.go b/pkg/metabase/api.go deleted file mode 100644 index 387e8d151e0..00000000000 --- a/pkg/metabase/api.go +++ /dev/null @@ -1,85 +0,0 @@ -package metabase - -import ( - "encoding/json" - "fmt" - "net/http" - "time" - - "github.com/dghubble/sling" - log "github.com/sirupsen/logrus" - - "github.com/crowdsecurity/crowdsec/pkg/cwversion" -) - -type MBClient struct { - CTX *sling.Sling - Client *http.Client -} - -const ( - sessionEndpoint = "login" - scanEndpoint = "scan" - resetPasswordEndpoint = "reset_password" - userEndpoint = "user" - databaseEndpoint = "database" -) - -var ( - routes = map[string]string{ - sessionEndpoint: "api/session", - scanEndpoint: "api/database/2/rescan_values", - resetPasswordEndpoint: "api/user/1/password", - userEndpoint: "api/user/1", - databaseEndpoint: "api/database/2", - } -) - -func NewMBClient(url string) (*MBClient, error) { - httpClient := &http.Client{Timeout: 20 * time.Second} - return &MBClient{ - CTX: sling.New().Client(httpClient).Base(url).Set("User-Agent", cwversion.UserAgent()), - Client: httpClient, - }, nil -} - -func (h *MBClient) Do(method string, route string, body interface{}) (interface{}, interface{}, error) { - var Success interface{} - var Error interface{} - var resp *http.Response - var err error - var data []byte - if body != nil { - data, _ = json.Marshal(body) - } - - switch method { - case "POST": - log.Debugf("POST /%s", route) - log.Debugf("%s", string(data)) - resp, err = h.CTX.New().Post(route).BodyJSON(body).Receive(&Success, &Error) - case "GET": - log.Debugf("GET /%s", route) - resp, err = h.CTX.New().Get(route).Receive(&Success, &Error) - case "PUT": - log.Debugf("PUT /%s", route) - log.Debugf("%s", string(data)) - resp, err = h.CTX.New().Put(route).BodyJSON(body).Receive(&Success, &Error) - case "DELETE": - default: - return nil, nil, fmt.Errorf("unsupported method '%s'", method) - } - if Error != nil { - return Success, Error, fmt.Errorf("http error: %v", Error) - } - - if resp != nil && resp.StatusCode != 200 && resp.StatusCode != 202 { - return Success, Error, fmt.Errorf("bad status code '%d': (success: %+v) | (error: %+v)", resp.StatusCode, Success, Error) - } - return Success, Error, err -} - -// Set headers as key:value -func (h *MBClient) Set(key string, value string) { - h.CTX = h.CTX.Set(key, value) -} diff --git a/pkg/metabase/container.go b/pkg/metabase/container.go deleted file mode 100644 index 8b3dd4084c0..00000000000 --- a/pkg/metabase/container.go +++ /dev/null @@ -1,178 +0,0 @@ -package metabase - -import ( - "bufio" - "context" - "fmt" - - "github.com/docker/docker/api/types" - "github.com/docker/docker/api/types/container" - "github.com/docker/docker/api/types/mount" - "github.com/docker/docker/client" - "github.com/docker/go-connections/nat" - log "github.com/sirupsen/logrus" - - "github.com/crowdsecurity/go-cs-lib/ptr" -) - -type Container struct { - ListenAddr string - ListenPort string - SharedFolder string - Image string - Name string - ID string - CLI *client.Client - MBDBUri string - DockerGroupID string -} - -func NewContainer(listenAddr string, listenPort string, sharedFolder string, containerName string, image string, mbDBURI string, dockerGroupID string) (*Container, error) { - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - return nil, fmt.Errorf("failed to create docker client : %s", err) - } - return &Container{ - ListenAddr: listenAddr, - ListenPort: listenPort, - SharedFolder: sharedFolder, - Image: image, - Name: containerName, - CLI: cli, - MBDBUri: mbDBURI, - DockerGroupID: dockerGroupID, - }, nil -} - -func (c *Container) Create() error { - ctx := context.Background() - log.Printf("Pulling docker image %s", c.Image) - reader, err := c.CLI.ImagePull(ctx, c.Image, types.ImagePullOptions{}) - if err != nil { - return fmt.Errorf("failed to pull docker image : %s", err) - } - defer reader.Close() - scanner := bufio.NewScanner(reader) - for scanner.Scan() { - fmt.Print(".") - } - if err := scanner.Err(); err != nil { - return fmt.Errorf("failed to read imagepull reader: %s", err) - } - fmt.Print("\n") - - hostConfig := &container.HostConfig{ - PortBindings: nat.PortMap{ - "3000/tcp": []nat.PortBinding{ - { - HostIP: c.ListenAddr, - HostPort: c.ListenPort, - }, - }, - }, - Mounts: []mount.Mount{ - { - Type: mount.TypeBind, - Source: c.SharedFolder, - Target: containerSharedFolder, - }, - }, - } - - env := []string{ - fmt.Sprintf("MB_DB_FILE=%s/metabase.db", containerSharedFolder), - } - if c.MBDBUri != "" { - env = append(env, c.MBDBUri) - } - - env = append(env, fmt.Sprintf("MGID=%s", c.DockerGroupID)) - dockerConfig := &container.Config{ - Image: c.Image, - Tty: true, - Env: env, - } - - log.Infof("creating container '%s'", c.Name) - resp, err := c.CLI.ContainerCreate(ctx, dockerConfig, hostConfig, nil, nil, c.Name) - if err != nil { - return fmt.Errorf("failed to create container : %s", err) - } - c.ID = resp.ID - - return nil -} - -func (c *Container) Start() error { - ctx := context.Background() - if err := c.CLI.ContainerStart(ctx, c.Name, types.ContainerStartOptions{}); err != nil { - return fmt.Errorf("failed while starting %s : %s", c.ID, err) - } - - return nil -} - -func StartContainer(name string) error { - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - return fmt.Errorf("failed to create docker client : %s", err) - } - ctx := context.Background() - if err := cli.ContainerStart(ctx, name, types.ContainerStartOptions{}); err != nil { - return fmt.Errorf("failed while starting %s : %s", name, err) - } - - return nil -} - -func StopContainer(name string) error { - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - return fmt.Errorf("failed to create docker client : %s", err) - } - ctx := context.Background() - to := container.StopOptions{Timeout: ptr.Of(20)} - if err := cli.ContainerStop(ctx, name, to); err != nil { - return fmt.Errorf("failed while stopping %s : %s", name, err) - } - log.Printf("container stopped successfully") - return nil -} - -func RemoveContainer(name string) error { - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - return fmt.Errorf("failed to create docker client : %s", err) - } - ctx := context.Background() - log.Printf("Removing docker metabase %s", name) - if err := cli.ContainerRemove(ctx, name, types.ContainerRemoveOptions{}); err != nil { - return fmt.Errorf("failed to remove container %s : %s", name, err) - } - return nil -} - -func RemoveImageContainer(image string) error { - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - return fmt.Errorf("failed to create docker client : %s", err) - } - ctx := context.Background() - log.Printf("Removing docker image '%s'", image) - if _, err := cli.ImageRemove(ctx, image, types.ImageRemoveOptions{}); err != nil { - return fmt.Errorf("failed to remove image container %s : %s", image, err) - } - return nil -} - -func IsContainerExist(name string) bool { - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - log.Fatalf("failed to create docker client : %s", err) - } - ctx := context.Background() - if _, err := cli.ContainerInspect(ctx, name); err != nil { - return false - } - return true -} diff --git a/pkg/metabase/database.go b/pkg/metabase/database.go deleted file mode 100644 index 273d06dee16..00000000000 --- a/pkg/metabase/database.go +++ /dev/null @@ -1,100 +0,0 @@ -package metabase - -import ( - "encoding/json" - "fmt" - "path/filepath" - "strings" - - "github.com/crowdsecurity/crowdsec/pkg/csconfig" -) - -type Database struct { - DBUrl string - Model *Model - Config *csconfig.DatabaseCfg - Client *MBClient - Details *Details - // in case mysql host is 127.0.0.1 the ip address of mysql/pgsql host will be the docker gateway since metabase run in a container -} - -type Details struct { - Db string `json:"db"` - Host string `json:"host"` - Port int `json:"port"` - Dbname string `json:"dbname"` - User string `json:"user"` - Password string `json:"password"` - Ssl bool `json:"ssl"` - AdditionalOptions interface{} `json:"additional-options"` - TunnelEnabled bool `json:"tunnel-enabled"` -} - -type Model struct { - Engine string `json:"engine"` - Name string `json:"name"` - Details *Details `json:"details"` - AutoRunQueries bool `json:"auto_run_queries"` - IsFullSync bool `json:"is_full_sync"` - IsOnDemand bool `json:"is_on_demand"` - Schedules map[string]interface{} `json:"schedules"` -} - -func NewDatabase(config *csconfig.DatabaseCfg, client *MBClient, remoteDBAddr string) (*Database, error) { - var details *Details - - database := Database{} - - switch config.Type { - case "mysql": - return nil, fmt.Errorf("database '%s' is not supported yet", config.Type) - case "sqlite": - database.DBUrl = metabaseSQLiteDBURL - localFolder := filepath.Dir(config.DbPath) - // replace /var/lib/crowdsec/data/ with /metabase-data/ - dbPath := strings.Replace(config.DbPath, localFolder, containerSharedFolder, 1) - details = &Details{ - Db: dbPath, - } - case "postgresql", "postgres", "pgsql": - return nil, fmt.Errorf("database '%s' is not supported yet", config.Type) - default: - return nil, fmt.Errorf("database '%s' not supported", config.Type) - } - database.Details = details - database.Client = client - database.Config = config - - return &database, nil -} - -func (d *Database) Update() error { - success, errormsg, err := d.Client.Do("GET", routes[databaseEndpoint], nil) - if err != nil { - return err - } - if errormsg != nil { - return fmt.Errorf("update sqlite db http error: %+v", errormsg) - } - - data, err := json.Marshal(success) - if err != nil { - return fmt.Errorf("update sqlite db response (marshal): %w", err) - } - - model := Model{} - - if err := json.Unmarshal(data, &model); err != nil { - return fmt.Errorf("update sqlite db response (unmarshal): %w", err) - } - model.Details = d.Details - _, errormsg, err = d.Client.Do("PUT", routes[databaseEndpoint], model) - if err != nil { - return err - } - if errormsg != nil { - return fmt.Errorf("update sqlite db http error: %+v", errormsg) - } - - return nil -} diff --git a/pkg/metabase/metabase.go b/pkg/metabase/metabase.go deleted file mode 100644 index 837bab796d5..00000000000 --- a/pkg/metabase/metabase.go +++ /dev/null @@ -1,387 +0,0 @@ -package metabase - -import ( - "archive/zip" - "bytes" - "context" - "errors" - "fmt" - "io" - "net/http" - "os" - "path/filepath" - "runtime" - "strings" - "time" - - "github.com/docker/docker/client" - log "github.com/sirupsen/logrus" - "gopkg.in/yaml.v2" - - "github.com/crowdsecurity/crowdsec/pkg/csconfig" -) - -type Metabase struct { - Config *Config - Client *MBClient - Container *Container - Database *Database - InternalDBURL string -} - -type Config struct { - Database *csconfig.DatabaseCfg `yaml:"database"` - ListenAddr string `yaml:"listen_addr"` - ListenPort string `yaml:"listen_port"` - ListenURL string `yaml:"listen_url"` - Username string `yaml:"username"` - Password string `yaml:"password"` - DBPath string `yaml:"metabase_db_path"` - DockerGroupID string `yaml:"-"` - Image string `yaml:"image"` -} - -var ( - metabaseDefaultUser = "crowdsec@crowdsec.net" - metabaseDefaultPassword = "!!Cr0wdS3c_M3t4b4s3??" - containerSharedFolder = "/metabase-data" - metabaseSQLiteDBURL = "https://crowdsec-statics-assets.s3-eu-west-1.amazonaws.com/metabase_sqlite.zip" -) - -func TestAvailability() error { - if runtime.GOARCH != "amd64" { - return fmt.Errorf("cscli dashboard is only available on amd64, but you are running %s", runtime.GOARCH) - } - - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - return fmt.Errorf("failed to create docker client : %s", err) - } - - _, err = cli.Ping(context.TODO()) - return err - -} - -func (m *Metabase) Init(containerName string, image string) error { - var err error - var DBConnectionURI string - var remoteDBAddr string - - switch m.Config.Database.Type { - case "mysql": - return fmt.Errorf("'mysql' is not supported yet for cscli dashboard") - //DBConnectionURI = fmt.Sprintf("MB_DB_CONNECTION_URI=mysql://%s:%d/%s?user=%s&password=%s&allowPublicKeyRetrieval=true", remoteDBAddr, m.Config.Database.Port, m.Config.Database.DbName, m.Config.Database.User, m.Config.Database.Password) - case "sqlite": - m.InternalDBURL = metabaseSQLiteDBURL - case "postgresql", "postgres", "pgsql": - return fmt.Errorf("'postgresql' is not supported yet by cscli dashboard") - default: - return fmt.Errorf("database '%s' not supported", m.Config.Database.Type) - } - - m.Client, err = NewMBClient(m.Config.ListenURL) - if err != nil { - return err - } - m.Database, err = NewDatabase(m.Config.Database, m.Client, remoteDBAddr) - if err != nil { - return err - } - m.Container, err = NewContainer(m.Config.ListenAddr, m.Config.ListenPort, m.Config.DBPath, containerName, image, DBConnectionURI, m.Config.DockerGroupID) - if err != nil { - return fmt.Errorf("container init: %w", err) - } - - return nil -} -func NewMetabase(configPath string, containerName string) (*Metabase, error) { - m := &Metabase{} - if err := m.LoadConfig(configPath); err != nil { - return m, err - } - if err := m.Init(containerName, m.Config.Image); err != nil { - return m, err - } - return m, nil -} - -func (m *Metabase) LoadConfig(configPath string) error { - yamlFile, err := os.ReadFile(configPath) - if err != nil { - return err - } - - config := &Config{} - - err = yaml.Unmarshal(yamlFile, config) - if err != nil { - return err - } - if config.Username == "" { - return fmt.Errorf("'username' not found in configuration file '%s'", configPath) - } - - if config.Password == "" { - return fmt.Errorf("'password' not found in configuration file '%s'", configPath) - } - - if config.ListenURL == "" { - return fmt.Errorf("'listen_url' not found in configuration file '%s'", configPath) - } - /* Default image for backporting */ - if config.Image == "" { - config.Image = "metabase/metabase:v0.41.5" - log.Warn("Image not found in configuration file, you are using an old dashboard setup (v0.41.5), please remove your dashboard and re-create it to use the latest version.") - } - m.Config = config - - return nil - -} - -func SetupMetabase(dbConfig *csconfig.DatabaseCfg, listenAddr string, listenPort string, username string, password string, mbDBPath string, dockerGroupID string, containerName string, image string) (*Metabase, error) { - metabase := &Metabase{ - Config: &Config{ - Database: dbConfig, - ListenAddr: listenAddr, - ListenPort: listenPort, - Username: username, - Password: password, - ListenURL: fmt.Sprintf("http://%s:%s", listenAddr, listenPort), - DBPath: mbDBPath, - DockerGroupID: dockerGroupID, - Image: image, - }, - } - if err := metabase.Init(containerName, image); err != nil { - return nil, fmt.Errorf("metabase setup init: %w", err) - } - - if err := metabase.DownloadDatabase(false); err != nil { - return nil, fmt.Errorf("metabase db download: %w", err) - } - - if err := metabase.Container.Create(); err != nil { - return nil, fmt.Errorf("container create: %w", err) - } - - if err := metabase.Container.Start(); err != nil { - return nil, fmt.Errorf("container start: %w", err) - } - - log.Infof("waiting for metabase to be up (can take up to a minute)") - if err := metabase.WaitAlive(); err != nil { - return nil, fmt.Errorf("wait alive: %w", err) - } - - if err := metabase.Database.Update(); err != nil { - return nil, fmt.Errorf("update database: %w", err) - } - - if err := metabase.Scan(); err != nil { - return nil, fmt.Errorf("db scan: %w", err) - } - - if err := metabase.ResetCredentials(); err != nil { - return nil, fmt.Errorf("reset creds: %w", err) - } - - return metabase, nil -} - -func (m *Metabase) WaitAlive() error { - var err error - for { - err = m.Login(metabaseDefaultUser, metabaseDefaultPassword) - if err != nil { - if strings.Contains(err.Error(), "password:did not match stored password") { - log.Errorf("Password mismatch error, is your dashboard already setup ? Run 'cscli dashboard remove' to reset it.") - return fmt.Errorf("password mismatch error: %w", err) - } - log.Debugf("%+v", err) - } else { - break - } - - fmt.Printf(".") - time.Sleep(2 * time.Second) - } - fmt.Printf("\n") - return nil -} - -func (m *Metabase) Login(username string, password string) error { - body := map[string]string{"username": username, "password": password} - successmsg, errormsg, err := m.Client.Do("POST", routes[sessionEndpoint], body) - if err != nil { - return err - } - - if errormsg != nil { - return fmt.Errorf("http login: %s", errormsg) - } - resp, ok := successmsg.(map[string]interface{}) - if !ok { - return fmt.Errorf("login: bad response type: %+v", successmsg) - } - if _, ok = resp["id"]; !ok { - return fmt.Errorf("login: can't update session id, no id in response: %v", successmsg) - } - id, ok := resp["id"].(string) - if !ok { - return fmt.Errorf("login: bad id type: %+v", resp["id"]) - } - m.Client.Set("Cookie", fmt.Sprintf("metabase.SESSION=%s", id)) - return nil -} - -func (m *Metabase) Scan() error { - _, errormsg, err := m.Client.Do("POST", routes[scanEndpoint], nil) - if err != nil { - return err - } - if errormsg != nil { - return fmt.Errorf("http scan: %s", errormsg) - } - - return nil -} - -func (m *Metabase) ResetPassword(current string, newPassword string) error { - body := map[string]string{ - "id": "1", - "password": newPassword, - "old_password": current, - } - _, errormsg, err := m.Client.Do("PUT", routes[resetPasswordEndpoint], body) - if err != nil { - return fmt.Errorf("reset username: %w", err) - } - if errormsg != nil { - return fmt.Errorf("http reset password: %s", errormsg) - } - return nil -} - -func (m *Metabase) ResetUsername(username string) error { - body := struct { - FirstName string `json:"first_name"` - LastName string `json:"last_name"` - Email string `json:"email"` - GroupIDs []int `json:"group_ids"` - }{ - FirstName: "Crowdsec", - LastName: "Crowdsec", - Email: username, - GroupIDs: []int{1, 2}, - } - - _, errormsg, err := m.Client.Do("PUT", routes[userEndpoint], body) - if err != nil { - return fmt.Errorf("reset username: %w", err) - } - - if errormsg != nil { - return fmt.Errorf("http reset username: %s", errormsg) - } - - return nil -} - -func (m *Metabase) ResetCredentials() error { - if err := m.ResetPassword(metabaseDefaultPassword, m.Config.Password); err != nil { - return err - } - - /*if err := m.ResetUsername(m.Config.Username); err != nil { - return err - }*/ - - return nil -} - -func (m *Metabase) DumpConfig(path string) error { - data, err := yaml.Marshal(m.Config) - if err != nil { - return err - } - return os.WriteFile(path, data, 0600) -} - -func (m *Metabase) DownloadDatabase(force bool) error { - - metabaseDBSubpath := filepath.Join(m.Config.DBPath, "metabase.db") - _, err := os.Stat(metabaseDBSubpath) - if err == nil && !force { - log.Printf("%s exists, skip.", metabaseDBSubpath) - return nil - } - - if err := os.MkdirAll(metabaseDBSubpath, 0755); err != nil { - return fmt.Errorf("failed to create %s : %s", metabaseDBSubpath, err) - } - - req, err := http.NewRequest(http.MethodGet, m.InternalDBURL, nil) - if err != nil { - return fmt.Errorf("failed to build request to fetch metabase db : %s", err) - } - //This needs to be removed once we move the zip out of github - //req.Header.Add("Accept", `application/vnd.github.v3.raw`) - resp, err := http.DefaultClient.Do(req) - if err != nil { - return fmt.Errorf("failed request to fetch metabase db : %s", err) - } - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("got http %d while requesting metabase db %s, stop", resp.StatusCode, m.InternalDBURL) - } - defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) - if err != nil { - return fmt.Errorf("failed request read while fetching metabase db : %s", err) - } - log.Debugf("Got %d bytes archive", len(body)) - - if err := m.ExtractDatabase(bytes.NewReader(body)); err != nil { - return fmt.Errorf("while extracting zip : %s", err) - } - return nil -} - -func (m *Metabase) ExtractDatabase(buf *bytes.Reader) error { - r, err := zip.NewReader(buf, int64(buf.Len())) - if err != nil { - return err - } - for _, f := range r.File { - if strings.Contains(f.Name, "..") { - return fmt.Errorf("invalid path '%s' in archive", f.Name) - } - tfname := fmt.Sprintf("%s/%s", m.Config.DBPath, f.Name) - log.Tracef("%s -> %d", f.Name, f.UncompressedSize64) - if f.UncompressedSize64 == 0 { - continue - } - tfd, err := os.OpenFile(tfname, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0644) - if err != nil { - return fmt.Errorf("failed opening target file '%s' : %s", tfname, err) - } - rc, err := f.Open() - if err != nil { - return fmt.Errorf("while opening zip content %s : %s", f.Name, err) - } - written, err := io.Copy(tfd, rc) - if errors.Is(err, io.EOF) { - log.Printf("files finished ok") - } else if err != nil { - return fmt.Errorf("while copying content to %s : %s", tfname, err) - } - log.Debugf("written %d bytes to %s", written, tfname) - rc.Close() - } - return nil -} - -func RemoveDatabase(dataDir string) error { - return os.RemoveAll(filepath.Join(dataDir, "metabase.db")) -} diff --git a/test/bats/01_cscli.bats b/test/bats/01_cscli.bats index 792274cc4f4..f59cff843a4 100644 --- a/test/bats/01_cscli.bats +++ b/test/bats/01_cscli.bats @@ -418,3 +418,8 @@ teardown() { # there are no retired features rune -0 cscli config feature-flags --retired } + +@test "cscli dashboard" { + rune -1 cscli dashboard xyz + assert_stderr --partial "command 'dashboard' has been removed, please read https://..." +} From b6c9a9356dba4f456ca6ef5faf2e6bfedf04e0c7 Mon Sep 17 00:00:00 2001 From: marco Date: Fri, 3 May 2024 10:40:20 +0200 Subject: [PATCH 3/8] mod tidy --- go.mod | 3 +-- go.sum | 4 ---- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/go.mod b/go.mod index af9d7550b94..fbe9ff4e5de 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,6 @@ require ( github.com/crowdsecurity/grokky v0.2.1 github.com/crowdsecurity/machineid v1.0.2 github.com/davecgh/go-spew v1.1.1 - github.com/dghubble/sling v1.4.2 github.com/docker/docker v24.0.9+incompatible github.com/docker/go-connections v0.4.0 github.com/expr-lang/expr v1.16.9 @@ -66,7 +65,6 @@ require ( github.com/nxadm/tail v1.4.8 github.com/oschwald/geoip2-golang v1.9.0 github.com/oschwald/maxminddb-golang v1.12.0 - github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.16.0 github.com/prometheus/client_model v0.4.0 @@ -110,6 +108,7 @@ require ( github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/creack/pty v1.1.18 // indirect github.com/docker/distribution v2.8.2+incompatible // indirect + github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/gabriel-vasile/mimetype v1.4.3 // indirect github.com/gin-contrib/sse v0.1.0 // indirect diff --git a/go.sum b/go.sum index 282f10d6367..e1dcfdde71e 100644 --- a/go.sum +++ b/go.sum @@ -113,8 +113,6 @@ github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dghubble/sling v1.4.2 h1:vs1HIGBbSl2SEALyU+irpYFLZMfc49Fp+jYryFebQjM= -github.com/dghubble/sling v1.4.2/go.mod h1:o0arCOz0HwfqYQJLrRtqunaWOn4X6jxE/6ORKRpVTD4= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v24.0.9+incompatible h1:HPGzNmwfLZWdxHqK9/II92pyi1EpYKsAqcl4G0Of9v0= @@ -547,8 +545,6 @@ github.com/oschwald/geoip2-golang v1.9.0 h1:uvD3O6fXAXs+usU+UGExshpdP13GAqp4GBrz github.com/oschwald/geoip2-golang v1.9.0/go.mod h1:BHK6TvDyATVQhKNbQBdrj9eAvuwOMi2zSFXizL3K81Y= github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs= github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY= -github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= -github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58/go.mod h1:DXv8WO4yhMYhSNPKjeNKa5WY9YCIEBRbNzFFPJbWO6Y= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE= From 082a4cabc6dc6acdf874b572a357ee451a9584a6 Mon Sep 17 00:00:00 2001 From: marco Date: Tue, 11 Jun 2024 10:33:13 +0200 Subject: [PATCH 4/8] link --- cmd/crowdsec-cli/dashboard.go | 2 +- test/bats/01_cscli.bats | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/cmd/crowdsec-cli/dashboard.go b/cmd/crowdsec-cli/dashboard.go index 532f2d2694c..21110d633a8 100644 --- a/cmd/crowdsec-cli/dashboard.go +++ b/cmd/crowdsec-cli/dashboard.go @@ -25,7 +25,7 @@ func (cli *cliDashboard) NewCommand() *cobra.Command { Short: "Manage your metabase dashboard container [requires local API]", DisableAutoGenTag: true, RunE: func(_ *cobra.Command, _ []string) error { - return errors.New("command 'dashboard' has been removed, please read https://...") + return errors.New("command 'dashboard' has been removed, please read https://docs.crowdsec.net/blog/cscli_dashboard_deprecation/") }, } diff --git a/test/bats/01_cscli.bats b/test/bats/01_cscli.bats index f59cff843a4..ad876f7f38c 100644 --- a/test/bats/01_cscli.bats +++ b/test/bats/01_cscli.bats @@ -414,12 +414,11 @@ teardown() { export CROWDSEC_FEATURE_CSCLI_SETUP="true" rune -0 cscli config feature-flags assert_line '✓ cscli_setup: Enable cscli setup command (service detection)' - # there are no retired features rune -0 cscli config feature-flags --retired } @test "cscli dashboard" { rune -1 cscli dashboard xyz - assert_stderr --partial "command 'dashboard' has been removed, please read https://..." + assert_stderr --partial "command 'dashboard' has been removed, please read https://docs.crowdsec.net/blog/cscli_dashboard_deprecation/" } From fe72cce5f2b8f5a5262903cea406a36d8bc4afa6 Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 12 Sep 2024 14:43:10 +0200 Subject: [PATCH 5/8] mod tidy --- go.mod | 1 - 1 file changed, 1 deletion(-) diff --git a/go.mod b/go.mod index ebbb479966d..67992a6c887 100644 --- a/go.mod +++ b/go.mod @@ -30,7 +30,6 @@ require ( github.com/crowdsecurity/machineid v1.0.2 github.com/davecgh/go-spew v1.1.1 github.com/docker/docker v24.0.9+incompatible - github.com/docker/go-connections v0.4.0 github.com/expr-lang/expr v1.16.9 github.com/fatih/color v1.16.0 github.com/fsnotify/fsnotify v1.7.0 From 1b6634bd40a384761b60906be4649d067996be97 Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 5 Dec 2024 11:59:37 +0100 Subject: [PATCH 6/8] tidy --- go.sum | 2 -- 1 file changed, 2 deletions(-) diff --git a/go.sum b/go.sum index 28b88dc4568..8d98c00691f 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,6 @@ github.com/aws/aws-lambda-go v1.47.0/go.mod h1:dpMpZgvWx5vuQJfBt0zqBha60q7Dd7Rfg github.com/aws/aws-sdk-go v1.34.28/go.mod h1:H7NKnBqNVzoTJpGfLrQkkD+ytBA93eiDYi/+8rV9s48= github.com/aws/aws-sdk-go v1.52.0 h1:ptgek/4B2v/ljsjYSEvLQ8LTD+SQyrqhOOWvHc/VGPI= github.com/aws/aws-sdk-go v1.52.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= -github.com/beevik/etree v1.3.0 h1:hQTc+pylzIKDb23yYprodCWWTt+ojFfUZyzU09a/hmU= -github.com/beevik/etree v1.3.0/go.mod h1:aiPf89g/1k3AShMVAzriilpcE4R/Vuor90y83zVZWFc= github.com/beevik/etree v1.4.1 h1:PmQJDDYahBGNKDcpdX8uPy1xRCwoCGVUiW669MEirVI= github.com/beevik/etree v1.4.1/go.mod h1:gPNJNaBGVZ9AwsidazFZyygnd+0pAU38N4D+WemwKNs= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= From 019c9d3628820bf3cc5d980c7b1c2dd2ef1bc026 Mon Sep 17 00:00:00 2001 From: marco Date: Mon, 16 Dec 2024 14:42:20 +0100 Subject: [PATCH 7/8] override --help --- cmd/crowdsec-cli/dashboard.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/cmd/crowdsec-cli/dashboard.go b/cmd/crowdsec-cli/dashboard.go index 21110d633a8..5a03820e8f9 100644 --- a/cmd/crowdsec-cli/dashboard.go +++ b/cmd/crowdsec-cli/dashboard.go @@ -4,6 +4,7 @@ package main import ( "errors" + "fmt" "github.com/spf13/cobra" ) @@ -18,6 +19,8 @@ func NewCLIDashboard(cfg configGetter) *cliDashboard { } } +var ErrDashboardDeprecated = errors.New("command 'dashboard' has been removed, please read https://docs.crowdsec.net/blog/cscli_dashboard_deprecation/") + func (cli *cliDashboard) NewCommand() *cobra.Command { cmd := &cobra.Command{ Use: "dashboard [command]", @@ -25,9 +28,13 @@ func (cli *cliDashboard) NewCommand() *cobra.Command { Short: "Manage your metabase dashboard container [requires local API]", DisableAutoGenTag: true, RunE: func(_ *cobra.Command, _ []string) error { - return errors.New("command 'dashboard' has been removed, please read https://docs.crowdsec.net/blog/cscli_dashboard_deprecation/") + return ErrDashboardDeprecated }, } + cmd.SetHelpFunc(func(cmd *cobra.Command, args []string) { + fmt.Println(ErrDashboardDeprecated.Error()) + }) + return cmd } From 2bacc930ac21bc822acc7deb2c8306be844a9008 Mon Sep 17 00:00:00 2001 From: marco Date: Thu, 19 Dec 2024 11:02:00 +0100 Subject: [PATCH 8/8] mod tidy --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index eae718197ed..9056e01d9d7 100644 --- a/go.mod +++ b/go.mod @@ -35,7 +35,7 @@ require ( github.com/davecgh/go-spew v1.1.1 github.com/distribution/reference v0.6.0 // indirect github.com/docker/docker v27.3.1+incompatible - github.com/docker/go-connections v0.5.0 + github.com/docker/go-connections v0.5.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/expr-lang/expr v1.16.9 github.com/fatih/color v1.16.0