Skip to content

Commit

Permalink
Merge 2deabff into 05aa76d
Browse files Browse the repository at this point in the history
  • Loading branch information
k1LoW authored Mar 13, 2022
2 parents 05aa76d + 2deabff commit 65d3881
Show file tree
Hide file tree
Showing 9 changed files with 257 additions and 3 deletions.
61 changes: 61 additions & 0 deletions exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package runn

import (
"bytes"
"context"
"fmt"
"os"
"strings"

"github.com/cli/safeexec"
"github.com/k1LoW/exec"
)

const execRunnerKey = "exec"

type execRunner struct {
operator *operator
}

type execCommand struct {
command string
stdin string
}

func newExecRunner(o *operator) (*execRunner, error) {
return &execRunner{
operator: o,
}, nil
}

func (rnr *execRunner) Run(ctx context.Context, c *execCommand) error {
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
if rnr.operator.debug {
_, _ = fmt.Fprintf(os.Stderr, "-----START COMMAND-----\n%s\n-----END COMMAND-----\n", c.command)
}
sh, err := safeexec.LookPath("sh")
if err != nil {
return err
}
cmd := exec.CommandContext(ctx, sh, "-c", c.command)
if strings.Trim(c.stdin, " \n") != "" {
cmd.Stdin = strings.NewReader(c.stdin)
if rnr.operator.debug {
_, _ = fmt.Fprintf(os.Stderr, "-----START STDIN-----\n%s\n-----END STDIN-----\n", c.stdin)
}
}
cmd.Stdout = stdout
cmd.Stderr = stderr
_ = cmd.Run()
if rnr.operator.debug {
_, _ = fmt.Fprintf(os.Stderr, "-----START STDOUT-----\n%s\n-----END STDOUT-----\n", stdout.String())
_, _ = fmt.Fprintf(os.Stderr, "-----START STDERR-----\n%s\n-----END STDERR-----\n", stderr.String())
}
rnr.operator.store.steps = append(rnr.operator.store.steps, map[string]interface{}{
"stdout": stdout.String(),
"stderr": stderr.String(),
"exit_code": cmd.ProcessState.ExitCode(),
})
return nil
}
47 changes: 47 additions & 0 deletions exec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package runn

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
)

