]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/hashicorp/go-getter/client.go
Initial transfer of provider code
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / hashicorp / go-getter / client.go
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 }