Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -6,7 +6,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 @@ -23,17 +26,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 @@ -58,6 +70,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 @@ -72,6 +72,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 @@ -49,6 +49,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 @@ -73,6 +77,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