diff --git a/.changelog/10089.txt b/.changelog/10089.txt new file mode 100644 index 000000000..6db075baf --- /dev/null +++ b/.changelog/10089.txt @@ -0,0 +1,4 @@ +```release-note:improvement +cli: snapshot inspect command can now inspect raw snapshots from a server's data +dir. +``` diff --git a/command/snapshot/inspect/snapshot_inspect.go b/command/snapshot/inspect/snapshot_inspect.go index e0bbd648c..a3b876426 100644 --- a/command/snapshot/inspect/snapshot_inspect.go +++ b/command/snapshot/inspect/snapshot_inspect.go @@ -1,10 +1,13 @@ package inspect import ( + "encoding/json" "flag" "fmt" "io" + "io/ioutil" "os" + "path" "sort" "strings" @@ -111,18 +114,41 @@ func (c *cmd) Run(args []string) int { } defer f.Close() - readFile, meta, err := snapshot.Read(hclog.New(nil), f) - if err != nil { - c.UI.Error(fmt.Sprintf("Error reading snapshot: %s", err)) + var readFile *os.File + var meta *raft.SnapshotMeta + + if strings.ToLower(path.Base(file)) == "state.bin" { + // This is an internal raw raft snapshot not a gzipped archive one + // downloaded from the API, we can read it directly + readFile = f + + // Assume the meta is colocated and error if not. + metaRaw, err := ioutil.ReadFile(path.Join(path.Dir(file), "meta.json")) + if err != nil { + c.UI.Error(fmt.Sprintf("Error reading meta.json from internal snapshot dir: %s", err)) + return 1 + } + var metaDecoded raft.SnapshotMeta + err = json.Unmarshal(metaRaw, &metaDecoded) + if err != nil { + c.UI.Error(fmt.Sprintf("Error parsing meta.json from internal snapshot dir: %s", err)) + return 1 + } + meta = &metaDecoded + } else { + readFile, meta, err = snapshot.Read(hclog.New(nil), f) + if err != nil { + c.UI.Error(fmt.Sprintf("Error reading snapshot: %s", err)) + } + defer func() { + if err := readFile.Close(); err != nil { + c.UI.Error(fmt.Sprintf("Failed to close temp snapshot: %v", err)) + } + if err := os.Remove(readFile.Name()); err != nil { + c.UI.Error(fmt.Sprintf("Failed to clean up temp snapshot: %v", err)) + } + }() } - defer func() { - if err := readFile.Close(); err != nil { - c.UI.Error(fmt.Sprintf("Failed to close temp snapshot: %v", err)) - } - if err := os.Remove(readFile.Name()); err != nil { - c.UI.Error(fmt.Sprintf("Failed to clean up temp snapshot: %v", err)) - } - }() info, err := c.enhance(readFile) if err != nil { diff --git a/command/snapshot/inspect/snapshot_inspect_test.go b/command/snapshot/inspect/snapshot_inspect_test.go index fbfc60feb..a9a7992dd 100644 --- a/command/snapshot/inspect/snapshot_inspect_test.go +++ b/command/snapshot/inspect/snapshot_inspect_test.go @@ -149,3 +149,23 @@ func TestSnapshotInspectKVDetailsDepthFilterCommand(t *testing.T) { want := golden(t, t.Name(), ui.OutputWriter.String()) require.Equal(t, want, ui.OutputWriter.String()) } + +// TestSnapshotInspectCommandRaw test reading a snaphost directly from a raft +// data dir. +func TestSnapshotInspectCommandRaw(t *testing.T) { + + filepath := "./testdata/raw/state.bin" + + // Inspect the snapshot + ui := cli.NewMockUi() + c := New(ui) + args := []string{filepath} + + code := c.Run(args) + if code != 0 { + t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String()) + } + + want := golden(t, t.Name(), ui.OutputWriter.String()) + require.Equal(t, want, ui.OutputWriter.String()) +} diff --git a/command/snapshot/inspect/testdata/TestSnapshotInspectCommandRaw.golden b/command/snapshot/inspect/testdata/TestSnapshotInspectCommandRaw.golden new file mode 100644 index 000000000..fc5af33af --- /dev/null +++ b/command/snapshot/inspect/testdata/TestSnapshotInspectCommandRaw.golden @@ -0,0 +1,19 @@ + ID 2-13-1602222343947 + Size 5141 + Index 13 + Term 2 + Version 1 + + Type Count Size + ---- ---- ---- + Register 3 1.7KB + ConnectCA 1 1.2KB + ConnectCAProviderState 1 1.1KB + Index 12 344B + Autopilot 1 199B + ConnectCAConfig 1 197B + FederationState 1 139B + SystemMetadata 1 68B + ChunkingState 1 12B + ---- ---- ---- + Total 5KB diff --git a/command/snapshot/inspect/testdata/raw/meta.json b/command/snapshot/inspect/testdata/raw/meta.json new file mode 100644 index 000000000..d88ce9ead --- /dev/null +++ b/command/snapshot/inspect/testdata/raw/meta.json @@ -0,0 +1 @@ +{"Version":1,"ID":"2-13-1602222343947","Index":13,"Term":2,"Peers":"ka4xMjcuMC4wLjE6ODMwMA==","Configuration":{"Servers":[{"Suffrage":0,"ID":"a577b288-b354-770e-e909-da0972eb20e8","Address":"127.0.0.1:8300"}]},"ConfigurationIndex":1,"Size":5141} diff --git a/command/snapshot/inspect/testdata/raw/state.bin b/command/snapshot/inspect/testdata/raw/state.bin new file mode 100644 index 000000000..7bffa887d --- /dev/null +++ b/command/snapshot/inspect/testdata/raw/state.bin @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3bbf950a3915300fc06648bd7e60d9ac767e94c6431a5b795d2bffc07a342c9a +size 5141 diff --git a/website/content/commands/snapshot/inspect.mdx b/website/content/commands/snapshot/inspect.mdx index adef21172..9db0bac0c 100644 --- a/website/content/commands/snapshot/inspect.mdx +++ b/website/content/commands/snapshot/inspect.mdx @@ -12,6 +12,15 @@ snapshot of the state of the Consul servers which includes key/value entries, service catalog, prepared queries, sessions, and ACLs. The snapshot is read from the given file. +-> Typically this is used with Consul self-contained Snapshot files obtained +using the [`consul snapshot`](/commands/snapshot) command or [Snapshot +API](/api-docs/snapshot#generate-snapshot). If the file provided is named +`state.bin` however, the command will assume it is a raw raft snapshot in a +Consul server data directory and will attempt to read it directly. The +`state.bin` file must still be in the same directory as it's associated +`meta.json` file. This is useful for debugging data on live servers without +making a complete new snapshot via the CLI or API first. + The following fields are displayed when inspecting a snapshot: - `ID` - A unique ID for the snapshot, only used for differentiation purposes. @@ -106,6 +115,29 @@ $ consul snapshot inspect -kvdetails -kvdepth 3 -kvfilter vault/core backup.snap Please see the [HTTP API](/api/snapshot) documentation for more details about snapshot internals. +To inspect an internal snapshot directly from a Consul server data directory: + +```shell-session +$ consul snapshot inspect /opt/consul/raft/snapshots/9-4600669-1618935304715/state.bin + ID 9-4600669-1618935304715 + Size 4625420898 + Index 4600669 + Term 9 + Version 1 + + Type Count Size + ---- ---- ---- + KVS 4089785 4.3GB + Register 9 5.2KB + CoordinateBatchUpdate 3 465B + Index 8 224B + Autopilot 1 199B + FederationState 1 139B + ChunkingState 1 12B + ---- ---- ---- + Total 4.3GB +``` + #### Command Options - `-kvdetails` - Optional, provides a space usage breakdown for any KV data stored in Consul.