Skip to content

Commit

Permalink
Add support for executing multiple commands
Browse files Browse the repository at this point in the history
  • Loading branch information
ayushjn20 committed May 15, 2019
1 parent e87b91d commit 1fc5645
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 59 deletions.
18 changes: 11 additions & 7 deletions .dunner.yaml
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
build:
- image: node:10.15.0
command: ["node", "--version"]
- image: node:10.15.0
command: ["npm", "--version"]
commands:
- ["node", "--version"]
- ["npm", "--version"]
- image: alpine
dir: pkg
command: ["pwd"]
commands:
- ["pwd"]
- image: alpine
command: ["apk", "update"]
commands:
- ["apk", "update"]
- image: alpine
command: ["printenv"]
commands:
- ["printenv"]
envs:
- PERM=775
- ID=dunner
Expand All @@ -19,7 +22,8 @@ build:
- '/root'
show:
- image: alpine
command: ["ls", "$1"]
commands:
- ["ls", "$1"]
mounts:
- '~/Downloads:/root/down'
- ~/Pictures:/root/pics:wr
Expand Down
14 changes: 7 additions & 7 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ type DirMount struct {

// Task describes a single task to be run in a docker container
type Task struct {
Name string `yaml:"name"`
Image string `yaml:"image"`
SubDir string `yaml:"dir"`
Command []string `yaml:"command"`
Envs []string `yaml:"envs"`
Mounts []string `yaml:"mounts"`
Args []string `yaml:"args"`
Name string `yaml:"name"`
Image string `yaml:"image"`
SubDir string `yaml:"dir"`
Commands [][]string `yaml:"commands"`
Envs []string `yaml:"envs"`
Mounts []string `yaml:"mounts"`
Args []string `yaml:"args"`
}

// Configs describes the parsed information from the dunner file
Expand Down
74 changes: 62 additions & 12 deletions pkg/docker/docker.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,21 @@
package docker

import (
"bytes"
"context"
"io"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"

"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount"
"github.com/docker/docker/client"
"github.com/docker/docker/pkg/jsonmessage"
"github.com/docker/docker/pkg/stdcopy"
"github.com/docker/docker/pkg/term"
"github.com/leopardslab/dunner/internal/logger"
"github.com/spf13/viper"
Expand All @@ -24,16 +28,23 @@ type Step struct {
Task string
Name string
Image string
Command []string
Commands [][]string
Env []string
WorkDir string
Volumes map[string]string
ExtMounts []mount.Mount
Args []string
}

// Result stores the output of commands run using docker exec
type Result struct {
Command string
Output string
Error string
}

// Exec method is used to execute the task described in the corresponding step
func (step Step) Exec() (*io.ReadCloser, error) {
func (step Step) Exec() (*[]Result, error) {

var (
hostMountFilepath = "./"
Expand Down Expand Up @@ -87,7 +98,7 @@ func (step Step) Exec() (*io.ReadCloser, error) {
ctx,
&container.Config{
Image: step.Image,
Cmd: step.Command,
Cmd: []string{"tail", "-f", "/dev/null"},
Env: step.Env,
WorkingDir: containerWorkingDir,
},
Expand All @@ -103,27 +114,66 @@ func (step Step) Exec() (*io.ReadCloser, error) {
log.Fatal(err)
}

if len(resp.Warnings) > 0 {
for warning := range resp.Warnings {
log.Warn(warning)
}
}

if err = cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}); err != nil {
log.Fatal(err)
}

statusCh, errCh := cli.ContainerWait(ctx, resp.ID, container.WaitConditionNotRunning)
select {
case err = <-errCh:
defer func() {
dur, err := time.ParseDuration("-1ns") // Negative duration means no force termination
if err != nil {
log.Fatal(err)
}
if err = cli.ContainerStop(ctx, resp.ID, &dur); err != nil {
log.Fatal(err)
}
}()

var results []Result
for _, cmd := range step.Commands {
r, err := runCmd(ctx, cli, resp.ID, cmd)
if err != nil {
log.Fatal(err)
}
case <-statusCh:
results = append(results, *r)
}

out, err = cli.ContainerLogs(ctx, resp.ID, types.ContainerLogsOptions{
ShowStdout: true,
ShowStderr: true,
return &results, nil
}

func runCmd(ctx context.Context, cli *client.Client, containerID string, command []string) (*Result, error) {
if len(command) == 0 {
return nil, fmt.Errorf(`config: Command cannot be empty`)
}

exec, err := cli.ContainerExecCreate(ctx, containerID, types.ExecConfig{
Cmd: command,
AttachStdout: true,
AttachStderr: true,
})
if err != nil {
log.Fatal(err)
}

return &out, nil
resp, err := cli.ContainerExecAttach(ctx, exec.ID, types.ExecStartCheck{})
if err != nil {
log.Fatal(err)
}
defer resp.Close()

var out, errOut bytes.Buffer
if _, err = stdcopy.StdCopy(&out, &errOut, resp.Reader); err != nil {
log.Fatal(err)
}
var result = Result{
Command: strings.Join(command, " "),
Output: out.String(),
Error: errOut.String(),
}
return &result, nil
}
67 changes: 34 additions & 33 deletions pkg/dunner/dunner.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
package dunner

import (
"os"
"fmt"
"regexp"
"strconv"
"strings"
"sync"

"github.com/docker/docker/pkg/stdcopy"
"github.com/leopardslab/dunner/internal/logger"
"github.com/leopardslab/dunner/pkg/config"
"github.com/leopardslab/dunner/pkg/docker"
Expand Down Expand Up @@ -44,13 +43,13 @@ func execTask(configs *config.Configs, taskName string, args []string) {
wg.Add(1)
}
step := docker.Step{
Task: taskName,
Name: stepDefinition.Name,
Image: stepDefinition.Image,
Command: stepDefinition.Command,
Env: stepDefinition.Envs,
WorkDir: stepDefinition.SubDir,
Args: stepDefinition.Args,
Task: taskName,
Name: stepDefinition.Name,
Image: stepDefinition.Image,
Commands: stepDefinition.Commands,
Env: stepDefinition.Envs,
WorkDir: stepDefinition.SubDir,
Args: stepDefinition.Args,
}

if err := config.DecodeMount(stepDefinition.Mounts, &step); err != nil {
Expand Down Expand Up @@ -95,38 +94,40 @@ func process(configs *config.Configs, s *docker.Step, wg *sync.WaitGroup, args [
log.Fatalf(`dunner: image repository name cannot be empty`)
}

pout, err := (*s).Exec()
results, err := (*s).Exec()
if err != nil {
log.Fatal(err)
}

log.Infof(
"Running task '%+v' on '%+v' Docker with command '%+v'",
s.Task,
s.Image,
strings.Join(s.Command, " "),
)

if _, err = stdcopy.StdCopy(os.Stdout, os.Stderr, *pout); err != nil {
log.Fatal(err)
}

if err = (*pout).Close(); err != nil {
log.Fatal(err)
for _, res := range *results {
log.Infof(
"Running task '%+v' on '%+v' Docker with command '%+v'",
s.Task,
s.Image,
res.Command,
)
if res.Output != "" {
fmt.Printf(`OUT: %s`, res.Output)
}
if res.Error != "" {
fmt.Printf(`ERR: %s`, res.Error)
}
}
}

func passArgs(s *docker.Step, args *[]string) error {
for i, subStr := range s.Command {
regex := regexp.MustCompile(`\$[1-9][0-9]*`)
subStr = regex.ReplaceAllStringFunc(subStr, func(str string) string {
j, err := strconv.Atoi(strings.Trim(str, "$"))
if err != nil {
log.Fatal(err)
}
return (*args)[j-1]
})
s.Command[i] = subStr
for i, cmd := range s.Commands {
for j, subStr := range cmd {
regex := regexp.MustCompile(`\$[1-9][0-9]*`)
subStr = regex.ReplaceAllStringFunc(subStr, func(str string) string {
j, err := strconv.Atoi(strings.Trim(str, "$"))
if err != nil {
log.Fatal(err)
}
return (*args)[j-1]
})
s.Commands[i][j] = subStr
}
}
return nil
}

0 comments on commit 1fc5645

Please sign in to comment.