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  }