github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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 14 "github.com/juju/juju/charm" 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.ReadBundle(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 archiveURL, disableSSLHostnameVerification, err := info.ArchiveURL() 49 if err != nil { 50 return err 51 } 52 defer errors.Maskf(&err, "failed to download charm %q from %q", info.URL(), archiveURL) 53 dir := d.downloadsPath() 54 if err := os.MkdirAll(dir, 0755); err != nil { 55 return err 56 } 57 aurl := archiveURL.String() 58 logger.Infof("downloading %s from %s", info.URL(), aurl) 59 if disableSSLHostnameVerification { 60 logger.Infof("SSL hostname verification disabled") 61 } 62 dl := downloader.New(aurl, dir, disableSSLHostnameVerification) 63 defer dl.Stop() 64 for { 65 select { 66 case <-abort: 67 logger.Infof("download aborted") 68 return fmt.Errorf("aborted") 69 case st := <-dl.Done(): 70 if st.Err != nil { 71 return st.Err 72 } 73 logger.Infof("download complete") 74 defer st.File.Close() 75 actualSha256, _, err := utils.ReadSHA256(st.File) 76 if err != nil { 77 return err 78 } 79 archiveSha256, err := info.ArchiveSha256() 80 if err != nil { 81 return err 82 } 83 if actualSha256 != archiveSha256 { 84 return fmt.Errorf( 85 "expected sha256 %q, got %q", archiveSha256, actualSha256, 86 ) 87 } 88 logger.Infof("download verified") 89 if err := os.MkdirAll(d.path, 0755); err != nil { 90 return err 91 } 92 return os.Rename(st.File.Name(), d.bundlePath(info)) 93 } 94 } 95 } 96 97 // bundlePath returns the path to the location where the verified charm 98 // bundle identified by info will be, or has been, saved. 99 func (d *BundlesDir) bundlePath(info BundleInfo) string { 100 return d.bundleURLPath(info.URL()) 101 } 102 103 // bundleURLPath returns the path to the location where the verified charm 104 // bundle identified by url will be, or has been, saved. 105 func (d *BundlesDir) bundleURLPath(url *charm.URL) string { 106 return path.Join(d.path, charm.Quote(url.String())) 107 } 108 109 // downloadsPath returns the path to the directory into which charms are 110 // downloaded. 111 func (d *BundlesDir) downloadsPath() string { 112 return path.Join(d.path, "downloads") 113 }