Files
Mark D Horn 59302346d6 linter: enable whitespace
Signed-off-by: Mark D Horn <mark.d.horn@intel.com>
2020-04-24 14:53:17 -07:00

195 lines
4.3 KiB
Go

// Copyright © 2020 Intel Corporation
//
// SPDX-License-Identifier: GPL-3.0-only
package cmd
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"os/exec"
"strings"
"github.com/clearlinux/clr-installer/log"
"github.com/clearlinux/clr-installer/proxy"
)
// Output interface allows implementors to process the output from a
// command according to their specific case
type Output interface {
Process(printPrefix, line string)
}
type runLogger struct{}
func (rl runLogger) Write(p []byte) (n int, err error) {
for _, curr := range strings.Split(string(p), "\n") {
if curr == "" {
continue
}
log.Debug(curr)
}
return len(p), nil
}
// RunAndLog executes a command (similar to Run) but takes care of writing
// the output to default logger
func RunAndLog(args ...string) error {
return Run(runLogger{}, args...)
}
// RunAndLogWithEnv does the same as RunAndLog but it changes the execution's environment
// variables adding the provided ones by the env argument
func RunAndLogWithEnv(env map[string]string, args ...string) error {
return run(nil, runLogger{}, env, args...)
}
// PipeRunAndLog is similar to RunAndLog runs a command and writes the output
// to default logger and also writes in to the process stdin
func PipeRunAndLog(in string, args ...string) error {
return run(func(cmd *exec.Cmd) error {
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}
go func() {
defer func() {
_ = stdin.Close()
}()
_, _ = io.WriteString(stdin, in)
}()
return nil
}, runLogger{}, nil, args...)
}
// PipeRunAndPipeOut is similar to PipeRunAndLog but runs a command by feeding
// a string to stdin of Cmd and output is written to a byte buffer instead of a log
func PipeRunAndPipeOut(in string, out *bytes.Buffer, args ...string) error {
return run(func(cmd *exec.Cmd) error {
stdin, err := cmd.StdinPipe()
if err != nil {
return err
}
go func() {
defer func() {
_ = stdin.Close()
}()
_, _ = io.WriteString(stdin, in)
}()
return nil
}, out, nil, args...)
}
func run(sw func(cmd *exec.Cmd) error, writer io.Writer, env map[string]string, args ...string) error {
var exe string
var cmdArgs []string
log.Debug("%s", strings.Join(args, " "))
exe = args[0]
cmdArgs = args[1:]
cmd := exec.Command(exe, cmdArgs...)
// Add any proxy environment variables
for _, pvar := range proxy.GetProxyValues() {
cmd.Env = append(cmd.Env, pvar)
}
log.Debug("cmd.Env: %+v", cmd.Env)
if sw != nil {
if err := sw(cmd); err != nil {
return err
}
}
cmd.Stdout = writer
cmd.Stderr = writer
if cmd.Stdin == nil {
cmd.Stdin = os.Stdin
}
for k, v := range env {
curr := fmt.Sprintf("%s=%s", k, v)
cmd.Args = append(cmd.Args, curr)
cmd.Env = append(cmd.Env, curr)
}
err := cmd.Run()
if err != nil {
return err
}
return nil
}
// Run executes a command and uses writer to write both stdout and stderr
// args are the actual command and its arguments
func Run(writer io.Writer, args ...string) error {
return run(nil, writer, nil, args...)
}
// RunAndProcessOutput executes a command and process the output from
// Stdout and Stderr according to the implementor
// args are the actual command and its arguments
func RunAndProcessOutput(printPrefix string, output Output, args ...string) error {
var exe string
var cmdArgs []string
log.Debug(strings.Join(args, " "))
exe = args[0]
cmdArgs = args[1:]
cmd := exec.Command(exe, cmdArgs...)
// Add any proxy environment variables
for _, pvar := range proxy.GetProxyValues() {
cmd.Env = append(cmd.Env, pvar)
}
log.Debug("cmd.Env: %+v", cmd.Env)
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Error("Could not connect a pipe to Stdout")
return err
}
// run the command but don't wait for it to finish
if err := cmd.Start(); err != nil {
log.Error("Failed to start command execution: %s", exe)
return err
}
// start scanning stdout for messages
scannerOut := bufio.NewScanner(stdout)
for scannerOut.Scan() {
// specific processing implementation
output.Process(printPrefix, scannerOut.Text())
}
if err := scannerOut.Err(); err != nil {
log.Error("An error occurred while reading stdout")
return err
}
// wait for the command to finish running
if err := cmd.Wait(); err != nil {
log.Error("An error occurred executing command: \"%s\". Error: %s", strings.Join(args, " "), err)
return err
}
return nil
}