mirror of https://github.com/knative/caching.git
123 lines
3.0 KiB
Go
123 lines
3.0 KiB
Go
/*
|
|
Copyright 2019 The Knative Authors
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package cmd
|
|
|
|
import (
|
|
"bytes"
|
|
"os/exec"
|
|
"strings"
|
|
"sync"
|
|
|
|
shell "github.com/kballard/go-shellquote"
|
|
|
|
"knative.dev/pkg/test/helpers"
|
|
)
|
|
|
|
const (
|
|
invalidInputErrorPrefix = "invalid input: "
|
|
defaultErrCode = 1
|
|
separator = "\n"
|
|
)
|
|
|
|
// RunCommand will run the command and return the standard output, plus error if there is one.
|
|
func RunCommand(cmdLine string) (string, error) {
|
|
cmdSplit, err := shell.Split(cmdLine)
|
|
if len(cmdSplit) == 0 || err != nil {
|
|
return "", &CommandLineError{
|
|
Command: cmdLine,
|
|
ErrorOutput: []byte(invalidInputErrorPrefix + cmdLine),
|
|
ErrorCode: defaultErrCode,
|
|
}
|
|
}
|
|
|
|
cmdName := cmdSplit[0]
|
|
args := cmdSplit[1:]
|
|
cmd := exec.Command(cmdName, args...)
|
|
var eb bytes.Buffer
|
|
cmd.Stderr = &eb
|
|
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
return string(out), &CommandLineError{
|
|
Command: cmdLine,
|
|
ErrorOutput: eb.Bytes(),
|
|
ErrorCode: getErrorCode(err),
|
|
}
|
|
}
|
|
|
|
return string(out), nil
|
|
}
|
|
|
|
// RunCommands will run the commands sequentially.
|
|
// If there is an error when running a command, it will return directly with all standard output so far and the error.
|
|
func RunCommands(cmdLines ...string) (string, error) {
|
|
var outputs []string
|
|
for _, cmdLine := range cmdLines {
|
|
output, err := RunCommand(cmdLine)
|
|
outputs = append(outputs, output)
|
|
if err != nil {
|
|
return strings.Join(outputs, separator), err
|
|
}
|
|
}
|
|
return strings.Join(outputs, separator), nil
|
|
}
|
|
|
|
// RunCommandsInParallel will run the commands in parallel.
|
|
// It will always finish running all commands, and return all standard output and errors together.
|
|
func RunCommandsInParallel(cmdLines ...string) (string, error) {
|
|
errCh := make(chan error, len(cmdLines))
|
|
outputCh := make(chan string, len(cmdLines))
|
|
mx := sync.Mutex{}
|
|
wg := sync.WaitGroup{}
|
|
for i := range cmdLines {
|
|
cmdLine := cmdLines[i]
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
output, err := RunCommand(cmdLine)
|
|
mx.Lock()
|
|
outputCh <- output
|
|
errCh <- err
|
|
mx.Unlock()
|
|
}()
|
|
}
|
|
|
|
wg.Wait()
|
|
close(outputCh)
|
|
close(errCh)
|
|
|
|
os := make([]string, 0, len(cmdLines))
|
|
es := make([]error, 0, len(cmdLines))
|
|
for o := range outputCh {
|
|
os = append(os, o)
|
|
}
|
|
for e := range errCh {
|
|
es = append(es, e)
|
|
}
|
|
|
|
return strings.Join(os, separator), helpers.CombineErrors(es)
|
|
}
|
|
|
|
// getErrorCode extracts the exit code of an *ExitError type
|
|
func getErrorCode(err error) int {
|
|
errorCode := defaultErrCode
|
|
if exitError, ok := err.(*exec.ExitError); ok {
|
|
errorCode = exitError.ExitCode()
|
|
}
|
|
return errorCode
|
|
}
|