From 4b0fb5af3607ae9af96d3d4d1a125daba34bdcd7 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 22 Feb 2019 21:25:41 -0700 Subject: [PATCH 1/4] Add `--json` support to `forget` command Fixes #2184 --- cmd/restic/cmd_forget.go | 60 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 54 insertions(+), 6 deletions(-) diff --git a/cmd/restic/cmd_forget.go b/cmd/restic/cmd_forget.go index bafd540bd..397b41915 100644 --- a/cmd/restic/cmd_forget.go +++ b/cmd/restic/cmd_forget.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "io" "sort" "strings" @@ -182,7 +183,11 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error { } if !policy.Empty() { - Verbosef("Applying Policy: %v\n", policy) + if !gopts.JSON { + Verbosef("Applying Policy: %v\n", policy) + } + + var jsonGroups []*ForgetGroup for k, snapshotGroup := range snapshotGroups { var key key @@ -190,36 +195,48 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error { return err } + var fg ForgetGroup // Info - Verbosef("snapshots") + if !gopts.JSON { + Verbosef("snapshots") + } var infoStrings []string if GroupByTag { infoStrings = append(infoStrings, "tags ["+strings.Join(key.Tags, ", ")+"]") + fg.Tags = key.Tags } if GroupByHost { infoStrings = append(infoStrings, "host ["+key.Hostname+"]") + fg.Host = key.Hostname } if GroupByPath { infoStrings = append(infoStrings, "paths ["+strings.Join(key.Paths, ", ")+"]") + fg.Paths = key.Paths } - if infoStrings != nil { + if infoStrings != nil && !gopts.JSON { Verbosef(" for (" + strings.Join(infoStrings, ", ") + ")") } - Verbosef(":\n\n") + if !gopts.JSON { + Verbosef(":\n\n") + } keep, remove, reasons := restic.ApplyPolicy(snapshotGroup, policy) - if len(keep) != 0 && !gopts.Quiet { + if len(keep) != 0 && !gopts.Quiet && !gopts.JSON { Printf("keep %d snapshots:\n", len(keep)) PrintSnapshots(globalOptions.stdout, keep, reasons, opts.Compact) Printf("\n") } + addJSONSnapshots(&fg.Keep, keep) - if len(remove) != 0 && !gopts.Quiet { + if len(remove) != 0 && !gopts.Quiet && !gopts.JSON { Printf("remove %d snapshots:\n", len(remove)) PrintSnapshots(globalOptions.stdout, remove, nil, opts.Compact) Printf("\n") } + addJSONSnapshots(&fg.Remove, remove) + + jsonGroups = append(jsonGroups, &fg) removeSnapshots += len(remove) @@ -233,6 +250,13 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error { } } } + + if gopts.JSON { + err = printJSONForget(gopts.stdout, jsonGroups) + if err != nil { + return err + } + } } if removeSnapshots > 0 && opts.Prune { @@ -244,3 +268,27 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error { return nil } + +// ForgetGroup helps to print what is forgotten in JSON. +type ForgetGroup struct { + Tags []string `json:"tags"` + Host string `json:"host"` + Paths []string `json:"paths"` + Keep []Snapshot `json:"keep"` + Remove []Snapshot `json:"remove"` +} + +func addJSONSnapshots(js *[]Snapshot, list restic.Snapshots) { + for _, sn := range list { + k := Snapshot{ + Snapshot: sn, + ID: sn.ID(), + ShortID: sn.ID().Str(), + } + *js = append(*js, k) + } +} + +func printJSONForget(stdout io.Writer, forgets []*ForgetGroup) error { + return json.NewEncoder(stdout).Encode(forgets) +} From d9e22c2df171d7e1e58039245d697ec6a2086640 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 22 Feb 2019 21:52:30 -0700 Subject: [PATCH 2/4] Add test for `--json` support for `forget` command This adds a test of the json output of the forget command, by running it once, asking it to keep one snapshot, and verifying that the output has the right number of snapshots listed in the Keep and Remove fields of the result. --- cmd/restic/integration_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/cmd/restic/integration_test.go b/cmd/restic/integration_test.go index e47000d34..612685f53 100644 --- a/cmd/restic/integration_test.go +++ b/cmd/restic/integration_test.go @@ -219,6 +219,35 @@ func testRunForget(t testing.TB, gopts GlobalOptions, args ...string) { rtest.OK(t, runForget(opts, gopts, args)) } +func testRunForgetJSON(t testing.TB, gopts GlobalOptions, args ...string) { + buf := bytes.NewBuffer(nil) + oldJSON := gopts.JSON + gopts.stdout = buf + gopts.JSON = true + defer func() { + gopts.stdout = os.Stdout + gopts.JSON = oldJSON + }() + + opts := ForgetOptions{ + DryRun: true, + Last: 1, + } + + rtest.OK(t, runForget(opts, gopts, args)) + + var forgets []*ForgetGroup + rtest.OK(t, json.Unmarshal(buf.Bytes(), &forgets)) + + rtest.Assert(t, len(forgets) == 1, + "Expected 1 snapshot group, got %v", len(forgets)) + rtest.Assert(t, len(forgets[0].Keep) == 1, + "Expected 1 snapshot to be kept, got %v", len(forgets[0].Keep)) + rtest.Assert(t, len(forgets[0].Remove) == 2, + "Expected 2 snapshots to be removed, got %v", len(forgets[0].Remove)) + return +} + func testRunPrune(t testing.TB, gopts GlobalOptions) { rtest.OK(t, runPrune(gopts)) } @@ -1051,6 +1080,7 @@ func TestPrune(t *testing.T) { rtest.Assert(t, len(snapshotIDs) == 3, "expected 3 snapshot, got %v", snapshotIDs) + testRunForgetJSON(t, env.gopts) testRunForget(t, env.gopts, firstSnapshot[0].String()) testRunPrune(t, env.gopts) testRunCheck(t, env.gopts) From 449c049ce90fbba3df7ec34f9813002a2f66b981 Mon Sep 17 00:00:00 2001 From: David Brown Date: Fri, 22 Feb 2019 22:05:21 -0700 Subject: [PATCH 3/4] Add changelog for #2184 --- changelog/unreleased/issue-2184 | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 changelog/unreleased/issue-2184 diff --git a/changelog/unreleased/issue-2184 b/changelog/unreleased/issue-2184 new file mode 100644 index 000000000..fe3ff83cf --- /dev/null +++ b/changelog/unreleased/issue-2184 @@ -0,0 +1,8 @@ +Enhancement: Add --json support to forget command + +The forget command now supports the --json argument, outputting the +information about what is (or would-be) kept and removed from the +repository. + +https://github.com/restic/restic/issues/2184 +https://github.com/restic/restic/pull/2185 From d19a29f79e2ce5f72086c8a9baa65a964385998c Mon Sep 17 00:00:00 2001 From: David Brown Date: Sat, 23 Feb 2019 09:38:33 -0700 Subject: [PATCH 4/4] Include reasons in json output of forget This dumps the reasons as well as the list of keeps and removes with the output from the forget command. --- cmd/restic/cmd_forget.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/cmd/restic/cmd_forget.go b/cmd/restic/cmd_forget.go index 397b41915..a9b7246be 100644 --- a/cmd/restic/cmd_forget.go +++ b/cmd/restic/cmd_forget.go @@ -236,6 +236,8 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error { } addJSONSnapshots(&fg.Remove, remove) + fg.Reasons = reasons + jsonGroups = append(jsonGroups, &fg) removeSnapshots += len(remove) @@ -271,11 +273,12 @@ func runForget(opts ForgetOptions, gopts GlobalOptions, args []string) error { // ForgetGroup helps to print what is forgotten in JSON. type ForgetGroup struct { - Tags []string `json:"tags"` - Host string `json:"host"` - Paths []string `json:"paths"` - Keep []Snapshot `json:"keep"` - Remove []Snapshot `json:"remove"` + Tags []string `json:"tags"` + Host string `json:"host"` + Paths []string `json:"paths"` + Keep []Snapshot `json:"keep"` + Remove []Snapshot `json:"remove"` + Reasons []restic.KeepReason `json:"reasons"` } func addJSONSnapshots(js *[]Snapshot, list restic.Snapshots) {