Skip to content

Commit 870ec13

Browse files
committed
Allow removing multiple secrets at one
RELEASE_NOTES=[ENHANCEMENT] Allow deleting multiple secrets Signed-off-by: Dominik Schulz <[email protected]>
1 parent b0e28bf commit 870ec13

File tree

3 files changed

+64
-22
lines changed

3 files changed

+64
-22
lines changed

internal/action/delete.go

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -21,48 +21,75 @@ func (s *Action) Delete(c *cli.Context) error {
2121
return exit.Error(exit.Usage, nil, "Usage: %s rm name", s.Name)
2222
}
2323

24-
if !recursive && s.Store.IsDir(ctx, name) && !s.Store.Exists(ctx, name) {
24+
if recursive {
25+
if len(c.Args().Tail()) > 1 {
26+
return exit.Error(exit.Usage, nil, "Deleting multiple keys is not supported in recursive mode")
27+
}
28+
29+
return s.deleteRecursive(ctx, name, c.Bool("force"))
30+
}
31+
32+
if s.Store.IsDir(ctx, name) && !s.Store.Exists(ctx, name) {
2533
return exit.Error(exit.Usage, nil, "Cannot remove %q: Is a directory. Use 'gopass rm -r %s' to delete", name, name)
2634
}
2735

2836
// specifying a key is optional.
2937
key := c.Args().Get(1)
3038

31-
if recursive && key != "" {
32-
return exit.Error(exit.Usage, nil, "Can not use -r with a key. Invoke delete either with a key or with -r")
39+
// multiple secrets, so not a key
40+
if len(c.Args().Tail()) > 1 {
41+
key = ""
42+
}
43+
44+
names := append([]string{name}, c.Args().Tail()...)
45+
46+
if key != "" && s.Store.Exists(ctx, key) {
47+
return exit.Error(exit.Unsupported, nil, "Ambiguous key %q, use 'gopass edit %s' to delete", key, name)
48+
}
49+
50+
if !s.Store.Exists(ctx, name) {
51+
return exit.Error(exit.NotFound, nil, "Secret %q does not exist", name)
3352
}
3453

3554
if !c.Bool("force") { // don't check if it's force anyway.
36-
recStr := ""
37-
if recursive {
38-
recStr = "recursively "
55+
qStr := fmt.Sprintf("☠ Are you sure you would like to delete %q?", names)
56+
if key != "" {
57+
qStr = fmt.Sprintf("☠ Are you sure you would like to delete %q from %q?", key, name)
3958
}
40-
if (s.Store.Exists(ctx, name) || s.Store.IsDir(ctx, name)) && key == "" && !termio.AskForConfirmation(ctx, fmt.Sprintf("☠ Are you sure you would like to %sdelete %s?", recStr, name)) {
59+
if (s.Store.Exists(ctx, name) || s.Store.IsDir(ctx, name)) && key == "" && !termio.AskForConfirmation(ctx, qStr) {
4160
return nil
4261
}
4362
}
4463

45-
if recursive && key == "" {
46-
debug.Log("pruning %q", name)
47-
if err := s.Store.Prune(ctx, name); err != nil {
48-
return exit.Error(exit.Unknown, err, "failed to prune %q: %s", name, err)
49-
}
50-
debug.Log("pruned %q", name)
51-
52-
return nil
53-
}
54-
5564
// deletes a single key from a YAML doc.
5665
if key != "" {
5766
debug.Log("removing key %q from %q", key, name)
5867

5968
return s.deleteKeyFromYAML(ctx, name, key)
6069
}
6170

62-
debug.Log("removing entry %q", name)
63-
if err := s.Store.Delete(ctx, name); err != nil {
64-
return exit.Error(exit.IO, err, "Can not delete %q: %s", name, err)
71+
for _, name := range names {
72+
debug.Log("removing entry %q", name)
73+
if err := s.Store.Delete(ctx, name); err != nil {
74+
return exit.Error(exit.IO, err, "Can not delete %q: %s", name, err)
75+
}
76+
}
77+
78+
return nil
79+
}
80+
81+
func (s *Action) deleteRecursive(ctx context.Context, name string, force bool) error {
82+
if !force { // don't check if it's force anyway.
83+
if (s.Store.Exists(ctx, name) || s.Store.IsDir(ctx, name)) && !termio.AskForConfirmation(ctx, fmt.Sprintf("☠ Are you sure you would like to recursively delete %q?", name)) {
84+
return nil
85+
}
86+
}
87+
88+
debug.Log("pruning %q", name)
89+
if err := s.Store.Prune(ctx, name); err != nil {
90+
return exit.Error(exit.Unknown, err, "failed to prune %q: %s", name, err)
6591
}
92+
debug.Log("pruned %q", name)
6693

6794
return nil
6895
}

internal/action/delete_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,19 @@ func TestDelete(t *testing.T) { //nolint:paralleltest
6464
c = gptest.CliCtxWithFlags(ctx, t, map[string]string{"recursive": "true"}, "foo", "bar")
6565
assert.Error(t, act.Delete(c))
6666
buf.Reset()
67+
68+
assert.NoError(t, act.Store.Set(ctx, "sec/1", sec))
69+
assert.NoError(t, act.Store.Set(ctx, "sec/2", sec))
70+
assert.NoError(t, act.Store.Set(ctx, "sec/3", sec))
71+
assert.NoError(t, act.Store.Set(ctx, "sec/4", sec))
72+
73+
// warn if key matching a secret is given
74+
c = gptest.CliCtx(ctx, t, "sec/1", "sec/2")
75+
assert.Error(t, act.Delete(c))
76+
buf.Reset()
77+
78+
// remove multiple secrets
79+
c = gptest.CliCtx(ctx, t, "sec/1", "sec/2", "sec/3", "sec/4")
80+
assert.NoError(t, act.Delete(c))
81+
buf.Reset()
6782
}

tests/delete_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ func TestDelete(t *testing.T) { //nolint:paralleltest
1919

2020
out, err = ts.run("delete foobarbaz")
2121
assert.Error(t, err)
22-
assert.Contains(t, out, "entry is not in the password store", out)
22+
assert.Contains(t, out, "does not exist", out)
2323

2424
ts.initSecrets("")
2525

@@ -31,6 +31,6 @@ func TestDelete(t *testing.T) { //nolint:paralleltest
3131

3232
out, err = ts.run("delete -f " + secret)
3333
assert.Error(t, err)
34-
assert.Contains(t, out, "entry is not in the password store\n", out)
34+
assert.Contains(t, out, "does not exist\n", out)
3535
}
3636
}

0 commit comments

Comments
 (0)