github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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, abort <-chan struct{}) (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  
    40  	return &BundlesDir{
    41  		path:       path,
    42  		downloader: dlr,
    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{}) (err error) {
    66  	// First download...
    67  	curl, err := url.Parse(info.URL().String())
    68  	if err != nil {
    69  		return errors.Annotate(err, "could not parse charm URL")
    70  	}
    71  	expectedSha256, err := info.ArchiveSha256()
    72  	req := downloader.Request{
    73  		URL:       curl,
    74  		TargetDir: d.downloadsPath(),
    75  		Verify:    downloader.NewSha256Verifier(expectedSha256),
    76  	}
    77  	logger.Infof("downloading %s from API server", info.URL())
    78  	filename, err := d.downloader.Download(req, abort)
    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  // downloadsPath returns the path to the directory into which charms are
   107  // downloaded.
   108  func (d *BundlesDir) downloadsPath() string {
   109  	return path.Join(d.path, "downloads")
   110  }