1
// Copyright 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
18
"github.com/juju/errors"
19
"github.com/juju/utils/symlink"
20
"github.com/juju/version"
22
coretools "github.com/juju/juju/tools"
27
guiArchiveFile = "downloaded-gui.txt"
28
toolsFile = "downloaded-tools.txt"
31
// SharedToolsDir returns the directory that is used to
32
// store binaries for the given version of the juju tools
33
// within the dataDir directory.
34
func SharedToolsDir(dataDir string, vers version.Binary) string {
35
return path.Join(dataDir, "tools", vers.String())
38
// SharedGUIDir returns the directory that is used to store release archives
39
// of the Juju GUI within the dataDir directory.
40
func SharedGUIDir(dataDir string) string {
41
return path.Join(dataDir, "gui")
44
// ToolsDir returns the directory that is used/ to store binaries for
45
// the tools used by the given agent within the given dataDir directory.
46
// Conventionally it is a symbolic link to the actual tools directory.
47
func ToolsDir(dataDir, agentName string) string {
48
//TODO(perrito666) ToolsDir and any other *Dir needs to take the
49
// agent series to use the right path, in this case, if filepath
50
// is used it ends up creating a bogus toolsdir when the client
52
return path.Join(dataDir, "tools", agentName)
55
// UnpackTools reads a set of juju tools in gzipped tar-archive
56
// format and unpacks them into the appropriate tools directory
57
// within dataDir. If a valid tools directory already exists,
58
// UnpackTools returns without error.
59
func UnpackTools(dataDir string, tools *coretools.Tools, r io.Reader) (err error) {
60
// Unpack the gzip file and compute the checksum.
61
sha256hash := sha256.New()
62
zr, err := gzip.NewReader(io.TeeReader(r, sha256hash))
67
f, err := ioutil.TempFile(os.TempDir(), "tools-tar")
71
_, err = io.Copy(f, zr)
75
defer os.Remove(f.Name())
76
// TODO(wallyworld) - 2013-09-24 bug=1229512
77
// When we can ensure all tools records have valid checksums recorded,
78
// we can remove this test short circuit.
79
gzipSHA256 := fmt.Sprintf("%x", sha256hash.Sum(nil))
80
if tools.SHA256 != "" && tools.SHA256 != gzipSHA256 {
81
return fmt.Errorf("tarball sha256 mismatch, expected %s, got %s", tools.SHA256, gzipSHA256)
84
// Make a temporary directory in the tools directory,
85
// first ensuring that the tools directory exists.
86
toolsDir := path.Join(dataDir, "tools")
87
err = os.MkdirAll(toolsDir, dirPerm)
91
dir, err := ioutil.TempDir(toolsDir, "unpacking-")
97
// Checksum matches, now reset the file and untar it.
102
tr := tar.NewReader(f)
104
hdr, err := tr.Next()
111
if strings.ContainsAny(hdr.Name, "/\\") {
112
return fmt.Errorf("bad name %q in tools archive", hdr.Name)
114
if hdr.Typeflag != tar.TypeReg {
115
return fmt.Errorf("bad file type %c in file %q in tools archive", hdr.Typeflag, hdr.Name)
117
name := path.Join(dir, hdr.Name)
118
if err := writeFile(name, os.FileMode(hdr.Mode&0777), tr); err != nil {
119
return errors.Annotatef(err, "tar extract %q failed", name)
122
toolsMetadataData, err := json.Marshal(tools)
126
err = ioutil.WriteFile(path.Join(dir, toolsFile), []byte(toolsMetadataData), 0644)
131
// The tempdir is created with 0700, so we need to make it more
132
// accessable for juju-run.
133
err = os.Chmod(dir, dirPerm)
138
err = os.Rename(dir, SharedToolsDir(dataDir, tools.Version))
139
// If we've failed to rename the directory, it may be because
140
// the directory already exists - if ReadTools succeeds, we
143
if _, err := ReadTools(dataDir, tools.Version); err == nil {
150
func removeAll(dir string) {
151
err := os.RemoveAll(dir)
152
if err == nil || os.IsNotExist(err) {
155
logger.Errorf("cannot remove %q: %v", dir, err)
158
func writeFile(name string, mode os.FileMode, r io.Reader) error {
159
f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode)
164
_, err = io.Copy(f, r)
168
// ReadTools checks that the tools information for the given version exists
169
// in the dataDir directory, and returns a Tools instance.
170
// The tools information is json encoded in a text file, "downloaded-tools.txt".
171
func ReadTools(dataDir string, vers version.Binary) (*coretools.Tools, error) {
172
dir := SharedToolsDir(dataDir, vers)
173
toolsData, err := ioutil.ReadFile(path.Join(dir, toolsFile))
175
return nil, fmt.Errorf("cannot read tools metadata in tools directory: %v", err)
177
var tools coretools.Tools
178
if err := json.Unmarshal(toolsData, &tools); err != nil {
179
return nil, fmt.Errorf("invalid tools metadata in tools directory %q: %v", dir, err)
184
// ReadGUIArchive reads the GUI information from the dataDir directory.
185
// The GUI information is JSON encoded in a text file, "downloaded-gui.txt".
186
func ReadGUIArchive(dataDir string) (*coretools.GUIArchive, error) {
187
dir := SharedGUIDir(dataDir)
188
toolsData, err := ioutil.ReadFile(path.Join(dir, guiArchiveFile))
190
if os.IsNotExist(err) {
191
return nil, errors.NotFoundf("GUI metadata")
193
return nil, fmt.Errorf("cannot read GUI metadata in tools directory: %v", err)
195
var gui coretools.GUIArchive
196
if err := json.Unmarshal(toolsData, &gui); err != nil {
197
return nil, fmt.Errorf("invalid GUI metadata in tools directory %q: %v", dir, err)
202
// ChangeAgentTools atomically replaces the agent-specific symlink
203
// under dataDir so it points to the previously unpacked
204
// version vers. It returns the new tools read.
205
func ChangeAgentTools(dataDir string, agentName string, vers version.Binary) (*coretools.Tools, error) {
206
tools, err := ReadTools(dataDir, vers)
210
// build absolute path to toolsDir. Windows implementation of symlink
211
// will check for the existance of the source file and error if it does
212
// not exists. This is a limitation of junction points (symlinks) on NTFS
213
toolPath := ToolsDir(dataDir, tools.Version.String())
214
toolsDir := ToolsDir(dataDir, agentName)
216
err = symlink.Replace(toolsDir, toolPath)
218
return nil, fmt.Errorf("cannot replace tools directory: %s", err)