|
|
|
@ -5,7 +5,6 @@ import (
|
|
|
|
|
"encoding/csv"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"math/big"
|
|
|
|
|
"os"
|
|
|
|
|
"strings"
|
|
|
|
@ -101,53 +100,18 @@ func getLastHeartbeat(m *ent.Machine) (string, bool) {
|
|
|
|
|
return hb, true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func getAgents(out io.Writer, dbClient *database.Client) error {
|
|
|
|
|
machines, err := dbClient.ListMachines()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to list machines: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch csConfig.Cscli.Output {
|
|
|
|
|
case "human":
|
|
|
|
|
getAgentsTable(out, machines)
|
|
|
|
|
case "json":
|
|
|
|
|
enc := json.NewEncoder(out)
|
|
|
|
|
enc.SetIndent("", " ")
|
|
|
|
|
if err := enc.Encode(machines); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to marshal")
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
case "raw":
|
|
|
|
|
csvwriter := csv.NewWriter(out)
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
for _, m := range machines {
|
|
|
|
|
validated := "false"
|
|
|
|
|
if m.IsValidated {
|
|
|
|
|
validated = "true"
|
|
|
|
|
}
|
|
|
|
|
hb, _ := getLastHeartbeat(m)
|
|
|
|
|
err := csvwriter.Write([]string{m.MachineId, m.IpAddress, m.UpdatedAt.Format(time.RFC3339), validated, m.Version, m.AuthType, hb})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("failed to write raw output: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
csvwriter.Flush()
|
|
|
|
|
default:
|
|
|
|
|
return fmt.Errorf("unknown output '%s'", csConfig.Cscli.Output)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
type cliMachines struct{
|
|
|
|
|
db *database.Client
|
|
|
|
|
cfg configGetter
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type cliMachines struct{}
|
|
|
|
|
|
|
|
|
|
func NewCLIMachines() *cliMachines {
|
|
|
|
|
return &cliMachines{}
|
|
|
|
|
func NewCLIMachines(getconfig configGetter) *cliMachines {
|
|
|
|
|
return &cliMachines{
|
|
|
|
|
cfg: getconfig,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cli cliMachines) NewCommand() *cobra.Command {
|
|
|
|
|
func (cli *cliMachines) NewCommand() *cobra.Command {
|
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
|
Use: "machines [action]",
|
|
|
|
|
Short: "Manage local API machines [requires local API]",
|
|
|
|
@ -159,10 +123,10 @@ Note: This command requires database direct access, so is intended to be run on
|
|
|
|
|
Aliases: []string{"machine"},
|
|
|
|
|
PersistentPreRunE: func(_ *cobra.Command, _ []string) error {
|
|
|
|
|
var err error
|
|
|
|
|
if err = require.LAPI(csConfig); err != nil {
|
|
|
|
|
if err = require.LAPI(cli.cfg()); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
dbClient, err = database.NewClient(csConfig.DbConfig)
|
|
|
|
|
cli.db, err = database.NewClient(cli.cfg().DbConfig)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to create new database client: %s", err)
|
|
|
|
|
}
|
|
|
|
@ -170,16 +134,63 @@ Note: This command requires database direct access, so is intended to be run on
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cmd.AddCommand(cli.NewListCmd())
|
|
|
|
|
cmd.AddCommand(cli.NewAddCmd())
|
|
|
|
|
cmd.AddCommand(cli.NewDeleteCmd())
|
|
|
|
|
cmd.AddCommand(cli.NewValidateCmd())
|
|
|
|
|
cmd.AddCommand(cli.NewPruneCmd())
|
|
|
|
|
cmd.AddCommand(cli.newListCmd())
|
|
|
|
|
cmd.AddCommand(cli.newAddCmd())
|
|
|
|
|
cmd.AddCommand(cli.newDeleteCmd())
|
|
|
|
|
cmd.AddCommand(cli.newValidateCmd())
|
|
|
|
|
cmd.AddCommand(cli.newPruneCmd())
|
|
|
|
|
|
|
|
|
|
return cmd
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cli cliMachines) NewListCmd() *cobra.Command {
|
|
|
|
|
func (cli *cliMachines) list() error {
|
|
|
|
|
out := color.Output
|
|
|
|
|
|
|
|
|
|
machines, err := cli.db.ListMachines()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to list machines: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch cli.cfg().Cscli.Output {
|
|
|
|
|
case "human":
|
|
|
|
|
getAgentsTable(out, machines)
|
|
|
|
|
case "json":
|
|
|
|
|
enc := json.NewEncoder(out)
|
|
|
|
|
enc.SetIndent("", " ")
|
|
|
|
|
|
|
|
|
|
if err := enc.Encode(machines); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to marshal")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
case "raw":
|
|
|
|
|
csvwriter := csv.NewWriter(out)
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, m := range machines {
|
|
|
|
|
validated := "false"
|
|
|
|
|
if m.IsValidated {
|
|
|
|
|
validated = "true"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
hb, _ := getLastHeartbeat(m)
|
|
|
|
|
|
|
|
|
|
if err := csvwriter.Write([]string{m.MachineId, m.IpAddress, m.UpdatedAt.Format(time.RFC3339), validated, m.Version, m.AuthType, hb}); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to write raw output: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
csvwriter.Flush()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cli *cliMachines) newListCmd() *cobra.Command {
|
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
|
Use: "list",
|
|
|
|
|
Short: "list all machines in the database",
|
|
|
|
@ -188,84 +199,60 @@ func (cli cliMachines) NewListCmd() *cobra.Command {
|
|
|
|
|
Args: cobra.NoArgs,
|
|
|
|
|
DisableAutoGenTag: true,
|
|
|
|
|
RunE: func(_ *cobra.Command, _ []string) error {
|
|
|
|
|
err := getAgents(color.Output, dbClient)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to list machines: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
return cli.list()
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cmd
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cli cliMachines) NewAddCmd() *cobra.Command {
|
|
|
|
|
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
|
|
|
|
|
Example: `cscli machines add --auto
|
|
|
|
|
cscli machines add MyTestMachine --auto
|
|
|
|
|
cscli machines add MyTestMachine --password MyPassword
|
|
|
|
|
`,
|
|
|
|
|
RunE: cli.add,
|
|
|
|
|
cscli machines add -f- --auto > /tmp/mycreds.yaml`,
|
|
|
|
|
RunE: func(_ *cobra.Command, args []string) error {
|
|
|
|
|
return cli.add(args, string(password), dumpFile, apiURL, interactive, autoAdd, force)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
flags := cmd.Flags()
|
|
|
|
|
flags.StringP("password", "p", "", "machine password to login to the API")
|
|
|
|
|
flags.StringP("file", "f", "", "output file destination (defaults to "+csconfig.DefaultConfigPath("local_api_credentials.yaml")+")")
|
|
|
|
|
flags.StringP("url", "u", "", "URL of the local API")
|
|
|
|
|
flags.BoolP("interactive", "i", false, "interfactive mode to enter the password")
|
|
|
|
|
flags.BoolP("auto", "a", false, "automatically generate password (and username if not provided)")
|
|
|
|
|
flags.Bool("force", false, "will force add the machine if it already exist")
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cli cliMachines) add(cmd *cobra.Command, args []string) error {
|
|
|
|
|
flags := cmd.Flags()
|
|
|
|
|
|
|
|
|
|
machinePassword, err := flags.GetString("password")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dumpFile, err := flags.GetString("file")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apiURL, err := flags.GetString("url")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interactive, err := flags.GetBool("interactive")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
autoAdd, err := flags.GetBool("auto")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
force, err := flags.GetBool("force")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var machineID string
|
|
|
|
|
func (cli *cliMachines) add(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 {
|
|
|
|
|
printHelp(cmd)
|
|
|
|
|
return nil
|
|
|
|
|
return fmt.Errorf("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)
|
|
|
|
@ -274,15 +261,18 @@ func (cli cliMachines) add(cmd *cobra.Command, args []string) error {
|
|
|
|
|
machineID = args[0]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
clientCfg := cli.cfg().API.Client
|
|
|
|
|
serverCfg := cli.cfg().API.Server
|
|
|
|
|
|
|
|
|
|
/*check if file already exists*/
|
|
|
|
|
if dumpFile == "" && csConfig.API.Client != nil && csConfig.API.Client.CredentialsFilePath != "" {
|
|
|
|
|
credFile := csConfig.API.Client.CredentialsFilePath
|
|
|
|
|
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 = csConfig.API.Client.CredentialsFilePath
|
|
|
|
|
dumpFile = credFile
|
|
|
|
|
case err != nil:
|
|
|
|
|
return fmt.Errorf("unable to stat '%s': %s", credFile, err)
|
|
|
|
|
default:
|
|
|
|
@ -302,78 +292,74 @@ func (cli cliMachines) add(cmd *cobra.Command, args []string) error {
|
|
|
|
|
machinePassword = generatePassword(passwordLength)
|
|
|
|
|
} else if machinePassword == "" && interactive {
|
|
|
|
|
qs := &survey.Password{
|
|
|
|
|
Message: "Please provide a password for the machine",
|
|
|
|
|
Message: "Please provide a password for the machine:",
|
|
|
|
|
}
|
|
|
|
|
survey.AskOne(qs, &machinePassword)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
password := strfmt.Password(machinePassword)
|
|
|
|
|
_, err = dbClient.CreateMachine(&machineID, &password, "", true, force, types.PasswordAuthType)
|
|
|
|
|
|
|
|
|
|
_, err = cli.db.CreateMachine(&machineID, &password, "", true, force, types.PasswordAuthType)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to create machine: %s", err)
|
|
|
|
|
}
|
|
|
|
|
fmt.Printf("Machine '%s' successfully added to the local API.\n", machineID)
|
|
|
|
|
|
|
|
|
|
fmt.Fprintf(os.Stderr, "Machine '%s' successfully added to the local API.\n", machineID)
|
|
|
|
|
|
|
|
|
|
if apiURL == "" {
|
|
|
|
|
if csConfig.API.Client != nil && csConfig.API.Client.Credentials != nil && csConfig.API.Client.Credentials.URL != "" {
|
|
|
|
|
apiURL = csConfig.API.Client.Credentials.URL
|
|
|
|
|
} else if csConfig.API.Server != nil && csConfig.API.Server.ListenURI != "" {
|
|
|
|
|
apiURL = "http://" + csConfig.API.Server.ListenURI
|
|
|
|
|
if clientCfg != nil && clientCfg.Credentials != nil && clientCfg.Credentials.URL != "" {
|
|
|
|
|
apiURL = clientCfg.Credentials.URL
|
|
|
|
|
} 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")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apiCfg := csconfig.ApiCredentialsCfg{
|
|
|
|
|
Login: machineID,
|
|
|
|
|
Password: password.String(),
|
|
|
|
|
URL: apiURL,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apiConfigDump, err := yaml.Marshal(apiCfg)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to marshal api credentials: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dumpFile != "" && dumpFile != "-" {
|
|
|
|
|
err = os.WriteFile(dumpFile, apiConfigDump, 0o600)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("write api credentials in '%s' failed: %s", dumpFile, err)
|
|
|
|
|
}
|
|
|
|
|
fmt.Printf("API credentials written to '%s'.\n", dumpFile)
|
|
|
|
|
fmt.Fprintf(os.Stderr, "API credentials written to '%s'.\n", dumpFile)
|
|
|
|
|
} else {
|
|
|
|
|
fmt.Printf("%s\n", string(apiConfigDump))
|
|
|
|
|
fmt.Print(string(apiConfigDump))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cli cliMachines) NewDeleteCmd() *cobra.Command {
|
|
|
|
|
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: func(_ *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
|
|
|
machines, err := dbClient.ListMachines()
|
|
|
|
|
if err != nil {
|
|
|
|
|
cobra.CompError("unable to list machines " + err.Error())
|
|
|
|
|
}
|
|
|
|
|
ret := make([]string, 0)
|
|
|
|
|
for _, machine := range machines {
|
|
|
|
|
if strings.Contains(machine.MachineId, toComplete) && !slices.Contains(args, machine.MachineId) {
|
|
|
|
|
ret = append(ret, machine.MachineId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return ret, cobra.ShellCompDirectiveNoFileComp
|
|
|
|
|
},
|
|
|
|
|
RunE: cli.delete,
|
|
|
|
|
func (cli *cliMachines) deleteValid(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
|
|
|
machines, err := cli.db.ListMachines()
|
|
|
|
|
if err != nil {
|
|
|
|
|
cobra.CompError("unable to list machines " + err.Error())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cmd
|
|
|
|
|
ret := []string{}
|
|
|
|
|
|
|
|
|
|
for _, machine := range machines {
|
|
|
|
|
if strings.Contains(machine.MachineId, toComplete) && !slices.Contains(args, machine.MachineId) {
|
|
|
|
|
ret = append(ret, machine.MachineId)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return ret, cobra.ShellCompDirectiveNoFileComp
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cli cliMachines) delete(_ *cobra.Command, args []string) error {
|
|
|
|
|
for _, machineID := range args {
|
|
|
|
|
err := dbClient.DeleteWatcher(machineID)
|
|
|
|
|
func (cli *cliMachines) delete(machines []string) error {
|
|
|
|
|
for _, machineID := range machines {
|
|
|
|
|
err := cli.db.DeleteWatcher(machineID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
log.Errorf("unable to delete machine '%s': %s", machineID, err)
|
|
|
|
|
return nil
|
|
|
|
@ -384,8 +370,83 @@ func (cli cliMachines) delete(_ *cobra.Command, args []string) error {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cli cliMachines) NewPruneCmd() *cobra.Command {
|
|
|
|
|
var parsedDuration time.Duration
|
|
|
|
|
func (cli *cliMachines) newDeleteCmd() *cobra.Command {
|
|
|
|
|
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.deleteValid,
|
|
|
|
|
RunE: func(_ *cobra.Command, args []string) error {
|
|
|
|
|
return cli.delete(args)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return cmd
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cli *cliMachines) prune(duration time.Duration, notValidOnly bool, force bool) error {
|
|
|
|
|
if duration < 2*time.Minute && !notValidOnly {
|
|
|
|
|
if yes, err := askYesNo(
|
|
|
|
|
"The duration you provided is less than 2 minutes. " +
|
|
|
|
|
"This can break installations if the machines are only temporarily disconnected. Continue?", false); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
} else if !yes {
|
|
|
|
|
fmt.Println("User aborted prune. No changes were made.")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
machines := []*ent.Machine{}
|
|
|
|
|
if pending, err := cli.db.QueryPendingMachine(); err == nil {
|
|
|
|
|
machines = append(machines, pending...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !notValidOnly {
|
|
|
|
|
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")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getAgentsTable(color.Output, machines)
|
|
|
|
|
|
|
|
|
|
if !force {
|
|
|
|
|
if yes, err := askYesNo(
|
|
|
|
|
"You are about to PERMANENTLY remove the above machines from the database. " +
|
|
|
|
|
"These will NOT be recoverable. Continue?", false); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
} else if !yes {
|
|
|
|
|
fmt.Println("User aborted prune. No changes were made.")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deleted, err := cli.db.BulkDeleteWatchers(machines)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to prune machines: %s", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fmt.Fprintf(os.Stderr, "successfully delete %d machines\n", deleted)
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cli *cliMachines) newPruneCmd() *cobra.Command {
|
|
|
|
|
var (
|
|
|
|
|
duration time.Duration
|
|
|
|
|
notValidOnly bool
|
|
|
|
|
force bool
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const defaultDuration = 10 * time.Minute
|
|
|
|
|
|
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
|
Use: "prune",
|
|
|
|
|
Short: "prune multiple machines from the database",
|
|
|
|
@ -395,76 +456,29 @@ cscli machines prune --duration 1h
|
|
|
|
|
cscli machines prune --not-validated-only --force`,
|
|
|
|
|
Args: cobra.NoArgs,
|
|
|
|
|
DisableAutoGenTag: true,
|
|
|
|
|
PreRunE: func(cmd *cobra.Command, _ []string) error {
|
|
|
|
|
dur, _ := cmd.Flags().GetString("duration")
|
|
|
|
|
var err error
|
|
|
|
|
parsedDuration, err = time.ParseDuration(fmt.Sprintf("-%s", dur))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to parse duration '%s': %s", dur, err)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
},
|
|
|
|
|
RunE: func(cmd *cobra.Command, _ []string) error {
|
|
|
|
|
notValidOnly, _ := cmd.Flags().GetBool("not-validated-only")
|
|
|
|
|
force, _ := cmd.Flags().GetBool("force")
|
|
|
|
|
if parsedDuration >= 0-60*time.Second && !notValidOnly {
|
|
|
|
|
var answer bool
|
|
|
|
|
prompt := &survey.Confirm{
|
|
|
|
|
Message: "The duration you provided is less than or equal 60 seconds this can break installations do you want to continue ?",
|
|
|
|
|
Default: false,
|
|
|
|
|
}
|
|
|
|
|
if err := survey.AskOne(prompt, &answer); err != nil {
|
|
|
|
|
return fmt.Errorf("unable to ask about prune check: %s", err)
|
|
|
|
|
}
|
|
|
|
|
if !answer {
|
|
|
|
|
fmt.Println("user aborted prune no changes were made")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
machines := make([]*ent.Machine, 0)
|
|
|
|
|
if pending, err := dbClient.QueryPendingMachine(); err == nil {
|
|
|
|
|
machines = append(machines, pending...)
|
|
|
|
|
}
|
|
|
|
|
if !notValidOnly {
|
|
|
|
|
if pending, err := dbClient.QueryLastValidatedHeartbeatLT(time.Now().UTC().Add(parsedDuration)); err == nil {
|
|
|
|
|
machines = append(machines, pending...)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if len(machines) == 0 {
|
|
|
|
|
fmt.Println("no machines to prune")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
getAgentsTable(color.Output, machines)
|
|
|
|
|
if !force {
|
|
|
|
|
var answer bool
|
|
|
|
|
prompt := &survey.Confirm{
|
|
|
|
|
Message: "You are about to PERMANENTLY remove the above machines from the database these will NOT be recoverable, continue ?",
|
|
|
|
|
Default: false,
|
|
|
|
|
}
|
|
|
|
|
if err := survey.AskOne(prompt, &answer); err != nil {
|
|
|
|
|
return fmt.Errorf("unable to ask about prune check: %s", err)
|
|
|
|
|
}
|
|
|
|
|
if !answer {
|
|
|
|
|
fmt.Println("user aborted prune no changes were made")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
nbDeleted, err := dbClient.BulkDeleteWatchers(machines)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("unable to prune machines: %s", err)
|
|
|
|
|
}
|
|
|
|
|
fmt.Printf("successfully delete %d machines\n", nbDeleted)
|
|
|
|
|
return nil
|
|
|
|
|
RunE: func(_ *cobra.Command, _ []string) error {
|
|
|
|
|
return cli.prune(duration, notValidOnly, force)
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
cmd.Flags().StringP("duration", "d", "10m", "duration of time since validated machine last heartbeat")
|
|
|
|
|
cmd.Flags().Bool("not-validated-only", false, "only prune machines that are not validated")
|
|
|
|
|
cmd.Flags().Bool("force", false, "force prune without asking for confirmation")
|
|
|
|
|
|
|
|
|
|
flags := cmd.Flags()
|
|
|
|
|
flags.DurationVarP(&duration, "duration", "d", defaultDuration, "duration of time since validated machine last heartbeat")
|
|
|
|
|
flags.BoolVar(¬ValidOnly, "not-validated-only", false, "only prune machines that are not validated")
|
|
|
|
|
flags.BoolVar(&force, "force", false, "force prune without asking for confirmation")
|
|
|
|
|
|
|
|
|
|
return cmd
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cli cliMachines) NewValidateCmd() *cobra.Command {
|
|
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
log.Infof("machine '%s' validated successfully", machineID)
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cli *cliMachines) newValidateCmd() *cobra.Command {
|
|
|
|
|
cmd := &cobra.Command{
|
|
|
|
|
Use: "validate",
|
|
|
|
|
Short: "validate a machine to access the local API",
|
|
|
|
@ -472,14 +486,8 @@ func (cli cliMachines) NewValidateCmd() *cobra.Command {
|
|
|
|
|
Example: `cscli machines validate "machine_name"`,
|
|
|
|
|
Args: cobra.ExactArgs(1),
|
|
|
|
|
DisableAutoGenTag: true,
|
|
|
|
|
RunE: func(_ *cobra.Command, args []string) error {
|
|
|
|
|
machineID := args[0]
|
|
|
|
|
if err := dbClient.ValidateMachine(machineID); err != nil {
|
|
|
|
|
return fmt.Errorf("unable to validate machine '%s': %s", machineID, err)
|
|
|
|
|
}
|
|
|
|
|
log.Infof("machine '%s' validated successfully", machineID)
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
RunE: func(cmd *cobra.Command, args []string) error {
|
|
|
|
|
return cli.validate(args[0])
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|