From 49e0735b536f8805d08b943b85ae6961dcb3e0dc Mon Sep 17 00:00:00 2001 From: mmetc <92726601+mmetc@users.noreply.github.com> Date: Mon, 11 Mar 2024 13:14:01 +0100 Subject: [PATCH] cscli tests + fix bouncer/machine prune (#2883) * func tests: "cscli config feature-flags" * func tests: "cscli bouncers list" * func tests + fix: "cscli bouncers/machines prune" * lint --- cmd/crowdsec-cli/bouncers.go | 2 +- cmd/crowdsec-cli/machines.go | 37 ++++++++++++++++++------------------ test/bats/01_cscli.bats | 21 ++++++++++++++++++++ test/bats/10_bouncers.bats | 18 +++++++++++++++++- test/bats/30_machines.bats | 14 ++++++++++++++ 5 files changed, 72 insertions(+), 20 deletions(-) diff --git a/cmd/crowdsec-cli/bouncers.go b/cmd/crowdsec-cli/bouncers.go index 35f4320c5..2e0adb9b9 100644 --- a/cmd/crowdsec-cli/bouncers.go +++ b/cmd/crowdsec-cli/bouncers.go @@ -259,7 +259,7 @@ func (cli *cliBouncers) prune(duration time.Duration, force bool) error { } } - bouncers, err := cli.db.QueryBouncersLastPulltimeLT(time.Now().UTC().Add(duration)) + bouncers, err := cli.db.QueryBouncersLastPulltimeLT(time.Now().UTC().Add(-duration)) if err != nil { return fmt.Errorf("unable to query bouncers: %w", err) } diff --git a/cmd/crowdsec-cli/machines.go b/cmd/crowdsec-cli/machines.go index 7c9b9708c..df225c06f 100644 --- a/cmd/crowdsec-cli/machines.go +++ b/cmd/crowdsec-cli/machines.go @@ -4,6 +4,7 @@ import ( saferand "crypto/rand" "encoding/csv" "encoding/json" + "errors" "fmt" "math/big" "os" @@ -134,7 +135,7 @@ Note: This command requires database direct access, so is intended to be run on } cli.db, err = database.NewClient(cli.cfg().DbConfig) if err != nil { - return fmt.Errorf("unable to create new database client: %s", err) + return fmt.Errorf("unable to create new database client: %w", err) } return nil @@ -155,7 +156,7 @@ func (cli *cliMachines) list() error { machines, err := cli.db.ListMachines() if err != nil { - return fmt.Errorf("unable to list machines: %s", err) + return fmt.Errorf("unable to list machines: %w", err) } switch cli.cfg().Cscli.Output { @@ -166,7 +167,7 @@ func (cli *cliMachines) list() error { enc.SetIndent("", " ") if err := enc.Encode(machines); err != nil { - return fmt.Errorf("failed to marshal") + return errors.New("failed to marshal") } return nil @@ -175,7 +176,7 @@ func (cli *cliMachines) list() error { err := csvwriter.Write([]string{"machine_id", "ip_address", "updated_at", "validated", "version", "auth_type", "last_heartbeat"}) if err != nil { - return fmt.Errorf("failed to write header: %s", err) + return fmt.Errorf("failed to write header: %w", err) } for _, m := range machines { @@ -257,12 +258,12 @@ func (cli *cliMachines) add(args []string, machinePassword string, dumpFile stri // create machineID if not specified by user if len(args) == 0 { if !autoAdd { - return fmt.Errorf("please specify a machine name to add, or use --auto") + return errors.New("please specify a machine name to add, or use --auto") } machineID, err = generateID("") if err != nil { - return fmt.Errorf("unable to generate machine id: %s", err) + return fmt.Errorf("unable to generate machine id: %w", err) } } else { machineID = args[0] @@ -281,20 +282,20 @@ func (cli *cliMachines) add(args []string, machinePassword string, dumpFile stri case os.IsNotExist(err) || force: dumpFile = credFile case err != nil: - return fmt.Errorf("unable to stat '%s': %s", credFile, err) + return fmt.Errorf("unable to stat '%s': %w", credFile, err) default: return fmt.Errorf(`credentials file '%s' already exists: please remove it, use "--force" or specify a different file with "-f" ("-f -" for standard output)`, credFile) } } if dumpFile == "" { - return fmt.Errorf(`please specify a file to dump credentials to, with -f ("-f -" for standard output)`) + return errors.New(`please specify a file to dump credentials to, with -f ("-f -" for standard output)`) } // create a password if it's not specified by user if machinePassword == "" && !interactive { if !autoAdd { - return fmt.Errorf("please specify a password with --password or use --auto") + return errors.New("please specify a password with --password or use --auto") } machinePassword = generatePassword(passwordLength) @@ -309,7 +310,7 @@ func (cli *cliMachines) add(args []string, machinePassword string, dumpFile stri _, err = cli.db.CreateMachine(&machineID, &password, "", true, force, types.PasswordAuthType) if err != nil { - return fmt.Errorf("unable to create machine: %s", err) + return fmt.Errorf("unable to create machine: %w", err) } fmt.Fprintf(os.Stderr, "Machine '%s' successfully added to the local API.\n", machineID) @@ -320,7 +321,7 @@ func (cli *cliMachines) add(args []string, machinePassword string, dumpFile stri } else if serverCfg != nil && serverCfg.ListenURI != "" { apiURL = "http://" + serverCfg.ListenURI } else { - return fmt.Errorf("unable to dump an api URL. Please provide it in your configuration or with the -u parameter") + return errors.New("unable to dump an api URL. Please provide it in your configuration or with the -u parameter") } } @@ -332,12 +333,12 @@ func (cli *cliMachines) add(args []string, machinePassword string, dumpFile stri apiConfigDump, err := yaml.Marshal(apiCfg) if err != nil { - return fmt.Errorf("unable to marshal api credentials: %s", err) + return fmt.Errorf("unable to marshal api credentials: %w", err) } if dumpFile != "" && dumpFile != "-" { if err = os.WriteFile(dumpFile, apiConfigDump, 0o600); err != nil { - return fmt.Errorf("write api credentials in '%s' failed: %s", dumpFile, err) + return fmt.Errorf("write api credentials in '%s' failed: %w", dumpFile, err) } fmt.Fprintf(os.Stderr, "API credentials written to '%s'.\n", dumpFile) @@ -413,13 +414,13 @@ func (cli *cliMachines) prune(duration time.Duration, notValidOnly bool, force b } if !notValidOnly { - if pending, err := cli.db.QueryLastValidatedHeartbeatLT(time.Now().UTC().Add(duration)); err == nil { + if pending, err := cli.db.QueryLastValidatedHeartbeatLT(time.Now().UTC().Add(-duration)); err == nil { machines = append(machines, pending...) } } if len(machines) == 0 { - fmt.Println("no machines to prune") + fmt.Println("No machines to prune.") return nil } @@ -438,7 +439,7 @@ func (cli *cliMachines) prune(duration time.Duration, notValidOnly bool, force b deleted, err := cli.db.BulkDeleteWatchers(machines) if err != nil { - return fmt.Errorf("unable to prune machines: %s", err) + return fmt.Errorf("unable to prune machines: %w", err) } fmt.Fprintf(os.Stderr, "successfully delete %d machines\n", deleted) @@ -479,7 +480,7 @@ cscli machines prune --not-validated-only --force`, func (cli *cliMachines) validate(machineID string) error { if err := cli.db.ValidateMachine(machineID); err != nil { - return fmt.Errorf("unable to validate machine '%s': %s", machineID, err) + return fmt.Errorf("unable to validate machine '%s': %w", machineID, err) } log.Infof("machine '%s' validated successfully", machineID) @@ -495,7 +496,7 @@ func (cli *cliMachines) newValidateCmd() *cobra.Command { Example: `cscli machines validate "machine_name"`, Args: cobra.ExactArgs(1), DisableAutoGenTag: true, - RunE: func(cmd *cobra.Command, args []string) error { + RunE: func(_ *cobra.Command, args []string) error { return cli.validate(args[0]) }, } diff --git a/test/bats/01_cscli.bats b/test/bats/01_cscli.bats index 60a65b98d..03f0132ea 100644 --- a/test/bats/01_cscli.bats +++ b/test/bats/01_cscli.bats @@ -358,3 +358,24 @@ teardown() { rune -0 cscli setup assert_output --partial 'cscli setup [command]' } + +@test "cscli config feature-flags" { + # disabled + rune -0 cscli config feature-flags + assert_line '✗ cscli_setup: Enable cscli setup command (service detection)' + + # enabled in feature.yaml + CONFIG_DIR=$(dirname "$CONFIG_YAML") + echo ' - cscli_setup' >> "$CONFIG_DIR"/feature.yaml + rune -0 cscli config feature-flags + assert_line '✓ cscli_setup: Enable cscli setup command (service detection)' + + # enabled in environment + # shellcheck disable=SC2031 + 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 +} diff --git a/test/bats/10_bouncers.bats b/test/bats/10_bouncers.bats index 3f6167ff6..1ef39ceb0 100644 --- a/test/bats/10_bouncers.bats +++ b/test/bats/10_bouncers.bats @@ -25,7 +25,13 @@ teardown() { @test "there are 0 bouncers" { rune -0 cscli bouncers list -o json - assert_output "[]" + assert_json '[]' + + rune -0 cscli bouncers list -o human + assert_output --partial "Name" + + rune -0 cscli bouncers list -o raw + assert_output --partial 'name' } @test "we can add one bouncer, and delete it" { @@ -68,3 +74,13 @@ teardown() { rune -1 cscli bouncers delete ciTestBouncer rune -1 cscli bouncers delete foobarbaz } + +@test "cscli bouncers prune" { + rune -0 cscli bouncers prune + assert_output 'No bouncers to prune.' + rune -0 cscli bouncers add ciTestBouncer + + rune -0 cscli bouncers prune + assert_output 'No bouncers to prune.' +} + diff --git a/test/bats/30_machines.bats b/test/bats/30_machines.bats index f32c376e5..2a04cc9bc 100644 --- a/test/bats/30_machines.bats +++ b/test/bats/30_machines.bats @@ -90,3 +90,17 @@ teardown() { rune -0 jq '. | length' <(output) assert_output 1 } + +@test "cscli machines prune" { + rune -0 cscli metrics + + rune -0 cscli machines prune + assert_output 'No machines to prune.' + + rune -0 cscli machines list -o json + rune -0 jq -r '.[-1].machineId' <(output) + rune -0 cscli machines delete "$output" + + rune -0 cscli machines prune + assert_output 'No machines to prune.' +}