github.com/vanadium-archive/go.jiri@v0.0.0-20160715023856-abfb8b131290/profiles/profilesutil/util.go (about) 1 // Copyright 2015 The Vanadium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // package profilesutil provides utility routines for implementing profiles. 6 package profilesutil 7 8 import ( 9 "archive/zip" 10 "bufio" 11 "bytes" 12 "fmt" 13 "io/ioutil" 14 "net/http" 15 "os" 16 "path/filepath" 17 "runtime" 18 "sync" 19 20 "v.io/jiri" 21 "v.io/jiri/tool" 22 ) 23 24 const ( 25 DefaultDirPerm = os.FileMode(0755) 26 DefaultFilePerm = os.FileMode(0644) 27 ) 28 29 // IsFNLHost returns true iff the host machine is running FNL 30 // TODO(bprosnitz) We should find a better way to detect that the machine is 31 // running FNL 32 // TODO(bprosnitz) This is needed in part because fnl is not currently a 33 // GOHOSTOS. This should probably be handled by having hosts that are separate 34 // from GOHOSTOSs similarly to how targets are defined. 35 func IsFNLHost() bool { 36 return os.Getenv("FNL_SYSTEM") != "" 37 } 38 39 var ( 40 usingAptitude = false 41 usingYum = false 42 usingPacman = false 43 44 testAptitudeOnce, testYumOnce, testPacmanOnce sync.Once 45 ) 46 47 // UsingAptitude returns true if the aptitude package manager (debian, ubuntu) 48 // is being used by the underlying OS. 49 func UsingAptitude(jirix *jiri.X) bool { 50 testAptitudeOnce.Do(func() { 51 usingAptitude = jirix.NewSeq().Last("apt-get", "-v") == nil 52 }) 53 return usingAptitude 54 } 55 56 // UsingYum returns true if the yum/rpm package manager (redhat) is being used 57 // by the underlying OS. 58 func UsingYum(jirix *jiri.X) bool { 59 testYumOnce.Do(func() { 60 usingYum = jirix.NewSeq().Last("yum", "--version") == nil 61 }) 62 return usingYum 63 } 64 65 // UsingPacman returns true if the pacman package manager (archlinux) is being 66 // used by the underlying OS. 67 func UsingPacman(jirix *jiri.X) bool { 68 testPacmanOnce.Do(func() { 69 usingPacman = jirix.NewSeq().Last("pacman", "-V") == nil 70 }) 71 return usingPacman 72 } 73 74 // AtomicAction performs an action 'atomically' by keeping track of successfully 75 // completed actions in the supplied completion log and re-running them if they 76 // are not successfully logged therein after deleting the entire contents of the 77 // dir parameter. Consequently it does not make sense to apply AtomicAction to 78 // the same directory in sequence. 79 func AtomicAction(jirix *jiri.X, installFn func() error, dir, message string) error { 80 atomicFn := func() error { 81 completionLogPath := filepath.Join(dir, ".complete") 82 s := jirix.NewSeq() 83 if dir != "" { 84 if exists, _ := s.IsDir(dir); exists { 85 // If the dir exists but the completionLogPath doesn't, then it 86 // means the previous action didn't finish. 87 // Remove the dir so we can perform the action again. 88 if exists, _ := s.IsFile(completionLogPath); !exists { 89 s.RemoveAll(dir).Done() 90 } else { 91 if jirix.Verbose() { 92 fmt.Fprintf(jirix.Stdout(), "AtomicAction: %s already completed in %s\n", message, dir) 93 } 94 return nil 95 } 96 } 97 } 98 if err := installFn(); err != nil { 99 if dir != "" { 100 s.RemoveAll(dir).Done() 101 } 102 return err 103 } 104 return s.WriteFile(completionLogPath, []byte("completed"), DefaultFilePerm).Done() 105 } 106 return jirix.NewSeq().Call(atomicFn, message).Done() 107 } 108 109 func brewList(jirix *jiri.X) (map[string]bool, error) { 110 var out bytes.Buffer 111 err := jirix.NewSeq().Capture(&out, &out).Last("brew", "list") 112 if err != nil || tool.VerboseFlag { 113 fmt.Fprintf(jirix.Stdout(), "%s", out.String()) 114 } 115 scanner := bufio.NewScanner(&out) 116 pkgs := map[string]bool{} 117 for scanner.Scan() { 118 pkgs[scanner.Text()] = true 119 } 120 return pkgs, err 121 } 122 123 func linuxList(jirix *jiri.X, pkgs []string) (map[string]bool, error) { 124 aptitude, yum, pacman := UsingAptitude(jirix), UsingYum(jirix), UsingPacman(jirix) 125 cmd := "" 126 opt := "" 127 switch { 128 case aptitude: 129 cmd = "dpkg" 130 opt = "-L" 131 case yum: 132 cmd = "yum" 133 opt = "list" 134 case pacman: 135 cmd = "pacman" 136 opt = "-Q" 137 default: 138 return nil, fmt.Errorf("no usable package manager found, tested for aptitude, yum and pacman") 139 } 140 s := jirix.NewSeq() 141 installedPkgs := map[string]bool{} 142 for _, pkg := range pkgs { 143 if err := s.Capture(ioutil.Discard, ioutil.Discard).Last(cmd, opt, pkg); err == nil { 144 installedPkgs[pkg] = true 145 } 146 } 147 return installedPkgs, nil 148 } 149 150 func linuxInstall(jirix *jiri.X, pkgs []string) []string { 151 aptitude, yum, pacman := UsingAptitude(jirix), UsingYum(jirix), UsingPacman(jirix) 152 var cmd []string 153 switch { 154 case aptitude: 155 cmd = append(cmd, "apt-get", "install", "-y") 156 case yum: 157 cmd = append(cmd, "yum", "install", "-y") 158 case pacman: 159 cmd = append(cmd, "pacman", "-S", "--noconfirm") 160 default: 161 fmt.Fprintf(jirix.Stdout(), "no usable package manager found, tested for aptitude, yum and pacman") 162 return nil 163 } 164 return append(cmd, pkgs...) 165 } 166 167 // MissingOSPackages returns the subset of the supplied packages that are 168 // missing from the underlying operating system and hence will need to 169 // be installed. 170 func MissingOSPackages(jirix *jiri.X, pkgs []string) ([]string, error) { 171 installedPkgs := map[string]bool{} 172 switch runtime.GOOS { 173 case "linux": 174 if IsFNLHost() { 175 fmt.Fprintf(jirix.Stdout(), "skipping %v on FNL host\n", pkgs) 176 break 177 } 178 var err error 179 installedPkgs, err = linuxList(jirix, pkgs) 180 if err != nil { 181 return nil, err 182 } 183 case "darwin": 184 var err error 185 installedPkgs, err = brewList(jirix) 186 if err != nil { 187 return nil, err 188 } 189 } 190 missing := []string{} 191 for _, pkg := range pkgs { 192 if !installedPkgs[pkg] { 193 missing = append(missing, pkg) 194 } 195 } 196 return missing, nil 197 } 198 199 // OSPackagesInstallCommands returns the list of commands required to 200 // install the specified packages on the underlying operating system. 201 func OSPackageInstallCommands(jirix *jiri.X, pkgs []string) [][]string { 202 cmds := make([][]string, 0, 1) 203 switch runtime.GOOS { 204 case "linux": 205 if IsFNLHost() { 206 fmt.Fprintf(jirix.Stdout(), "skipping %v on FNL host\n", pkgs) 207 break 208 } 209 if len(pkgs) > 0 { 210 cmds = append(cmds, linuxInstall(jirix, pkgs)) 211 } 212 case "darwin": 213 if len(pkgs) > 0 { 214 return append(cmds, append([]string{"brew", "install"}, pkgs...)) 215 } 216 } 217 return cmds 218 } 219 220 // Fetch downloads the specified url and saves it to dst. 221 func Fetch(jirix *jiri.X, dst, url string) error { 222 s := jirix.NewSeq() 223 s.Output([]string{"fetching " + url}) 224 resp, err := http.Get(url) 225 if err != nil { 226 return err 227 } 228 defer resp.Body.Close() 229 if resp.StatusCode != http.StatusOK { 230 return fmt.Errorf("got non-200 status code while getting %v: %v", url, resp.StatusCode) 231 } 232 file, err := s.Create(dst) 233 if err != nil { 234 return err 235 } 236 if _, err := s.Copy(file, resp.Body); err != nil { 237 return err 238 } 239 return file.Close() 240 } 241 242 // Untar untars the file in srcFile and puts resulting files in directory dstDir. 243 func Untar(jirix *jiri.X, srcFile, dstDir string) error { 244 s := jirix.NewSeq() 245 if err := s.MkdirAll(dstDir, 0755).Done(); err != nil { 246 return err 247 } 248 return s.Output([]string{"untarring " + srcFile + " into " + dstDir}). 249 Pushd(dstDir). 250 Last("tar", "xvf", srcFile) 251 } 252 253 // Unzip unzips the file in srcFile and puts resulting files in directory dstDir. 254 func Unzip(jirix *jiri.X, srcFile, dstDir string) error { 255 r, err := zip.OpenReader(srcFile) 256 if err != nil { 257 return err 258 } 259 defer r.Close() 260 261 unzipFn := func(zFile *zip.File) error { 262 rc, err := zFile.Open() 263 if err != nil { 264 return err 265 } 266 defer rc.Close() 267 268 s := jirix.NewSeq() 269 fileDst := filepath.Join(dstDir, zFile.Name) 270 if zFile.FileInfo().IsDir() { 271 return s.MkdirAll(fileDst, zFile.Mode()).Done() 272 } 273 274 // Make sure the parent directory exists. Note that sometimes files 275 // can appear in a zip file before their directory. 276 dirmode := zFile.Mode() | 0100 277 if dirmode&0060 != 0 { 278 // "group" has read or write permissions, so give 279 // execute permissions on the directory. 280 dirmode = dirmode | 0010 281 } 282 if err := s.MkdirAll(filepath.Dir(fileDst), dirmode).Done(); err != nil { 283 return err 284 } 285 file, err := s.OpenFile(fileDst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, zFile.Mode()) 286 if err != nil { 287 288 return err 289 } 290 defer file.Close() 291 _, err = s.Copy(file, rc) 292 return err 293 } 294 s := jirix.NewSeq() 295 s.Output([]string{"unzipping " + srcFile}) 296 for _, zFile := range r.File { 297 s.Output([]string{"extracting " + zFile.Name}) 298 s.Call(func() error { return unzipFn(zFile) }, "unzipFn(%s)", zFile.Name) 299 } 300 return s.Done() 301 }