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