github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "fmt" 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 // BundlesDir is responsible for storing and retrieving charm bundles 19 // identified by state charms. 20 type BundlesDir struct { 21 path string 22 } 23 24 // NewBundlesDir returns a new BundlesDir which uses path for storage. 25 func NewBundlesDir(path string) *BundlesDir { 26 return &BundlesDir{path} 27 } 28 29 // Read returns a charm bundle from the directory. If no bundle exists yet, 30 // one will be downloaded and validated and copied into the directory before 31 // being returned. Downloads will be aborted if a value is received on abort. 32 func (d *BundlesDir) Read(info BundleInfo, abort <-chan struct{}) (Bundle, error) { 33 path := d.bundlePath(info) 34 if _, err := os.Stat(path); err != nil { 35 if !os.IsNotExist(err) { 36 return nil, err 37 } else if err = d.download(info, abort); err != nil { 38 return nil, err 39 } 40 } 41 return charm.ReadCharmArchive(path) 42 } 43 44 // download fetches the supplied charm and checks that it has the correct sha256 45 // hash, then copies it into the directory. If a value is received on abort, the 46 // download will be stopped. 47 func (d *BundlesDir) download(info BundleInfo, abort <-chan struct{}) (err error) { 48 archiveURLs, err := info.ArchiveURLs() 49 if err != nil { 50 return errors.Annotatef(err, "failed to get download URLs for charm %q", info.URL()) 51 } 52 defer errors.DeferredAnnotatef(&err, "failed to download charm %q from %q", info.URL(), archiveURLs) 53 dir := d.downloadsPath() 54 if err := os.MkdirAll(dir, 0755); err != nil { 55 return err 56 } 57 var st downloader.Status 58 for _, archiveURL := range archiveURLs { 59 aurl := archiveURL.String() 60 logger.Infof("downloading %s from %s", info.URL(), aurl) 61 st, err = tryDownload(aurl, dir, abort) 62 if err == nil { 63 break 64 } 65 } 66 if err != nil { 67 return err 68 } 69 logger.Infof("download complete") 70 defer st.File.Close() 71 actualSha256, _, err := utils.ReadSHA256(st.File) 72 if err != nil { 73 return err 74 } 75 archiveSha256, err := info.ArchiveSha256() 76 if err != nil { 77 return err 78 } 79 if actualSha256 != archiveSha256 { 80 return fmt.Errorf( 81 "expected sha256 %q, got %q", archiveSha256, actualSha256, 82 ) 83 } 84 logger.Infof("download verified") 85 if err := os.MkdirAll(d.path, 0755); err != nil { 86 return err 87 } 88 // Renaming an open file is not possible on Windows 89 st.File.Close() 90 return os.Rename(st.File.Name(), d.bundlePath(info)) 91 } 92 93 func tryDownload(url, dir string, abort <-chan struct{}) (downloader.Status, error) { 94 // Downloads always go through the API server, which at 95 // present cannot be verified due to the certificates 96 // being inadequate. We always verify the SHA-256 hash, 97 // and the data transferred is not sensitive, so this 98 // does not pose a problem. 99 dl := downloader.New(url, dir, utils.NoVerifySSLHostnames) 100 defer dl.Stop() 101 select { 102 case <-abort: 103 logger.Infof("download aborted") 104 return downloader.Status{}, errors.New("aborted") 105 case st := <-dl.Done(): 106 if st.Err != nil { 107 return downloader.Status{}, st.Err 108 } 109 return st, nil 110 } 111 } 112 113 // bundlePath returns the path to the location where the verified charm 114 // bundle identified by info will be, or has been, saved. 115 func (d *BundlesDir) bundlePath(info BundleInfo) string { 116 return d.bundleURLPath(info.URL()) 117 } 118 119 // bundleURLPath returns the path to the location where the verified charm 120 // bundle identified by url will be, or has been, saved. 121 func (d *BundlesDir) bundleURLPath(url *charm.URL) string { 122 return path.Join(d.path, charm.Quote(url.String())) 123 } 124 125 // downloadsPath returns the path to the directory into which charms are 126 // downloaded. 127 func (d *BundlesDir) downloadsPath() string { 128 return path.Join(d.path, "downloads") 129 }