-
-
Notifications
You must be signed in to change notification settings - Fork 479
/
merge.go
112 lines (94 loc) · 3.1 KB
/
merge.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
package action
import (
"bytes"
"errors"
"fmt"
"time"
"github.com/gopasspw/gopass/internal/action/exit"
"github.com/gopasspw/gopass/internal/audit"
"github.com/gopasspw/gopass/internal/editor"
"github.com/gopasspw/gopass/internal/out"
"github.com/gopasspw/gopass/internal/queue"
"github.com/gopasspw/gopass/internal/store"
"github.com/gopasspw/gopass/pkg/ctxutil"
"github.com/gopasspw/gopass/pkg/debug"
"github.com/gopasspw/gopass/pkg/gopass/secrets"
"github.com/urfave/cli/v2"
)
// Merge implements the merge subcommand that allows merging multiple entries.
func (s *Action) Merge(c *cli.Context) error {
ctx := ctxutil.WithGlobalFlags(c)
to := c.Args().First()
from := c.Args().Tail()
if to == "" {
return exit.Error(exit.Usage, nil, "usage: %s merge <to> <from> [<from>]", s.Name)
}
if len(from) < 1 {
return exit.Error(exit.Usage, nil, "usage: %s merge <to> <from> [<from>]", s.Name)
}
ed := editor.Path(c)
content := &bytes.Buffer{}
for _, k := range c.Args().Slice() {
if !s.Store.Exists(ctx, k) {
continue
}
sec, err := s.Store.Get(ctxutil.WithShowParsing(ctx, false), k)
if err != nil {
return exit.Error(exit.Decrypt, err, "failed to decrypt: %s: %s", k, err)
}
_, err = content.WriteString("\n# Secret: " + k + "\n")
if err != nil {
return exit.Error(exit.Unknown, err, "failed to write: %s", err)
}
_, err = content.Write(sec.Bytes())
if err != nil {
return exit.Error(exit.Unknown, err, "failed to write: %s", err)
}
}
newContent := content.Bytes()
if !c.Bool("force") {
var err error
// invoke the editor to let the user edit the content
newContent, err = editor.Invoke(ctx, ed, content.Bytes())
if err != nil {
return exit.Error(exit.Unknown, err, "failed to invoke editor: %s", err)
}
// If content is equal, nothing changed, exiting
if bytes.Equal(content.Bytes(), newContent) {
return nil
}
}
nSec := secrets.ParseAKV(newContent)
// if the secret has a password, we check it's strength
if pw := nSec.Password(); pw != "" && !c.Bool("force") {
audit.Single(ctx, pw)
}
// write result (back) to store
if err := s.Store.Set(ctxutil.WithCommitMessage(ctx, fmt.Sprintf("Merged %+v", c.Args().Slice())), to, nSec); err != nil {
if !errors.Is(err, store.ErrMeaninglessWrite) {
return exit.Error(exit.Encrypt, err, "failed to encrypt secret %s: %s", to, err)
}
out.Warningf(ctx, "No need to write: the secret is already there and with the right value")
}
if !c.Bool("delete") {
return nil
}
// wait until the previous commit is done
// This wouldn't be necessary if we could handle merging and deleting
// in a single commit, but then we'd need to expose additional implementation
// details of the underlying VCS. Or create some kind of transaction on top
// of the Git wrapper.
if err := queue.GetQueue(ctx).Idle(time.Minute); err != nil {
return err
}
for _, old := range from {
if !s.Store.Exists(ctx, old) {
continue
}
debug.Log("deleting merged entry %s", old)
if err := s.Store.Delete(ctx, old); err != nil {
return exit.Error(exit.Unknown, err, "failed to delete %s: %s", old, err)
}
}
return nil
}