-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathaction.go
156 lines (143 loc) · 4.55 KB
/
action.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package slackchatops
import (
"bytes"
"fmt"
"log"
"os/exec"
"os/user"
"path/filepath"
"strconv"
"strings"
"syscall"
)
// Action represents what the system should perform. This is typically some type of command
type Action struct {
Name string // friendly name of the action
Description string // description of the action
Command string // actual command being called
WorkingDir string // working directory for the command to be called in
Params []string // parameters the command needs to run. When executed the user will pass these in as arguments. They will be appended to the Args list
Args []string // arguments to pass to the command. If any are predefined in the config.yaml file (defaults) then user passed arguments (Params) will be appended to the end
OutputFile string // if the command being executed writes to a file. StdErr and StdOut are already captured. This could be an html document from a set of unit tests for example
AuthorizedUsers []string // list of autorized users that are allowed to execute this action. This should be their slackId
}
// Result of an Action being executed on the system
type Result struct {
ReturnCode int
StdOut string
StdError string
}
// Run actually executes the command
func (a *Action) Run(args ...string) (Result, error) {
mergedArgs := a.ParseArgs(args)
cmd := exec.Command(a.Command, mergedArgs...)
if a.WorkingDir != "" {
path, _ := ExpandPath(a.WorkingDir)
cmd.Dir = path
}
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
exitCode := 0
outStr, errStr := stdout.String(), stderr.String()
if err != nil {
// try to get the exit code
if exitError, ok := err.(*exec.ExitError); ok {
ws := exitError.Sys().(syscall.WaitStatus)
exitCode = ws.ExitStatus()
} else {
// This will happen (in OSX) if `name` is not available in $PATH,
// in this situation, exit code could not be get, and stderr will be
// empty string very likely, so we use the default fail code, and format err
// to string and set to stderr
log.Printf("Could not get exit code for failed program: %v, %v", a.Command, a.Args)
exitCode = 1
if errStr == "" {
errStr = err.Error()
}
}
} else {
// success, exitCode should be 0 if go is ok
ws := cmd.ProcessState.Sys().(syscall.WaitStatus)
exitCode = ws.ExitStatus()
}
return Result{
ReturnCode: exitCode,
StdError: errStr,
StdOut: outStr,
}, err
}
// ValidateArgs will ensure all tokenized parameters {x} have been replaced
func (a *Action) ValidateArgs() error {
//ensure given number of params we have same number of tokens
for i := 0; i < len(a.Params); i++ {
p := "{" + strconv.Itoa(i) + "}"
valid := false
for _, ar := range a.Args {
if strings.Contains(ar, p) {
valid = true
break
}
}
if !valid {
return fmt.Errorf("Action %s is missing argument %s for parameter %s", a.Name, p, a.Params[i])
}
}
//Now parse args and ensure
args := a.ParseArgs(a.Params)
for i := 0; i < 20; i++ { //choosing arbitrary number (20)
p := "{" + strconv.Itoa(i) + "}"
for _, ar := range args {
if strings.Contains(ar, p) {
return fmt.Errorf("Action %s has too many tokenized arguments. %s is not used", a.Name, p)
}
}
}
return nil
}
// ParseArgs will combine the the user input with the parameters to
// generate the final argument list
func (a *Action) ParseArgs(args []string) []string {
result := []string{}
result = append(result, a.Args...)
if len(args) == 0 {
return result
}
for i, arg := range args {
for j, argDef := range result {
replace := "{" + strconv.Itoa(i) + "}"
result[j] = strings.Replace(argDef, replace, arg, -1)
}
}
return result
}
// func (a *Action) MergeArgs(args []string) ([]string, error) {
// var result []string
// if len(args) == 0 {
// return a.Args, nil
// }
// for _, arg := range a.Args {
// if strings.HasPrefix(arg, "$") {
// arg1 := strings.Replace(arg, "$", "", -1)
// i, err := strconv.Atoi(arg1)
// if err != nil {
// return nil, err
// }
// result = append(result, args[i-1])
// } else {
// result = append(result, arg)
// }
// }
// return result, nil
// }
func ExpandPath(path string) (string, error) {
if len(path) == 0 || path[0] != '~' {
return path, nil
}
usr, err := user.Current()
if err != nil {
return "", err
}
return filepath.Join(usr.HomeDir, path[1:]), nil
}