1
// Copyright 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
14
"launchpad.net/juju-core/state"
15
"launchpad.net/juju-core/version"
18
var ErrNoTools = errors.New("no tools available")
19
var ErrNoMatches = errors.New("no matching tools available")
22
DefaultToolPrefix = "tools/juju-"
26
var toolPrefix string = DefaultToolPrefix
28
// SetToolPrefix changes the prefix used to compose the tools tarball file name.
29
func SetToolPrefix(prefix string) {
33
// StorageName returns the name that is used to store and retrieve the
34
// given version of the juju tools.
35
func StorageName(vers version.Binary) string {
36
return toolPrefix + vers.String() + toolSuffix
39
// URLLister exposes to ReadList the relevant capabilities of an
40
// environs.Storage; it exists to foil an import cycle.
41
type URLLister interface {
42
URL(name string) (string, error)
43
List(prefix string) ([]string, error)
46
// ReadList returns a List of the tools in store with the given major version.
47
// If store contains no such tools, it returns ErrNoMatches.
48
func ReadList(storage URLLister, majorVersion int) (List, error) {
49
logger.Debugf("reading v%d.* tools", majorVersion)
50
names, err := storage.List(toolPrefix)
55
var foundAnyTools bool
56
for _, name := range names {
57
if !strings.HasPrefix(name, toolPrefix) || !strings.HasSuffix(name, toolSuffix) {
61
vers := name[len(toolPrefix) : len(name)-len(toolSuffix)]
62
if t.Binary, err = version.ParseBinary(vers); err != nil {
66
if t.Major != majorVersion {
69
logger.Debugf("found %s", vers)
70
if t.URL, err = storage.URL(name); err != nil {
73
list = append(list, &t)
77
return nil, ErrNoMatches
79
return nil, ErrNoTools
84
// URLPutter exposes to Upload the relevant capabilities of an
85
// environs.Storage; it exists to foil an import cycle.
86
type URLPutter interface {
87
URL(name string) (string, error)
88
Put(name string, r io.Reader, length int64) error
91
// Upload builds whatever version of launchpad.net/juju-core is in $GOPATH,
92
// uploads it to the given storage, and returns a Tools instance describing
93
// them. If forceVersion is not nil, the uploaded tools bundle will report
94
// the given version number; if any fakeSeries are supplied, additional copies
95
// of the built tools will be uploaded for use by machines of those series.
96
// Juju tools built for one series do not necessarily run on another, but this
97
// func exists only for development use cases.
98
func Upload(storage URLPutter, forceVersion *version.Number, fakeSeries ...string) (*state.Tools, error) {
99
// TODO(rog) find binaries from $PATH when not using a development
100
// version of juju within a $GOPATH.
102
logger.Debugf("Uploading tools for %v", fakeSeries)
103
// We create the entire archive before asking the environment to
104
// start uploading so that we can be sure we have archived
106
f, err := ioutil.TempFile("", "juju-tgz")
111
defer os.Remove(f.Name())
112
toolsVersion, err := bundleTools(f, forceVersion)
116
fileInfo, err := f.Stat()
118
return nil, fmt.Errorf("cannot stat newly made tools archive: %v", err)
120
size := fileInfo.Size()
121
logger.Infof("built %v (%dkB)", toolsVersion, (size+512)/1024)
122
putTools := func(vers version.Binary) (string, error) {
123
if _, err := f.Seek(0, 0); err != nil {
124
return "", fmt.Errorf("cannot seek to start of tools archive: %v", err)
126
name := StorageName(vers)
127
logger.Infof("uploading %s", vers)
128
if err := storage.Put(name, f, size); err != nil {
133
for _, series := range fakeSeries {
134
if series != toolsVersion.Series {
135
fakeVersion := toolsVersion
136
fakeVersion.Series = series
137
if _, err := putTools(fakeVersion); err != nil {
142
name, err := putTools(toolsVersion)
146
url, err := storage.URL(name)
150
return &state.Tools{toolsVersion, url}, nil