github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/worker/uniter/charm/charm.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package charm 5 6 import ( 7 "fmt" 8 "net/url" 9 "os" 10 "runtime" 11 // "path" 12 "path/filepath" 13 14 "launchpad.net/juju-core/charm" 15 "launchpad.net/juju-core/downloader" 16 "launchpad.net/juju-core/log" 17 "launchpad.net/juju-core/utils" 18 ) 19 20 // BundleReader primarily exists to make BundlesDir mockable. 21 type BundleReader interface { 22 23 // Read returns the bundle identified by the supplied info. The abort chan 24 // can be used to notify an implementation that it need not complete the 25 // operation, and can immediately error out if it is convenient to do so. 26 Read(bi BundleInfo, abort <-chan struct{}) (*charm.Bundle, error) 27 } 28 29 // BundleInfo holds bundle information for a charm. 30 type BundleInfo interface { 31 URL() *charm.URL 32 ArchiveURL() (*url.URL, bool, error) 33 ArchiveSha256() (string, error) 34 } 35 36 // BundlesDir is responsible for storing and retrieving charm bundles 37 // identified by state charms. 38 type BundlesDir struct { 39 path string 40 } 41 42 // NewBundlesDir returns a new BundlesDir which uses path for storage. 43 func NewBundlesDir(path string) *BundlesDir { 44 return &BundlesDir{path} 45 } 46 47 // Read returns a charm bundle from the directory. If no bundle exists yet, 48 // one will be downloaded and validated and copied into the directory before 49 // being returned. Downloads will be aborted if a value is received on abort. 50 func (d *BundlesDir) Read(info BundleInfo, abort <-chan struct{}) (*charm.Bundle, error) { 51 path := d.bundlePath(info) 52 if _, err := os.Stat(path); err != nil { 53 if !os.IsNotExist(err) { 54 return nil, err 55 } else if err = d.download(info, abort); err != nil { 56 return nil, err 57 } 58 } 59 return charm.ReadBundle(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, abort <-chan struct{}) (err error) { 66 archiveURL, disableSSLHostnameVerification, err := info.ArchiveURL() 67 if err != nil { 68 return err 69 } 70 defer utils.ErrorContextf(&err, "failed to download charm %q from %q", info.URL(), archiveURL) 71 dir := d.downloadsPath() 72 if err := os.MkdirAll(dir, 0755); err != nil { 73 return err 74 } 75 aurl := archiveURL.String() 76 log.Infof("worker/uniter/charm: downloading %s from %s", info.URL(), aurl) 77 if disableSSLHostnameVerification { 78 log.Infof("worker/uniter/charm: SSL hostname verification disabled") 79 } 80 dl := downloader.New(aurl, dir, disableSSLHostnameVerification) 81 defer dl.Stop() 82 for { 83 select { 84 case <-abort: 85 log.Infof("worker/uniter/charm: download aborted") 86 return fmt.Errorf("aborted") 87 case st := <-dl.Done(): 88 if st.Err != nil { 89 return st.Err 90 } 91 log.Infof("worker/uniter/charm: download complete") 92 actualSha256, _, err := utils.ReadSHA256(st.File) 93 // Renaming an open file is not possible on windows 94 if runtime.GOOS == "windows" { 95 st.File.Close() 96 }else{ 97 defer st.File.Close() 98 } 99 if err != nil { 100 return err 101 } 102 archiveSha256, err := info.ArchiveSha256() 103 if err != nil { 104 return err 105 } 106 if actualSha256 != archiveSha256 { 107 return fmt.Errorf( 108 "expected sha256 %q, got %q", archiveSha256, actualSha256, 109 ) 110 } 111 log.Infof("worker/uniter/charm: download verified") 112 if err := os.MkdirAll(d.path, 0755); err != nil { 113 return err 114 } 115 return os.Rename(st.File.Name(), d.bundlePath(info)) 116 } 117 } 118 } 119 120 // bundlePath returns the path to the location where the verified charm 121 // bundle identified by info will be, or has been, saved. 122 func (d *BundlesDir) bundlePath(info BundleInfo) string { 123 return d.bundleURLPath(info.URL()) 124 } 125 126 // bundleURLPath returns the path to the location where the verified charm 127 // bundle identified by url will be, or has been, saved. 128 func (d *BundlesDir) bundleURLPath(url *charm.URL) string { 129 return filepath.Join(d.path, charm.Quote(url.String())) 130 } 131 132 // downloadsPath returns the path to the directory into which charms are 133 // downloaded. 134 func (d *BundlesDir) downloadsPath() string { 135 return filepath.Join(d.path, "downloads") 136 }