github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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) (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 return &BundlesDir{ 40 path: path, 41 downloader: dlr, 42 } 43 } 44 45 // Read returns a charm bundle from the directory. If no bundle exists yet, 46 // one will be downloaded and validated and copied into the directory before 47 // being returned. Downloads will be aborted if a value is received on abort. 48 func (d *BundlesDir) Read(info BundleInfo, abort <-chan struct{}) (Bundle, error) { 49 path := d.bundlePath(info) 50 if _, err := os.Stat(path); err != nil { 51 if !os.IsNotExist(err) { 52 return nil, err 53 } 54 if err := d.download(info, path, abort); err != nil { 55 return nil, err 56 } 57 } 58 return charm.ReadCharmArchive(path) 59 } 60 61 // download fetches the supplied charm and checks that it has the correct sha256 62 // hash, then copies it into the directory. If a value is received on abort, the 63 // download will be stopped. 64 func (d *BundlesDir) download(info BundleInfo, target string, abort <-chan struct{}) (err error) { 65 // First download... 66 curl, err := url.Parse(info.URL().String()) 67 if err != nil { 68 return errors.Annotate(err, "could not parse charm URL") 69 } 70 expectedSha256, err := info.ArchiveSha256() 71 req := downloader.Request{ 72 URL: curl, 73 TargetDir: downloadsPath(d.path), 74 Verify: downloader.NewSha256Verifier(expectedSha256), 75 Abort: abort, 76 } 77 logger.Infof("downloading %s from API server", info.URL()) 78 filename, err := d.downloader.Download(req) 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 // ClearDownloads removes any entries in the temporary bundle download 107 // directory. It is intended to be called on uniter startup. 108 func ClearDownloads(bundlesDir string) error { 109 downloadDir := downloadsPath(bundlesDir) 110 err := os.RemoveAll(downloadDir) 111 return errors.Annotate(err, "unable to clear bundle downloads") 112 } 113 114 // downloadsPath returns the path to the directory into which charms are 115 // downloaded. 116 func downloadsPath(bunsDir string) string { 117 return path.Join(bunsDir, "downloads") 118 }