LEFT | RIGHT |
1 package environs | 1 package environs |
2 | 2 |
3 import ( | 3 import ( |
4 "archive/tar" | 4 "archive/tar" |
5 "compress/gzip" | 5 "compress/gzip" |
6 "fmt" | 6 "fmt" |
7 "io" | 7 "io" |
8 "io/ioutil" | 8 "io/ioutil" |
9 "launchpad.net/juju/go/log" | 9 "launchpad.net/juju/go/log" |
10 "launchpad.net/juju/go/version" | 10 "launchpad.net/juju/go/version" |
11 "os" | 11 "os" |
12 "os/exec" | 12 "os/exec" |
13 "path/filepath" | 13 "path/filepath" |
14 "regexp" | 14 "regexp" |
15 "runtime" | 15 "runtime" |
16 "strings" | 16 "strings" |
17 ) | 17 ) |
18 | 18 |
19 var CurrentUbuntuRelease = "precise" // TODO find out actual vers
ion. | 19 // TODO find out actual architecture and Ubuntu release. |
20 var CurrentArch = ubuntuArch(runtime.GOARCH) // TODO better than this | 20 var CurrentSeries = "precise" // current Ubuntu release name.····· |
| 21 var CurrentArch = ubuntuArch(runtime.GOARCH) |
21 | 22 |
22 func ubuntuArch(arch string) string { | 23 func ubuntuArch(arch string) string { |
23 if arch == "386" { | 24 if arch == "386" { |
24 arch = "i386" | 25 arch = "i386" |
25 } | 26 } |
26 return arch | 27 return arch |
27 } | 28 } |
28 | 29 |
29 var toolPrefix = "tools/juju-" | 30 var toolPrefix = "tools/juju-" |
30 | 31 |
31 var toolFilePat = regexp.MustCompile(`^`+toolPrefix+`(\d+\.\d+\.\d+)-([^-]+)-([^
-]+)\.tgz$`) | 32 var toolFilePat = regexp.MustCompile(`^` + toolPrefix + `(\d+\.\d+\.\d+)-([^-]+)
-([^-]+)\.tgz$`) |
32 | 33 |
33 // toolsPathForVersion returns a path for the juju tools with the | 34 // toolsPathForVersion returns a path for the juju tools with the |
34 // given version, OS and architecture. | 35 // given version, OS and architecture. |
35 func toolsPathForVersion(v version.Version, series, arch string) string { | 36 func toolsPathForVersion(v version.Version, series, arch string) string { |
36 return fmt.Sprintf(toolPrefix+"%v-%s-%s.tgz", v, series, arch) | 37 return fmt.Sprintf(toolPrefix+"%v-%s-%s.tgz", v, series, arch) |
37 } | 38 } |
38 | 39 |
39 // ToolsPath gives the path for the current juju tools, as expected | 40 // ToolsPath gives the path for the current juju tools, as expected |
40 // by environs.Environ.PutFile, for example. | 41 // by environs.Environ.PutFile, for example. |
41 var toolsPath = toolsPathForVersion(version.Current, CurrentUbuntuRelease, Curre
ntArch) | 42 var toolsPath = toolsPathForVersion(version.Current, CurrentSeries, CurrentArch) |
42 | 43 |
43 // PutTools uploads the current version of the juju tools | 44 // PutTools uploads the current version of the juju tools |
44 // executables to the given storage. | 45 // executables to the given storage. |
45 // TODO find binaries from $PATH when go dev environment not available. | 46 // TODO find binaries from $PATH when not using a development |
| 47 // version of juju within a $GOPATH. |
46 func PutTools(storage StorageWriter) error { | 48 func PutTools(storage StorageWriter) error { |
47 // We create the entire archive before asking the environment to | 49 // We create the entire archive before asking the environment to |
48 // start uploading so that we can be sure we have archived | 50 // start uploading so that we can be sure we have archived |
49 // correctly. | 51 // correctly. |
50 f, err := ioutil.TempFile("", "juju-tgz") | 52 f, err := ioutil.TempFile("", "juju-tgz") |
51 if err != nil { | 53 if err != nil { |
52 return err | 54 return err |
53 } | 55 } |
54 defer f.Close() | 56 defer f.Close() |
55 defer os.Remove(f.Name()) | 57 defer os.Remove(f.Name()) |
(...skipping 32 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
88 if !isExecutable(ent) { | 90 if !isExecutable(ent) { |
89 return fmt.Errorf("archive: found non-executable file %q
", filepath.Join(dir, ent.Name())) | 91 return fmt.Errorf("archive: found non-executable file %q
", filepath.Join(dir, ent.Name())) |
90 } | 92 } |
91 h := tarHeader(ent) | 93 h := tarHeader(ent) |
92 // ignore local umask | 94 // ignore local umask |
93 h.Mode = 0755 | 95 h.Mode = 0755 |
94 err := tarw.WriteHeader(h) | 96 err := tarw.WriteHeader(h) |
95 if err != nil { | 97 if err != nil { |
96 return err | 98 return err |
97 } | 99 } |
98 » » if err := readFile(tarw, filepath.Join(dir, ent.Name())); err !=
nil { | 100 » » if err := copyFile(tarw, filepath.Join(dir, ent.Name())); err !=
nil { |
99 return err | 101 return err |
100 } | 102 } |
101 } | 103 } |
102 return nil | 104 return nil |
103 } | 105 } |
104 | 106 |
105 // readFile writes the contents of the given file to w. | 107 // copyFile writes the contents of the given file to w. |
106 func readFile(w io.Writer, file string) error { | 108 func copyFile(w io.Writer, file string) error { |
107 f, err := os.Open(file) | 109 f, err := os.Open(file) |
108 if err != nil { | 110 if err != nil { |
109 return err | 111 return err |
110 } | 112 } |
111 defer f.Close() | 113 defer f.Close() |
112 _, err = io.Copy(w, f) | 114 _, err = io.Copy(w, f) |
113 return err | 115 return err |
114 } | 116 } |
115 | 117 |
116 // tarHeader returns a tar file header given the file's stat | 118 // tarHeader returns a tar file header given the file's stat |
(...skipping 20 matching lines...) Expand all Loading... |
137 | 139 |
138 // closeErrorCheck means that we can ensure that | 140 // closeErrorCheck means that we can ensure that |
139 // Close errors do not get lost even when we defer them, | 141 // Close errors do not get lost even when we defer them, |
140 func closeErrorCheck(errp *error, c io.Closer) { | 142 func closeErrorCheck(errp *error, c io.Closer) { |
141 err := c.Close() | 143 err := c.Close() |
142 if *errp == nil { | 144 if *errp == nil { |
143 *errp = err | 145 *errp = err |
144 } | 146 } |
145 } | 147 } |
146 | 148 |
147 // FindTools tries to find a set of tools appropriate for the current | 149 type toolsSpec struct { |
148 // version and platform and returns a URL that can be used to access | 150 » vers version.Version |
149 // them in gzipped tar archive format. | 151 » series string |
150 func FindTools(env Environ) (url string, err error) { | 152 » arch string |
151 » storage, path, err := findTools(env) | 153 } |
| 154 |
| 155 // FindTools tries to find a set of tools appropriate for the given |
| 156 // version, Ubuntu series and architecture, and returns a URL that can |
| 157 // be used to access them in gzipped tar archive format. |
| 158 func FindTools(env Environ, vers version.Version, series, arch string) (url stri
ng, err error) { |
| 159 » storage, path, err := findTools(env, toolsSpec{vers, series, arch}) |
152 if err != nil { | 160 if err != nil { |
153 return "", err | 161 return "", err |
154 } | 162 } |
155 return storage.URL(path) | 163 return storage.URL(path) |
156 } | 164 } |
157 | 165 |
158 // GetTools finds the latest compatible version of the juju tools | 166 // GetTools finds the latest compatible version of the juju tools |
159 // and downloads them into the given directory. | 167 // and downloads them into the given directory. |
160 func GetTools(env Environ, dir string) error { | 168 func GetTools(env Environ, dir string) error { |
161 » storage, path, err := findTools(env) | 169 » storage, path, err := findTools(env, toolsSpec{version.Current, CurrentS
eries, CurrentArch}) |
162 if err != nil { | 170 if err != nil { |
163 return err | 171 return err |
164 } | 172 } |
165 r, err := storage.Get(path) | 173 r, err := storage.Get(path) |
166 if err != nil { | 174 if err != nil { |
167 return err | 175 return err |
168 } | 176 } |
169 defer r.Close() | 177 defer r.Close() |
170 | 178 |
171 r, err = gzip.NewReader(r) | 179 r, err = gzip.NewReader(r) |
(...skipping 17 matching lines...) Expand all Loading... |
189 | 197 |
190 name := filepath.Join(dir, hdr.Name) | 198 name := filepath.Join(dir, hdr.Name) |
191 if err := writeFile(name, os.FileMode(hdr.Mode&0777), tr); err !
= nil { | 199 if err := writeFile(name, os.FileMode(hdr.Mode&0777), tr); err !
= nil { |
192 return fmt.Errorf("tar extract %q failed: %v", name, err
) | 200 return fmt.Errorf("tar extract %q failed: %v", name, err
) |
193 } | 201 } |
194 } | 202 } |
195 panic("not reached") | 203 panic("not reached") |
196 } | 204 } |
197 | 205 |
198 // findToolsPath is an internal version of FindTools that returns the | 206 // findToolsPath is an internal version of FindTools that returns the |
199 // found StorageReader and the path within that storage. | 207 // storage in which the tools have been found, and the path within that storage. |
200 func findTools(env Environ) (storage StorageReader, path string, err error) { | 208 func findTools(env Environ, spec toolsSpec) (storage StorageReader, path string,
err error) { |
201 storage = env.Storage() | 209 storage = env.Storage() |
202 » path, err = findToolsPath(storage) | 210 » path, err = findToolsPath(storage, spec) |
203 if _, ok := err.(*NotFoundError); ok { | 211 if _, ok := err.(*NotFoundError); ok { |
204 storage = env.PublicStorage() | 212 storage = env.PublicStorage() |
205 » » path, err = findToolsPath(storage) | 213 » » path, err = findToolsPath(storage, spec) |
206 } | 214 } |
207 if err != nil { | 215 if err != nil { |
208 return nil, "", err | 216 return nil, "", err |
209 } | 217 } |
210 return | 218 return |
211 } | 219 } |
212 | 220 |
213 // findToolsPath looks for the tools in the given storage. | 221 // findToolsPath looks for the tools in the given storage. |
214 func findToolsPath(store StorageReader) (path string, err error) { | 222 func findToolsPath(store StorageReader, spec toolsSpec) (path string, err error)
{ |
215 » names, err := store.List(fmt.Sprintf("%s%d.", toolPrefix, version.Curren
t.Major)) | 223 » names, err := store.List(fmt.Sprintf("%s%d.", toolPrefix, spec.vers.Majo
r)) |
216 if err != nil { | 224 if err != nil { |
217 return "", err | 225 return "", err |
218 } | 226 } |
219 if len(names) == 0 { | 227 if len(names) == 0 { |
220 return "", &NotFoundError{fmt.Errorf("no compatible tools found"
)} | 228 return "", &NotFoundError{fmt.Errorf("no compatible tools found"
)} |
221 } | 229 } |
222 bestVersion := version.Version{Major: -1} | 230 bestVersion := version.Version{Major: -1} |
223 bestName := "" | 231 bestName := "" |
224 for _, name := range names { | 232 for _, name := range names { |
225 m := toolFilePat.FindStringSubmatch(name) | 233 m := toolFilePat.FindStringSubmatch(name) |
226 if m == nil { | 234 if m == nil { |
227 log.Printf("unexpected tools file found %q", name) | 235 log.Printf("unexpected tools file found %q", name) |
228 continue | 236 continue |
229 } | 237 } |
230 vers, err := version.Parse(m[1]) | 238 vers, err := version.Parse(m[1]) |
231 if err != nil { | 239 if err != nil { |
232 log.Printf("failed to parse version %q: %v", name, err) | 240 log.Printf("failed to parse version %q: %v", name, err) |
233 continue | 241 continue |
234 } | 242 } |
235 » » if m[2] != CurrentUbuntuRelease { | 243 » » if m[2] != spec.series { |
236 continue | 244 continue |
237 } | 245 } |
238 // TODO allow different architectures. | 246 // TODO allow different architectures. |
239 » » if m[3] != CurrentArch { | 247 » » if m[3] != spec.arch { |
240 » » » continue | 248 » » » continue |
241 » » } | 249 » » } |
242 » » if vers.Major != version.Current.Major { | 250 » » if vers.Major != spec.vers.Major { |
243 continue | 251 continue |
244 } | 252 } |
245 if bestVersion.Less(vers) { | 253 if bestVersion.Less(vers) { |
246 bestVersion = vers | 254 bestVersion = vers |
247 bestName = name | 255 bestName = name |
248 } | 256 } |
249 } | 257 } |
250 if bestVersion.Major < 0 { | 258 if bestVersion.Major < 0 { |
251 return "", &NotFoundError{fmt.Errorf("no compatible tools found"
)} | 259 return "", &NotFoundError{fmt.Errorf("no compatible tools found"
)} |
252 } | 260 } |
(...skipping 24 matching lines...) Expand all Loading... |
277 func writeFile(name string, mode os.FileMode, r io.Reader) error { | 285 func writeFile(name string, mode os.FileMode, r io.Reader) error { |
278 f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode) | 286 f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode) |
279 if err != nil { | 287 if err != nil { |
280 return err | 288 return err |
281 } | 289 } |
282 defer f.Close() | 290 defer f.Close() |
283 _, err = io.Copy(f, r) | 291 _, err = io.Copy(f, r) |
284 return err | 292 return err |
285 } | 293 } |
286 | 294 |
287 // EmptyStorage holds a StorageReader object | 295 // EmptyStorage holds a StorageReader object that contains nothing. |
288 // that contains nothing. | |
289 var EmptyStorage StorageReader = emptyStorage{} | 296 var EmptyStorage StorageReader = emptyStorage{} |
290 | 297 |
291 type emptyStorage struct{} | 298 type emptyStorage struct{} |
292 | 299 |
293 func (s emptyStorage) Get(name string) (io.ReadCloser, error) { | 300 func (s emptyStorage) Get(name string) (io.ReadCloser, error) { |
294 return nil, &NotFoundError{fmt.Errorf("file %q not found in empty storag
e", name)} | 301 return nil, &NotFoundError{fmt.Errorf("file %q not found in empty storag
e", name)} |
295 } | 302 } |
296 | 303 |
297 func (s emptyStorage) URL(string) (string, error) { | 304 func (s emptyStorage) URL(string) (string, error) { |
298 return "", fmt.Errorf("empty storage has no URLs") | 305 return "", fmt.Errorf("empty storage has no URLs") |
299 } | 306 } |
300 | 307 |
301 func (s emptyStorage) List(prefix string) ([]string, error) { | 308 func (s emptyStorage) List(prefix string) ([]string, error) { |
302 return nil, nil | 309 return nil, nil |
303 } | 310 } |
LEFT | RIGHT |