launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/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  	"os"
     8  	"path"
     9  
    10  	"launchpad.net/errgo/errors"
    11  	"launchpad.net/juju-core/charm"
    12  	"launchpad.net/juju-core/downloader"
    13  	"launchpad.net/juju-core/log"
    14  	"launchpad.net/juju-core/state/api/uniter"
    15  	"launchpad.net/juju-core/utils"
    16  )
    17  
    18  var mask = errors.Mask
    19  
    20  // BundlesDir is responsible for storing and retrieving charm bundles
    21  // identified by state charms.
    22  type BundlesDir struct {
    23  	path string
    24  }
    25  
    26  // NewBundlesDir returns a new BundlesDir which uses path for storage.
    27  func NewBundlesDir(path string) *BundlesDir {
    28  	return &BundlesDir{path}
    29  }
    30  
    31  // Read returns a charm bundle from the directory. If no bundle exists yet,
    32  // one will be downloaded and validated and copied into the directory before
    33  // being returned. Downloads will be aborted if a value is received on abort.
    34  func (d *BundlesDir) Read(sch *uniter.Charm, abort <-chan struct{}) (*charm.Bundle, error) {
    35  	path := d.bundlePath(sch)
    36  	if _, err := os.Stat(path); err != nil {
    37  		if !os.IsNotExist(err) {
    38  			return nil, err
    39  		} else if err = d.download(sch, abort); err != nil {
    40  			return nil, mask(err)
    41  		}
    42  	}
    43  	return charm.ReadBundle(path)
    44  }
    45  
    46  // download fetches the supplied charm and checks that it has the correct sha256
    47  // hash, then copies it into the directory. If a value is received on abort, the
    48  // download will be stopped.
    49  func (d *BundlesDir) download(sch *uniter.Charm, abort <-chan struct{}) (err error) {
    50  	archiveURL, disableSSLHostnameVerification, err := sch.ArchiveURL()
    51  	if err != nil {
    52  		return mask(err)
    53  	}
    54  	defer utils.ErrorContextf(&err, "failed to download charm %q from %q", sch.URL(), archiveURL)
    55  	dir := d.downloadsPath()
    56  	if err := os.MkdirAll(dir, 0755); err != nil {
    57  		return mask(err)
    58  	}
    59  	aurl := archiveURL.String()
    60  	log.Infof("worker/uniter/charm: downloading %s from %s", sch.URL(), aurl)
    61  	if disableSSLHostnameVerification {
    62  		log.Infof("worker/uniter/charm: SSL hostname verification disabled")
    63  	}
    64  	dl := downloader.New(aurl, dir, disableSSLHostnameVerification)
    65  	defer dl.Stop()
    66  	for {
    67  		select {
    68  		case <-abort:
    69  			log.Infof("worker/uniter/charm: download aborted")
    70  			return errors.Newf("aborted")
    71  		case st := <-dl.Done():
    72  			if st.Err != nil {
    73  				return st.Err
    74  			}
    75  			log.Infof("worker/uniter/charm: download complete")
    76  			defer st.File.Close()
    77  			actualSha256, _, err := utils.ReadSHA256(st.File)
    78  			if err != nil {
    79  				return mask(err)
    80  			}
    81  			archiveSha256, err := sch.ArchiveSha256()
    82  			if err != nil {
    83  				return mask(err)
    84  			}
    85  			if actualSha256 != archiveSha256 {
    86  				return errors.Newf(
    87  					"expected sha256 %q, got %q", archiveSha256, actualSha256,
    88  				)
    89  			}
    90  			log.Infof("worker/uniter/charm: download verified")
    91  			if err := os.MkdirAll(d.path, 0755); err != nil {
    92  				return mask(err)
    93  			}
    94  			return os.Rename(st.File.Name(), d.bundlePath(sch))
    95  		}
    96  	}
    97  }
    98  
    99  // bundlePath returns the path to the location where the verified charm
   100  // bundle identified by sch will be, or has been, saved.
   101  func (d *BundlesDir) bundlePath(sch *uniter.Charm) string {
   102  	return path.Join(d.path, charm.Quote(sch.URL().String()))
   103  }
   104  
   105  // downloadsPath returns the path to the directory into which charms are
   106  // downloaded.
   107  func (d *BundlesDir) downloadsPath() string {
   108  	return path.Join(d.path, "downloads")
   109  }