OLD | NEW |
1 // Copyright 2013 Canonical Ltd. | 1 // Copyright 2013 Canonical Ltd. |
2 // Licensed under the AGPLv3, see LICENCE file for details. | 2 // Licensed under the AGPLv3, see LICENCE file for details. |
3 | 3 |
4 package apiserver | 4 package apiserver |
5 | 5 |
6 import ( | 6 import ( |
7 "archive/zip" | 7 "archive/zip" |
8 "crypto/sha256" | 8 "crypto/sha256" |
9 "encoding/hex" | 9 "encoding/hex" |
10 "encoding/json" | 10 "encoding/json" |
11 "fmt" | 11 "fmt" |
12 "io" | 12 "io" |
13 "io/ioutil" | 13 "io/ioutil" |
14 "mime" | 14 "mime" |
15 "net/http" | 15 "net/http" |
16 "net/url" | 16 "net/url" |
17 "os" | 17 "os" |
18 "path" | 18 "path" |
19 "path/filepath" | 19 "path/filepath" |
20 "sort" | 20 "sort" |
21 "strconv" | 21 "strconv" |
22 "strings" | 22 "strings" |
23 | 23 |
24 » "github.com/errgo/errgo" | 24 » "github.com/juju/errors" |
25 | 25 |
26 "launchpad.net/juju-core/charm" | 26 "launchpad.net/juju-core/charm" |
27 "launchpad.net/juju-core/environs" | 27 "launchpad.net/juju-core/environs" |
28 "launchpad.net/juju-core/state/api/params" | 28 "launchpad.net/juju-core/state/api/params" |
29 ziputil "launchpad.net/juju-core/utils/zip" | 29 ziputil "launchpad.net/juju-core/utils/zip" |
30 ) | 30 ) |
31 | 31 |
32 // charmsHandler handles charm upload through HTTPS in the API server. | 32 // charmsHandler handles charm upload through HTTPS in the API server. |
33 type charmsHandler struct { | 33 type charmsHandler struct { |
34 httpHandler | 34 httpHandler |
(...skipping 194 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
229 if err != nil { | 229 if err != nil { |
230 return err | 230 return err |
231 } | 231 } |
232 defer f.Close() | 232 defer f.Close() |
233 fi, err := f.Stat() | 233 fi, err := f.Stat() |
234 if err != nil { | 234 if err != nil { |
235 return err | 235 return err |
236 } | 236 } |
237 zipr, err := zip.NewReader(f, fi.Size()) | 237 zipr, err := zip.NewReader(f, fi.Size()) |
238 if err != nil { | 238 if err != nil { |
239 » » return errgo.Annotate(err, "cannot open charm archive") | 239 » » return errors.Annotate(err, "cannot open charm archive") |
240 } | 240 } |
241 | 241 |
242 // Find out the root dir prefix from the archive. | 242 // Find out the root dir prefix from the archive. |
243 rootDir, err := h.findArchiveRootDir(zipr) | 243 rootDir, err := h.findArchiveRootDir(zipr) |
244 if err != nil { | 244 if err != nil { |
245 » » return errgo.Annotate(err, "cannot read charm archive") | 245 » » return errors.Annotate(err, "cannot read charm archive") |
246 } | 246 } |
247 if rootDir == "." { | 247 if rootDir == "." { |
248 // Normal charm, just use charm.ReadBundle(). | 248 // Normal charm, just use charm.ReadBundle(). |
249 return nil | 249 return nil |
250 } | 250 } |
251 | 251 |
252 // There is one or more subdirs, so we need extract it to a temp | 252 // There is one or more subdirs, so we need extract it to a temp |
253 // dir and then read it as a charm dir. | 253 // dir and then read it as a charm dir. |
254 tempDir, err := ioutil.TempDir("", "charm-extract") | 254 tempDir, err := ioutil.TempDir("", "charm-extract") |
255 if err != nil { | 255 if err != nil { |
256 » » return errgo.Annotate(err, "cannot create temp directory") | 256 » » return errors.Annotate(err, "cannot create temp directory") |
257 } | 257 } |
258 defer os.RemoveAll(tempDir) | 258 defer os.RemoveAll(tempDir) |
259 if err := ziputil.Extract(zipr, tempDir, rootDir); err != nil { | 259 if err := ziputil.Extract(zipr, tempDir, rootDir); err != nil { |
260 » » return errgo.Annotate(err, "cannot extract charm archive") | 260 » » return errors.Annotate(err, "cannot extract charm archive") |
261 } | 261 } |
262 dir, err := charm.ReadDir(tempDir) | 262 dir, err := charm.ReadDir(tempDir) |
263 if err != nil { | 263 if err != nil { |
264 » » return errgo.Annotate(err, "cannot read extracted archive") | 264 » » return errors.Annotate(err, "cannot read extracted archive") |
265 } | 265 } |
266 | 266 |
267 // Now repackage the dir as a bundle at the original path. | 267 // Now repackage the dir as a bundle at the original path. |
268 if err := f.Truncate(0); err != nil { | 268 if err := f.Truncate(0); err != nil { |
269 return err | 269 return err |
270 } | 270 } |
271 if err := dir.BundleTo(f); err != nil { | 271 if err := dir.BundleTo(f); err != nil { |
272 return err | 272 return err |
273 } | 273 } |
274 return nil | 274 return nil |
(...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
306 func (d byDepth) Less(i, j int) bool { return depth(d[i]) < depth(d[j]) } | 306 func (d byDepth) Less(i, j int) bool { return depth(d[i]) < depth(d[j]) } |
307 | 307 |
308 // repackageAndUploadCharm expands the given charm archive to a | 308 // repackageAndUploadCharm expands the given charm archive to a |
309 // temporary directoy, repackages it with the given curl's revision, | 309 // temporary directoy, repackages it with the given curl's revision, |
310 // then uploads it to providr storage, and finally updates the state. | 310 // then uploads it to providr storage, and finally updates the state. |
311 func (h *charmsHandler) repackageAndUploadCharm(archive *charm.Bundle, curl *cha
rm.URL) error { | 311 func (h *charmsHandler) repackageAndUploadCharm(archive *charm.Bundle, curl *cha
rm.URL) error { |
312 // Create a temp dir to contain the extracted charm | 312 // Create a temp dir to contain the extracted charm |
313 // dir and the repackaged archive. | 313 // dir and the repackaged archive. |
314 tempDir, err := ioutil.TempDir("", "charm-download") | 314 tempDir, err := ioutil.TempDir("", "charm-download") |
315 if err != nil { | 315 if err != nil { |
316 » » return errgo.Annotate(err, "cannot create temp directory") | 316 » » return errors.Annotate(err, "cannot create temp directory") |
317 } | 317 } |
318 defer os.RemoveAll(tempDir) | 318 defer os.RemoveAll(tempDir) |
319 extractPath := filepath.Join(tempDir, "extracted") | 319 extractPath := filepath.Join(tempDir, "extracted") |
320 repackagedPath := filepath.Join(tempDir, "repackaged.zip") | 320 repackagedPath := filepath.Join(tempDir, "repackaged.zip") |
321 repackagedArchive, err := os.Create(repackagedPath) | 321 repackagedArchive, err := os.Create(repackagedPath) |
322 if err != nil { | 322 if err != nil { |
323 » » return errgo.Annotate(err, "cannot repackage uploaded charm") | 323 » » return errors.Annotate(err, "cannot repackage uploaded charm") |
324 } | 324 } |
325 defer repackagedArchive.Close() | 325 defer repackagedArchive.Close() |
326 | 326 |
327 // Expand and repack it with the revision specified by curl. | 327 // Expand and repack it with the revision specified by curl. |
328 archive.SetRevision(curl.Revision) | 328 archive.SetRevision(curl.Revision) |
329 if err := archive.ExpandTo(extractPath); err != nil { | 329 if err := archive.ExpandTo(extractPath); err != nil { |
330 » » return errgo.Annotate(err, "cannot extract uploaded charm") | 330 » » return errors.Annotate(err, "cannot extract uploaded charm") |
331 } | 331 } |
332 charmDir, err := charm.ReadDir(extractPath) | 332 charmDir, err := charm.ReadDir(extractPath) |
333 if err != nil { | 333 if err != nil { |
334 » » return errgo.Annotate(err, "cannot read extracted charm") | 334 » » return errors.Annotate(err, "cannot read extracted charm") |
335 } | 335 } |
336 | 336 |
337 // Bundle the charm and calculate its sha256 hash at the | 337 // Bundle the charm and calculate its sha256 hash at the |
338 // same time. | 338 // same time. |
339 hash := sha256.New() | 339 hash := sha256.New() |
340 err = charmDir.BundleTo(io.MultiWriter(hash, repackagedArchive)) | 340 err = charmDir.BundleTo(io.MultiWriter(hash, repackagedArchive)) |
341 if err != nil { | 341 if err != nil { |
342 » » return errgo.Annotate(err, "cannot repackage uploaded charm") | 342 » » return errors.Annotate(err, "cannot repackage uploaded charm") |
343 } | 343 } |
344 bundleSHA256 := hex.EncodeToString(hash.Sum(nil)) | 344 bundleSHA256 := hex.EncodeToString(hash.Sum(nil)) |
345 size, err := repackagedArchive.Seek(0, 2) | 345 size, err := repackagedArchive.Seek(0, 2) |
346 if err != nil { | 346 if err != nil { |
347 » » return errgo.Annotate(err, "cannot get charm file size") | 347 » » return errors.Annotate(err, "cannot get charm file size") |
348 } | 348 } |
349 | 349 |
350 // Now upload to provider storage. | 350 // Now upload to provider storage. |
351 if _, err := repackagedArchive.Seek(0, 0); err != nil { | 351 if _, err := repackagedArchive.Seek(0, 0); err != nil { |
352 » » return errgo.Annotate(err, "cannot rewind the charm file reader"
) | 352 » » return errors.Annotate(err, "cannot rewind the charm file reader
") |
353 } | 353 } |
354 storage, err := environs.GetStorage(h.state) | 354 storage, err := environs.GetStorage(h.state) |
355 if err != nil { | 355 if err != nil { |
356 » » return errgo.Annotate(err, "cannot access provider storage") | 356 » » return errors.Annotate(err, "cannot access provider storage") |
357 } | 357 } |
358 name := charm.Quote(curl.String()) | 358 name := charm.Quote(curl.String()) |
359 if err := storage.Put(name, repackagedArchive, size); err != nil { | 359 if err := storage.Put(name, repackagedArchive, size); err != nil { |
360 » » return errgo.Annotate(err, "cannot upload charm to provider stor
age") | 360 » » return errors.Annotate(err, "cannot upload charm to provider sto
rage") |
361 } | 361 } |
362 storageURL, err := storage.URL(name) | 362 storageURL, err := storage.URL(name) |
363 if err != nil { | 363 if err != nil { |
364 » » return errgo.Annotate(err, "cannot get storage URL for charm") | 364 » » return errors.Annotate(err, "cannot get storage URL for charm") |
365 } | 365 } |
366 bundleURL, err := url.Parse(storageURL) | 366 bundleURL, err := url.Parse(storageURL) |
367 if err != nil { | 367 if err != nil { |
368 » » return errgo.Annotate(err, "cannot parse storage URL") | 368 » » return errors.Annotate(err, "cannot parse storage URL") |
369 } | 369 } |
370 | 370 |
371 // And finally, update state. | 371 // And finally, update state. |
372 _, err = h.state.UpdateUploadedCharm(archive, curl, bundleURL, bundleSHA
256) | 372 _, err = h.state.UpdateUploadedCharm(archive, curl, bundleURL, bundleSHA
256) |
373 if err != nil { | 373 if err != nil { |
374 » » return errgo.Annotate(err, "cannot update uploaded charm in stat
e") | 374 » » return errors.Annotate(err, "cannot update uploaded charm in sta
te") |
375 } | 375 } |
376 return nil | 376 return nil |
377 } | 377 } |
378 | 378 |
379 // processGet handles a charm file GET request after authentication. | 379 // processGet handles a charm file GET request after authentication. |
380 // It returns the bundle path, the requested file path (if any) and an error. | 380 // It returns the bundle path, the requested file path (if any) and an error. |
381 func (h *charmsHandler) processGet(r *http.Request) (string, string, error) { | 381 func (h *charmsHandler) processGet(r *http.Request) (string, string, error) { |
382 query := r.URL.Query() | 382 query := r.URL.Query() |
383 | 383 |
384 // Retrieve and validate query parameters. | 384 // Retrieve and validate query parameters. |
(...skipping 24 matching lines...) Expand all Loading... |
409 } | 409 } |
410 return charmArchivePath, filePath, nil | 410 return charmArchivePath, filePath, nil |
411 } | 411 } |
412 | 412 |
413 // downloadCharm downloads the given charm name from the provider storage and | 413 // downloadCharm downloads the given charm name from the provider storage and |
414 // saves the corresponding zip archive to the given charmArchivePath. | 414 // saves the corresponding zip archive to the given charmArchivePath. |
415 func (h *charmsHandler) downloadCharm(name, charmArchivePath string) error { | 415 func (h *charmsHandler) downloadCharm(name, charmArchivePath string) error { |
416 // Get the provider storage. | 416 // Get the provider storage. |
417 storage, err := environs.GetStorage(h.state) | 417 storage, err := environs.GetStorage(h.state) |
418 if err != nil { | 418 if err != nil { |
419 » » return errgo.Annotate(err, "cannot access provider storage") | 419 » » return errors.Annotate(err, "cannot access provider storage") |
420 } | 420 } |
421 | 421 |
422 // Use the storage to retrieve and save the charm archive. | 422 // Use the storage to retrieve and save the charm archive. |
423 reader, err := storage.Get(name) | 423 reader, err := storage.Get(name) |
424 if err != nil { | 424 if err != nil { |
425 » » return errgo.Annotate(err, "charm not found in the provider stor
age") | 425 » » return errors.Annotate(err, "charm not found in the provider sto
rage") |
426 } | 426 } |
427 defer reader.Close() | 427 defer reader.Close() |
428 data, err := ioutil.ReadAll(reader) | 428 data, err := ioutil.ReadAll(reader) |
429 if err != nil { | 429 if err != nil { |
430 » » return errgo.Annotate(err, "cannot read charm data") | 430 » » return errors.Annotate(err, "cannot read charm data") |
431 } | 431 } |
432 // In order to avoid races, the archive is saved in a temporary file whi
ch | 432 // In order to avoid races, the archive is saved in a temporary file whi
ch |
433 // is then atomically renamed. The temporary file is created in the | 433 // is then atomically renamed. The temporary file is created in the |
434 // charm cache directory so that we can safely assume the rename source
and | 434 // charm cache directory so that we can safely assume the rename source
and |
435 // target live in the same file system. | 435 // target live in the same file system. |
436 cacheDir := filepath.Dir(charmArchivePath) | 436 cacheDir := filepath.Dir(charmArchivePath) |
437 if err = os.MkdirAll(cacheDir, 0755); err != nil { | 437 if err = os.MkdirAll(cacheDir, 0755); err != nil { |
438 » » return errgo.Annotate(err, "cannot create the charms cache") | 438 » » return errors.Annotate(err, "cannot create the charms cache") |
439 } | 439 } |
440 tempCharmArchive, err := ioutil.TempFile(cacheDir, "charm") | 440 tempCharmArchive, err := ioutil.TempFile(cacheDir, "charm") |
441 if err != nil { | 441 if err != nil { |
442 » » return errgo.Annotate(err, "cannot create charm archive temp fil
e") | 442 » » return errors.Annotate(err, "cannot create charm archive temp fi
le") |
443 } | 443 } |
444 defer tempCharmArchive.Close() | 444 defer tempCharmArchive.Close() |
445 if err = ioutil.WriteFile(tempCharmArchive.Name(), data, 0644); err != n
il { | 445 if err = ioutil.WriteFile(tempCharmArchive.Name(), data, 0644); err != n
il { |
446 » » return errgo.Annotate(err, "error processing charm archive downl
oad") | 446 » » return errors.Annotate(err, "error processing charm archive down
load") |
447 } | 447 } |
448 if err = os.Rename(tempCharmArchive.Name(), charmArchivePath); err != ni
l { | 448 if err = os.Rename(tempCharmArchive.Name(), charmArchivePath); err != ni
l { |
449 defer os.Remove(tempCharmArchive.Name()) | 449 defer os.Remove(tempCharmArchive.Name()) |
450 » » return errgo.Annotate(err, "error renaming the charm archive") | 450 » » return errors.Annotate(err, "error renaming the charm archive") |
451 } | 451 } |
452 return nil | 452 return nil |
453 } | 453 } |
OLD | NEW |