]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blame - vendor/github.com/hashicorp/go-getter/get_git.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / go-getter / get_git.go
CommitLineData
bae9f6d2
JC
1package getter
2
3import (
107c1cdb 4 "context"
bae9f6d2
JC
5 "encoding/base64"
6 "fmt"
7 "io/ioutil"
8 "net/url"
9 "os"
10 "os/exec"
11 "path/filepath"
107c1cdb
ND
12 "runtime"
13 "strconv"
bae9f6d2
JC
14 "strings"
15
16 urlhelper "github.com/hashicorp/go-getter/helper/url"
107c1cdb
ND
17 safetemp "github.com/hashicorp/go-safetemp"
18 version "github.com/hashicorp/go-version"
bae9f6d2
JC
19)
20
21// GitGetter is a Getter implementation that will download a module from
22// a git repository.
107c1cdb
ND
23type GitGetter struct {
24 getter
25}
bae9f6d2
JC
26
27func (g *GitGetter) ClientMode(_ *url.URL) (ClientMode, error) {
28 return ClientModeDir, nil
29}
30
31func (g *GitGetter) Get(dst string, u *url.URL) error {
107c1cdb 32 ctx := g.Context()
bae9f6d2
JC
33 if _, err := exec.LookPath("git"); err != nil {
34 return fmt.Errorf("git must be available and on the PATH")
35 }
36
107c1cdb
ND
37 // The port number must be parseable as an integer. If not, the user
38 // was probably trying to use a scp-style address, in which case the
39 // ssh:// prefix must be removed to indicate that.
40 if portStr := u.Port(); portStr != "" {
41 if _, err := strconv.ParseUint(portStr, 10, 16); err != nil {
42 return fmt.Errorf("invalid port number %q; if using the \"scp-like\" git address scheme where a colon introduces the path instead, remove the ssh:// portion and use just the git:: prefix", portStr)
43 }
44 }
45
bae9f6d2
JC
46 // Extract some query parameters we use
47 var ref, sshKey string
107c1cdb 48 var depth int
bae9f6d2
JC
49 q := u.Query()
50 if len(q) > 0 {
51 ref = q.Get("ref")
52 q.Del("ref")
53
54 sshKey = q.Get("sshkey")
55 q.Del("sshkey")
56
107c1cdb
ND
57 if n, err := strconv.Atoi(q.Get("depth")); err == nil {
58 depth = n
59 }
60 q.Del("depth")
61
bae9f6d2
JC
62 // Copy the URL
63 var newU url.URL = *u
64 u = &newU
65 u.RawQuery = q.Encode()
66 }
67
68 var sshKeyFile string
69 if sshKey != "" {
70 // Check that the git version is sufficiently new.
71 if err := checkGitVersion("2.3"); err != nil {
72 return fmt.Errorf("Error using ssh key: %v", err)
73 }
74
75 // We have an SSH key - decode it.
76 raw, err := base64.StdEncoding.DecodeString(sshKey)
77 if err != nil {
78 return err
79 }
80
81 // Create a temp file for the key and ensure it is removed.
82 fh, err := ioutil.TempFile("", "go-getter")
83 if err != nil {
84 return err
85 }
86 sshKeyFile = fh.Name()
87 defer os.Remove(sshKeyFile)
88
89 // Set the permissions prior to writing the key material.
90 if err := os.Chmod(sshKeyFile, 0600); err != nil {
91 return err
92 }
93
94 // Write the raw key into the temp file.
95 _, err = fh.Write(raw)
96 fh.Close()
97 if err != nil {
98 return err
99 }
100 }
101
102 // Clone or update the repository
103 _, err := os.Stat(dst)
104 if err != nil && !os.IsNotExist(err) {
105 return err
106 }
107 if err == nil {
107c1cdb 108 err = g.update(ctx, dst, sshKeyFile, ref, depth)
bae9f6d2 109 } else {
107c1cdb 110 err = g.clone(ctx, dst, sshKeyFile, u, depth)
bae9f6d2
JC
111 }
112 if err != nil {
113 return err
114 }
115
116 // Next: check out the proper tag/branch if it is specified, and checkout
117 if ref != "" {
118 if err := g.checkout(dst, ref); err != nil {
119 return err
120 }
121 }
122
123 // Lastly, download any/all submodules.
107c1cdb 124 return g.fetchSubmodules(ctx, dst, sshKeyFile, depth)
bae9f6d2
JC
125}
126
127// GetFile for Git doesn't support updating at this time. It will download
128// the file every time.
129func (g *GitGetter) GetFile(dst string, u *url.URL) error {
15c0b25d 130 td, tdcloser, err := safetemp.Dir("", "getter")
bae9f6d2
JC
131 if err != nil {
132 return err
133 }
15c0b25d 134 defer tdcloser.Close()
bae9f6d2
JC
135
136 // Get the filename, and strip the filename from the URL so we can
137 // just get the repository directly.
138 filename := filepath.Base(u.Path)
139 u.Path = filepath.Dir(u.Path)
140
141 // Get the full repository
142 if err := g.Get(td, u); err != nil {
143 return err
144 }
145
146 // Copy the single file
147 u, err = urlhelper.Parse(fmtFileURL(filepath.Join(td, filename)))
148 if err != nil {
149 return err
150 }
151
152 fg := &FileGetter{Copy: true}
153 return fg.GetFile(dst, u)
154}
155
156func (g *GitGetter) checkout(dst string, ref string) error {
157 cmd := exec.Command("git", "checkout", ref)
158 cmd.Dir = dst
159 return getRunCommand(cmd)
160}
161
107c1cdb
ND
162func (g *GitGetter) clone(ctx context.Context, dst, sshKeyFile string, u *url.URL, depth int) error {
163 args := []string{"clone"}
164
165 if depth > 0 {
166 args = append(args, "--depth", strconv.Itoa(depth))
167 }
168
169 args = append(args, u.String(), dst)
170 cmd := exec.CommandContext(ctx, "git", args...)
bae9f6d2
JC
171 setupGitEnv(cmd, sshKeyFile)
172 return getRunCommand(cmd)
173}
174
107c1cdb 175func (g *GitGetter) update(ctx context.Context, dst, sshKeyFile, ref string, depth int) error {
bae9f6d2
JC
176 // Determine if we're a branch. If we're NOT a branch, then we just
177 // switch to master prior to checking out
107c1cdb 178 cmd := exec.CommandContext(ctx, "git", "show-ref", "-q", "--verify", "refs/heads/"+ref)
bae9f6d2
JC
179 cmd.Dir = dst
180
181 if getRunCommand(cmd) != nil {
182 // Not a branch, switch to master. This will also catch non-existent
183 // branches, in which case we want to switch to master and then
184 // checkout the proper branch later.
185 ref = "master"
186 }
187
188 // We have to be on a branch to pull
189 if err := g.checkout(dst, ref); err != nil {
190 return err
191 }
192
107c1cdb
ND
193 if depth > 0 {
194 cmd = exec.Command("git", "pull", "--depth", strconv.Itoa(depth), "--ff-only")
195 } else {
196 cmd = exec.Command("git", "pull", "--ff-only")
197 }
198
bae9f6d2
JC
199 cmd.Dir = dst
200 setupGitEnv(cmd, sshKeyFile)
201 return getRunCommand(cmd)
202}
203
204// fetchSubmodules downloads any configured submodules recursively.
107c1cdb
ND
205func (g *GitGetter) fetchSubmodules(ctx context.Context, dst, sshKeyFile string, depth int) error {
206 args := []string{"submodule", "update", "--init", "--recursive"}
207 if depth > 0 {
208 args = append(args, "--depth", strconv.Itoa(depth))
209 }
210 cmd := exec.CommandContext(ctx, "git", args...)
bae9f6d2
JC
211 cmd.Dir = dst
212 setupGitEnv(cmd, sshKeyFile)
213 return getRunCommand(cmd)
214}
215
216// setupGitEnv sets up the environment for the given command. This is used to
217// pass configuration data to git and ssh and enables advanced cloning methods.
218func setupGitEnv(cmd *exec.Cmd, sshKeyFile string) {
15c0b25d
AP
219 const gitSSHCommand = "GIT_SSH_COMMAND="
220 var sshCmd []string
221
222 // If we have an existing GIT_SSH_COMMAND, we need to append our options.
223 // We will also remove our old entry to make sure the behavior is the same
224 // with versions of Go < 1.9.
225 env := os.Environ()
226 for i, v := range env {
107c1cdb 227 if strings.HasPrefix(v, gitSSHCommand) && len(v) > len(gitSSHCommand) {
15c0b25d
AP
228 sshCmd = []string{v}
229
230 env[i], env[len(env)-1] = env[len(env)-1], env[i]
231 env = env[:len(env)-1]
232 break
233 }
234 }
235
236 if len(sshCmd) == 0 {
237 sshCmd = []string{gitSSHCommand + "ssh"}
238 }
bae9f6d2
JC
239
240 if sshKeyFile != "" {
241 // We have an SSH key temp file configured, tell ssh about this.
107c1cdb
ND
242 if runtime.GOOS == "windows" {
243 sshKeyFile = strings.Replace(sshKeyFile, `\`, `/`, -1)
244 }
15c0b25d 245 sshCmd = append(sshCmd, "-i", sshKeyFile)
bae9f6d2
JC
246 }
247
15c0b25d
AP
248 env = append(env, strings.Join(sshCmd, " "))
249 cmd.Env = env
bae9f6d2
JC
250}
251
252// checkGitVersion is used to check the version of git installed on the system
253// against a known minimum version. Returns an error if the installed version
254// is older than the given minimum.
255func checkGitVersion(min string) error {
256 want, err := version.NewVersion(min)
257 if err != nil {
258 return err
259 }
260
261 out, err := exec.Command("git", "version").Output()
262 if err != nil {
263 return err
264 }
265
266 fields := strings.Fields(string(out))
107c1cdb 267 if len(fields) < 3 {
bae9f6d2
JC
268 return fmt.Errorf("Unexpected 'git version' output: %q", string(out))
269 }
107c1cdb
ND
270 v := fields[2]
271 if runtime.GOOS == "windows" && strings.Contains(v, ".windows.") {
272 // on windows, git version will return for example:
273 // git version 2.20.1.windows.1
274 // Which does not follow the semantic versionning specs
275 // https://semver.org. We remove that part in order for
276 // go-version to not error.
277 v = v[:strings.Index(v, ".windows.")]
278 }
bae9f6d2 279
107c1cdb 280 have, err := version.NewVersion(v)
bae9f6d2
JC
281 if err != nil {
282 return err
283 }
284
285 if have.LessThan(want) {
286 return fmt.Errorf("Required git version = %s, have %s", want, have)
287 }
288
289 return nil
290}