Unverified Commit bcd8b67d authored by Brian Downs's avatar Brian Downs Committed by GitHub

Add the ability to list etcd snapshots (#3303)

* add ability to list local and s3 etcd snapshots
parent 02a5bee6
...@@ -13,7 +13,7 @@ import ( ...@@ -13,7 +13,7 @@ import (
func main() { func main() {
app := cmds.NewApp() app := cmds.NewApp()
app.Commands = []cli.Command{ 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 { if err := app.Run(configfilearg.MustParse(os.Args)); err != nil {
......
...@@ -44,7 +44,7 @@ func main() { ...@@ -44,7 +44,7 @@ func main() {
cmds.NewCRICTL(externalCLIAction("crictl", dataDir)), cmds.NewCRICTL(externalCLIAction("crictl", dataDir)),
cmds.NewCtrCommand(externalCLIAction("ctr", dataDir)), cmds.NewCtrCommand(externalCLIAction("ctr", dataDir)),
cmds.NewCheckConfigCommand(externalCLIAction("check-config", 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 { if err := app.Run(os.Args); err != nil {
......
...@@ -43,7 +43,7 @@ func main() { ...@@ -43,7 +43,7 @@ func main() {
cmds.NewKubectlCommand(kubectl.Run), cmds.NewKubectlCommand(kubectl.Run),
cmds.NewCRICTL(crictl.Run), cmds.NewCRICTL(crictl.Run),
cmds.NewCtrCommand(ctr.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)) err := app.Run(configfilearg.MustParse(os.Args))
......
...@@ -27,7 +27,7 @@ func main() { ...@@ -27,7 +27,7 @@ func main() {
cmds.NewAgentCommand(agent.Run), cmds.NewAgentCommand(agent.Run),
cmds.NewKubectlCommand(kubectl.Run), cmds.NewKubectlCommand(kubectl.Run),
cmds.NewCRICTL(crictl.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 { if err := app.Run(configfilearg.MustParse(os.Args)); err != nil {
......
...@@ -95,7 +95,7 @@ func NewEtcdSnapshotCommand(action func(*cli.Context) error, subcommands []cli.C ...@@ -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{ return []cli.Command{
{ {
Name: "delete", Name: "delete",
...@@ -105,5 +105,14 @@ func NewEtcdSnapshotSubcommands(delete func(ctx *cli.Context) error) []cli.Comma ...@@ -105,5 +105,14 @@ func NewEtcdSnapshotSubcommands(delete func(ctx *cli.Context) error) []cli.Comma
Action: delete, Action: delete,
Flags: EtcdSnapshotFlags, Flags: EtcdSnapshotFlags,
}, },
{
Name: "ls",
Aliases: []string{"list", "l"},
Usage: "List etcd snapshots",
SkipFlagParsing: false,
SkipArgReorder: true,
Action: list,
Flags: EtcdSnapshotFlags,
},
} }
} }
...@@ -3,8 +3,11 @@ package etcdsnapshot ...@@ -3,8 +3,11 @@ package etcdsnapshot
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"text/tabwriter"
"time"
"github.com/erikdubbelboer/gspt" "github.com/erikdubbelboer/gspt"
"github.com/rancher/k3s/pkg/cli/cmds" "github.com/rancher/k3s/pkg/cli/cmds"
...@@ -18,7 +21,7 @@ import ( ...@@ -18,7 +21,7 @@ import (
// commandSetup setups up common things needed // commandSetup setups up common things needed
// for each etcd command. // 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]) gspt.SetProcTitle(os.Args[0])
nodeName := app.String("node-name") nodeName := app.String("node-name")
...@@ -32,6 +35,21 @@ func commandSetup(app *cli.Context, cfg *cmds.Server) (string, error) { ...@@ -32,6 +35,21 @@ func commandSetup(app *cli.Context, cfg *cmds.Server) (string, error) {
os.Setenv("NODE_NAME", nodeName) 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) return server.ResolveDataDir(cfg.DataDir)
} }
...@@ -43,7 +61,9 @@ func Run(app *cli.Context) error { ...@@ -43,7 +61,9 @@ func Run(app *cli.Context) error {
} }
func run(app *cli.Context, cfg *cmds.Server) 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 { if err != nil {
return err return err
} }
...@@ -52,22 +72,8 @@ func run(app *cli.Context, cfg *cmds.Server) error { ...@@ -52,22 +72,8 @@ func run(app *cli.Context, cfg *cmds.Server) error {
return cmds.ErrCommandNoArgs return cmds.ErrCommandNoArgs
} }
var serverConfig server.Config
serverConfig.DisableAgent = true
serverConfig.ControlConfig.DataDir = dataDir serverConfig.ControlConfig.DataDir = dataDir
serverConfig.ControlConfig.EtcdSnapshotName = cfg.EtcdSnapshotName
serverConfig.ControlConfig.EtcdSnapshotDir = cfg.EtcdSnapshotDir
serverConfig.ControlConfig.EtcdSnapshotRetention = 0 // disable retention check 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.ETCDServerCA = filepath.Join(dataDir, "tls", "etcd", "server-ca.crt")
serverConfig.ControlConfig.Runtime.ClientETCDCert = filepath.Join(dataDir, "tls", "etcd", "client.crt") serverConfig.ControlConfig.Runtime.ClientETCDCert = filepath.Join(dataDir, "tls", "etcd", "client.crt")
serverConfig.ControlConfig.Runtime.ClientETCDKey = filepath.Join(dataDir, "tls", "etcd", "client.key") serverConfig.ControlConfig.Runtime.ClientETCDKey = filepath.Join(dataDir, "tls", "etcd", "client.key")
...@@ -108,7 +114,9 @@ func Delete(app *cli.Context) error { ...@@ -108,7 +114,9 @@ func Delete(app *cli.Context) error {
} }
func delete(app *cli.Context, cfg *cmds.Server) 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 { if err != nil {
return err return err
} }
...@@ -118,21 +126,7 @@ func delete(app *cli.Context, cfg *cmds.Server) error { ...@@ -118,21 +126,7 @@ func delete(app *cli.Context, cfg *cmds.Server) error {
return errors.New("no snapshots given for removal") return errors.New("no snapshots given for removal")
} }
var serverConfig server.Config
serverConfig.DisableAgent = true
serverConfig.ControlConfig.DataDir = dataDir 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") serverConfig.ControlConfig.Runtime.KubeConfigAdmin = filepath.Join(dataDir, "cred", "admin.kubeconfig")
ctx := signals.SetupSignalHandler(context.Background()) ctx := signals.SetupSignalHandler(context.Background())
...@@ -147,3 +141,43 @@ func delete(app *cli.Context, cfg *cmds.Server) error { ...@@ -147,3 +141,43 @@ func delete(app *cli.Context, cfg *cmds.Server) error {
return e.DeleteSnapshots(ctx, app.Args()) 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
}
...@@ -871,9 +871,9 @@ type s3Config struct { ...@@ -871,9 +871,9 @@ type s3Config struct {
Folder string `json:"folder,omitempty"` Folder string `json:"folder,omitempty"`
} }
// snapshotFile represents a single snapshot and it's // SnapshotFile represents a single snapshot and it's
// metadata. // metadata.
type snapshotFile struct { type SnapshotFile struct {
Name string `json:"name"` Name string `json:"name"`
// Location contains the full path of the snapshot. For // Location contains the full path of the snapshot. For
// local paths, the location will be prefixed with "file://". // local paths, the location will be prefixed with "file://".
...@@ -887,8 +887,8 @@ type snapshotFile struct { ...@@ -887,8 +887,8 @@ type snapshotFile struct {
// listSnapshots provides a list of the currently stored // listSnapshots provides a list of the currently stored
// snapshots on disk or in S3 along with their relevant // snapshots on disk or in S3 along with their relevant
// metadata. // metadata.
func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]snapshotFile, error) { func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]SnapshotFile, error) {
var snapshots []snapshotFile var snapshots []SnapshotFile
if e.config.EtcdS3 { if e.config.EtcdS3 {
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
...@@ -913,7 +913,7 @@ func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]snapsho ...@@ -913,7 +913,7 @@ func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]snapsho
return nil, err return nil, err
} }
snapshots = append(snapshots, snapshotFile{ snapshots = append(snapshots, SnapshotFile{
Name: filepath.Base(obj.Key), Name: filepath.Base(obj.Key),
NodeName: "s3", NodeName: "s3",
CreatedAt: &metav1.Time{ CreatedAt: &metav1.Time{
...@@ -942,7 +942,7 @@ func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]snapsho ...@@ -942,7 +942,7 @@ func (e *ETCD) listSnapshots(ctx context.Context, snapshotDir string) ([]snapsho
nodeName := os.Getenv("NODE_NAME") nodeName := os.Getenv("NODE_NAME")
for _, f := range files { for _, f := range files {
snapshots = append(snapshots, snapshotFile{ snapshots = append(snapshots, SnapshotFile{
Name: f.Name(), Name: f.Name(),
Location: "file://" + filepath.Join(snapshotDir, f.Name()), Location: "file://" + filepath.Join(snapshotDir, f.Name()),
NodeName: nodeName, NodeName: nodeName,
...@@ -970,6 +970,17 @@ func (e *ETCD) initS3IfNil(ctx context.Context) error { ...@@ -970,6 +970,17 @@ func (e *ETCD) initS3IfNil(ctx context.Context) error {
return nil 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 // deleteSnapshots removes the given snapshots from
// either local storage or S3. // either local storage or S3.
func (e *ETCD) DeleteSnapshots(ctx context.Context, snapshots []string) error { func (e *ETCD) DeleteSnapshots(ctx context.Context, snapshots []string) error {
...@@ -1051,7 +1062,7 @@ 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. // 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 { for _, v := range snapshotFiles {
b, err := json.Marshal(v) b, err := json.Marshal(v)
if err != nil { if err != nil {
...@@ -1106,14 +1117,14 @@ func (e *ETCD) StoreSnapshotData(ctx context.Context) error { ...@@ -1106,14 +1117,14 @@ func (e *ETCD) StoreSnapshotData(ctx context.Context) error {
} }
if snapshotConfigMap.Data == nil { if snapshotConfigMap.Data == nil {
snapshotConfigMap.Data = make(map[string]string, 0) snapshotConfigMap.Data = make(map[string]string)
} }
nodeName := os.Getenv("NODE_NAME") nodeName := os.Getenv("NODE_NAME")
// remove entries for this node only // remove entries for this node only
for k, v := range snapshotConfigMap.Data { for k, v := range snapshotConfigMap.Data {
var sf snapshotFile var sf SnapshotFile
if err := json.Unmarshal([]byte(v), &sf); err != nil { if err := json.Unmarshal([]byte(v), &sf); err != nil {
return err return err
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment