diff options
Diffstat (limited to 'vendor/github.com/hashicorp/go-getter/client.go')
-rw-r--r-- | vendor/github.com/hashicorp/go-getter/client.go | 335 |
1 files changed, 335 insertions, 0 deletions
diff --git a/vendor/github.com/hashicorp/go-getter/client.go b/vendor/github.com/hashicorp/go-getter/client.go new file mode 100644 index 0000000..876812a --- /dev/null +++ b/vendor/github.com/hashicorp/go-getter/client.go | |||
@@ -0,0 +1,335 @@ | |||
1 | package getter | ||
2 | |||
3 | import ( | ||
4 | "bytes" | ||
5 | "crypto/md5" | ||
6 | "crypto/sha1" | ||
7 | "crypto/sha256" | ||
8 | "crypto/sha512" | ||
9 | "encoding/hex" | ||
10 | "fmt" | ||
11 | "hash" | ||
12 | "io" | ||
13 | "io/ioutil" | ||
14 | "os" | ||
15 | "path/filepath" | ||
16 | "strconv" | ||
17 | "strings" | ||
18 | |||
19 | urlhelper "github.com/hashicorp/go-getter/helper/url" | ||
20 | ) | ||
21 | |||
22 | // Client is a client for downloading things. | ||
23 | // | ||
24 | // Top-level functions such as Get are shortcuts for interacting with a client. | ||
25 | // Using a client directly allows more fine-grained control over how downloading | ||
26 | // is done, as well as customizing the protocols supported. | ||
27 | type Client struct { | ||
28 | // Src is the source URL to get. | ||
29 | // | ||
30 | // Dst is the path to save the downloaded thing as. If Dir is set to | ||
31 | // true, then this should be a directory. If the directory doesn't exist, | ||
32 | // it will be created for you. | ||
33 | // | ||
34 | // Pwd is the working directory for detection. If this isn't set, some | ||
35 | // detection may fail. Client will not default pwd to the current | ||
36 | // working directory for security reasons. | ||
37 | Src string | ||
38 | Dst string | ||
39 | Pwd string | ||
40 | |||
41 | // Mode is the method of download the client will use. See ClientMode | ||
42 | // for documentation. | ||
43 | Mode ClientMode | ||
44 | |||
45 | // Detectors is the list of detectors that are tried on the source. | ||
46 | // If this is nil, then the default Detectors will be used. | ||
47 | Detectors []Detector | ||
48 | |||
49 | // Decompressors is the map of decompressors supported by this client. | ||
50 | // If this is nil, then the default value is the Decompressors global. | ||
51 | Decompressors map[string]Decompressor | ||
52 | |||
53 | // Getters is the map of protocols supported by this client. If this | ||
54 | // is nil, then the default Getters variable will be used. | ||
55 | Getters map[string]Getter | ||
56 | |||
57 | // Dir, if true, tells the Client it is downloading a directory (versus | ||
58 | // a single file). This distinction is necessary since filenames and | ||
59 | // directory names follow the same format so disambiguating is impossible | ||
60 | // without knowing ahead of time. | ||
61 | // | ||
62 | // WARNING: deprecated. If Mode is set, that will take precedence. | ||
63 | Dir bool | ||
64 | } | ||
65 | |||
66 | // Get downloads the configured source to the destination. | ||
67 | func (c *Client) Get() error { | ||
68 | // Store this locally since there are cases we swap this | ||
69 | mode := c.Mode | ||
70 | if mode == ClientModeInvalid { | ||
71 | if c.Dir { | ||
72 | mode = ClientModeDir | ||
73 | } else { | ||
74 | mode = ClientModeFile | ||
75 | } | ||
76 | } | ||
77 | |||
78 | // Default decompressor value | ||
79 | decompressors := c.Decompressors | ||
80 | if decompressors == nil { | ||
81 | decompressors = Decompressors | ||
82 | } | ||
83 | |||
84 | // Detect the URL. This is safe if it is already detected. | ||
85 | detectors := c.Detectors | ||
86 | if detectors == nil { | ||
87 | detectors = Detectors | ||
88 | } | ||
89 | src, err := Detect(c.Src, c.Pwd, detectors) | ||
90 | if err != nil { | ||
91 | return err | ||
92 | } | ||
93 | |||
94 | // Determine if we have a forced protocol, i.e. "git::http://..." | ||
95 | force, src := getForcedGetter(src) | ||
96 | |||
97 | // If there is a subdir component, then we download the root separately | ||
98 | // and then copy over the proper subdir. | ||
99 | var realDst string | ||
100 | dst := c.Dst | ||
101 | src, subDir := SourceDirSubdir(src) | ||
102 | if subDir != "" { | ||
103 | tmpDir, err := ioutil.TempDir("", "tf") | ||
104 | if err != nil { | ||
105 | return err | ||
106 | } | ||
107 | if err := os.RemoveAll(tmpDir); err != nil { | ||
108 | return err | ||
109 | } | ||
110 | defer os.RemoveAll(tmpDir) | ||
111 | |||
112 | realDst = dst | ||
113 | dst = tmpDir | ||
114 | } | ||
115 | |||
116 | u, err := urlhelper.Parse(src) | ||
117 | if err != nil { | ||
118 | return err | ||
119 | } | ||
120 | if force == "" { | ||
121 | force = u.Scheme | ||
122 | } | ||
123 | |||
124 | getters := c.Getters | ||
125 | if getters == nil { | ||
126 | getters = Getters | ||
127 | } | ||
128 | |||
129 | g, ok := getters[force] | ||
130 | if !ok { | ||
131 | return fmt.Errorf( | ||
132 | "download not supported for scheme '%s'", force) | ||
133 | } | ||
134 | |||
135 | // We have magic query parameters that we use to signal different features | ||
136 | q := u.Query() | ||
137 | |||
138 | // Determine if we have an archive type | ||
139 | archiveV := q.Get("archive") | ||
140 | if archiveV != "" { | ||
141 | // Delete the paramter since it is a magic parameter we don't | ||
142 | // want to pass on to the Getter | ||
143 | q.Del("archive") | ||
144 | u.RawQuery = q.Encode() | ||
145 | |||
146 | // If we can parse the value as a bool and it is false, then | ||
147 | // set the archive to "-" which should never map to a decompressor | ||
148 | if b, err := strconv.ParseBool(archiveV); err == nil && !b { | ||
149 | archiveV = "-" | ||
150 | } | ||
151 | } | ||
152 | if archiveV == "" { | ||
153 | // We don't appear to... but is it part of the filename? | ||
154 | matchingLen := 0 | ||
155 | for k, _ := range decompressors { | ||
156 | if strings.HasSuffix(u.Path, "."+k) && len(k) > matchingLen { | ||
157 | archiveV = k | ||
158 | matchingLen = len(k) | ||
159 | } | ||
160 | } | ||
161 | } | ||
162 | |||
163 | // If we have a decompressor, then we need to change the destination | ||
164 | // to download to a temporary path. We unarchive this into the final, | ||
165 | // real path. | ||
166 | var decompressDst string | ||
167 | var decompressDir bool | ||
168 | decompressor := decompressors[archiveV] | ||
169 | if decompressor != nil { | ||
170 | // Create a temporary directory to store our archive. We delete | ||
171 | // this at the end of everything. | ||
172 | td, err := ioutil.TempDir("", "getter") | ||
173 | if err != nil { | ||
174 | return fmt.Errorf( | ||
175 | "Error creating temporary directory for archive: %s", err) | ||
176 | } | ||
177 | defer os.RemoveAll(td) | ||
178 | |||
179 | // Swap the download directory to be our temporary path and | ||
180 | // store the old values. | ||
181 | decompressDst = dst | ||
182 | decompressDir = mode != ClientModeFile | ||
183 | dst = filepath.Join(td, "archive") | ||
184 | mode = ClientModeFile | ||
185 | } | ||
186 | |||
187 | // Determine if we have a checksum | ||
188 | var checksumHash hash.Hash | ||
189 | var checksumValue []byte | ||
190 | if v := q.Get("checksum"); v != "" { | ||
191 | // Delete the query parameter if we have it. | ||
192 | q.Del("checksum") | ||
193 | u.RawQuery = q.Encode() | ||
194 | |||
195 | // Determine the checksum hash type | ||
196 | checksumType := "" | ||
197 | idx := strings.Index(v, ":") | ||
198 | if idx > -1 { | ||
199 | checksumType = v[:idx] | ||
200 | } | ||
201 | switch checksumType { | ||
202 | case "md5": | ||
203 | checksumHash = md5.New() | ||
204 | case "sha1": | ||
205 | checksumHash = sha1.New() | ||
206 | case "sha256": | ||
207 | checksumHash = sha256.New() | ||
208 | case "sha512": | ||
209 | checksumHash = sha512.New() | ||
210 | default: | ||
211 | return fmt.Errorf( | ||
212 | "unsupported checksum type: %s", checksumType) | ||
213 | } | ||
214 | |||
215 | // Get the remainder of the value and parse it into bytes | ||
216 | b, err := hex.DecodeString(v[idx+1:]) | ||
217 | if err != nil { | ||
218 | return fmt.Errorf("invalid checksum: %s", err) | ||
219 | } | ||
220 | |||
221 | // Set our value | ||
222 | checksumValue = b | ||
223 | } | ||
224 | |||
225 | if mode == ClientModeAny { | ||
226 | // Ask the getter which client mode to use | ||
227 | mode, err = g.ClientMode(u) | ||
228 | if err != nil { | ||
229 | return err | ||
230 | } | ||
231 | |||
232 | // Destination is the base name of the URL path in "any" mode when | ||
233 | // a file source is detected. | ||
234 | if mode == ClientModeFile { | ||
235 | dst = filepath.Join(dst, filepath.Base(u.Path)) | ||
236 | } | ||
237 | } | ||
238 | |||
239 | // If we're not downloading a directory, then just download the file | ||
240 | // and return. | ||
241 | if mode == ClientModeFile { | ||
242 | err := g.GetFile(dst, u) | ||
243 | if err != nil { | ||
244 | return err | ||
245 | } | ||
246 | |||
247 | if checksumHash != nil { | ||
248 | if err := checksum(dst, checksumHash, checksumValue); err != nil { | ||
249 | return err | ||
250 | } | ||
251 | } | ||
252 | |||
253 | if decompressor != nil { | ||
254 | // We have a decompressor, so decompress the current destination | ||
255 | // into the final destination with the proper mode. | ||
256 | err := decompressor.Decompress(decompressDst, dst, decompressDir) | ||
257 | if err != nil { | ||
258 | return err | ||
259 | } | ||
260 | |||
261 | // Swap the information back | ||
262 | dst = decompressDst | ||
263 | if decompressDir { | ||
264 | mode = ClientModeAny | ||
265 | } else { | ||
266 | mode = ClientModeFile | ||
267 | } | ||
268 | } | ||
269 | |||
270 | // We check the dir value again because it can be switched back | ||
271 | // if we were unarchiving. If we're still only Get-ing a file, then | ||
272 | // we're done. | ||
273 | if mode == ClientModeFile { | ||
274 | return nil | ||
275 | } | ||
276 | } | ||
277 | |||
278 | // If we're at this point we're either downloading a directory or we've | ||
279 | // downloaded and unarchived a directory and we're just checking subdir. | ||
280 | // In the case we have a decompressor we don't Get because it was Get | ||
281 | // above. | ||
282 | if decompressor == nil { | ||
283 | // If we're getting a directory, then this is an error. You cannot | ||
284 | // checksum a directory. TODO: test | ||
285 | if checksumHash != nil { | ||
286 | return fmt.Errorf( | ||
287 | "checksum cannot be specified for directory download") | ||
288 | } | ||
289 | |||
290 | // We're downloading a directory, which might require a bit more work | ||
291 | // if we're specifying a subdir. | ||
292 | err := g.Get(dst, u) | ||
293 | if err != nil { | ||
294 | err = fmt.Errorf("error downloading '%s': %s", src, err) | ||
295 | return err | ||
296 | } | ||
297 | } | ||
298 | |||
299 | // If we have a subdir, copy that over | ||
300 | if subDir != "" { | ||
301 | if err := os.RemoveAll(realDst); err != nil { | ||
302 | return err | ||
303 | } | ||
304 | if err := os.MkdirAll(realDst, 0755); err != nil { | ||
305 | return err | ||
306 | } | ||
307 | |||
308 | return copyDir(realDst, filepath.Join(dst, subDir), false) | ||
309 | } | ||
310 | |||
311 | return nil | ||
312 | } | ||
313 | |||
314 | // checksum is a simple method to compute the checksum of a source file | ||
315 | // and compare it to the given expected value. | ||
316 | func checksum(source string, h hash.Hash, v []byte) error { | ||
317 | f, err := os.Open(source) | ||
318 | if err != nil { | ||
319 | return fmt.Errorf("Failed to open file for checksum: %s", err) | ||
320 | } | ||
321 | defer f.Close() | ||
322 | |||
323 | if _, err := io.Copy(h, f); err != nil { | ||
324 | return fmt.Errorf("Failed to hash: %s", err) | ||
325 | } | ||
326 | |||
327 | if actual := h.Sum(nil); !bytes.Equal(actual, v) { | ||
328 | return fmt.Errorf( | ||
329 | "Checksums did not match.\nExpected: %s\nGot: %s", | ||
330 | hex.EncodeToString(v), | ||
331 | hex.EncodeToString(actual)) | ||
332 | } | ||
333 | |||
334 | return nil | ||
335 | } | ||