This repository has been archived by the owner on Jul 14, 2023. It is now read-only.
/
tasks.go
146 lines (123 loc) · 3.27 KB
/
tasks.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
package tasks
import (
"errors"
"fmt"
"os"
"os/exec"
"regexp"
"strings"
"github.com/BurntSushi/toml"
)
var (
ErrNoTaskFile = errors.New(".draft-tasks.toml not found")
)
const (
PreUp = "PreUp"
PostDeploy = "PostDeploy"
PostDelete = "PostDelete"
)
var (
// reEnvironmentVariable matches environment variables embedded in
// strings. Only simple expressions ($FOO) are supported. Variables
// may escaped to avoid interpolation, in the form $$FOO or \$FOO
reEnvironmentVariable = regexp.MustCompile(`([\\\$]?\$[a-zA-Z_][a-zA-Z0-9_]*)`)
)
// Runner runs the given command. An alternative to DefaultRunner can
// be used in tests.
type Runner func(c *exec.Cmd) error
// DefaultRunner runs the given command
var DefaultRunner = func(c *exec.Cmd) error { return c.Run() }
type Tasks struct {
PreUp map[string]string `toml:"pre-up"`
PostDeploy map[string]string `toml:"post-deploy"`
PostDelete map[string]string `toml:"cleanup"`
}
type Result struct {
Kind string
Command []string
Pass bool
Message string
}
// Load takes a path to file where tasks are defined and loads them in tasks
func Load(path string) (*Tasks, error) {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
return nil, ErrNoTaskFile
}
return nil, err
}
t := Tasks{}
if _, err := toml.DecodeFile(path, &t); err != nil {
return nil, err
}
return &t, nil
}
func (t *Tasks) Run(runner Runner, kind, podName string) ([]Result, error) {
results := []Result{}
switch kind {
case PreUp:
for _, task := range t.PreUp {
result := executeTask(runner, task, kind)
results = append(results, result)
}
case PostDeploy:
for _, task := range t.PostDeploy {
cmd := preparePostDeployTask(evaluateArgs(task), podName)
result := runTask(runner, cmd, kind)
results = append(results, result)
}
case PostDelete:
for _, task := range t.PostDelete {
result := executeTask(runner, task, kind)
results = append(results, result)
}
default:
return results, fmt.Errorf("Task kind: %s not supported", kind)
}
return results, nil
}
func executeTask(runner Runner, task, kind string) Result {
args := evaluateArgs(task)
cmd := prepareTask(args)
return runTask(runner, cmd, kind)
}
func runTask(runner Runner, cmd *exec.Cmd, kind string) Result {
result := Result{Kind: kind, Pass: false}
result.Command = append([]string{cmd.Path}, cmd.Args[0:]...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err := runner(cmd)
if err != nil {
result.Pass = false
result.Message = err.Error()
return result
}
result.Pass = true
return result
}
func prepareTask(args []string) *exec.Cmd {
var cmd *exec.Cmd
if len(args) < 2 {
cmd = exec.Command(args[0])
} else {
cmd = exec.Command(args[0], args[1:]...)
}
return cmd
}
func preparePostDeployTask(args []string, podName string) *exec.Cmd {
args = append([]string{"exec", podName, "--"}, args[0:]...)
return exec.Command("kubectl", args[0:]...)
}
func evaluateArgs(task string) []string {
args := strings.Split(task, " ")
for i, arg := range args {
args[i] = reEnvironmentVariable.ReplaceAllStringFunc(arg, func(expr string) string {
// $$FOO and \$FOO are kept as-is
if strings.HasPrefix(expr, "$$") || strings.HasPrefix(expr, "\\$") {
return expr[1:]
}
return os.Getenv(expr[1:])
})
}
return args
}