diff --git a/cmd/etcdsnapshot/main.go b/cmd/etcdsnapshot/main.go index bcc5decb7f..2fd55364a2 100644 --- a/cmd/etcdsnapshot/main.go +++ b/cmd/etcdsnapshot/main.go @@ -13,7 +13,7 @@ import ( func main() { app := cmds.NewApp() app.Commands = []cli.Command{ - cmds.NewEtcdSnapshotCommand(etcdsnapshot.Run, cmds.NewEtcdSnapshotSubcommands(etcdsnapshot.Delete)), + cmds.NewEtcdSnapshotCommand(etcdsnapshot.Run, cmds.NewEtcdSnapshotSubcommands(etcdsnapshot.Delete, etcdsnapshot.List)), } if err := app.Run(configfilearg.MustParse(os.Args)); err != nil { diff --git a/cmd/k3s/main.go b/cmd/k3s/main.go index fe2dacec9f..2efc0920d9 100644 --- a/cmd/k3s/main.go +++ b/cmd/k3s/main.go @@ -44,7 +44,7 @@ func main() { cmds.NewCRICTL(externalCLIAction("crictl", dataDir)), cmds.NewCtrCommand(externalCLIAction("ctr", dataDir)), cmds.NewCheckConfigCommand(externalCLIAction("check-config", dataDir)), - cmds.NewEtcdSnapshotCommand(etcdsnapshotCommand, cmds.NewEtcdSnapshotSubcommands(etcdsnapshotCommand)), + cmds.NewEtcdSnapshotCommand(etcdsnapshotCommand, cmds.NewEtcdSnapshotSubcommands(etcdsnapshotCommand, etcdsnapshotCommand)), } if err := app.Run(os.Args); err != nil { diff --git a/cmd/server/main.go b/cmd/server/main.go index b9e247e154..6b24460beb 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -43,7 +43,7 @@ func main() { cmds.NewKubectlCommand(kubectl.Run), cmds.NewCRICTL(crictl.Run), cmds.NewCtrCommand(ctr.Run), - cmds.NewEtcdSnapshotCommand(etcdsnapshot.Run, cmds.NewEtcdSnapshotSubcommands(etcdsnapshot.Delete)), + cmds.NewEtcdSnapshotCommand(etcdsnapshot.Run, cmds.NewEtcdSnapshotSubcommands(etcdsnapshot.Delete, etcdsnapshot.List)), } err := app.Run(configfilearg.MustParse(os.Args)) diff --git a/main.go b/main.go index faccdeb4b6..5c02c9aad5 100644 --- a/main.go +++ b/main.go @@ -27,7 +27,7 @@ func main() { cmds.NewAgentCommand(agent.Run), cmds.NewKubectlCommand(kubectl.Run), cmds.NewCRICTL(crictl.Run), - cmds.NewEtcdSnapshotCommand(etcdsnapshot.Run, cmds.NewEtcdSnapshotSubcommands(etcdsnapshot.Delete)), + cmds.NewEtcdSnapshotCommand(etcdsnapshot.Run, cmds.NewEtcdSnapshotSubcommands(etcdsnapshot.Delete, etcdsnapshot.List)), } if err := app.Run(configfilearg.MustParse(os.Args)); err != nil { diff --git a/pkg/cli/cmds/etcd_snapshot.go b/pkg/cli/cmds/etcd_snapshot.go index 6b30ad2e53..220a34535c 100644 --- a/pkg/cli/cmds/etcd_snapshot.go +++ b/pkg/cli/cmds/etcd_snapshot.go @@ -95,7 +95,7 @@ func NewEtcdSnapshotCommand(action func(*cli.Context) error, subcommands []cli.C } } -func NewEtcdSnapshotSubcommands(delete func(ctx *cli.Context) error) []cli.Command { +func NewEtcdSnapshotSubcommands(delete, list func(ctx *cli.Context) error) []cli.Command { return []cli.Command{ { Name: "delete", @@ -105,5 +105,14 @@ func NewEtcdSnapshotSubcommands(delete func(ctx *cli.Context) error) []cli.Comma Action: delete, Flags: EtcdSnapshotFlags, }, + { + Name: "ls", + Aliases: []string{"list", "l"}, + Usage: "List etcd snapshots", + SkipFlagParsing: false, + SkipArgReorder: true, + Action: list, + Flags: EtcdSnapshotFlags, + }, } } diff --git a/pkg/cli/etcdsnapshot/etcd_snapshot.go b/pkg/cli/etcdsnapshot/etcd_snapshot.go index 051d80625c..850b433781 100644 --- a/pkg/cli/etcdsnapshot/etcd_snapshot.go +++ b/pkg/cli/etcdsnapshot/etcd_snapshot.go @@ -3,8 +3,11 @@ package etcdsnapshot import ( "context" "errors" + "fmt" "os" "path/filepath" + "text/tabwriter" + "time" "github.com/erikdubbelboer/gspt" "github.com/rancher/k3s/pkg/cli/cmds" @@ -18,7 +21,7 @@ import ( // commandSetup setups up common things needed // for each etcd command. -func commandSetup(app *cli.Context, cfg *cmds.Server) (string, error) { +func commandSetup(app *cli.Context, cfg *cmds.Server, sc *server.Config) (string, error) { gspt.SetProcTitle(os.Args[0]) nodeName := app.String("node-name") @@ -32,6 +35,21 @@ func commandSetup(app *cli.Context, cfg *cmds.Server) (string, error) { os.Setenv("NODE_NAME", nodeName) + sc.DisableAgent = true + sc.ControlConfig.DataDir = cfg.DataDir + sc.ControlConfig.EtcdSnapshotName = cfg.EtcdSnapshotName + sc.ControlConfig.EtcdSnapshotDir = cfg.EtcdSnapshotDir + sc.ControlConfig.EtcdS3 = cfg.EtcdS3 + sc.ControlConfig.EtcdS3Endpoint = cfg.EtcdS3Endpoint + sc.ControlConfig.EtcdS3EndpointCA = cfg.EtcdS3EndpointCA + sc.ControlConfig.EtcdS3SkipSSLVerify = cfg.EtcdS3SkipSSLVerify + sc.ControlConfig.EtcdS3AccessKey = cfg.EtcdS3AccessKey + sc.ControlConfig.EtcdS3SecretKey = cfg.EtcdS3SecretKey + sc.ControlConfig.EtcdS3BucketName = cfg.EtcdS3BucketName + sc.ControlConfig.EtcdS3Region = cfg.EtcdS3Region + sc.ControlConfig.EtcdS3Folder = cfg.EtcdS3Folder + sc.ControlConfig.Runtime = &config.ControlRuntime{} + return server.ResolveDataDir(cfg.DataDir) } @@ -43,7 +61,9 @@ func Run(app *cli.Context) error { } func run(app *cli.Context, cfg *cmds.Server) error { - dataDir, err := commandSetup(app, cfg) + var serverConfig server.Config + + dataDir, err := commandSetup(app, cfg, &serverConfig) if err != nil { return err } @@ -52,22 +72,8 @@ func run(app *cli.Context, cfg *cmds.Server) error { return cmds.ErrCommandNoArgs } - var serverConfig server.Config - serverConfig.DisableAgent = true serverConfig.ControlConfig.DataDir = dataDir - serverConfig.ControlConfig.EtcdSnapshotName = cfg.EtcdSnapshotName - serverConfig.ControlConfig.EtcdSnapshotDir = cfg.EtcdSnapshotDir serverConfig.ControlConfig.EtcdSnapshotRetention = 0 // disable retention check - serverConfig.ControlConfig.EtcdS3 = cfg.EtcdS3 - serverConfig.ControlConfig.EtcdS3Endpoint = cfg.EtcdS3Endpoint - serverConfig.ControlConfig.EtcdS3EndpointCA = cfg.EtcdS3EndpointCA - serverConfig.ControlConfig.EtcdS3SkipSSLVerify = cfg.EtcdS3SkipSSLVerify - serverConfig.ControlConfig.EtcdS3AccessKey = cfg.EtcdS3AccessKey - serverConfig.ControlConfig.EtcdS3SecretKey = cfg.EtcdS3SecretKey - serverConfig.ControlConfig.EtcdS3BucketName = cfg.EtcdS3BucketName - serverConfig.ControlConfig.EtcdS3Region = cfg.EtcdS3Region - serverConfig.ControlConfig.EtcdS3Folder = cfg.EtcdS3Folder - serverConfig.ControlConfig.Runtime = &config.ControlRuntime{} serverConfig.ControlConfig.Runtime.ETCDServerCA = filepath.Join(dataDir, "tls", "etcd", "server-ca.crt") serverConfig.ControlConfig.Runtime.ClientETCDCert = filepath.Join(dataDir, "tls", "etcd", "client.crt") serverConfig.ControlConfig.Runtime.ClientETCDKey = filepath.Join(dataDir, "tls", "etcd", "client.key") @@ -108,7 +114,9 @@ func Delete(app *cli.Context) error { } func delete(app *cli.Context, cfg *cmds.Server) error { - dataDir, err := commandSetup(app, cfg) + var serverConfig server.Config + + dataDir, err := commandSetup(app, cfg, &serverConfig) if err != nil { return err } @@ -118,21 +126,7 @@ func delete(app *cli.Context, cfg *cmds.Server) error { return errors.New("no snapshots given for removal") } - var serverConfig server.Config - serverConfig.DisableAgent = true serverConfig.ControlConfig.DataDir = dataDir - serverConfig.ControlConfig.EtcdSnapshotName = cfg.EtcdSnapshotName - serverConfig.ControlConfig.EtcdSnapshotDir = cfg.EtcdSnapshotDir - serverConfig.ControlConfig.EtcdS3 = cfg.EtcdS3 - serverConfig.ControlConfig.EtcdS3Endpoint = cfg.EtcdS3Endpoint - serverConfig.ControlConfig.EtcdS3EndpointCA = cfg.EtcdS3EndpointCA - serverConfig.ControlConfig.EtcdS3SkipSSLVerify = cfg.EtcdS3SkipSSLVerify - serverConfig.ControlConfig.EtcdS3AccessKey = cfg.EtcdS3AccessKey - serverConfig.ControlConfig.EtcdS3SecretKey = cfg.EtcdS3SecretKey - serverConfig.ControlConfig.EtcdS3BucketName = cfg.EtcdS3BucketName - serverConfig.ControlConfig.EtcdS3Region = cfg.EtcdS3Region - serverConfig.ControlConfig.EtcdS3Folder = cfg.EtcdS3Folder - serverConfig.ControlConfig.Runtime = &config.ControlRuntime{} serverConfig.ControlConfig.Runtime.KubeConfigAdmin = filepath.Join(dataDir, "cred", "admin.kubeconfig") ctx := signals.SetupSignalHandler(context.Background()) @@ -147,3 +141,43 @@ func delete(app *cli.Context, cfg *cmds.Server) error { return e.DeleteSnapshots(ctx, app.Args()) } + +func List(app *cli.Context) error { + if err := cmds.InitLogging(); err != nil { + return err + } + return list(app, &cmds.ServerConfig) +} + +func list(app *cli.Context, cfg *cmds.Server) error { + var serverConfig server.Config + + dataDir, err := commandSetup(app, cfg, &serverConfig) + if err != nil { + return err + } + + serverConfig.ControlConfig.DataDir = dataDir + + ctx := signals.SetupSignalHandler(context.Background()) + e := etcd.NewETCD() + e.SetControlConfig(&serverConfig.ControlConfig) + + sf, err := e.ListSnapshots(ctx) + if err != nil { + return err + } + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 1, ' ', 0) + defer w.Flush() + + for _, s := range sf { + if cfg.EtcdS3 { + fmt.Fprintf(w, "%s\t%d\t%s\n", s.Name, s.Size, s.CreatedAt.Format(time.RFC3339)) + } else { + fmt.Fprintf(w, "%s\t%s\t%d\t%s\n", s.Name, s.Location, s.Size, s.CreatedAt.Format(time.RFC3339)) + } + } + + return nil +} diff --git a/pkg/etcd/etcd.go b/pkg/etcd/etcd.go index 1def02b45c..13f462385a 100644 --- a/pkg/etcd/etcd.go +++ b/pkg/etcd/etcd.go @@ -871,9 +871,9 @@ type s3Config struct { Folder string `json:"folder,omitempty"` } -// snapshotFile represents a single snapshot and it's +// SnapshotFile represents a single snapshot and it's // metadata. -type snapshotFile struct { +type SnapshotFile struct { Name string `json:"name"` // Location contains the full path of the snapshot. For // local paths, the location will be prefixed with "file://". @@ -887,8 +887,8 @@ type snapshotFile struct { // listSnapshots provides a list of the currently stored // snapshots on disk or in S3 along with their relevant // metadata. -func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]snapshotFile, error) { - var snapshots []snapshotFile +func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]SnapshotFile, error) { + var snapshots []SnapshotFile if e.config.EtcdS3 { ctx, cancel := context.WithCancel(ctx) @@ -913,7 +913,7 @@ func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]snapsho return nil, err } - snapshots = append(snapshots, snapshotFile{ + snapshots = append(snapshots, SnapshotFile{ Name: filepath.Base(obj.Key), NodeName: "s3", CreatedAt: &metav1.Time{ @@ -942,7 +942,7 @@ func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]snapsho nodeName := os.Getenv("NODE_NAME") for _, f := range files { - snapshots = append(snapshots, snapshotFile{ + snapshots = append(snapshots, SnapshotFile{ Name: f.Name(), Location: "file://" + filepath.Join(snapshotDir, f.Name()), NodeName: nodeName, @@ -970,6 +970,17 @@ func (e *ETCD) initS3IfNil(ctx context.Context) error { return nil } +// ListSnapshots is an exported wrapper method that wraps an +// unexported method of the same name. +func (e *ETCD) ListSnapshots(ctx context.Context) ([]SnapshotFile, error) { + snapshotDir, err := snapshotDir(e.config) + if err != nil { + return nil, errors.Wrap(err, "failed to get the snapshot dir") + } + + return e.listSnapshots(ctx, snapshotDir) +} + // deleteSnapshots removes the given snapshots from // either local storage or S3. func (e *ETCD) DeleteSnapshots(ctx context.Context, snapshots []string) error { @@ -1051,7 +1062,7 @@ func (e *ETCD) DeleteSnapshots(ctx context.Context, snapshots []string) error { } // updateSnapshotData populates the given map with the contents of the given slice. -func updateSnapshotData(data map[string]string, snapshotFiles []snapshotFile) error { +func updateSnapshotData(data map[string]string, snapshotFiles []SnapshotFile) error { for _, v := range snapshotFiles { b, err := json.Marshal(v) if err != nil { @@ -1106,14 +1117,14 @@ func (e *ETCD) StoreSnapshotData(ctx context.Context) error { } if snapshotConfigMap.Data == nil { - snapshotConfigMap.Data = make(map[string]string, 0) + snapshotConfigMap.Data = make(map[string]string) } nodeName := os.Getenv("NODE_NAME") // remove entries for this node only for k, v := range snapshotConfigMap.Data { - var sf snapshotFile + var sf SnapshotFile if err := json.Unmarshal([]byte(v), &sf); err != nil { return err }