]>
Commit | Line | Data |
---|---|---|
bae9f6d2 JC |
1 | package getter |
2 | ||
3 | import ( | |
107c1cdb | 4 | "context" |
bae9f6d2 | 5 | "fmt" |
bae9f6d2 JC |
6 | "io/ioutil" |
7 | "os" | |
8 | "path/filepath" | |
9 | "strconv" | |
10 | "strings" | |
11 | ||
12 | urlhelper "github.com/hashicorp/go-getter/helper/url" | |
107c1cdb | 13 | safetemp "github.com/hashicorp/go-safetemp" |
bae9f6d2 JC |
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 { | |
107c1cdb ND |
22 | // Ctx for cancellation |
23 | Ctx context.Context | |
24 | ||
bae9f6d2 JC |
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 | |
107c1cdb ND |
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 | |
bae9f6d2 JC |
67 | } |
68 | ||
69 | // Get downloads the configured source to the destination. | |
70 | func (c *Client) Get() error { | |
107c1cdb ND |
71 | if err := c.Configure(c.Options...); err != nil { |
72 | return err | |
73 | } | |
74 | ||
bae9f6d2 JC |
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 | ||
107c1cdb | 85 | src, err := Detect(c.Src, c.Pwd, c.Detectors) |
bae9f6d2 JC |
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 != "" { | |
15c0b25d | 99 | td, tdcloser, err := safetemp.Dir("", "getter") |
bae9f6d2 JC |
100 | if err != nil { |
101 | return err | |
102 | } | |
15c0b25d | 103 | defer tdcloser.Close() |
bae9f6d2 JC |
104 | |
105 | realDst = dst | |
15c0b25d | 106 | dst = td |
bae9f6d2 JC |
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 | ||
107c1cdb | 117 | g, ok := c.Getters[force] |
bae9f6d2 JC |
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 | |
107c1cdb | 143 | for k := range c.Decompressors { |
bae9f6d2 JC |
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 | |
107c1cdb | 156 | decompressor := c.Decompressors[archiveV] |
bae9f6d2 JC |
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 | ||
107c1cdb ND |
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) | |
bae9f6d2 JC |
179 | } |
180 | ||
107c1cdb ND |
181 | // Delete the query parameter if we have it. |
182 | q.Del("checksum") | |
183 | u.RawQuery = q.Encode() | |
184 | ||
bae9f6d2 JC |
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 { | |
15c0b25d AP |
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) | |
bae9f6d2 JC |
207 | } |
208 | } | |
209 | ||
210 | // If we're not downloading a directory, then just download the file | |
211 | // and return. | |
212 | if mode == ClientModeFile { | |
107c1cdb ND |
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 | } | |
bae9f6d2 | 219 | } |
107c1cdb ND |
220 | if getFile { |
221 | err := g.GetFile(dst, u) | |
222 | if err != nil { | |
bae9f6d2 JC |
223 | return err |
224 | } | |
107c1cdb ND |
225 | |
226 | if checksum != nil { | |
227 | if err := checksum.checksum(dst); err != nil { | |
228 | return err | |
229 | } | |
230 | } | |
bae9f6d2 JC |
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 | |
107c1cdb | 265 | if checksum != nil { |
bae9f6d2 JC |
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 | ||
15c0b25d AP |
288 | // Process any globs |
289 | subDir, err := SubdirGlob(dst, subDir) | |
290 | if err != nil { | |
291 | return err | |
292 | } | |
293 | ||
107c1cdb | 294 | return copyDir(c.Ctx, realDst, subDir, false) |
bae9f6d2 JC |
295 | } |
296 | ||
297 | return nil | |
298 | } |