github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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  	"gopkg.in/juju/charm.v6-unstable"
    14  
    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.ReadCharmArchive(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  	archiveURLs, err := info.ArchiveURLs()
    49  	if err != nil {
    50  		return errors.Annotatef(err, "failed to get download URLs for charm %q", info.URL())
    51  	}
    52  	defer errors.DeferredAnnotatef(&err, "failed to download charm %q from %q", info.URL(), archiveURLs)
    53  	dir := d.downloadsPath()
    54  	if err := os.MkdirAll(dir, 0755); err != nil {
    55  		return err
    56  	}
    57  	var st downloader.Status
    58  	for _, archiveURL := range archiveURLs {
    59  		aurl := archiveURL.String()
    60  		logger.Infof("downloading %s from %s", info.URL(), aurl)
    61  		st, err = tryDownload(aurl, dir, abort)
    62  		if err == nil {
    63  			break
    64  		}
    65  	}
    66  	if err != nil {
    67  		return err
    68  	}
    69  	logger.Infof("download complete")
    70  	defer st.File.Close()
    71  	actualSha256, _, err := utils.ReadSHA256(st.File)
    72  	if err != nil {
    73  		return err
    74  	}
    75  	archiveSha256, err := info.ArchiveSha256()
    76  	if err != nil {
    77  		return err
    78  	}
    79  	if actualSha256 != archiveSha256 {
    80  		return fmt.Errorf(
    81  			"expected sha256 %q, got %q", archiveSha256, actualSha256,
    82  		)
    83  	}
    84  	logger.Infof("download verified")
    85  	if err := os.MkdirAll(d.path, 0755); err != nil {
    86  		return err
    87  	}
    88  	// Renaming an open file is not possible on Windows
    89  	st.File.Close()
    90  	return os.Rename(st.File.Name(), d.bundlePath(info))
    91  }
    92  
    93  func tryDownload(url, dir string, abort <-chan struct{}) (downloader.Status, error) {
    94  	// Downloads always go through the API server, which at
    95  	// present cannot be verified due to the certificates
    96  	// being inadequate. We always verify the SHA-256 hash,
    97  	// and the data transferred is not sensitive, so this
    98  	// does not pose a problem.
    99  	dl := downloader.New(url, dir, utils.NoVerifySSLHostnames)
   100  	defer dl.Stop()
   101  	select {
   102  	case <-abort:
   103  		logger.Infof("download aborted")
   104  		return downloader.Status{}, errors.New("aborted")
   105  	case st := <-dl.Done():
   106  		if st.Err != nil {
   107  			return downloader.Status{}, st.Err
   108  		}
   109  		return st, nil
   110  	}
   111  }
   112  
   113  // bundlePath returns the path to the location where the verified charm
   114  // bundle identified by info will be, or has been, saved.
   115  func (d *BundlesDir) bundlePath(info BundleInfo) string {
   116  	return d.bundleURLPath(info.URL())
   117  }
   118  
   119  // bundleURLPath returns the path to the location where the verified charm
   120  // bundle identified by url will be, or has been, saved.
   121  func (d *BundlesDir) bundleURLPath(url *charm.URL) string {
   122  	return path.Join(d.path, charm.Quote(url.String()))
   123  }
   124  
   125  // downloadsPath returns the path to the directory into which charms are
   126  // downloaded.
   127  func (d *BundlesDir) downloadsPath() string {
   128  	return path.Join(d.path, "downloads")
   129  }