Skip to content

Commit

Permalink
NET 1594 - Snapshot Agent Filename Should Include Consul Version / Da…
Browse files Browse the repository at this point in the history
…tacenter (#18625)

* init

* tests added and few fixes

* revert arg message

* changelog added

* removed var declaration

* fix CI

* fix test

* added node name and status

* updated save.mdx

* added example

* fix tense

* fix description
  • Loading branch information
absolutelightning committed Sep 4, 2023
1 parent b1febc6 commit 8ec7130
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 4 deletions.
5 changes: 5 additions & 0 deletions .changelog/18625.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
```release-note:improvement
Adds flag -append-filename (which works on values version, dc, node and status) to consul snapshot save command.
Adding the flag -append-filename version,dc,node,status will add consul version, consul datacenter, node name and leader/follower
(status) in the file name given in the snapshot save command before the file extension.
```
76 changes: 72 additions & 4 deletions command/snapshot/save/snapshot_save.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ package save
import (
"flag"
"fmt"
"golang.org/x/exp/slices"
"os"
"path/filepath"
"strings"

"github.com/mitchellh/cli"
"github.com/rboyer/safeio"
Expand All @@ -20,17 +23,26 @@ func New(ui cli.Ui) *cmd {
}

type cmd struct {
UI cli.Ui
flags *flag.FlagSet
http *flags.HTTPFlags
help string
UI cli.Ui
flags *flag.FlagSet
http *flags.HTTPFlags
help string
appendFileNameFlag flags.StringValue
}

func (c *cmd) getAppendFileNameFlag() *flag.FlagSet {
fs := flag.NewFlagSet("", flag.ContinueOnError)
fs.Var(&c.appendFileNameFlag, "append-filename", "Append filename flag supports the following "+
"comma-separated arguments. 1. version, 2. dc. 3. node 4. status. It appends these values to the filename provided in the command")
return fs
}

func (c *cmd) init() {
c.flags = flag.NewFlagSet("", flag.ContinueOnError)
c.http = &flags.HTTPFlags{}
flags.Merge(c.flags, c.http.ClientFlags())
flags.Merge(c.flags, c.http.ServerFlags())
flags.Merge(c.flags, c.getAppendFileNameFlag())
c.help = flags.Usage(help, c.flags)
}

Expand All @@ -55,6 +67,62 @@ func (c *cmd) Run(args []string) int {

// Create and test the HTTP client
client, err := c.http.APIClient()

appendFileNameFlags := strings.Split(c.appendFileNameFlag.String(), ",")

if len(appendFileNameFlags) != 0 && len(c.appendFileNameFlag.String()) > 0 {
agentSelfResponse, err := client.Agent().Self()
if err != nil {
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent and fetching datacenter/version: %s", err))
return 1
}

fileExt := filepath.Ext(file)
fileNameWithoutExt := strings.TrimSuffix(file, fileExt)

if slices.Contains(appendFileNameFlags, "version") {
if config, ok := agentSelfResponse["Config"]; ok {
if version, ok := config["Version"]; ok {
fileNameWithoutExt = fileNameWithoutExt + "-" + version.(string)
}
}
}

if slices.Contains(appendFileNameFlags, "dc") {
if config, ok := agentSelfResponse["Config"]; ok {
if datacenter, ok := config["Datacenter"]; ok {
fileNameWithoutExt = fileNameWithoutExt + "-" + datacenter.(string)
}
}
}

if slices.Contains(appendFileNameFlags, "node") {
if config, ok := agentSelfResponse["Config"]; ok {
if nodeName, ok := config["NodeName"]; ok {
fileNameWithoutExt = fileNameWithoutExt + "-" + nodeName.(string)
}
}
}

if slices.Contains(appendFileNameFlags, "status") {
if status, ok := agentSelfResponse["Stats"]; ok {
if config, ok := status["consul"]; ok {
configMap := config.(map[string]interface{})
if leader, ok := configMap["leader"]; ok {
if leader == "true" {
fileNameWithoutExt = fileNameWithoutExt + "-" + "leader"
} else {
fileNameWithoutExt = fileNameWithoutExt + "-" + "follower"
}
}
}
}
}

//adding extension back
file = fileNameWithoutExt + fileExt
}

if err != nil {
c.UI.Error(fmt.Sprintf("Error connecting to Consul agent: %s", err))
return 1
Expand Down
52 changes: 52 additions & 0 deletions command/snapshot/save/snapshot_save_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,58 @@ func TestSnapshotSaveCommand_Validation(t *testing.T) {
}
}

func TestSnapshotSaveCommandWithAppendFileNameFlag(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
}

t.Parallel()
a := agent.NewTestAgent(t, ``)
defer a.Shutdown()
client := a.Client()

ui := cli.NewMockUi()
c := New(ui)

dir := testutil.TempDir(t, "snapshot")
file := filepath.Join(dir, "backup.tgz")
args := []string{
"-append-filename=version,dc,node,status",
"-http-addr=" + a.HTTPAddr(),
file,
}

stats := a.Stats()

status := "follower"

if stats["consul"]["leader"] == "true" {
status = "leader"
}

newFilePath := filepath.Join(dir, "backup"+"-"+a.Config.Version+"-"+a.Config.Datacenter+
"-"+a.Config.NodeName+"-"+status+".tgz")

code := c.Run(args)
if code != 0 {
t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
}

fi, err := os.Stat(newFilePath)
require.NoError(t, err)
require.Equal(t, fi.Mode(), os.FileMode(0600))

f, err := os.Open(newFilePath)
if err != nil {
t.Fatalf("err: %v", err)
}
defer f.Close()

if err := client.Snapshot().Restore(nil, f); err != nil {
t.Fatalf("err: %v", err)
}
}

func TestSnapshotSaveCommand(t *testing.T) {
if testing.Short() {
t.Skip("too slow for testing.Short")
Expand Down
15 changes: 15 additions & 0 deletions website/content/commands/snapshot/save.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ Usage: `consul snapshot save [options] FILE`

@include 'http_api_options_server.mdx'

- `-append-filename=<value>` - Value can be - version,dc,node,status
Adds consul version, datacenter name, node name, and status (leader/follower)
to the file name before the extension separated by `-`

## Examples

To create a snapshot from the leader server and save it to "backup.snap":
Expand All @@ -71,6 +75,17 @@ $ consul snapshot save -stale backup.snap
# ...
```

To create snapshot file with consul version, datacenter, node name and leader/follower info,
run

```shell-session
$ consul snapshot save -append-filename node,status,version,dc backup.snap
#...
```

File name created will be like backup-%CONSUL_VERSION%-%DC_NAME%-%NODE_NAME%-%STATUS.snap
example - backup-1.17.0-dc1-local-machine-leader.tgz

This is useful for situations where a cluster is in a degraded state and no
leader is available. To target a specific server for a snapshot, you can run
the `consul snapshot save` command on that specific server.
Expand Down

0 comments on commit 8ec7130

Please sign in to comment.