github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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/errors"
    12  	"github.com/juju/utils"
    13  	"gopkg.in/juju/charm.v6-unstable"
    14  
    15  	"github.com/juju/juju/downloader"
    16  )
    17  
    18  // Download exposes the downloader.Download methods needed here.
    19  type Downloader interface {
    20  	// Download starts a new charm archive download, waits for it to
    21  	// complete, and returns the local name of the file.
    22  	Download(req downloader.Request) (string, error)
    23  }
    24  
    25  // BundlesDir is responsible for storing and retrieving charm bundles
    26  // identified by state charms.
    27  type BundlesDir struct {
    28  	path       string
    29  	downloader Downloader
    30  }
    31  
    32  // NewBundlesDir returns a new BundlesDir which uses path for storage.
    33  func NewBundlesDir(path string, dlr Downloader) *BundlesDir {
    34  	if dlr == nil {
    35  		dlr = downloader.New(downloader.NewArgs{
    36  			HostnameVerification: utils.NoVerifySSLHostnames,
    37  		})
    38  	}
    39  	return &BundlesDir{
    40  		path:       path,
    41  		downloader: dlr,
    42  	}
    43  }
    44  
    45  // Read returns a charm bundle from the directory. If no bundle exists yet,
    46  // one will be downloaded and validated and copied into the directory before
    47  // being returned. Downloads will be aborted if a value is received on abort.
    48  func (d *BundlesDir) Read(info BundleInfo, abort <-chan struct{}) (Bundle, error) {
    49  	path := d.bundlePath(info)
    50  	if _, err := os.Stat(path); err != nil {
    51  		if !os.IsNotExist(err) {
    52  			return nil, err
    53  		}
    54  		if err := d.download(info, path, abort); err != nil {
    55  			return nil, err
    56  		}
    57  	}
    58  	return charm.ReadCharmArchive(path)
    59  }
    60  
    61  // download fetches the supplied charm and checks that it has the correct sha256
    62  // hash, then copies it into the directory. If a value is received on abort, the
    63  // download will be stopped.
    64  func (d *BundlesDir) download(info BundleInfo, target string, abort <-chan struct{}) (err error) {
    65  	// First download...
    66  	curl, err := url.Parse(info.URL().String())
    67  	if err != nil {
    68  		return errors.Annotate(err, "could not parse charm URL")
    69  	}
    70  	expectedSha256, err := info.ArchiveSha256()
    71  	req := downloader.Request{
    72  		URL:       curl,
    73  		TargetDir: downloadsPath(d.path),
    74  		Verify:    downloader.NewSha256Verifier(expectedSha256),
    75  		Abort:     abort,
    76  	}
    77  	logger.Infof("downloading %s from API server", info.URL())
    78  	filename, err := d.downloader.Download(req)
    79  	if err != nil {
    80  		return errors.Annotatef(err, "failed to download charm %q from API server", info.URL())
    81  	}
    82  	defer errors.DeferredAnnotatef(&err, "downloaded but failed to copy charm to %q from %q", target, filename)
    83  
    84  	// ...then move the right location.
    85  	if err := os.MkdirAll(d.path, 0755); err != nil {
    86  		return errors.Trace(err)
    87  	}
    88  	if err := os.Rename(filename, target); err != nil {
    89  		return errors.Trace(err)
    90  	}
    91  	return nil
    92  }
    93  
    94  // bundlePath returns the path to the location where the verified charm
    95  // bundle identified by info will be, or has been, saved.
    96  func (d *BundlesDir) bundlePath(info BundleInfo) string {
    97  	return d.bundleURLPath(info.URL())
    98  }
    99  
   100  // bundleURLPath returns the path to the location where the verified charm
   101  // bundle identified by url will be, or has been, saved.
   102  func (d *BundlesDir) bundleURLPath(url *charm.URL) string {
   103  	return path.Join(d.path, charm.Quote(url.String()))
   104  }
   105  
   106  // ClearDownloads removes any entries in the temporary bundle download
   107  // directory. It is intended to be called on uniter startup.
   108  func ClearDownloads(bundlesDir string) error {
   109  	downloadDir := downloadsPath(bundlesDir)
   110  	err := os.RemoveAll(downloadDir)
   111  	return errors.Annotate(err, "unable to clear bundle downloads")
   112  }
   113  
   114  // downloadsPath returns the path to the directory into which charms are
   115  // downloaded.
   116  func downloadsPath(bunsDir string) string {
   117  	return path.Join(bunsDir, "downloads")
   118  }