OLD | NEW |
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-core/log" | 9 "launchpad.net/juju-core/log" |
10 "launchpad.net/juju-core/state" | 10 "launchpad.net/juju-core/state" |
11 "launchpad.net/juju-core/version" | 11 "launchpad.net/juju-core/version" |
12 "os" | 12 "os" |
13 "os/exec" | 13 "os/exec" |
14 "path" | 14 "path" |
15 "path/filepath" | 15 "path/filepath" |
16 "strings" | 16 "strings" |
17 ) | 17 ) |
18 | 18 |
19 // VarDir is the directory where juju data is stored. | |
20 // The tools directories are stored inside the "tools" subdirectory | |
21 // inside VarDir. | |
22 var VarDir = "/var/lib/juju" | |
23 | |
24 var toolPrefix = "tools/juju-" | 19 var toolPrefix = "tools/juju-" |
25 | 20 |
26 // ToolsList holds a list of available tools. Private tools take | 21 // ToolsList holds a list of available tools. Private tools take |
27 // precedence over public tools, even if they have a lower | 22 // precedence over public tools, even if they have a lower |
28 // version number. | 23 // version number. |
29 type ToolsList struct { | 24 type ToolsList struct { |
30 Private []*state.Tools | 25 Private []*state.Tools |
31 Public []*state.Tools | 26 Public []*state.Tools |
32 } | 27 } |
33 | 28 |
(...skipping 199 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
233 if bestTools == nil || bestTools.Number.Less(t.Number) { | 228 if bestTools == nil || bestTools.Number.Less(t.Number) { |
234 bestTools = t | 229 bestTools = t |
235 } | 230 } |
236 } | 231 } |
237 return bestTools | 232 return bestTools |
238 } | 233 } |
239 | 234 |
240 const urlFile = "downloaded-url.txt" | 235 const urlFile = "downloaded-url.txt" |
241 | 236 |
242 // toolsParentDir returns the tools parent directory. | 237 // toolsParentDir returns the tools parent directory. |
243 func toolsParentDir() string { | 238 func toolsParentDir(dataDir string) string { |
244 » return path.Join(VarDir, "tools") | 239 » return path.Join(dataDir, "tools") |
245 } | 240 } |
246 | 241 |
247 // UnpackTools reads a set of juju tools in gzipped tar-archive | 242 // UnpackTools reads a set of juju tools in gzipped tar-archive |
248 // format and unpacks them into the appropriate tools directory. | 243 // format and unpacks them into the appropriate tools directory |
249 // If a valid tools directory already exists, UnpackTools returns | 244 // within dataDir. If a valid tools directory already exists, |
250 // without error. | 245 // UnpackTools returns without error. |
251 func UnpackTools(tools *state.Tools, r io.Reader) (err error) { | 246 func UnpackTools(dataDir string, tools *state.Tools, r io.Reader) (err error) { |
252 zr, err := gzip.NewReader(r) | 247 zr, err := gzip.NewReader(r) |
253 if err != nil { | 248 if err != nil { |
254 return err | 249 return err |
255 } | 250 } |
256 defer zr.Close() | 251 defer zr.Close() |
257 | 252 |
258 // Make a temporary directory in the tools directory, | 253 // Make a temporary directory in the tools directory, |
259 // first ensuring that the tools directory exists. | 254 // first ensuring that the tools directory exists. |
260 » err = os.MkdirAll(toolsParentDir(), 0755) | 255 » err = os.MkdirAll(toolsParentDir(dataDir), 0755) |
261 if err != nil { | 256 if err != nil { |
262 return err | 257 return err |
263 } | 258 } |
264 » dir, err := ioutil.TempDir(toolsParentDir(), "unpacking-") | 259 » dir, err := ioutil.TempDir(toolsParentDir(dataDir), "unpacking-") |
265 if err != nil { | 260 if err != nil { |
266 return err | 261 return err |
267 } | 262 } |
268 defer removeAll(dir) | 263 defer removeAll(dir) |
269 | 264 |
270 tr := tar.NewReader(zr) | 265 tr := tar.NewReader(zr) |
271 for { | 266 for { |
272 hdr, err := tr.Next() | 267 hdr, err := tr.Next() |
273 if err == io.EOF { | 268 if err == io.EOF { |
274 break | 269 break |
(...skipping 10 matching lines...) Expand all Loading... |
285 name := filepath.Join(dir, hdr.Name) | 280 name := filepath.Join(dir, hdr.Name) |
286 if err := writeFile(name, os.FileMode(hdr.Mode&0777), tr); err !
= nil { | 281 if err := writeFile(name, os.FileMode(hdr.Mode&0777), tr); err !
= nil { |
287 return fmt.Errorf("tar extract %q failed: %v", name, err
) | 282 return fmt.Errorf("tar extract %q failed: %v", name, err
) |
288 } | 283 } |
289 } | 284 } |
290 err = ioutil.WriteFile(filepath.Join(dir, urlFile), []byte(tools.URL), 0
644) | 285 err = ioutil.WriteFile(filepath.Join(dir, urlFile), []byte(tools.URL), 0
644) |
291 if err != nil { | 286 if err != nil { |
292 return err | 287 return err |
293 } | 288 } |
294 | 289 |
295 » err = os.Rename(dir, ToolsDir(tools.Binary)) | 290 » err = os.Rename(dir, ToolsDir(dataDir, tools.Binary)) |
296 // If we've failed to rename the directory, it may be because | 291 // If we've failed to rename the directory, it may be because |
297 // the directory already exists - if ReadTools succeeds, we | 292 // the directory already exists - if ReadTools succeeds, we |
298 // assume all's ok. | 293 // assume all's ok. |
299 if err != nil { | 294 if err != nil { |
300 » » _, err := ReadTools(tools.Binary) | 295 » » _, err := ReadTools(dataDir, tools.Binary) |
301 if err == nil { | 296 if err == nil { |
302 return nil | 297 return nil |
303 } | 298 } |
304 } | 299 } |
305 return nil | 300 return nil |
306 } | 301 } |
307 | 302 |
308 func removeAll(dir string) { | 303 func removeAll(dir string) { |
309 err := os.RemoveAll(dir) | 304 err := os.RemoveAll(dir) |
310 if err == nil || os.IsNotExist(err) { | 305 if err == nil || os.IsNotExist(err) { |
311 return | 306 return |
312 } | 307 } |
313 log.Printf("environs: cannot remove %q: %v", dir, err) | 308 log.Printf("environs: cannot remove %q: %v", dir, err) |
314 } | 309 } |
315 | 310 |
316 func writeFile(name string, mode os.FileMode, r io.Reader) error { | 311 func writeFile(name string, mode os.FileMode, r io.Reader) error { |
317 f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode) | 312 f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode) |
318 if err != nil { | 313 if err != nil { |
319 return err | 314 return err |
320 } | 315 } |
321 defer f.Close() | 316 defer f.Close() |
322 _, err = io.Copy(f, r) | 317 _, err = io.Copy(f, r) |
323 return err | 318 return err |
324 } | 319 } |
325 | 320 |
326 // ReadTools checks that the tools for the given version exist | 321 // ReadTools checks that the tools for the given version exist |
327 // and returns a Tools instance describing them. | 322 // in the dataDir directory, and returns a Tools instance describing them. |
328 func ReadTools(vers version.Binary) (*state.Tools, error) { | 323 func ReadTools(dataDir string, vers version.Binary) (*state.Tools, error) { |
329 » dir := ToolsDir(vers) | 324 » dir := ToolsDir(dataDir, vers) |
330 urlData, err := ioutil.ReadFile(filepath.Join(dir, urlFile)) | 325 urlData, err := ioutil.ReadFile(filepath.Join(dir, urlFile)) |
331 if err != nil { | 326 if err != nil { |
332 return nil, fmt.Errorf("cannot read URL in tools directory: %v",
err) | 327 return nil, fmt.Errorf("cannot read URL in tools directory: %v",
err) |
333 } | 328 } |
334 url := strings.TrimSpace(string(urlData)) | 329 url := strings.TrimSpace(string(urlData)) |
335 if len(url) == 0 { | 330 if len(url) == 0 { |
336 return nil, fmt.Errorf("empty URL in tools directory %q", dir) | 331 return nil, fmt.Errorf("empty URL in tools directory %q", dir) |
337 } | 332 } |
338 // TODO(rog): do more verification here too, such as checking | 333 // TODO(rog): do more verification here too, such as checking |
339 // for the existence of certain files. | 334 // for the existence of certain files. |
340 return &state.Tools{ | 335 return &state.Tools{ |
341 URL: url, | 336 URL: url, |
342 Binary: vers, | 337 Binary: vers, |
343 }, nil | 338 }, nil |
344 } | 339 } |
345 | 340 |
346 // ChangeAgentTools atomically replaces the agent-specific symlink | 341 // ChangeAgentTools atomically replaces the agent-specific symlink |
347 // under the tools directory so it points to the previously unpacked | 342 // under dataDir so it points to the previously unpacked |
348 // version vers. It returns the new tools read. | 343 // version vers. It returns the new tools read. |
349 func ChangeAgentTools(agentName string, vers version.Binary) (*state.Tools, erro
r) { | 344 func ChangeAgentTools(dataDir string, agentName string, vers version.Binary) (*s
tate.Tools, error) { |
350 » tools, err := ReadTools(vers) | 345 » tools, err := ReadTools(dataDir, vers) |
351 if err != nil { | 346 if err != nil { |
352 return nil, err | 347 return nil, err |
353 } | 348 } |
354 » tmpName := AgentToolsDir("tmplink-" + agentName) | 349 » tmpName := AgentToolsDir(dataDir, "tmplink-"+agentName) |
355 err = os.Symlink(tools.Binary.String(), tmpName) | 350 err = os.Symlink(tools.Binary.String(), tmpName) |
356 if err != nil { | 351 if err != nil { |
357 return nil, fmt.Errorf("cannot create tools symlink: %v", err) | 352 return nil, fmt.Errorf("cannot create tools symlink: %v", err) |
358 } | 353 } |
359 » err = os.Rename(tmpName, AgentToolsDir(agentName)) | 354 » err = os.Rename(tmpName, AgentToolsDir(dataDir, agentName)) |
360 if err != nil { | 355 if err != nil { |
361 return nil, fmt.Errorf("cannot update tools symlink: %v", err) | 356 return nil, fmt.Errorf("cannot update tools symlink: %v", err) |
362 } | 357 } |
363 return tools, nil | 358 return tools, nil |
364 } | 359 } |
365 | 360 |
366 // ToolsStoragePath returns the slash-separated path that is used to store and | 361 // ToolsStoragePath returns the slash-separated path that is used to store and |
367 // retrieve the given version of the juju tools in a Storage. | 362 // retrieve the given version of the juju tools in a Storage. |
368 func ToolsStoragePath(vers version.Binary) string { | 363 func ToolsStoragePath(vers version.Binary) string { |
369 return toolPrefix + vers.String() + ".tgz" | 364 return toolPrefix + vers.String() + ".tgz" |
370 } | 365 } |
371 | 366 |
372 // ToolsDir returns the slash-separated directory name that is used to | 367 // ToolsDir returns the slash-separated directory name that is used to |
373 // store binaries for the given version of the juju tools. | 368 // store binaries for the given version of the juju tools |
374 func ToolsDir(vers version.Binary) string { | 369 // within the dataDir directory. |
375 » return path.Join(VarDir, "tools", vers.String()) | 370 func ToolsDir(dataDir string, vers version.Binary) string { |
| 371 » return path.Join(dataDir, "tools", vers.String()) |
376 } | 372 } |
377 | 373 |
378 // AgentToolsDir returns the slash-separated directory name that is used | 374 // AgentToolsDir returns the slash-separated directory name that is used |
379 // to store binaries for the tools used by the given agent. | 375 // to store binaries for the tools used by the given agent |
| 376 // within the given dataDir directory. |
380 // Conventionally it is a symbolic link to the actual tools directory. | 377 // Conventionally it is a symbolic link to the actual tools directory. |
381 func AgentToolsDir(agentName string) string { | 378 func AgentToolsDir(dataDir, agentName string) string { |
382 » return path.Join(VarDir, "tools", agentName) | 379 » return path.Join(dataDir, "tools", agentName) |
383 } | 380 } |
384 | 381 |
385 // ToolsSearchFlags gives options when searching | 382 // ToolsSearchFlags gives options when searching |
386 // for tools. | 383 // for tools. |
387 type ToolsSearchFlags int | 384 type ToolsSearchFlags int |
388 | 385 |
389 const ( | 386 const ( |
390 // HighestVersion indicates that versions above the version being | 387 // HighestVersion indicates that versions above the version being |
391 // searched for may be included in the search. The default behavior | 388 // searched for may be included in the search. The default behavior |
392 // is to search for versions <= the one provided. | 389 // is to search for versions <= the one provided. |
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
477 return nil, &NotFoundError{fmt.Errorf("file %q not found in empty storag
e", name)} | 474 return nil, &NotFoundError{fmt.Errorf("file %q not found in empty storag
e", name)} |
478 } | 475 } |
479 | 476 |
480 func (s emptyStorage) URL(string) (string, error) { | 477 func (s emptyStorage) URL(string) (string, error) { |
481 return "", fmt.Errorf("empty storage has no URLs") | 478 return "", fmt.Errorf("empty storage has no URLs") |
482 } | 479 } |
483 | 480 |
484 func (s emptyStorage) List(prefix string) ([]string, error) { | 481 func (s emptyStorage) List(prefix string) ([]string, error) { |
485 return nil, nil | 482 return nil, nil |
486 } | 483 } |
OLD | NEW |