47
// listTools is like ListTools, but only returns the tools from
48
// a particular storage.
49
func listTools(store StorageReader, majorVersion int) (tools.List, error) {
50
dir := fmt.Sprintf("%s%d.", toolPrefix, majorVersion)
51
log.Debugf("listing tools in dir: %s", dir)
52
names, err := store.List(dir)
56
var toolsList tools.List
57
for _, name := range names {
58
log.Debugf("looking at tools file %s", name)
59
if !strings.HasPrefix(name, toolPrefix) || !strings.HasSuffix(name, ".tgz") {
60
log.Warningf("environs: unexpected tools file found %q", name)
63
vers := name[len(toolPrefix) : len(name)-len(".tgz")]
65
t.Binary, err = version.ParseBinary(vers)
67
log.Warningf("environs: failed to parse %q: %v", vers, err)
70
if t.Major != majorVersion {
71
log.Warningf("environs: tool %q found in wrong directory %q", name, dir)
74
t.URL, err = store.URL(name)
75
log.Debugf("tools URL is %s", t.URL)
77
log.Warningf("environs: cannot get URL for %q: %v", name, err)
80
toolsList = append(toolsList, &t)
85
// PutTools builds whatever version of launchpad.net/juju-core is in $GOPATH,
86
// uploads it to the given storage, and returns a Tools instance describing
87
// them. If forceVersion is not nil, the uploaded tools bundle will report
88
// the given version number; if any fakeSeries are supplied, additional copies
89
// of the built tools will be uploaded for use by machines of those series.
90
// Juju tools built for one series do not necessarily run on another, but this
91
// func exists only for development use cases.
92
func PutTools(storage Storage, forceVersion *version.Number, fakeSeries ...string) (*state.Tools, error) {
93
// TODO(rog) find binaries from $PATH when not using a development
94
// version of juju within a $GOPATH.
96
// We create the entire archive before asking the environment to
97
// start uploading so that we can be sure we have archived
99
f, err := ioutil.TempFile("", "juju-tgz")
104
defer os.Remove(f.Name())
105
toolsVersion, err := bundleTools(f, forceVersion)
111
return nil, fmt.Errorf("cannot stat newly made tools archive: %v", err)
114
log.Infof("environs: built tools %v (%dkB)", toolsVersion, (size+512)/1024)
115
putTools := func(vers version.Binary) (string, error) {
116
if _, err := f.Seek(0, 0); err != nil {
117
return "", fmt.Errorf("cannot seek to start of tools archive: %v", err)
119
path := ToolsStoragePath(vers)
120
log.Infof("environs: putting tools %v", path)
121
if err := storage.Put(path, f, size); err != nil {
126
for _, series := range fakeSeries {
127
if series != toolsVersion.Series {
128
fakeVersion := toolsVersion
129
fakeVersion.Series = series
130
if _, err := putTools(fakeVersion); err != nil {
135
path, err := putTools(toolsVersion)
139
url, err := storage.URL(path)
143
return &state.Tools{toolsVersion, url}, nil
146
// archive writes the executable files found in the given directory in
147
// gzipped tar format to w. An error is returned if an entry inside dir
148
// is not a regular executable file.
149
func archive(w io.Writer, dir string) (err error) {
150
entries, err := ioutil.ReadDir(dir)
155
gzw := gzip.NewWriter(w)
156
defer closeErrorCheck(&err, gzw)
158
tarw := tar.NewWriter(gzw)
159
defer closeErrorCheck(&err, tarw)
161
for _, ent := range entries {
163
// ignore local umask
164
if isExecutable(ent) {
169
err := tarw.WriteHeader(h)
173
if err := copyFile(tarw, filepath.Join(dir, ent.Name())); err != nil {
180
// copyFile writes the contents of the given file to w.
181
func copyFile(w io.Writer, file string) error {
182
f, err := os.Open(file)
187
_, err = io.Copy(w, f)
191
// tarHeader returns a tar file header given the file's stat
193
func tarHeader(i os.FileInfo) *tar.Header {
195
Typeflag: tar.TypeReg,
198
Mode: int64(i.Mode() & 0777),
199
ModTime: i.ModTime(),
200
AccessTime: i.ModTime(),
201
ChangeTime: i.ModTime(),
207
// isExecutable returns whether the given info
208
// represents a regular file executable by (at least) the user.
209
func isExecutable(i os.FileInfo) bool {
210
return i.Mode()&(0100|os.ModeType) == 0100
213
// closeErrorCheck means that we can ensure that
214
// Close errors do not get lost even when we defer them,
215
func closeErrorCheck(errp *error, c io.Closer) {
222
37
// BestTools returns the most recent version
223
38
// from the set of tools in the ToolsList that are
224
39
// compatible with the given version, using flags
301
110
return tools, nil
304
func setenv(env []string, val string) []string {
305
prefix := val[0 : strings.Index(val, "=")+1]
306
for i, eval := range env {
307
if strings.HasPrefix(eval, prefix) {
312
return append(env, val)
315
// bundleTools bundles all the current juju tools in gzipped tar
316
// format to the given writer.
317
// If forceVersion is not nil, a FORCE-VERSION file is included in
318
// the tools bundle so it will lie about its current version number.
319
func bundleTools(w io.Writer, forceVersion *version.Number) (version.Binary, error) {
320
dir, err := ioutil.TempDir("", "juju-tools")
322
return version.Binary{}, err
324
defer os.RemoveAll(dir)
327
{"go", "install", "launchpad.net/juju-core/cmd/jujud"},
328
{"strip", dir + "/jujud"},
330
env := setenv(os.Environ(), "GOBIN="+dir)
331
for _, args := range cmds {
332
cmd := exec.Command(args[0], args[1:]...)
334
out, err := cmd.CombinedOutput()
336
return version.Binary{}, fmt.Errorf("build command %q failed: %v; %s", args[0], err, out)
339
if forceVersion != nil {
340
if err := ioutil.WriteFile(filepath.Join(dir, "FORCE-VERSION"), []byte(forceVersion.String()), 0666); err != nil {
341
return version.Binary{}, err
344
cmd := exec.Command(filepath.Join(dir, "jujud"), "version")
345
out, err := cmd.CombinedOutput()
347
return version.Binary{}, fmt.Errorf("cannot get version from %q: %v; %s", cmd.Args[0], err, out)
349
tvs := strings.TrimSpace(string(out))
350
tvers, err := version.ParseBinary(tvs)
352
return version.Binary{}, fmt.Errorf("invalid version %q printed by jujud", tvs)
354
err = archive(w, dir)
356
return version.Binary{}, err
361
// EmptyStorage holds a StorageReader object that contains nothing.
362
var EmptyStorage StorageReader = emptyStorage{}
364
type emptyStorage struct{}
366
func (s emptyStorage) Get(name string) (io.ReadCloser, error) {
367
return nil, &NotFoundError{fmt.Errorf("file %q not found in empty storage", name)}
370
func (s emptyStorage) URL(string) (string, error) {
371
return "", fmt.Errorf("empty storage has no URLs")
374
func (s emptyStorage) List(prefix string) ([]string, error) {