2021-02-03 20:00:11 +01:00
|
|
|
package sampleapp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
2021-02-03 23:00:31 +01:00
|
|
|
"io/ioutil"
|
2021-02-03 20:00:11 +01:00
|
|
|
"os/exec"
|
2021-02-03 23:00:31 +01:00
|
|
|
"path/filepath"
|
2021-02-03 20:00:11 +01:00
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/kris-nova/logger"
|
|
|
|
)
|
|
|
|
|
2021-02-03 23:00:31 +01:00
|
|
|
// Script is a set of commands delimited by newlines
|
|
|
|
// Comments # and // are ignored.
|
2021-02-03 20:00:11 +01:00
|
|
|
type Script struct {
|
|
|
|
commands []string
|
|
|
|
}
|
|
|
|
|
2021-02-03 23:00:31 +01:00
|
|
|
// NewScriptFromPath is used to build an executable script from a path of disk.
|
|
|
|
func NewScriptFromPath(path string) (*Script, error) {
|
|
|
|
path, err := filepath.Abs(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to calculate fully qualified path for path: %s: %v", path, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
bytes, err := ioutil.ReadFile(path)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to read from path %s: %v", path, err)
|
|
|
|
}
|
|
|
|
content := string(bytes)
|
|
|
|
return NewScriptFromString(content), nil
|
|
|
|
}
|
|
|
|
|
2021-02-04 04:19:19 +01:00
|
|
|
const (
|
|
|
|
// IgnoreSpacesBash is the amount of (spaces - 1) that we see in common \\n delimited commands
|
|
|
|
IgnoreSpacesBash string = " "
|
|
|
|
|
|
|
|
// IgnoreTabs is the tab character
|
|
|
|
IgnoreTabs string = "\t"
|
|
|
|
)
|
|
|
|
|
2021-02-03 23:00:31 +01:00
|
|
|
// NewScriptFromString is used to build an executable script from the content in string form.
|
|
|
|
func NewScriptFromString(str string) *Script {
|
2021-02-03 20:00:11 +01:00
|
|
|
script := &Script{}
|
2021-02-04 04:19:19 +01:00
|
|
|
removeRuleF := func(str string, rs []string) string {
|
|
|
|
for _, r := range rs {
|
|
|
|
str = strings.Replace(str, r, "", -1)
|
|
|
|
}
|
|
|
|
return str
|
|
|
|
}
|
|
|
|
str = strings.Replace(str, "\\\n", "", -1)
|
|
|
|
str = removeRuleF(str, []string{IgnoreSpacesBash, IgnoreTabs})
|
2021-02-03 20:00:11 +01:00
|
|
|
spl := strings.Split(str, "\n")
|
|
|
|
//logger.Info("Script lines: %d", len(spl))
|
|
|
|
for _, line := range spl {
|
|
|
|
script.commands = append(script.commands, line)
|
|
|
|
}
|
|
|
|
return script
|
|
|
|
}
|
|
|
|
|
2021-02-03 23:00:31 +01:00
|
|
|
// Interpret is used to procedurally execute a script. The script will execute each line independently
|
|
|
|
// and can error at any point in the executation path.
|
2021-02-03 20:00:11 +01:00
|
|
|
func (s *Script) Interpret() error {
|
|
|
|
//logger.Info("Running script...")
|
|
|
|
for i, cmdStr := range s.commands {
|
|
|
|
// Exec will hang for output
|
|
|
|
// Ignore newlines
|
|
|
|
// Ignore comments starting with #
|
|
|
|
// Ignore comments starting with //
|
2021-02-03 23:00:31 +01:00
|
|
|
if cmdStr == "\n" || cmdStr == "" || strings.HasPrefix(cmdStr, "#") || strings.HasPrefix(cmdStr, "//") {
|
2021-02-03 20:00:11 +01:00
|
|
|
continue
|
|
|
|
}
|
2021-02-04 04:19:19 +01:00
|
|
|
logger.Info("Executing: [%s]", cmdStr)
|
2021-02-03 20:00:11 +01:00
|
|
|
result, err := Exec(cmdStr)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error executing running command [%s] on line [%d]\n%v\n", cmdStr, i+1, err)
|
|
|
|
} else if result.exitCode != 0 {
|
2021-02-03 23:00:31 +01:00
|
|
|
return fmt.Errorf("non zero exit code running command [%s] on line [%d]\n%s\n%s\n", cmdStr, i+1, result.Stdout(), result.Stderr())
|
2021-02-03 20:00:11 +01:00
|
|
|
}
|
|
|
|
// Here is where we log STDOUT from a "script"
|
|
|
|
// Right now it is set to DEBUG which can be enabled by
|
|
|
|
// setting logger.Level = 4
|
|
|
|
logger.Debug(result.Stdout())
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type ExecResult struct {
|
|
|
|
stderr string
|
|
|
|
stdout string
|
|
|
|
exitCode int
|
|
|
|
execErr exec.ExitError
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *ExecResult) Stdout() string {
|
|
|
|
return e.stdout
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *ExecResult) Stderr() string {
|
|
|
|
return e.stderr
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *ExecResult) ExitCode() int {
|
|
|
|
if e == nil {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return e.exitCode
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *ExecResult) ExecError() exec.ExitError {
|
|
|
|
return e.execErr
|
|
|
|
}
|
|
|
|
|
|
|
|
// Exec will take an arbitrary executable string
|
|
|
|
// and hang until the command exits
|
|
|
|
func Exec(str string) (*ExecResult, error) {
|
|
|
|
//logger.Info("Exec [%s]", str)
|
|
|
|
var cmdstr string
|
|
|
|
var args []string
|
|
|
|
var l int
|
|
|
|
spl := strings.Split(str, " ")
|
|
|
|
l = len(spl)
|
|
|
|
if l == 1 {
|
|
|
|
// <cmd>
|
|
|
|
cmdstr = spl[0]
|
|
|
|
} else if l > 1 {
|
|
|
|
// <cmd> <arg>...
|
|
|
|
cmdstr = spl[0]
|
|
|
|
for i := 1; i < l; i++ {
|
|
|
|
args = append(args, spl[i])
|
|
|
|
}
|
|
|
|
} else if l < 1 {
|
|
|
|
return nil, fmt.Errorf("invalid Exec() string %s", str)
|
|
|
|
}
|
|
|
|
fqpcmd, err := exec.LookPath(cmdstr)
|
|
|
|
if err != nil {
|
2021-02-03 23:00:31 +01:00
|
|
|
logger.Debug("unable to find fully qualified path for executable %s: %v", cmdstr, err)
|
2021-02-03 20:00:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
//logger.Info("Command: %s", fqpcmd)
|
|
|
|
//logger.Info("Args: %v", args)
|
|
|
|
|
|
|
|
stdoutBuffer := bytes.Buffer{}
|
|
|
|
stderrBuffer := bytes.Buffer{}
|
2021-02-03 23:00:31 +01:00
|
|
|
e := []string{fqpcmd}
|
|
|
|
for _, arg := range args {
|
|
|
|
e = append(e, arg)
|
|
|
|
}
|
2021-02-03 20:00:11 +01:00
|
|
|
cmd := exec.Command(e[0], e[1:]...)
|
|
|
|
cmd.Stdout = &stdoutBuffer
|
|
|
|
cmd.Stderr = &stderrBuffer
|
|
|
|
result := &ExecResult{}
|
|
|
|
err = cmd.Run()
|
|
|
|
if err != nil {
|
|
|
|
if eerr, ok := err.(*exec.ExitError); ok {
|
|
|
|
result.stderr = stderrBuffer.String()
|
|
|
|
result.stdout = stdoutBuffer.String()
|
|
|
|
result.exitCode = eerr.ExitCode()
|
|
|
|
result.execErr = *eerr
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
return nil, fmt.Errorf("major error running command [%s]: %v", str, err)
|
|
|
|
}
|
|
|
|
result.stderr = stderrBuffer.String()
|
|
|
|
result.stdout = stdoutBuffer.String()
|
|
|
|
result.exitCode = 0
|
|
|
|
return result, nil
|
|
|
|
}
|