~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/agent/tools/toolsdir.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2013 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package tools
 
5
 
 
6
import (
 
7
        "archive/tar"
 
8
        "compress/gzip"
 
9
        "crypto/sha256"
 
10
        "encoding/json"
 
11
        "fmt"
 
12
        "io"
 
13
        "io/ioutil"
 
14
        "os"
 
15
        "path"
 
16
        "strings"
 
17
 
 
18
        "github.com/juju/errors"
 
19
        "github.com/juju/utils/symlink"
 
20
        "github.com/juju/version"
 
21
 
 
22
        coretools "github.com/juju/juju/tools"
 
23
)
 
24
 
 
25
const (
 
26
        dirPerm        = 0755
 
27
        guiArchiveFile = "downloaded-gui.txt"
 
28
        toolsFile      = "downloaded-tools.txt"
 
29
)
 
30
 
 
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())
 
36
}
 
37
 
 
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")
 
42
}
 
43
 
 
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
 
51
        // is in windows.
 
52
        return path.Join(dataDir, "tools", agentName)
 
53
}
 
54
 
 
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))
 
63
        if err != nil {
 
64
                return err
 
65
        }
 
66
        defer zr.Close()
 
67
        f, err := ioutil.TempFile(os.TempDir(), "tools-tar")
 
68
        if err != nil {
 
69
                return err
 
70
        }
 
71
        _, err = io.Copy(f, zr)
 
72
        if err != nil {
 
73
                return err
 
74
        }
 
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)
 
82
        }
 
83
 
 
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)
 
88
        if err != nil {
 
89
                return err
 
90
        }
 
91
        dir, err := ioutil.TempDir(toolsDir, "unpacking-")
 
92
        if err != nil {
 
93
                return err
 
94
        }
 
95
        defer removeAll(dir)
 
96
 
 
97
        // Checksum matches, now reset the file and untar it.
 
98
        _, err = f.Seek(0, 0)
 
99
        if err != nil {
 
100
                return err
 
101
        }
 
102
        tr := tar.NewReader(f)
 
103
        for {
 
104
                hdr, err := tr.Next()
 
105
                if err == io.EOF {
 
106
                        break
 
107
                }
 
108
                if err != nil {
 
109
                        return err
 
110
                }
 
111
                if strings.ContainsAny(hdr.Name, "/\\") {
 
112
                        return fmt.Errorf("bad name %q in tools archive", hdr.Name)
 
113
                }
 
114
                if hdr.Typeflag != tar.TypeReg {
 
115
                        return fmt.Errorf("bad file type %c in file %q in tools archive", hdr.Typeflag, hdr.Name)
 
116
                }
 
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)
 
120
                }
 
121
        }
 
122
        toolsMetadataData, err := json.Marshal(tools)
 
123
        if err != nil {
 
124
                return err
 
125
        }
 
126
        err = ioutil.WriteFile(path.Join(dir, toolsFile), []byte(toolsMetadataData), 0644)
 
127
        if err != nil {
 
128
                return err
 
129
        }
 
130
 
 
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)
 
134
        if err != nil {
 
135
                return err
 
136
        }
 
137
 
 
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
 
141
        // assume all's ok.
 
142
        if err != nil {
 
143
                if _, err := ReadTools(dataDir, tools.Version); err == nil {
 
144
                        return nil
 
145
                }
 
146
        }
 
147
        return err
 
148
}
 
149
 
 
150
func removeAll(dir string) {
 
151
        err := os.RemoveAll(dir)
 
152
        if err == nil || os.IsNotExist(err) {
 
153
                return
 
154
        }
 
155
        logger.Errorf("cannot remove %q: %v", dir, err)
 
156
}
 
157
 
 
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)
 
160
        if err != nil {
 
161
                return err
 
162
        }
 
163
        defer f.Close()
 
164
        _, err = io.Copy(f, r)
 
165
        return err
 
166
}
 
167
 
 
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))
 
174
        if err != nil {
 
175
                return nil, fmt.Errorf("cannot read tools metadata in tools directory: %v", err)
 
176
        }
 
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)
 
180
        }
 
181
        return &tools, nil
 
182
}
 
183
 
 
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))
 
189
        if err != nil {
 
190
                if os.IsNotExist(err) {
 
191
                        return nil, errors.NotFoundf("GUI metadata")
 
192
                }
 
193
                return nil, fmt.Errorf("cannot read GUI metadata in tools directory: %v", err)
 
194
        }
 
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)
 
198
        }
 
199
        return &gui, nil
 
200
}
 
201
 
 
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)
 
207
        if err != nil {
 
208
                return nil, err
 
209
        }
 
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)
 
215
 
 
216
        err = symlink.Replace(toolsDir, toolPath)
 
217
        if err != nil {
 
218
                return nil, fmt.Errorf("cannot replace tools directory: %s", err)
 
219
        }
 
220
        return tools, nil
 
221
}