]> ruderich.org/simon Gitweb - safcm/safcm.git/blobdiff - remote/run/cmd.go
Move implementation of cmd/safcm-remote/ to remote/
[safcm/safcm.git] / remote / run / cmd.go
diff --git a/remote/run/cmd.go b/remote/run/cmd.go
new file mode 100644 (file)
index 0000000..a1c5e7f
--- /dev/null
@@ -0,0 +1,94 @@
+// Helper type to run and log commands
+
+// Copyright (C) 2021  Simon Ruderich
+//
+// 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 run
+
+import (
+       "bytes"
+       "fmt"
+       "os/exec"
+       "strings"
+
+       "ruderich.org/simon/safcm/remote/log"
+)
+
+type Cmd struct {
+       Runner Runner
+       logger *log.Logger
+}
+
+func NewCmd(runner Runner, logger *log.Logger) *Cmd {
+       return &Cmd{
+               Runner: runner,
+               logger: logger,
+       }
+}
+
+// Run runs a command and return stdout and stderr.
+//
+// Use this only for commands running on our behalf where we need to parse the
+// output. Use CombinedOutput() for user commands because these should show
+// the complete output and have stdout and stderr in the proper order and not
+// split.
+func (c *Cmd) Run(module string, args ...string) ([]byte, []byte, error) {
+       var stdout, stderr bytes.Buffer
+       cmd := exec.Command(args[0], args[1:]...)
+       cmd.Stdout = &stdout
+       cmd.Stderr = &stderr
+       quoted := QuoteForDebug(cmd)
+       c.logger.Debugf("%s: running %s", module, quoted)
+       err := c.Runner.Run(cmd)
+       if stdout.Len() > 0 {
+               c.logger.Debug2f("%s: command stdout:\n%s",
+                       module, stdout.Bytes())
+       }
+       if stderr.Len() > 0 {
+               c.logger.Debug2f("%s: command stderr:\n%s",
+                       module, stderr.Bytes())
+       }
+       if err != nil {
+               return nil, nil, fmt.Errorf(
+                       "%s failed: %v; stdout: %q, stderr: %q",
+                       quoted, err, stdout.String(), stderr.String())
+       }
+       return stdout.Bytes(), stderr.Bytes(), nil
+}
+
+// CombinedOutputCmd runs the command and returns its combined stdout and
+// stderr.
+func (c *Cmd) CombinedOutputCmd(module string, cmd *exec.Cmd) ([]byte, error) {
+       quoted := QuoteForDebug(cmd)
+       c.logger.Debugf("%s: running %s", module, quoted)
+       out, err := c.Runner.CombinedOutput(cmd)
+       if len(out) > 0 {
+               c.logger.Debug2f("%s: command output:\n%s", module, out)
+       }
+       if err != nil {
+               return nil, fmt.Errorf("%s failed: %v; output: %q",
+                       quoted, err, out)
+       }
+       return out, nil
+}
+
+func QuoteForDebug(cmd *exec.Cmd) string {
+       // TODO: proper shell escaping, remove quotes when not necessary
+       var quoted []string
+       for _, x := range append([]string{cmd.Path}, cmd.Args[1:]...) {
+               quoted = append(quoted, fmt.Sprintf("%q", x))
+       }
+       return strings.Join(quoted, " ")
+}