// 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 }