github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/worker/uniter/charm/bundles.go (about) 1 // Copyright 2012-2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package charm 5 6 import ( 7 "net/url" 8 "os" 9 "path" 10 11 "github.com/juju/errors" 12 "github.com/juju/utils" 13 "gopkg.in/juju/charm.v6-unstable" 14 15 "github.com/juju/juju/downloader" 16 ) 17 18 // Download exposes the downloader.Download methods needed here. 19 type Downloader interface { 20 // Download starts a new charm archive download, waits for it to 21 // complete, and returns the local name of the file. 22 Download(req downloader.Request, abort <-chan struct{}) (string, error) 23 } 24 25 // BundlesDir is responsible for storing and retrieving charm bundles 26 // identified by state charms. 27 type BundlesDir struct { 28 path string 29 downloader Downloader 30 } 31 32 // NewBundlesDir returns a new BundlesDir which uses path for storage. 33 func NewBundlesDir(path string, dlr Downloader) *BundlesDir { 34 if dlr == nil { 35 dlr = downloader.New(downloader.NewArgs{ 36 HostnameVerification: utils.NoVerifySSLHostnames, 37 }) 38 } 39 40 return &BundlesDir{ 41 path: path, 42 downloader: dlr, 43 } 44 } 45 46 // Read returns a charm bundle from the directory. If no bundle exists yet, 47 // one will be downloaded and validated and copied into the directory before 48 // being returned. Downloads will be aborted if a value is received on abort. 49 func (d *BundlesDir) Read(info BundleInfo, abort <-chan struct{}) (Bundle, error) { 50 path := d.bundlePath(info) 51 if _, err := os.Stat(path); err != nil { 52 if !os.IsNotExist(err) { 53 return nil, err 54 } 55 if err := d.download(info, path, abort); err != nil { 56 return nil, err 57 } 58 } 59 return charm.ReadCharmArchive(path) 60 } 61 62 // download fetches the supplied charm and checks that it has the correct sha256 63 // hash, then copies it into the directory. If a value is received on abort, the 64 // download will be stopped. 65 func (d *BundlesDir) download(info BundleInfo, target string, abort <-chan struct{}) (err error) { 66 // First download... 67 curl, err := url.Parse(info.URL().String()) 68 if err != nil { 69 return errors.Annotate(err, "could not parse charm URL") 70 } 71 expectedSha256, err := info.ArchiveSha256() 72 req := downloader.Request{ 73 URL: curl, 74 TargetDir: d.downloadsPath(), 75 Verify: downloader.NewSha256Verifier(expectedSha256), 76 } 77 logger.Infof("downloading %s from API server", info.URL()) 78 filename, err := d.downloader.Download(req, abort) 79 if err != nil { 80 return errors.Annotatef(err, "failed to download charm %q from API server", info.URL()) 81 } 82 defer errors.DeferredAnnotatef(&err, "downloaded but failed to copy charm to %q from %q", target, filename) 83 84 // ...then move the right location. 85 if err := os.MkdirAll(d.path, 0755); err != nil { 86 return errors.Trace(err) 87 } 88 if err := os.Rename(filename, target); err != nil { 89 return errors.Trace(err) 90 } 91 return nil 92 } 93 94 // bundlePath returns the path to the location where the verified charm 95 // bundle identified by info will be, or has been, saved. 96 func (d *BundlesDir) bundlePath(info BundleInfo) string { 97 return d.bundleURLPath(info.URL()) 98 } 99 100 // bundleURLPath returns the path to the location where the verified charm 101 // bundle identified by url will be, or has been, saved. 102 func (d *BundlesDir) bundleURLPath(url *charm.URL) string { 103 return path.Join(d.path, charm.Quote(url.String())) 104 } 105 106 // downloadsPath returns the path to the directory into which charms are 107 // downloaded. 108 func (d *BundlesDir) downloadsPath() string { 109 return path.Join(d.path, "downloads") 110 }