grim/convey

Add a .reviewboardrc file

2022-03-26, Gary Kramlich
8fea0c778f8e
Add a .reviewboardrc file
// Convey
// Copyright 2016-2018 Gary Kramlich <grim@reaperworld.com>
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
// Package exec provides utilities for executing external commands.
package exec
import (
"bufio"
"fmt"
"io"
"os/exec"
"sync"
"time"
log "github.com/sirupsen/logrus"
"keep.imfreedom.org/grim/convey/logging"
)
// Run runs the command specified in argv and logs stderr and stdout to a new
// log adapter.
func Run(name string, cmdv []string, timeout time.Duration) error {
var (
logger = logging.NewAdapter(name)
outCollector = newLogCollector(logger, log.InfoLevel)
errCollector = newLogCollector(logger, log.ErrorLevel)
)
return run(name, cmdv, nil, timeout, &sync.WaitGroup{}, outCollector, errCollector)
}
// RunWithStdin runs the command specified in argv and passes stdin to the
// process's stdin. stderr and stdout are logged to a new log adapter.
func RunWithStdin(name string, cmdv []string, stdin io.Reader, timeout time.Duration) error {
var (
logger = logging.NewAdapter(name)
outCollector = newLogCollector(logger, log.InfoLevel)
errCollector = newLogCollector(logger, log.ErrorLevel)
)
return run(name, cmdv, stdin, timeout, &sync.WaitGroup{}, outCollector, errCollector)
}
// RunOutput works just like Run but returns stdout and stderr instead of
// logging it.
func RunOutput(name string, cmdv []string, timeout time.Duration) (string, string, error) {
var (
wg = &sync.WaitGroup{}
outCollector = newStringCollector(wg)
errCollector = newStringCollector(wg)
)
err := run(name, cmdv, nil, timeout, wg, outCollector.handler, errCollector.handler)
wg.Wait()
return outCollector.output, errCollector.output, err
}
func run(name string, cmdv []string, stdin io.Reader, timeout time.Duration, wg *sync.WaitGroup, outHandler, errHandler collector) error {
logger := logging.NewAdapter(name)
logger.Debugf("running command \"%v\"", cmdv)
cmd := exec.Command(cmdv[0], cmdv[1:]...) // nolint: gas
// setup the stdout wrapper
outReader, err := cmd.StdoutPipe()
if err != nil {
return err
}
// setup the stderr wrapper
errReader, err := cmd.StderrPipe()
if err != nil {
return err
}
if stdin != nil {
stdinPipe, err := cmd.StdinPipe()
if err != nil {
return err
}
go func() {
defer stdinPipe.Close()
io.Copy(stdinPipe, stdin)
}()
}
// at this point we're committed to running the log handlers so increment
// the waitgroup
wg.Add(2)
// Pass output to the handlers
go outHandler(bufio.NewScanner(outReader))
go errHandler(bufio.NewScanner(errReader))
// now run the command
err = cmd.Start()
if err != nil {
return err
}
var timer *time.Timer
if timeout != 0 {
timer = time.AfterFunc(timeout, func() {
// kill process, unblock the cmd.Wait() below
killErr := cmd.Process.Kill()
if killErr != nil {
fmt.Printf("error killing process: %s\n", killErr)
}
// log that the process timed out
logger.Errorf("command %v timed out after %v", cmdv, timeout)
})
}
err = cmd.Wait()
// if cmd.Wait() returns before the timeout, we'll have a timer stop
if timer != nil {
timer.Stop()
}
return err
}