Skip to content

Commit

Permalink
refact cscli machines
Browse files Browse the repository at this point in the history
  • Loading branch information
mmetc committed Oct 29, 2024
1 parent 35f0480 commit 8efc6c1
Show file tree
Hide file tree
Showing 7 changed files with 656 additions and 585 deletions.
152 changes: 152 additions & 0 deletions cmd/crowdsec-cli/climachine/add.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package climachine

import (
"context"
"errors"
"fmt"
"os"

"github.com/AlecAivazis/survey/v2"
"github.com/go-openapi/strfmt"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"

"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/idgen"
"github.com/crowdsecurity/crowdsec/pkg/csconfig"
"github.com/crowdsecurity/crowdsec/pkg/types"
)

func (cli *cliMachines) add(ctx context.Context, args []string, machinePassword string, dumpFile string, apiURL string, interactive bool, autoAdd bool, force bool) error {
var (
err error
machineID string
)

// create machineID if not specified by user
if len(args) == 0 {
if !autoAdd {
return errors.New("please specify a machine name to add, or use --auto")
}

Check warning on line 29 in cmd/crowdsec-cli/climachine/add.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/add.go#L28-L29

Added lines #L28 - L29 were not covered by tests

machineID, err = idgen.GenerateMachineID("")
if err != nil {
return fmt.Errorf("unable to generate machine id: %w", err)
}

Check warning on line 34 in cmd/crowdsec-cli/climachine/add.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/add.go#L33-L34

Added lines #L33 - L34 were not covered by tests
} else {
machineID = args[0]
}

clientCfg := cli.cfg().API.Client
serverCfg := cli.cfg().API.Server

/*check if file already exists*/
if dumpFile == "" && clientCfg != nil && clientCfg.CredentialsFilePath != "" {
credFile := clientCfg.CredentialsFilePath
// use the default only if the file does not exist
_, err = os.Stat(credFile)

switch {
case os.IsNotExist(err) || force:
dumpFile = credFile
case err != nil:
return fmt.Errorf("unable to stat '%s': %w", credFile, err)

Check warning on line 52 in cmd/crowdsec-cli/climachine/add.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/add.go#L51-L52

Added lines #L51 - L52 were not covered by tests
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 errors.New(`please specify a file to dump credentials to, with -f ("-f -" for standard output)`)
}

Check warning on line 60 in cmd/crowdsec-cli/climachine/add.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/add.go#L59-L60

Added lines #L59 - L60 were not covered by tests

// create a password if it's not specified by user
if machinePassword == "" && !interactive {
if !autoAdd {
return errors.New("please specify a password with --password or use --auto")
}

Check warning on line 66 in cmd/crowdsec-cli/climachine/add.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/add.go#L65-L66

Added lines #L65 - L66 were not covered by tests

machinePassword = idgen.GeneratePassword(idgen.PasswordLength)
} else if machinePassword == "" && interactive {
qs := &survey.Password{
Message: "Please provide a password for the machine:",
}
survey.AskOne(qs, &machinePassword)
}

Check warning on line 74 in cmd/crowdsec-cli/climachine/add.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/add.go#L69-L74

Added lines #L69 - L74 were not covered by tests

password := strfmt.Password(machinePassword)

_, err = cli.db.CreateMachine(ctx, &machineID, &password, "", true, force, types.PasswordAuthType)
if err != nil {
return fmt.Errorf("unable to create machine: %w", err)
}

Check warning on line 81 in cmd/crowdsec-cli/climachine/add.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/add.go#L80-L81

Added lines #L80 - L81 were not covered by tests

fmt.Fprintf(os.Stderr, "Machine '%s' successfully added to the local API.\n", machineID)

if apiURL == "" {
if clientCfg != nil && clientCfg.Credentials != nil && clientCfg.Credentials.URL != "" {
apiURL = clientCfg.Credentials.URL

Check warning on line 87 in cmd/crowdsec-cli/climachine/add.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/add.go#L87

Added line #L87 was not covered by tests
} else if serverCfg.ClientURL() != "" {
apiURL = serverCfg.ClientURL()
} else {
return errors.New("unable to dump an api URL. Please provide it in your configuration or with the -u parameter")
}

Check warning on line 92 in cmd/crowdsec-cli/climachine/add.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/add.go#L91-L92

Added lines #L91 - L92 were not covered by tests
}

apiCfg := csconfig.ApiCredentialsCfg{
Login: machineID,
Password: password.String(),
URL: apiURL,
}

apiConfigDump, err := yaml.Marshal(apiCfg)
if err != nil {
return fmt.Errorf("unable to serialize api credentials: %w", err)
}

Check warning on line 104 in cmd/crowdsec-cli/climachine/add.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/add.go#L103-L104

Added lines #L103 - L104 were not covered by tests

if dumpFile != "" && dumpFile != "-" {
if err = os.WriteFile(dumpFile, apiConfigDump, 0o600); err != nil {
return fmt.Errorf("write api credentials in '%s' failed: %w", dumpFile, err)
}

Check warning on line 109 in cmd/crowdsec-cli/climachine/add.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/add.go#L108-L109

Added lines #L108 - L109 were not covered by tests

fmt.Fprintf(os.Stderr, "API credentials written to '%s'.\n", dumpFile)
} else {
fmt.Print(string(apiConfigDump))

Check failure

Code scanning / CodeQL

Clear-text logging of sensitive information High

Sensitive data returned by an access to Password
flows to a logging call.
}

Check warning on line 114 in cmd/crowdsec-cli/climachine/add.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/add.go#L112-L114

Added lines #L112 - L114 were not covered by tests

return nil
}

func (cli *cliMachines) newAddCmd() *cobra.Command {
var (
password MachinePassword
dumpFile string
apiURL string
interactive bool
autoAdd bool
force bool
)

cmd := &cobra.Command{
Use: "add",
Short: "add a single machine to the database",
DisableAutoGenTag: true,
Long: `Register a new machine in the database. cscli should be on the same machine as LAPI.`,
Example: `cscli machines add --auto
cscli machines add MyTestMachine --auto
cscli machines add MyTestMachine --password MyPassword
cscli machines add -f- --auto > /tmp/mycreds.yaml`,
RunE: func(cmd *cobra.Command, args []string) error {
return cli.add(cmd.Context(), args, string(password), dumpFile, apiURL, interactive, autoAdd, force)
},
}

flags := cmd.Flags()
flags.VarP(&password, "password", "p", "machine password to login to the API")
flags.StringVarP(&dumpFile, "file", "f", "", "output file destination (defaults to "+csconfig.DefaultConfigPath("local_api_credentials.yaml")+")")
flags.StringVarP(&apiURL, "url", "u", "", "URL of the local API")
flags.BoolVarP(&interactive, "interactive", "i", false, "interfactive mode to enter the password")
flags.BoolVarP(&autoAdd, "auto", "a", false, "automatically generate password (and username if not provided)")
flags.BoolVar(&force, "force", false, "will force add the machine if it already exist")

return cmd
}
52 changes: 52 additions & 0 deletions cmd/crowdsec-cli/climachine/delete.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package climachine

import (
"context"
"errors"

log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"

"github.com/crowdsecurity/crowdsec/pkg/database"
)

func (cli *cliMachines) delete(ctx context.Context, machines []string, ignoreMissing bool) error {
for _, machineID := range machines {
if err := cli.db.DeleteWatcher(ctx, machineID); err != nil {
var notFoundErr *database.MachineNotFoundError
if ignoreMissing && errors.As(err, &notFoundErr) {
return nil
}

log.Errorf("unable to delete machine: %s", err)

return nil
}

log.Infof("machine '%s' deleted successfully", machineID)
}

return nil
}

func (cli *cliMachines) newDeleteCmd() *cobra.Command {
var ignoreMissing bool

cmd := &cobra.Command{
Use: "delete [machine_name]...",
Short: "delete machine(s) by name",
Example: `cscli machines delete "machine1" "machine2"`,
Args: cobra.MinimumNArgs(1),
Aliases: []string{"remove"},
DisableAutoGenTag: true,
ValidArgsFunction: cli.validMachineID,
RunE: func(cmd *cobra.Command, args []string) error {
return cli.delete(cmd.Context(), args, ignoreMissing)
},
}

flags := cmd.Flags()
flags.BoolVar(&ignoreMissing, "ignore-missing", false, "don't print errors if one or more machines don't exist")

return cmd
}
184 changes: 184 additions & 0 deletions cmd/crowdsec-cli/climachine/inspect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package climachine

import (
"encoding/csv"
"encoding/json"
"errors"
"fmt"
"io"

"github.com/fatih/color"
"github.com/jedib0t/go-pretty/v6/table"
"github.com/spf13/cobra"

"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/clientinfo"
"github.com/crowdsecurity/crowdsec/cmd/crowdsec-cli/cstable"
"github.com/crowdsecurity/crowdsec/pkg/cwhub"
"github.com/crowdsecurity/crowdsec/pkg/database/ent"
)

func (cli *cliMachines) inspectHubHuman(out io.Writer, machine *ent.Machine) {
state := machine.Hubstate

if len(state) == 0 {
fmt.Println("No hub items found for this machine")
return
}

Check warning on line 26 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L20-L26

Added lines #L20 - L26 were not covered by tests

// group state rows by type for multiple tables
rowsByType := make(map[string][]table.Row)

for itemType, items := range state {
for _, item := range items {
if _, ok := rowsByType[itemType]; !ok {
rowsByType[itemType] = make([]table.Row, 0)
}

Check warning on line 35 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L29-L35

Added lines #L29 - L35 were not covered by tests

row := table.Row{item.Name, item.Status, item.Version}
rowsByType[itemType] = append(rowsByType[itemType], row)

Check warning on line 38 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L37-L38

Added lines #L37 - L38 were not covered by tests
}
}

for itemType, rows := range rowsByType {
t := cstable.New(out, cli.cfg().Cscli.Color).Writer
t.AppendHeader(table.Row{"Name", "Status", "Version"})
t.SetTitle(itemType)
t.AppendRows(rows)
io.WriteString(out, t.Render()+"\n")
}

Check warning on line 48 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L42-L48

Added lines #L42 - L48 were not covered by tests
}

func (cli *cliMachines) inspectHuman(out io.Writer, machine *ent.Machine) {
t := cstable.New(out, cli.cfg().Cscli.Color).Writer

t.SetTitle("Machine: " + machine.MachineId)

t.SetColumnConfigs([]table.ColumnConfig{
{Number: 1, AutoMerge: true},
})

t.AppendRows([]table.Row{
{"IP Address", machine.IpAddress},
{"Created At", machine.CreatedAt},
{"Last Update", machine.UpdatedAt},
{"Last Heartbeat", machine.LastHeartbeat},
{"Validated?", machine.IsValidated},
{"CrowdSec version", machine.Version},
{"OS", clientinfo.GetOSNameAndVersion(machine)},
{"Auth type", machine.AuthType},
})

for dsName, dsCount := range machine.Datasources {
t.AppendRow(table.Row{"Datasources", fmt.Sprintf("%s: %d", dsName, dsCount)})
}

Check warning on line 73 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L51-L73

Added lines #L51 - L73 were not covered by tests

for _, ff := range clientinfo.GetFeatureFlagList(machine) {
t.AppendRow(table.Row{"Feature Flags", ff})
}

Check warning on line 77 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L75-L77

Added lines #L75 - L77 were not covered by tests

for _, coll := range machine.Hubstate[cwhub.COLLECTIONS] {
t.AppendRow(table.Row{"Collections", coll.Name})
}

Check warning on line 81 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L79-L81

Added lines #L79 - L81 were not covered by tests

io.WriteString(out, t.Render()+"\n")

Check warning on line 83 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L83

Added line #L83 was not covered by tests
}

func (cli *cliMachines) inspect(machine *ent.Machine) error {
out := color.Output
outputFormat := cli.cfg().Cscli.Output

switch outputFormat {
case "human":
cli.inspectHuman(out, machine)

Check warning on line 92 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L91-L92

Added lines #L91 - L92 were not covered by tests
case "json":
enc := json.NewEncoder(out)
enc.SetIndent("", " ")

if err := enc.Encode(newMachineInfo(machine)); err != nil {
return errors.New("failed to serialize")
}

Check warning on line 99 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L98-L99

Added lines #L98 - L99 were not covered by tests

return nil
default:
return fmt.Errorf("output format '%s' not supported for this command", outputFormat)

Check warning on line 103 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L102-L103

Added lines #L102 - L103 were not covered by tests
}

return nil

Check warning on line 106 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L106

Added line #L106 was not covered by tests
}

func (cli *cliMachines) inspectHub(machine *ent.Machine) error {
out := color.Output

switch cli.cfg().Cscli.Output {
case "human":
cli.inspectHubHuman(out, machine)
case "json":
enc := json.NewEncoder(out)
enc.SetIndent("", " ")

if err := enc.Encode(machine.Hubstate); err != nil {
return errors.New("failed to serialize")
}

Check warning on line 121 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L109-L121

Added lines #L109 - L121 were not covered by tests

return nil
case "raw":
csvwriter := csv.NewWriter(out)

err := csvwriter.Write([]string{"type", "name", "status", "version"})
if err != nil {
return fmt.Errorf("failed to write header: %w", err)
}

Check warning on line 130 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L123-L130

Added lines #L123 - L130 were not covered by tests

rows := make([][]string, 0)

for itemType, items := range machine.Hubstate {
for _, item := range items {
rows = append(rows, []string{itemType, item.Name, item.Status, item.Version})
}

Check warning on line 137 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L132-L137

Added lines #L132 - L137 were not covered by tests
}

for _, row := range rows {
if err := csvwriter.Write(row); err != nil {
return fmt.Errorf("failed to write raw output: %w", err)
}

Check warning on line 143 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L140-L143

Added lines #L140 - L143 were not covered by tests
}

csvwriter.Flush()

Check warning on line 146 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L146

Added line #L146 was not covered by tests
}

return nil

Check warning on line 149 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L149

Added line #L149 was not covered by tests
}

func (cli *cliMachines) newInspectCmd() *cobra.Command {
var showHub bool

cmd := &cobra.Command{
Use: "inspect [machine_name]",
Short: "inspect a machine by name",
Example: `cscli machines inspect "machine1"`,
Args: cobra.ExactArgs(1),
DisableAutoGenTag: true,
ValidArgsFunction: cli.validMachineID,
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
machineID := args[0]

machine, err := cli.db.QueryMachineByID(ctx, machineID)
if err != nil {
return fmt.Errorf("unable to read machine data '%s': %w", machineID, err)
}

if showHub {
return cli.inspectHub(machine)
}

Check warning on line 173 in cmd/crowdsec-cli/climachine/inspect.go

View check run for this annotation

Codecov / codecov/patch

cmd/crowdsec-cli/climachine/inspect.go#L172-L173

Added lines #L172 - L173 were not covered by tests

return cli.inspect(machine)
},
}

flags := cmd.Flags()

flags.BoolVarP(&showHub, "hub", "H", false, "show hub state")

return cmd
}
Loading

0 comments on commit 8efc6c1

Please sign in to comment.