forked from hashicorp/terraform
-
Notifications
You must be signed in to change notification settings - Fork 0
/
get.go
207 lines (178 loc) · 4.85 KB
/
get.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
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
package module
import (
"bytes"
"fmt"
"io/ioutil"
"net/url"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"syscall"
urlhelper "github.com/hashicorp/terraform/helper/url"
)
// Getter defines the interface that schemes must implement to download
// and update modules.
type Getter interface {
// Get downloads the given URL into the given directory. This always
// assumes that we're updating and gets the latest version that it can.
//
// The directory may already exist (if we're updating). If it is in a
// format that isn't understood, an error should be returned. Get shouldn't
// simply nuke the directory.
Get(string, *url.URL) error
}
// Getters is the mapping of scheme to the Getter implementation that will
// be used to get a dependency.
var Getters map[string]Getter
// forcedRegexp is the regular expression that finds forced getters. This
// syntax is schema::url, example: git::https://foo.com
var forcedRegexp = regexp.MustCompile(`^([A-Za-z]+)::(.+)$`)
func init() {
httpGetter := new(HttpGetter)
Getters = map[string]Getter{
"file": new(FileGetter),
"git": new(GitGetter),
"hg": new(HgGetter),
"http": httpGetter,
"https": httpGetter,
}
}
// Get downloads the module specified by src into the folder specified by
// dst. If dst already exists, Get will attempt to update it.
//
// src is a URL, whereas dst is always just a file path to a folder. This
// folder doesn't need to exist. It will be created if it doesn't exist.
func Get(dst, src string) error {
var force string
force, src = getForcedGetter(src)
// If there is a subdir component, then we download the root separately
// and then copy over the proper subdir.
var realDst string
src, subDir := getDirSubdir(src)
if subDir != "" {
tmpDir, err := ioutil.TempDir("", "tf")
if err != nil {
return err
}
if err := os.RemoveAll(tmpDir); err != nil {
return err
}
defer os.RemoveAll(tmpDir)
realDst = dst
dst = tmpDir
}
u, err := urlhelper.Parse(src)
if err != nil {
return err
}
if force == "" {
force = u.Scheme
}
g, ok := Getters[force]
if !ok {
return fmt.Errorf(
"module download not supported for scheme '%s'", force)
}
err = g.Get(dst, u)
if err != nil {
err = fmt.Errorf("error downloading module '%s': %s", src, err)
return err
}
// If we have a subdir, copy that over
if subDir != "" {
if err := os.RemoveAll(realDst); err != nil {
return err
}
if err := os.MkdirAll(realDst, 0755); err != nil {
return err
}
return copyDir(realDst, filepath.Join(dst, subDir))
}
return nil
}
// GetCopy is the same as Get except that it downloads a copy of the
// module represented by source.
//
// This copy will omit and dot-prefixed files (such as .git/, .hg/) and
// can't be updated on its own.
func GetCopy(dst, src string) error {
// Create the temporary directory to do the real Get to
tmpDir, err := ioutil.TempDir("", "tf")
if err != nil {
return err
}
if err := os.RemoveAll(tmpDir); err != nil {
return err
}
defer os.RemoveAll(tmpDir)
// Get to that temporary dir
if err := Get(tmpDir, src); err != nil {
return err
}
// Make sure the destination exists
if err := os.MkdirAll(dst, 0755); err != nil {
return err
}
// Copy to the final location
return copyDir(dst, tmpDir)
}
// getRunCommand is a helper that will run a command and capture the output
// in the case an error happens.
func getRunCommand(cmd *exec.Cmd) error {
var buf bytes.Buffer
cmd.Stdout = &buf
cmd.Stderr = &buf
err := cmd.Run()
if err == nil {
return nil
}
if exiterr, ok := err.(*exec.ExitError); ok {
// The program has exited with an exit code != 0
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
return fmt.Errorf(
"%s exited with %d: %s",
cmd.Path,
status.ExitStatus(),
buf.String())
}
}
return fmt.Errorf("error running %s: %s", cmd.Path, buf.String())
}
// getDirSubdir takes a source and returns a tuple of the URL without
// the subdir and the URL with the subdir.
func getDirSubdir(src string) (string, string) {
// Calcaulate an offset to avoid accidentally marking the scheme
// as the dir.
var offset int
if idx := strings.Index(src, "://"); idx > -1 {
offset = idx + 3
}
// First see if we even have an explicit subdir
idx := strings.Index(src[offset:], "//")
if idx == -1 {
return src, ""
}
idx += offset
subdir := src[idx+2:]
src = src[:idx]
// Next, check if we have query parameters and push them onto the
// URL.
if idx = strings.Index(subdir, "?"); idx > -1 {
query := subdir[idx:]
subdir = subdir[:idx]
src += query
}
return src, subdir
}
// getForcedGetter takes a source and returns the tuple of the forced
// getter and the raw URL (without the force syntax).
func getForcedGetter(src string) (string, string) {
var forced string
if ms := forcedRegexp.FindStringSubmatch(src); ms != nil {
forced = ms[1]
src = ms[2]
}
return forced, src
}