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  }