Skip to content

Commit

Permalink
exe and shell exe added
Browse files Browse the repository at this point in the history
  • Loading branch information
danmux committed May 2, 2018
1 parent 08914cc commit 60f6af2
Show file tree
Hide file tree
Showing 16 changed files with 544 additions and 147 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,3 +102,6 @@ Triggers are the things that start a flow off there are a few types of trigger.
The most common type of node - executes a command, e.g. runs a mke command or a bash script.

`ignore-fail` - Only ever send the good event, even if the node failed. Can't be used in conjunction with UseStatus.
`cmd` - Use this if you are running an executable that only depends on the binary
`shell` - Use this if you are running something that requires the shell, e.g. bash scripts.
`args` - An array of command line arguments - for simple arguments these can be included space delimited in the `cmd` or `shell` lines, if there are quote enclosed arguments then use this args array.
15 changes: 8 additions & 7 deletions config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,20 @@ flows:
listen: task.checkout.good
opts:
cmd: "echo"
args: "'dan is a goon'"
file: "BUILD.floe" # the command to execute
args: ["dan is a goon"]

- name: Build
type: exec
listen: task.checkout.good
opts:
cmd: "ls" # the command to execute
args: "-lrt /"
args: ["-lrt", "/"]

- name: Test
type: exec # execute a command
listen: task.build.good
opts:
cmd: "sleep" # the command to execute
args: "10"
shell: "sleep 10" # the command to execute

- name: Sign Off
type: data
Expand Down Expand Up @@ -96,6 +94,9 @@ flows:
type: end # getting here means the flow was a success 'end' is the special definitive end event

- id: danmux
env:
- GOPATH=${ws}
# - PATH=$PATH:${ws}/bin
ver: 1

triggers:
Expand All @@ -105,14 +106,14 @@ flows:
form:
title: Start
fields:
- id: ref
- id: ref # ref is a magic name that the git-checkout task type will use
prompt: Git Ref (branch or hash etc)
type: text

- name: Every 5 Minutes
type: timer # fires every period seconds
opts:
period: 300 # how often to fire in seconds
period: 300 # how often to fire in seconds

