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
This commit is contained in:
mmetc 2024-03-11 13:14:01 +01:00 committed by GitHub
parent 6daaab1789
commit 49e0735b53
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 72 additions and 20 deletions

View file

@ -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 { if err != nil {
return fmt.Errorf("unable to query bouncers: %w", err) return fmt.Errorf("unable to query bouncers: %w", err)
} }

View file

@ -4,6 +4,7 @@ import (
saferand "crypto/rand" saferand "crypto/rand"
"encoding/csv" "encoding/csv"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"math/big" "math/big"
"os" "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) cli.db, err = database.NewClient(cli.cfg().DbConfig)
if err != nil { 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 return nil
@ -155,7 +156,7 @@ func (cli *cliMachines) list() error {
machines, err := cli.db.ListMachines() machines, err := cli.db.ListMachines()
if err != nil { 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 { switch cli.cfg().Cscli.Output {
@ -166,7 +167,7 @@ func (cli *cliMachines) list() error {
enc.SetIndent("", " ") enc.SetIndent("", " ")
if err := enc.Encode(machines); err != nil { if err := enc.Encode(machines); err != nil {
return fmt.Errorf("failed to marshal") return errors.New("failed to marshal")
} }
return nil 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"}) err := csvwriter.Write([]string{"machine_id", "ip_address", "updated_at", "validated", "version", "auth_type", "last_heartbeat"})
if err != nil { 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 { 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 // create machineID if not specified by user
if len(args) == 0 { if len(args) == 0 {
if !autoAdd { 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("") machineID, err = generateID("")
if err != nil { if err != nil {
return fmt.Errorf("unable to generate machine id: %s", err) return fmt.Errorf("unable to generate machine id: %w", err)
} }
} else { } else {
machineID = args[0] machineID = args[0]
@ -281,20 +282,20 @@ func (cli *cliMachines) add(args []string, machinePassword string, dumpFile stri
case os.IsNotExist(err) || force: case os.IsNotExist(err) || force:
dumpFile = credFile dumpFile = credFile
case err != nil: case err != nil:
return fmt.Errorf("unable to stat '%s': %s", credFile, err) return fmt.Errorf("unable to stat '%s': %w", credFile, err)
default: 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) 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 == "" { 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 // create a password if it's not specified by user
if machinePassword == "" && !interactive { if machinePassword == "" && !interactive {
if !autoAdd { 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) 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) _, err = cli.db.CreateMachine(&machineID, &password, "", true, force, types.PasswordAuthType)
if err != nil { 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) 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 != "" { } else if serverCfg != nil && serverCfg.ListenURI != "" {
apiURL = "http://" + serverCfg.ListenURI apiURL = "http://" + serverCfg.ListenURI
} else { } 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) apiConfigDump, err := yaml.Marshal(apiCfg)
if err != nil { 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 dumpFile != "" && dumpFile != "-" {
if err = os.WriteFile(dumpFile, apiConfigDump, 0o600); err != nil { 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) 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 !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...) machines = append(machines, pending...)
} }
} }
if len(machines) == 0 { if len(machines) == 0 {
fmt.Println("no machines to prune") fmt.Println("No machines to prune.")
return nil return nil
} }
@ -438,7 +439,7 @@ func (cli *cliMachines) prune(duration time.Duration, notValidOnly bool, force b
deleted, err := cli.db.BulkDeleteWatchers(machines) deleted, err := cli.db.BulkDeleteWatchers(machines)
if err != nil { 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) 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 { func (cli *cliMachines) validate(machineID string) error {
if err := cli.db.ValidateMachine(machineID); err != nil { 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) log.Infof("machine '%s' validated successfully", machineID)
@ -495,7 +496,7 @@ func (cli *cliMachines) newValidateCmd() *cobra.Command {
Example: `cscli machines validate "machine_name"`, Example: `cscli machines validate "machine_name"`,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
DisableAutoGenTag: true, DisableAutoGenTag: true,
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(_ *cobra.Command, args []string) error {
return cli.validate(args[0]) return cli.validate(args[0])
}, },
} }

View file

@ -358,3 +358,24 @@ teardown() {
rune -0 cscli setup rune -0 cscli setup
assert_output --partial 'cscli setup [command]' 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
}

View file

@ -25,7 +25,13 @@ teardown() {
@test "there are 0 bouncers" { @test "there are 0 bouncers" {
rune -0 cscli bouncers list -o json 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" { @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 ciTestBouncer
rune -1 cscli bouncers delete foobarbaz 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.'
}

View file

@ -90,3 +90,17 @@ teardown() {
rune -0 jq '. | length' <(output) rune -0 jq '. | length' <(output)
assert_output 1 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.'
}