1
// Copyright 2012-2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
11
"github.com/juju/errors"
12
"github.com/juju/utils"
13
"gopkg.in/juju/charm.v6-unstable"
15
"github.com/juju/juju/downloader"
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)
25
// BundlesDir is responsible for storing and retrieving charm bundles
26
// identified by state charms.
27
type BundlesDir struct {
32
// NewBundlesDir returns a new BundlesDir which uses path for storage.
33
func NewBundlesDir(path string, dlr Downloader) *BundlesDir {
35
dlr = downloader.New(downloader.NewArgs{
36
HostnameVerification: utils.NoVerifySSLHostnames,
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) {
55
if err := d.download(info, path, abort); err != nil {
59
return charm.ReadCharmArchive(path)
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) {
67
curl, err := url.Parse(info.URL().String())
69
return errors.Annotate(err, "could not parse charm URL")
71
expectedSha256, err := info.ArchiveSha256()
72
req := downloader.Request{
74
TargetDir: d.downloadsPath(),
75
Verify: downloader.NewSha256Verifier(expectedSha256),
77
logger.Infof("downloading %s from API server", info.URL())
78
filename, err := d.downloader.Download(req, abort)
80
return errors.Annotatef(err, "failed to download charm %q from API server", info.URL())
82
defer errors.DeferredAnnotatef(&err, "downloaded but failed to copy charm to %q from %q", target, filename)
84
// ...then move the right location.
85
if err := os.MkdirAll(d.path, 0755); err != nil {
86
return errors.Trace(err)
88
if err := os.Rename(filename, target); err != nil {
89
return errors.Trace(err)
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())
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()))
106
// downloadsPath returns the path to the directory into which charms are
108
func (d *BundlesDir) downloadsPath() string {
109
return path.Join(d.path, "downloads")