tasks:
- name: Checkout
Expand Down
2 changes: 2 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ type FoundFlow struct {
ReuseSpace bool
ResourceTags []string
HostTags []string
Env []string

Nodes []*node // nodes that matched the criteria to have fund the node
}
Expand Down Expand Up @@ -76,6 +77,7 @@ func (c *Config) FindFlowsByTriggers(eType string, flow FlowRef, opts nt.Opts) m
ReuseSpace: f.ReuseSpace,
ResourceTags: f.ResourceTags,
HostTags: f.HostTags,
Env: f.Env,
}
}
ff.Nodes = []*node{ns[0]} // use the first one
Expand Down
1 change: 1 addition & 0 deletions config/flow.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ type Flow struct {
ReuseSpace bool `yaml:"reuse-space"` // if true then will use the single workspace and will mutex with other instances of this Flow
HostTags []string `yaml:"host-tags"` // tags that must match the tags on the host
ResourceTags []string `yaml:"resource-tags"` // tags that if any flow is running with any matching tags then don't launch
Env []string // key=value environment variables with

// The Various node types included in this flow
Triggers []*node
Expand Down
36 changes: 36 additions & 0 deletions config/node_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package config

import (
"testing"

nt "github.com/floeit/floe/config/nodetype"
)

func TestNodeExec(t *testing.T) {
output := make(chan string)
captured := make(chan bool)
cl := 0
go func() {
for l := range output {
println(l)
cl++
}
captured <- true
}()
n := &node{
// what flow is this node attached to
Class: "task",
Type: "exec",
Opts: nt.Opts{
"shell": "export",
"env": []string{"DAN=fart"},
},
}

status, _, err := n.Execute(&nt.Workspace{}, nt.Opts{}, output)
println(status)
println(err)
close(output)
<-captured
println(cl)
}
95 changes: 61 additions & 34 deletions config/nodetype/exec.go
Original file line number Diff line number Diff line change
@@ -1,74 +1,101 @@
package nodetype

import (
"bufio"
"fmt"
"io"
"path/filepath"
"strings"

"github.com/davecgh/go-spew/spew"
"github.com/floeit/floe/exe"
"github.com/floeit/floe/log"
)

// exec node executes an external task
type exec struct{}
type exec struct {
Cmd string
Shell string
Args []string
SubDir string `json:"sub-dir"`
Env []string
}

func (e exec) Match(ol, or Opts) bool {
return true
}

func (e exec) Execute(ws *Workspace, in Opts, output chan string) (int, Opts, error) {
// TODO - consider mapstructure
// support directory to run the command from

cmd := ""
if c, ok := in["cmd"]; ok {
cmd = c.(string)
} else {
return 255, nil, fmt.Errorf("missing cmd option")
func (e exec) cmdAndArgs() (cmd string, args []string) {
cmd = e.Cmd
shell := false
if cmd == "" {
cmd = e.Shell
shell = true
}

args := ""
if a, ok := in["args"]; ok {
args = a.(string)
}
// if no explicit args then look at the cmd if it is in the form of "arg cmd.."
if args == "" {
args = e.Args
if len(args) == 00 {
p := strings.Split(cmd, " ")
if len(p) > 1 {
args = cmd[len(p[0])+1:]
cmd = p[0]
args = p[1:]
}
}
if shell {
args = []string{"-c", fmt.Sprintf(`%s %s`, cmd, strings.Join(args, " "))}
cmd = "bash"
}
return cmd, args
}

func (e exec) Execute(ws *Workspace, in Opts, output chan string) (int, Opts, error) {
spew.Dump(in)
err := decode(in, &e)
if err != nil {
return 255, nil, err
}

status := doRun(cmd, args, ws.BasePath /*todo add subpath*/, output)
cmd, args := e.cmdAndArgs()

if cmd == "" {
return 255, nil, fmt.Errorf("missing cmd or shell option")
}
// expand the workspace var
e.Env = expandEnvOpts(e.Env, ws.BasePath)

e.Env = append(e.Env, "FLOEWS="+ws.BasePath)

status := doRun(filepath.Join(ws.BasePath, e.SubDir), e.Env, output, cmd, args...)

return status, Opts{}, nil
}

func doRun(cmd, args, path string, output chan string) int {
pr, pw := io.Pipe()
stop := make(chan bool, 1)
func doRun(dir string, env []string, output chan string, cmd string, args ...string) int {
stop := make(chan bool)
out := make(chan string)

output <- "in dir: " + path + "\n"
output <- "in dir: " + dir + "\n"
go func() {
scanner := bufio.NewScanner(pr)
for scanner.Scan() {
output <- scanner.Text()
}
if err := scanner.Err(); err != nil {
output <- "scanning output failed with: " + err.Error()
for o := range out {
output <- o
}
close(stop)
stop <- true
}()

status := exe.Run(log.Log{}, cmd, args, path, pw)
status := exe.Run(log.Log{}, out, env, dir, cmd, args...)

// wait for scanner to complete
// wait for output to complete
<-stop

if status != 0 {
output <- fmt.Sprintf("\nexited with status: %d", status)
}

return status
}

// expand the workspace template item with the actual workspace
func expandEnvOpts(es []string, path string) []string {
ne := make([]string, len(es))
for i, e := range es {
ne[i] = strings.Replace(e, "${ws}", path, -1)
}
return ne
}
100 changes: 100 additions & 0 deletions config/nodetype/exec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package nodetype

import (
"strings"
"testing"
)

func TestExec(t *testing.T) {
e := exec{}
op := make(chan string)
go func() {
for l := range op {
println(l)
}
}()

e.Execute(&Workspace{}, Opts{
"cmd": "echo foo",
}, op)

e.Execute(&Workspace{}, Opts{
"shell": "export",
}, op)

close(op)
}

func TestCmdAndArgs(t *testing.T) {
e := exec{
Cmd: "foo bar",
}
cmd, arg := e.cmdAndArgs()
if cmd != "foo" {
t.Error("cmd should be foo")
}
if arg[0] != "bar" {
t.Error("arg should be bar")
}

e = exec{
Shell: "foo bar",
}
cmd, arg = e.cmdAndArgs()
if cmd != "bash" {
t.Error("cmd should be bash", cmd)
}
if arg[0] != "-c" || arg[1] != "foo bar" {
t.Error("arg should be '-c' 'foo bar'", arg)
}
}

func TestEnvVars(t *testing.T) {
op := make(chan string)
var out []string
captured := make(chan bool)
go func() {
for l := range op {
out = append(out, l)
}
captured <- true
}()

e := exec{}
e.Execute(&Workspace{
BasePath: ".",
}, Opts{
"shell": "export",
"env": []string{"DAN=fart"},
}, op)

close(op)

<-captured

expected := []string{`DAN="fart"`, `FLOEWS="."`}
for _, x := range expected {
found := false
for _, l := range out {
if strings.Contains(l, x) {
found = true
break
}
}
if !found {
t.Error("did not find env var:", x)
for _, o := range out {
println(o)
}
}
}
}

func TestExpandEnvOpts(t *testing.T) {
t.Parallel()
env := []string{"OOF=${ws}/oof"}
env = expandEnvOpts(env, "base/path")
if env[0] != "OOF=base/path/oof" {
t.Error("expand failed", env[0])
}
}
5 changes: 2 additions & 3 deletions config/nodetype/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package nodetype
import (
"fmt"
"path/filepath"
"strings"

"github.com/floeit/floe/log"
)
Expand Down Expand Up @@ -78,8 +77,8 @@ func (g gitCheckout) Execute(ws *Workspace, in Opts, output chan string) (int, O
}

// git clone --branch mytag0.1 --depth 1 https://example.com/my/repo.git
args := strings.Join([]string{"clone --branch", gop.Ref, "--depth 1", gop.URL}, " ")
status := doRun("git", args, filepath.Join(ws.BasePath, gop.SubDir), output)
args := []string{"clone", "--branch", gop.Ref, "--depth", "1", gop.URL}
status := doRun(filepath.Join(ws.BasePath, gop.SubDir), nil, output, "git", args...)

return status, nil, nil
}
Loading

0 comments on commit 60f6af2

Please sign in to comment.