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