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