github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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/charm/v12" 12 "github.com/juju/errors" 13 14 "github.com/juju/juju/downloader" 15 ) 16 17 // Download exposes the downloader.Download methods needed here. 18 type Downloader interface { 19 // Download starts a new charm archive download, waits for it to 20 // complete, and returns the local name of the file. 21 Download(req downloader.Request) (string, error) 22 } 23 24 // BundlesDir is responsible for storing and retrieving charm bundles 25 // identified by state charms. 26 type BundlesDir struct { 27 path string 28 downloader Downloader 29 logger Logger 30 } 31 32 // NewBundlesDir returns a new BundlesDir which uses path for storage. 33 func NewBundlesDir(path string, dlr Downloader, logger Logger) *BundlesDir { 34 if dlr == nil { 35 dlr = downloader.New(downloader.NewArgs{ 36 HostnameVerification: false, 37 }) 38 } 39 return &BundlesDir{ 40 path: path, 41 downloader: dlr, 42 logger: logger, 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{}) error { 66 // First download... 67 curl, err := url.Parse(info.URL()) 68 if err != nil { 69 return errors.Annotate(err, "could not parse charm URL") 70 } 71 expectedSha256, err := info.ArchiveSha256() 72 if err != nil { 73 return errors.Annotatef(err, "failed to get archive sha256 for charm %q", info.URL()) 74 } 75 req := downloader.Request{ 76 ArchiveSha256: expectedSha256, 77 URL: curl, 78 TargetDir: downloadsPath(d.path), 79 Verify: downloader.NewSha256Verifier(expectedSha256), 80 Abort: abort, 81 } 82 d.logger.Infof("downloading %s from API server", info.URL()) 83 filename, err := d.downloader.Download(req) 84 if err != nil { 85 return errors.Annotatef(err, "failed to download charm %q from API server", info.URL()) 86 } 87 defer errors.DeferredAnnotatef(&err, "downloaded but failed to copy charm to %q from %q", target, filename) 88 89 // ...then move the right location. 90 if err := os.MkdirAll(d.path, 0755); err != nil { 91 return errors.Trace(err) 92 } 93 if err := os.Rename(filename, target); err != nil { 94 return errors.Trace(err) 95 } 96 return nil 97 } 98 99 // bundlePath returns the path to the location where the verified charm 100 // bundle identified by info will be, or has been, saved. 101 func (d *BundlesDir) bundlePath(info BundleInfo) string { 102 return d.bundleURLPath(info.URL()) 103 } 104 105 // bundleURLPath returns the path to the location where the verified charm 106 // bundle identified by url will be, or has been, saved. 107 func (d *BundlesDir) bundleURLPath(url string) string { 108 return path.Join(d.path, charm.Quote(url)) 109 } 110 111 // ClearDownloads removes any entries in the temporary bundle download 112 // directory. It is intended to be called on uniter startup. 113 func ClearDownloads(bundlesDir string) error { 114 downloadDir := downloadsPath(bundlesDir) 115 err := os.RemoveAll(downloadDir) 116 return errors.Annotate(err, "unable to clear bundle downloads") 117 } 118 119 // downloadsPath returns the path to the directory into which charms are 120 // downloaded. 121 func downloadsPath(bunsDir string) string { 122 return path.Join(bunsDir, "downloads") 123 }