github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/agent/tools/toolsdir.go (about) 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 21 coretools "github.com/juju/juju/tools" 22 "github.com/juju/juju/version" 23 ) 24 25 const toolsFile = "downloaded-tools.txt" 26 const dirPerm = 0755 27 28 // SharedToolsDir returns the directory that is used to 29 // store binaries for the given version of the juju tools 30 // within the dataDir directory. 31 func SharedToolsDir(dataDir string, vers version.Binary) string { 32 return path.Join(dataDir, "tools", vers.String()) 33 } 34 35 // ToolsDir returns the directory that is used/ to store binaries for 36 // the tools used by the given agent within the given dataDir directory. 37 // Conventionally it is a symbolic link to the actual tools directory. 38 func ToolsDir(dataDir, agentName string) string { 39 //TODO(perrito666) ToolsDir and any other *Dir needs to take the 40 // agent series to use the right path, in this case, if filepath 41 // is used it ends up creating a bogus toolsdir when the client 42 // is in windows. 43 return path.Join(dataDir, "tools", agentName) 44 } 45 46 // UnpackTools reads a set of juju tools in gzipped tar-archive 47 // format and unpacks them into the appropriate tools directory 48 // within dataDir. If a valid tools directory already exists, 49 // UnpackTools returns without error. 50 func UnpackTools(dataDir string, tools *coretools.Tools, r io.Reader) (err error) { 51 // Unpack the gzip file and compute the checksum. 52 sha256hash := sha256.New() 53 zr, err := gzip.NewReader(io.TeeReader(r, sha256hash)) 54 if err != nil { 55 return err 56 } 57 defer zr.Close() 58 f, err := ioutil.TempFile(os.TempDir(), "tools-tar") 59 if err != nil { 60 return err 61 } 62 _, err = io.Copy(f, zr) 63 if err != nil { 64 return err 65 } 66 defer os.Remove(f.Name()) 67 // TODO(wallyworld) - 2013-09-24 bug=1229512 68 // When we can ensure all tools records have valid checksums recorded, 69 // we can remove this test short circuit. 70 gzipSHA256 := fmt.Sprintf("%x", sha256hash.Sum(nil)) 71 if tools.SHA256 != "" && tools.SHA256 != gzipSHA256 { 72 return fmt.Errorf("tarball sha256 mismatch, expected %s, got %s", tools.SHA256, gzipSHA256) 73 } 74 75 // Make a temporary directory in the tools directory, 76 // first ensuring that the tools directory exists. 77 toolsDir := path.Join(dataDir, "tools") 78 err = os.MkdirAll(toolsDir, dirPerm) 79 if err != nil { 80 return err 81 } 82 dir, err := ioutil.TempDir(toolsDir, "unpacking-") 83 if err != nil { 84 return err 85 } 86 defer removeAll(dir) 87 88 // Checksum matches, now reset the file and untar it. 89 _, err = f.Seek(0, 0) 90 if err != nil { 91 return err 92 } 93 tr := tar.NewReader(f) 94 for { 95 hdr, err := tr.Next() 96 if err == io.EOF { 97 break 98 } 99 if err != nil { 100 return err 101 } 102 if strings.ContainsAny(hdr.Name, "/\\") { 103 return fmt.Errorf("bad name %q in tools archive", hdr.Name) 104 } 105 if hdr.Typeflag != tar.TypeReg { 106 return fmt.Errorf("bad file type %c in file %q in tools archive", hdr.Typeflag, hdr.Name) 107 } 108 name := path.Join(dir, hdr.Name) 109 if err := writeFile(name, os.FileMode(hdr.Mode&0777), tr); err != nil { 110 return errors.Annotatef(err, "tar extract %q failed", name) 111 } 112 } 113 toolsMetadataData, err := json.Marshal(tools) 114 if err != nil { 115 return err 116 } 117 err = ioutil.WriteFile(path.Join(dir, toolsFile), []byte(toolsMetadataData), 0644) 118 if err != nil { 119 return err 120 } 121 122 // The tempdir is created with 0700, so we need to make it more 123 // accessable for juju-run. 124 err = os.Chmod(dir, dirPerm) 125 if err != nil { 126 return err 127 } 128 129 err = os.Rename(dir, SharedToolsDir(dataDir, tools.Version)) 130 // If we've failed to rename the directory, it may be because 131 // the directory already exists - if ReadTools succeeds, we 132 // assume all's ok. 133 if err != nil { 134 if _, err := ReadTools(dataDir, tools.Version); err == nil { 135 return nil 136 } 137 } 138 return err 139 } 140 141 func removeAll(dir string) { 142 err := os.RemoveAll(dir) 143 if err == nil || os.IsNotExist(err) { 144 return 145 } 146 logger.Warningf("cannot remove %q: %v", dir, err) 147 } 148 149 func writeFile(name string, mode os.FileMode, r io.Reader) error { 150 f, err := os.OpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, mode) 151 if err != nil { 152 return err 153 } 154 defer f.Close() 155 _, err = io.Copy(f, r) 156 return err 157 } 158 159 // ReadTools checks that the tools information for the given version exists 160 // in the dataDir directory, and returns a Tools instance. 161 // The tools information is json encoded in a text file, "downloaded-tools.txt". 162 func ReadTools(dataDir string, vers version.Binary) (*coretools.Tools, error) { 163 dir := SharedToolsDir(dataDir, vers) 164 toolsData, err := ioutil.ReadFile(path.Join(dir, toolsFile)) 165 if err != nil { 166 return nil, fmt.Errorf("cannot read tools metadata in tools directory: %v", err) 167 } 168 var tools coretools.Tools 169 if err := json.Unmarshal(toolsData, &tools); err != nil { 170 return nil, fmt.Errorf("invalid tools metadata in tools directory %q: %v", dir, err) 171 } 172 return &tools, nil 173 } 174 175 // ChangeAgentTools atomically replaces the agent-specific symlink 176 // under dataDir so it points to the previously unpacked 177 // version vers. It returns the new tools read. 178 func ChangeAgentTools(dataDir string, agentName string, vers version.Binary) (*coretools.Tools, error) { 179 tools, err := ReadTools(dataDir, vers) 180 if err != nil { 181 return nil, err 182 } 183 // build absolute path to toolsDir. Windows implementation of symlink 184 // will check for the existance of the source file and error if it does 185 // not exists. This is a limitation of junction points (symlinks) on NTFS 186 toolPath := ToolsDir(dataDir, tools.Version.String()) 187 toolsDir := ToolsDir(dataDir, agentName) 188 189 err = symlink.Replace(toolsDir, toolPath) 190 if err != nil { 191 return nil, fmt.Errorf("cannot replace tools directory: %s", err) 192 } 193 return tools, nil 194 }