github.com/rohankumardubey/draft-classic@v0.16.0/pkg/tasks/tasks.go (about) 1 package tasks 2 3 import ( 4 "errors" 5 "fmt" 6 "os" 7 "os/exec" 8 "regexp" 9 "strings" 10 11 "github.com/BurntSushi/toml" 12 ) 13 14 var ( 15 ErrNoTaskFile = errors.New(".draft-tasks.toml not found") 16 ) 17 18 const ( 19 PreUp = "PreUp" 20 PostDeploy = "PostDeploy" 21 PostDelete = "PostDelete" 22 ) 23 24 var ( 25 // reEnvironmentVariable matches environment variables embedded in 26 // strings. Only simple expressions ($FOO) are supported. Variables 27 // may escaped to avoid interpolation, in the form $$FOO or \$FOO 28 reEnvironmentVariable = regexp.MustCompile(`([\\\$]?\$[a-zA-Z_][a-zA-Z0-9_]*)`) 29 ) 30 31 // Runner runs the given command. An alternative to DefaultRunner can 32 // be used in tests. 33 type Runner func(c *exec.Cmd) error 34 35 // DefaultRunner runs the given command 36 var DefaultRunner = func(c *exec.Cmd) error { return c.Run() } 37 38 type Tasks struct { 39 PreUp map[string]string `toml:"pre-up"` 40 PostDeploy map[string]string `toml:"post-deploy"` 41 PostDelete map[string]string `toml:"cleanup"` 42 } 43 44 type Result struct { 45 Kind string 46 Command []string 47 Pass bool 48 Message string 49 } 50 51 // Load takes a path to file where tasks are defined and loads them in tasks 52 func Load(path string) (*Tasks, error) { 53 if _, err := os.Stat(path); err != nil { 54 if os.IsNotExist(err) { 55 return nil, ErrNoTaskFile 56 } 57 return nil, err 58 } 59 60 t := Tasks{} 61 if _, err := toml.DecodeFile(path, &t); err != nil { 62 return nil, err 63 } 64 65 return &t, nil 66 } 67 68 func (t *Tasks) Run(runner Runner, kind, podName string) ([]Result, error) { 69 results := []Result{} 70 71 switch kind { 72 case PreUp: 73 for _, task := range t.PreUp { 74 result := executeTask(runner, task, kind) 75 results = append(results, result) 76 } 77 case PostDeploy: 78 for _, task := range t.PostDeploy { 79 cmd := preparePostDeployTask(evaluateArgs(task), podName) 80 result := runTask(runner, cmd, kind) 81 results = append(results, result) 82 } 83 case PostDelete: 84 for _, task := range t.PostDelete { 85 result := executeTask(runner, task, kind) 86 results = append(results, result) 87 } 88 default: 89 return results, fmt.Errorf("Task kind: %s not supported", kind) 90 } 91 92 return results, nil 93 } 94 95 func executeTask(runner Runner, task, kind string) Result { 96 args := evaluateArgs(task) 97 cmd := prepareTask(args) 98 return runTask(runner, cmd, kind) 99 } 100 101 func runTask(runner Runner, cmd *exec.Cmd, kind string) Result { 102 result := Result{Kind: kind, Pass: false} 103 result.Command = append([]string{cmd.Path}, cmd.Args[0:]...) 104 105 cmd.Stdout = os.Stdout 106 cmd.Stderr = os.Stderr 107 err := runner(cmd) 108 if err != nil { 109 result.Pass = false 110 result.Message = err.Error() 111 return result 112 } 113 result.Pass = true 114 115 return result 116 } 117 118 func prepareTask(args []string) *exec.Cmd { 119 var cmd *exec.Cmd 120 if len(args) < 2 { 121 cmd = exec.Command(args[0]) 122 } else { 123 cmd = exec.Command(args[0], args[1:]...) 124 } 125 return cmd 126 } 127 128 func preparePostDeployTask(args []string, podName string) *exec.Cmd { 129 args = append([]string{"exec", podName, "--"}, args[0:]...) 130 return exec.Command("kubectl", args[0:]...) 131 } 132 133 func evaluateArgs(task string) []string { 134 args := strings.Split(task, " ") 135 for i, arg := range args { 136 args[i] = reEnvironmentVariable.ReplaceAllStringFunc(arg, func(expr string) string { 137 // $$FOO and \$FOO are kept as-is 138 if strings.HasPrefix(expr, "$$") || strings.HasPrefix(expr, "\\$") { 139 return expr[1:] 140 } 141 142 return os.Getenv(expr[1:]) 143 }) 144 } 145 return args 146 }