github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/environs/tools/build.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 "fmt" 11 "io" 12 "io/ioutil" 13 "os" 14 "os/exec" 15 "path/filepath" 16 "strings" 17 18 "github.com/juju/juju/juju/names" 19 "github.com/juju/juju/version" 20 ) 21 22 // Archive writes the executable files found in the given directory in 23 // gzipped tar format to w. 24 func Archive(w io.Writer, dir string) error { 25 entries, err := ioutil.ReadDir(dir) 26 if err != nil { 27 return err 28 } 29 30 gzw := gzip.NewWriter(w) 31 defer closeErrorCheck(&err, gzw) 32 33 tarw := tar.NewWriter(gzw) 34 defer closeErrorCheck(&err, tarw) 35 36 for _, ent := range entries { 37 h := tarHeader(ent) 38 logger.Debugf("adding entry: %#v", h) 39 // ignore local umask 40 if isExecutable(ent) { 41 h.Mode = 0755 42 } else { 43 h.Mode = 0644 44 } 45 err := tarw.WriteHeader(h) 46 if err != nil { 47 return err 48 } 49 fileName := filepath.Join(dir, ent.Name()) 50 if err := copyFile(tarw, fileName); err != nil { 51 return err 52 } 53 } 54 return nil 55 } 56 57 // archiveAndSHA256 calls Archive with the provided arguments, 58 // and returns a hex-encoded SHA256 hash of the resulting 59 // archive. 60 func archiveAndSHA256(w io.Writer, dir string) (sha256hash string, err error) { 61 h := sha256.New() 62 if err := Archive(io.MultiWriter(h, w), dir); err != nil { 63 return "", err 64 } 65 return fmt.Sprintf("%x", h.Sum(nil)), err 66 } 67 68 // copyFile writes the contents of the given file to w. 69 func copyFile(w io.Writer, file string) error { 70 f, err := os.Open(file) 71 if err != nil { 72 return err 73 } 74 defer f.Close() 75 _, err = io.Copy(w, f) 76 return err 77 } 78 79 // tarHeader returns a tar file header given the file's stat 80 // information. 81 func tarHeader(i os.FileInfo) *tar.Header { 82 return &tar.Header{ 83 Typeflag: tar.TypeReg, 84 Name: i.Name(), 85 Size: i.Size(), 86 Mode: int64(i.Mode() & 0777), 87 ModTime: i.ModTime(), 88 AccessTime: i.ModTime(), 89 ChangeTime: i.ModTime(), 90 Uname: "ubuntu", 91 Gname: "ubuntu", 92 } 93 } 94 95 // isExecutable returns whether the given info 96 // represents a regular file executable by (at least) the user. 97 func isExecutable(i os.FileInfo) bool { 98 return i.Mode()&(0100|os.ModeType) == 0100 99 } 100 101 // closeErrorCheck means that we can ensure that 102 // Close errors do not get lost even when we defer them, 103 func closeErrorCheck(errp *error, c io.Closer) { 104 err := c.Close() 105 if *errp == nil { 106 *errp = err 107 } 108 } 109 110 func setenv(env []string, val string) []string { 111 prefix := val[0 : strings.Index(val, "=")+1] 112 for i, eval := range env { 113 if strings.HasPrefix(eval, prefix) { 114 env[i] = val 115 return env 116 } 117 } 118 return append(env, val) 119 } 120 121 func findExecutable(execFile string) (string, error) { 122 logger.Debugf("looking for: %s", execFile) 123 if filepath.IsAbs(execFile) { 124 return execFile, nil 125 } 126 127 dir, file := filepath.Split(execFile) 128 129 // Now we have two possibilities: 130 // file == path indicating that the PATH was searched 131 // dir != "" indicating that it is a relative path 132 133 if dir == "" { 134 path := os.Getenv("PATH") 135 for _, name := range filepath.SplitList(path) { 136 result := filepath.Join(name, file) 137 // Use exec.LookPath() to check if the file exists and is executable` 138 f, err := exec.LookPath(result) 139 if err == nil { 140 return f, nil 141 } 142 } 143 144 return "", fmt.Errorf("could not find %q in the path", file) 145 } 146 cwd, err := os.Getwd() 147 if err != nil { 148 return "", err 149 } 150 return filepath.Clean(filepath.Join(cwd, execFile)), nil 151 } 152 153 func copyExistingJujud(dir string) error { 154 // Assume that the user is running juju. 155 jujuLocation, err := findExecutable(os.Args[0]) 156 if err != nil { 157 logger.Infof("%v", err) 158 return err 159 } 160 jujudLocation := filepath.Join(filepath.Dir(jujuLocation), names.Jujud) 161 logger.Debugf("checking: %s", jujudLocation) 162 info, err := os.Stat(jujudLocation) 163 if err != nil { 164 logger.Infof("couldn't find existing jujud") 165 return err 166 } 167 logger.Infof("found existing jujud") 168 // TODO(thumper): break this out into a util function. 169 // copy the file into the dir. 170 source, err := os.Open(jujudLocation) 171 if err != nil { 172 logger.Infof("open source failed: %v", err) 173 return err 174 } 175 defer source.Close() 176 target := filepath.Join(dir, names.Jujud) 177 logger.Infof("target: %v", target) 178 destination, err := os.OpenFile(target, os.O_RDWR|os.O_TRUNC|os.O_CREATE, info.Mode()) 179 if err != nil { 180 logger.Infof("open destination failed: %v", err) 181 return err 182 } 183 defer destination.Close() 184 _, err = io.Copy(destination, source) 185 if err != nil { 186 return err 187 } 188 return nil 189 } 190 191 func buildJujud(dir string) error { 192 logger.Infof("building jujud") 193 cmds := [][]string{ 194 {"go", "build", "-gccgoflags=-static-libgo", "-o", filepath.Join(dir, names.Jujud), "github.com/juju/juju/cmd/jujud"}, 195 } 196 for _, args := range cmds { 197 cmd := exec.Command(args[0], args[1:]...) 198 out, err := cmd.CombinedOutput() 199 if err != nil { 200 return fmt.Errorf("build command %q failed: %v; %s", args[0], err, out) 201 } 202 } 203 return nil 204 } 205 206 // BundleToolsFunc is a function which can bundle all the current juju tools 207 // in gzipped tar format to the given writer. 208 type BundleToolsFunc func(w io.Writer, forceVersion *version.Number) (version.Binary, string, error) 209 210 // Override for testing. 211 var BundleTools BundleToolsFunc = bundleTools 212 213 // BundleTools bundles all the current juju tools in gzipped tar 214 // format to the given writer. 215 // If forceVersion is not nil, a FORCE-VERSION file is included in 216 // the tools bundle so it will lie about its current version number. 217 func bundleTools(w io.Writer, forceVersion *version.Number) (tvers version.Binary, sha256Hash string, err error) { 218 dir, err := ioutil.TempDir("", "juju-tools") 219 if err != nil { 220 return version.Binary{}, "", err 221 } 222 defer os.RemoveAll(dir) 223 224 if err := copyExistingJujud(dir); err != nil { 225 logger.Debugf("copy existing failed: %v", err) 226 if err := buildJujud(dir); err != nil { 227 return version.Binary{}, "", err 228 } 229 } 230 231 if forceVersion != nil { 232 logger.Debugf("forcing version to %s", forceVersion) 233 if err := ioutil.WriteFile(filepath.Join(dir, "FORCE-VERSION"), []byte(forceVersion.String()), 0666); err != nil { 234 return version.Binary{}, "", err 235 } 236 } 237 cmd := exec.Command(filepath.Join(dir, names.Jujud), "version") 238 out, err := cmd.CombinedOutput() 239 if err != nil { 240 return version.Binary{}, "", fmt.Errorf("cannot get version from %q: %v; %s", cmd.Args[0], err, out) 241 } 242 tvs := strings.TrimSpace(string(out)) 243 tvers, err = version.ParseBinary(tvs) 244 if err != nil { 245 return version.Binary{}, "", fmt.Errorf("invalid version %q printed by jujud", tvs) 246 } 247 248 sha256hash, err := archiveAndSHA256(w, dir) 249 if err != nil { 250 return version.Binary{}, "", err 251 } 252 return tvers, sha256hash, err 253 }