func TestExecRun(t *testing.T) {
tests := []struct {
command string
stdin string
want map[string]interface{}
}{
{"echo hello!!", "", map[string]interface{}{
"stdout": "hello!!\n",
"stderr": "",
"exit_code": 0,
}},
{"cat", "hello!!", map[string]interface{}{
"stdout": "hello!!",
"stderr": "",
"exit_code": 0,
}},
}
ctx := context.Background()
for _, tt := range tests {
o, err := New()
if err != nil {
t.Fatal(err)
}
r, err := newExecRunner(o)
if err != nil {
t.Fatal(err)
}
c := &execCommand{command: tt.command, stdin: tt.stdin}
if err := r.Run(ctx, c); err != nil {
t.Error(err)
return
}
got := o.store.steps[0]
if diff := cmp.Diff(got, tt.want, nil); diff != "" {
t.Errorf("%s", diff)
}
}
}
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ go 1.17
require (
github.com/antonmedv/expr v1.9.0
github.com/bmatcuk/doublestar/v4 v4.0.2
github.com/cli/safeexec v1.0.0
github.com/go-sql-driver/mysql v1.6.0
github.com/goccy/go-json v0.9.5
github.com/goccy/go-yaml v1.9.5
github.com/google/go-cmp v0.5.6
github.com/k1LoW/exec v0.2.0
github.com/k1LoW/expand v0.3.0
github.com/lib/pq v1.10.4
github.com/mattn/go-sqlite3 v1.14.12
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5P
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I=
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
github.com/cli/safeexec v1.0.0/go.mod h1:Z/D4tTN8Vs5gXYHDCbaM1S/anmEDnJb1iW0+EJ5zx3Q=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
Expand Down Expand Up @@ -256,6 +258,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/k1LoW/exec v0.2.0 h1:b3mOuJtNgPWpvpdW/fRz64wDS7tskW/deAslTyCL9e8=
github.com/k1LoW/exec v0.2.0/go.mod h1:zW7HJpLP/ZndnbPrspA9Z3Q2CxHMK/PAUztlvZem+Ro=
github.com/k1LoW/expand v0.3.0 h1:+rFpjcpv7JBRyPGGLZlLjYuoHPHQNaWOMIYOS6s9hGg=
github.com/k1LoW/expand v0.3.0/go.mod h1:k4H0gjr9CzvgybORyMxihidPSueJzD6FeWXeGpdlAWw=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
Expand Down
36 changes: 35 additions & 1 deletion operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ type step struct {
httpRequest map[string]interface{}
dbRunner *dbRunner
dbQuery map[string]interface{}
execRunner *execRunner
execCommand map[string]interface{}
testRunner *testRunner
testCond string
dumpRunner *dumpRunner
Expand Down Expand Up @@ -79,7 +81,7 @@ func New(opts ...Option) (*operator, error) {

for k, v := range bk.Runners {
switch {
case k == includeRunnerKey || k == testRunnerKey || k == dumpRunnerKey:
case k == includeRunnerKey || k == testRunnerKey || k == dumpRunnerKey || k == execRunnerKey:
return nil, fmt.Errorf("runner name '%s' is reserved for built-in runner", k)
case strings.Index(v, "https://") == 0 || strings.Index(v, "http://") == 0:
hc, err := newHTTPRunner(k, v, o)
Expand Down Expand Up @@ -172,6 +174,19 @@ func (o *operator) AppendStep(s map[string]interface{}) error {
step.includePath = vv
continue
}
if k == execRunnerKey {
er, err := newExecRunner(o)
if err != nil {
return err
}
step.execRunner = er
vv, ok := v.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid exec command: %v", v)
}
step.execCommand = vv
continue
}
h, ok := o.httpRunners[k]
if ok {
step.httpRunner = h
Expand Down Expand Up @@ -253,6 +268,25 @@ func (o *operator) run(ctx context.Context) error {
if err := s.dbRunner.Run(ctx, query); err != nil {
return fmt.Errorf("db query failed on steps[%d]: %v", i, err)
}
case s.execRunner != nil && s.execCommand != nil:
if o.debug {
_, _ = fmt.Fprintf(os.Stderr, "Run '%s' on steps[%d]\n", execRunnerKey, i)
}
e, err := o.expand(s.execCommand)
if err != nil {
return err
}
cmd, ok := e.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid steps[%d]: %v", i, e)
}
command, err := parseExecCommand(cmd)
if err != nil {
return fmt.Errorf("invalid steps[%d]: %v", i, cmd)
}
if err := s.execRunner.Run(ctx, command); err != nil {
return fmt.Errorf("exec command failed on steps[%d]: %v", i, err)
}
case s.testRunner != nil && s.testCond != "":
if o.debug {
_, _ = fmt.Fprintf(os.Stderr, "Run '%s' on steps[%d]\n", testRunnerKey, i)
Expand Down
4 changes: 2 additions & 2 deletions operator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,8 +163,8 @@ func TestLoad(t *testing.T) {
path string
want int
}{
{"testdata/book/*", 4},
{"testdata/**/*", 4},
{"testdata/book/*", 5},
{"testdata/**/*", 5},
}
for _, tt := range tests {
ops, err := Load(tt.path, Runner("req", "https://api.github.com"), Runner("db", "sqlite://path/to/test.db"))
Expand Down
30 changes: 30 additions & 0 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,33 @@ func parseDBQuery(v map[string]interface{}) (*dbQuery, error) {
q.stmt = strings.Trim(stmt, " \n")
return q, nil
}

func parseExecCommand(v map[string]interface{}) (*execCommand, error) {
c := &execCommand{}
part, err := yaml.Marshal(v)
if err != nil {
return nil, err
}
if len(v) != 1 && len(v) != 2 {
return nil, fmt.Errorf("invalid command: %s", string(part))
}
cs, ok := v["command"]
if !ok {
return nil, fmt.Errorf("invalid command: %s", string(part))
}
command, ok := cs.(string)
if !ok || strings.Trim(command, " ") == "" {
return nil, fmt.Errorf("invalid command: %s", string(part))
}
c.command = strings.Trim(command, " \n")
ss, ok := v["stdin"]
if !ok {
return c, nil
}
stdin, ok := ss.(string)
if !ok {
return nil, fmt.Errorf("invalid stdin: %s", string(part))
}
c.stdin = stdin
return c, nil
}
63 changes: 63 additions & 0 deletions parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,66 @@ query: |
}
}
}

func TestParseExecCommand(t *testing.T) {
tests := []struct {
in string
want *execCommand
wantErr bool
}{
{
`
command: echo hello > test.txt
`,
&execCommand{
command: "echo hello > test.txt",
},
false,
},
{
`
command: echo hello > test.txt
stdin: |
alice
bob
charlie
`,
&execCommand{
command: "echo hello > test.txt",
stdin: "alice\nbob\ncharlie\n",
},
false,
},
{
`
stdin: |
alice
bob
charlie
`,
nil,
true,
},
}

for _, tt := range tests {
var v map[string]interface{}
if err := yaml.Unmarshal([]byte(tt.in), &v); err != nil {
t.Fatal(err)
}
got, err := parseExecCommand(v)
if err != nil {
if !tt.wantErr {
t.Error(err)
}
continue
}
if tt.wantErr {
t.Error("want error")
}
opts := cmp.AllowUnexported(execCommand{})
if diff := cmp.Diff(got, tt.want, opts); diff != "" {
t.Errorf("%s", diff)
}
}
}
13 changes: 13 additions & 0 deletions testdata/book/exec.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
desc: Exec test
steps:
-
exec:
command: echo hello world!!
-
test: 'steps[0].stdout contains "hello"'
-
exec:
command: cat
stdin: '{{ steps[0].stdout }}'
-
test: 'steps[2].stdout contains "hello"'

0 comments on commit 65d3881

Please sign in to comment.