-
-
Notifications
You must be signed in to change notification settings - Fork 496
/
clone.go
148 lines (126 loc) · 4.81 KB
/
clone.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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package action
import (
"context"
"path/filepath"
"github.com/justwatchcom/gopass/pkg/backend"
"github.com/justwatchcom/gopass/pkg/backend/crypto/xc"
gitcli "github.com/justwatchcom/gopass/pkg/backend/rcs/git/cli"
"github.com/justwatchcom/gopass/pkg/backend/rcs/git/gogit"
"github.com/justwatchcom/gopass/pkg/config"
"github.com/justwatchcom/gopass/pkg/cui"
"github.com/justwatchcom/gopass/pkg/fsutil"
"github.com/justwatchcom/gopass/pkg/out"
"github.com/justwatchcom/gopass/pkg/termio"
"github.com/fatih/color"
"github.com/urfave/cli"
)
// Clone will fetch and mount a new password store from a git repo
func (s *Action) Clone(ctx context.Context, c *cli.Context) error {
ctx = backend.WithCryptoBackendString(ctx, c.String("crypto"))
ctx = backend.WithRCSBackendString(ctx, c.String("sync"))
if len(c.Args()) < 1 {
return ExitError(ctx, ExitUsage, nil, "Usage: %s clone repo [mount]", s.Name)
}
repo := c.Args()[0]
mount := ""
if len(c.Args()) > 1 {
mount = c.Args()[1]
}
path := c.String("path")
return s.clone(ctx, repo, mount, path)
}
func (s *Action) clone(ctx context.Context, repo, mount, path string) error {
if path == "" {
path = config.PwStoreDir(mount)
}
if mount == "" && s.Store.Initialized(ctx) {
return ExitError(ctx, ExitAlreadyInitialized, nil, "Can not clone %s to the root store, as this store is already initialized. Please try cloning to a submount: `%s clone %s sub`", repo, s.Name, repo)
}
// clone repo
switch backend.GetRCSBackend(ctx) {
case backend.GoGit:
if _, err := gogit.Clone(ctx, repo, path); err != nil {
return ExitError(ctx, ExitGit, err, "failed to clone repo '%s' to '%s'", repo, path)
}
case backend.GitCLI:
fallthrough
default:
if _, err := gitcli.Clone(ctx, repo, path); err != nil {
return ExitError(ctx, ExitGit, err, "failed to clone repo '%s' to '%s'", repo, path)
}
}
// detect crypto backend based on cloned repo
ctx = backend.WithCryptoBackend(ctx, detectCryptoBackend(ctx, path))
// add mount
if err := s.cloneAddMount(ctx, mount, path); err != nil {
return err
}
// save new mount in config file
if err := s.cfg.Save(); err != nil {
return ExitError(ctx, ExitIO, err, "Failed to update config: %s", err)
}
// try to init git config
out.Green(ctx, "Configuring git repository ...")
// ask for git config values
username, email, err := s.cloneGetGitConfig(ctx, mount)
if err != nil {
return err
}
// initialize git config
if err := s.Store.GitInitConfig(ctx, mount, username, email); err != nil {
out.Debug(ctx, "Stacktrace: %+v\n", err)
out.Red(ctx, "Failed to configure git: %s", err)
}
if mount != "" {
mount = " " + mount
}
out.Green(ctx, "Your password store is ready to use! Have a look around: `%s list%s`\n", s.Name, mount)
return nil
}
func (s *Action) cloneAddMount(ctx context.Context, mount, path string) error {
if mount == "" {
s.cfg.Root.Path.Crypto = backend.GetCryptoBackend(ctx)
s.cfg.Root.Path.RCS = backend.GetRCSBackend(ctx)
s.cfg.Root.Path.Storage = backend.GetStorageBackend(ctx)
return nil
}
if !s.Store.Initialized(ctx) {
return ExitError(ctx, ExitNotInitialized, nil, "Root-Store is not initialized. Clone or init root store first")
}
if err := s.Store.AddMount(ctx, mount, path); err != nil {
return ExitError(ctx, ExitMount, err, "Failed to add mount: %s", err)
}
out.Green(ctx, "Mounted password store %s at mount point `%s` ...", path, mount)
s.cfg.Mounts[mount].Path.Crypto = backend.GetCryptoBackend(ctx)
s.cfg.Mounts[mount].Path.RCS = backend.GetRCSBackend(ctx)
s.cfg.Mounts[mount].Path.Storage = backend.GetStorageBackend(ctx)
return nil
}
func (s *Action) cloneGetGitConfig(ctx context.Context, name string) (string, string, error) {
// for convenience, set defaults to user-selected values from available private keys
// NB: discarding returned error since this is merely a best-effort look-up for convenience
username, email, _ := cui.AskForGitConfigUser(ctx, s.Store.Crypto(ctx, name), name)
if username == "" {
var err error
username, err = termio.AskForString(ctx, color.CyanString("Please enter a user name for password store git config"), username)
if err != nil {
return "", "", ExitError(ctx, ExitIO, err, "Failed to read user input: %s", err)
}
}
if email == "" {
var err error
email, err = termio.AskForString(ctx, color.CyanString("Please enter an email address for password store git config"), email)
if err != nil {
return "", "", ExitError(ctx, ExitIO, err, "Failed to read user input: %s", err)
}
}
return username, email, nil
}
// detectCryptoBackend tries to detect the crypto backend used in a cloned repo
// This detection is very shallow and doesn't support all backends, yet
func detectCryptoBackend(ctx context.Context, path string) backend.CryptoBackend {
if fsutil.IsFile(filepath.Join(path, xc.IDFile)) {
return backend.XC
}
return backend.GPGCLI
}