blob: 866fe8b1cb055eebbb43b93b2885995bed5a1996 [file]
// Copyright 2020 The LUCI Authors. All rights reserved.
// Use of this source code is governed under the Apache License, Version 2.0
// that can be found in the LICENSE file.
package plugsupport
import (
"bytes"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/logging"
)
type shell struct {
repo *repo
cwd string // relative to repo.root
}
func (s *shell) Cd(path string) {
newRel := s.computeRepoRelative(path)
abs := filepath.Join(s.repo.root, newRel)
st, err := os.Stat(abs)
if err != nil {
panic(errors.Annotate(err, "Cd(%q) -> /%s", path, newRel).Err())
}
if !st.IsDir() {
panic(errors.Reason("Cd(%q) -> /%s: not a directory", path, newRel).Err())
}
s.cwd = newRel
}
func (s *shell) computeRepoRelative(path string) string {
if len(path) == 0 {
return s.cwd
}
var newRel string
if strings.HasPrefix(path, "/") {
newRel = filepath.Clean(path)
} else {
newRel = filepath.Join(s.cwd, path)
}
if strings.HasPrefix(newRel, ".."+string(filepath.Separator)) {
panic(errors.Reason("computeRepoRelative(%q) would leave the repo", path).Err())
}
return newRel
}
func (s *shell) ModifyFile(path string, modify func(oldContents string) string, mode ...os.FileMode) {
abspath := filepath.Join(s.repo.root, s.computeRepoRelative(path))
newMode := os.FileMode(0666)
oldContent, err := ioutil.ReadFile(abspath)
if err == nil {
if len(mode) == 0 {
st, err := os.Stat(abspath)
if err != nil {
panic(errors.Annotate(err, "statting %q", abspath).Err())
}
newMode = st.Mode()
}
} else if !os.IsNotExist(err) {
panic(errors.Annotate(err, "while attempting to read/modify/write %q", abspath).Err())
}
newContent := []byte(modify(string(oldContent)))
os.MkdirAll(filepath.Dir(abspath), 0777)
err = ioutil.WriteFile(abspath, newContent, newMode)
if err != nil {
panic(errors.Annotate(err, "writing file %q", abspath).Err())
}
}
func (s *shell) Stat(path string) os.FileInfo {
relpath := s.computeRepoRelative(path)
abspath := filepath.Join(s.repo.root, relpath)
st, err := os.Stat(abspath)
if os.IsNotExist(err) {
return nil
}
if err == nil {
return st
}
panic(errors.Annotate(err, "Stat(%q)", relpath).Err())
}
func (s *shell) Run(name string, args ...string) {
cmd := exec.CommandContext(s.repo.ctx, name, args...)
cmd.Dir = filepath.Join(s.repo.root, s.cwd)
logging.Infof(s.repo.ctx, "%q: Run(%q %q)", cmd.Dir, name, args)
if err := redirectIOAndWait(cmd, defaultLogger(s.repo.ctx)); err != nil {
panic(errors.Annotate(err, "Run(%q, %q)", name, args).Err())
}
}
func (s *shell) Retval(name string, args ...string) int {
cmd := exec.CommandContext(s.repo.ctx, name, args...)
cmd.Dir = filepath.Join(s.repo.root, s.cwd)
logging.Infof(s.repo.ctx, "%q: Retval(%q %q)", cmd.Dir, name, args)
if err := redirectIOAndWait(cmd, defaultLogger(s.repo.ctx)); err != nil {
if exitError, ok := err.(*exec.ExitError); ok {
return exitError.ExitCode()
}
panic(errors.Annotate(err, "Retval(%q, %q)", name, args).Err())
}
return 0
}
func (s *shell) Stdout(name string, args ...string) string {
cmd := exec.CommandContext(s.repo.ctx, name, args...)
cmd.Dir = filepath.Join(s.repo.root, s.cwd)
var buffer bytes.Buffer
cmd.Stdout = &buffer
logging.Infof(s.repo.ctx, "%q: Stdout(%q %q)", cmd.Dir, name, args)
if err := redirectIOAndWait(cmd, defaultLogger(s.repo.ctx)); err != nil {
panic(errors.Annotate(err, "Stdout(%q, %q)", name, args).Err())
}
return buffer.String()
}