github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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  	"fmt"
     8  	"net/url"
     9  	"os"
    10  	"runtime"
    11  	// "path"
    12  	"path/filepath"
    13  
    14  	"launchpad.net/juju-core/charm"
    15  	"launchpad.net/juju-core/downloader"
    16  	"launchpad.net/juju-core/log"
    17  	"launchpad.net/juju-core/utils"
    18  )
    19  
    20  // BundleReader primarily exists to make BundlesDir mockable.
    21  type BundleReader interface {
    22  
    23  	// Read returns the bundle identified by the supplied info. The abort chan
    24  	// can be used to notify an implementation that it need not complete the
    25  	// operation, and can immediately error out if it is convenient to do so.
    26  	Read(bi BundleInfo, abort <-chan struct{}) (*charm.Bundle, error)
    27  }
    28  
    29  // BundleInfo holds bundle information for a charm.
    30  type BundleInfo interface {
    31  	URL() *charm.URL
    32  	ArchiveURL() (*url.URL, bool, error)
    33  	ArchiveSha256() (string, error)
    34  }
    35  
    36  // BundlesDir is responsible for storing and retrieving charm bundles
    37  // identified by state charms.
    38  type BundlesDir struct {
    39  	path string
    40  }
    41  
    42  // NewBundlesDir returns a new BundlesDir which uses path for storage.
    43  func NewBundlesDir(path string) *BundlesDir {
    44  	return &BundlesDir{path}
    45  }
    46  
    47  // Read returns a charm bundle from the directory. If no bundle exists yet,
    48  // one will be downloaded and validated and copied into the directory before
    49  // being returned. Downloads will be aborted if a value is received on abort.
    50  func (d *BundlesDir) Read(info BundleInfo, abort <-chan struct{}) (*charm.Bundle, error) {
    51  	path := d.bundlePath(info)
    52  	if _, err := os.Stat(path); err != nil {
    53  		if !os.IsNotExist(err) {
    54  			return nil, err
    55  		} else if err = d.download(info, abort); err != nil {
    56  			return nil, err
    57  		}
    58  	}
    59  	return charm.ReadBundle(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, abort <-chan struct{}) (err error) {
    66  	archiveURL, disableSSLHostnameVerification, err := info.ArchiveURL()
    67  	if err != nil {
    68  		return err
    69  	}
    70  	defer utils.ErrorContextf(&err, "failed to download charm %q from %q", info.URL(), archiveURL)
    71  	dir := d.downloadsPath()
    72  	if err := os.MkdirAll(dir, 0755); err != nil {
    73  		return err
    74  	}
    75  	aurl := archiveURL.String()
    76  	log.Infof("worker/uniter/charm: downloading %s from %s", info.URL(), aurl)
    77  	if disableSSLHostnameVerification {
    78  		log.Infof("worker/uniter/charm: SSL hostname verification disabled")
    79  	}
    80  	dl := downloader.New(aurl, dir, disableSSLHostnameVerification)
    81  	defer dl.Stop()
    82  	for {
    83  		select {
    84  		case <-abort:
    85  			log.Infof("worker/uniter/charm: download aborted")
    86  			return fmt.Errorf("aborted")
    87  		case st := <-dl.Done():
    88  			if st.Err != nil {
    89  				return st.Err
    90  			}
    91  			log.Infof("worker/uniter/charm: download complete")
    92  			actualSha256, _, err := utils.ReadSHA256(st.File)
    93  			// Renaming an open file is not possible on windows
    94  			if runtime.GOOS == "windows" {
    95  				st.File.Close()
    96  			}else{
    97  				defer st.File.Close()
    98  			}
    99  			if err != nil {
   100  				return err
   101  			}
   102  			archiveSha256, err := info.ArchiveSha256()
   103  			if err != nil {
   104  				return err
   105  			}
   106  			if actualSha256 != archiveSha256 {
   107  				return fmt.Errorf(
   108  					"expected sha256 %q, got %q", archiveSha256, actualSha256,
   109  				)
   110  			}
   111  			log.Infof("worker/uniter/charm: download verified")
   112  			if err := os.MkdirAll(d.path, 0755); err != nil {
   113  				return err
   114  			}
   115  			return os.Rename(st.File.Name(), d.bundlePath(info))
   116  		}
   117  	}
   118  }
   119  
   120  // bundlePath returns the path to the location where the verified charm
   121  // bundle identified by info will be, or has been, saved.
   122  func (d *BundlesDir) bundlePath(info BundleInfo) string {
   123  	return d.bundleURLPath(info.URL())
   124  }
   125  
   126  // bundleURLPath returns the path to the location where the verified charm
   127  // bundle identified by url will be, or has been, saved.
   128  func (d *BundlesDir) bundleURLPath(url *charm.URL) string {
   129  	return filepath.Join(d.path, charm.Quote(url.String()))
   130  }
   131  
   132  // downloadsPath returns the path to the directory into which charms are
   133  // downloaded.
   134  func (d *BundlesDir) downloadsPath() string {
   135  	return filepath.Join(d.path, "downloads")
   136  }