@@ -3,30 +3,36 @@ package runbook
33import (
44 "bytes"
55 "context"
6- "encoding/json"
76 "errors"
87 "os"
98 "os/exec"
9+ "strings"
1010 "text/template"
1111)
1212
1313type execConfig struct {
14- Path string `yaml:"path"`
15- Args []string `yaml:"args"`
14+ Path string `yaml:"path"`
15+ Expr string `yaml:"expr"`
16+ Runtime string `yaml:"runtime"` // optional, default to "sh -s"
17+ Args []string `yaml:"args"`
1618}
1719
1820type execAction struct {
1921 cfg execConfig
2022}
2123
2224func newExecAction (cfg execConfig ) (Action , error ) {
23- if cfg .Path == "" {
24- return nil , errors .New ("exec.path is required" )
25+ if cfg .Path == "" && cfg .Expr == "" {
26+ return nil , errors .New ("either exec.path or exec.expr is required" )
27+ }
28+ if cfg .Path != "" && cfg .Expr != "" {
29+ return nil , errors .New ("exec.path and exec.expr are mutually exclusive" )
2530 }
2631 return & execAction {cfg : cfg }, nil
2732}
2833
2934func (e * execAction ) Execute (ctx context.Context , cre map [string ]any ) error {
35+ // Template substitution for args
3036 args := make ([]string , len (e .cfg .Args ))
3137 for i , a := range e .cfg .Args {
3238 tmpl , err := template .New ("arg" ).Funcs (funcMap ()).Parse (a )
@@ -38,14 +44,48 @@ func (e *execAction) Execute(ctx context.Context, cre map[string]any) error {
3844 }
3945 }
4046
41- raw , err := json .Marshal (cre )
42- if err != nil {
43- return err
47+ var cmd * exec.Cmd
48+
49+ switch {
50+ // External command with args
51+ case e .cfg .Path != "" :
52+ cmd = exec .CommandContext (ctx , e .cfg .Path , args ... )
53+
54+ // expr + runtime piped via stdin
55+ case e .cfg .Expr != "" :
56+ // Expand template variables
57+ expr , err := renderTemplate (e .cfg .Expr , cre )
58+ if err != nil {
59+ return err
60+ }
61+
62+ runtime := e .cfg .Runtime
63+ if runtime == "" {
64+ runtime = "sh -s"
65+ }
66+
67+ parts := splitRuntime (runtime )
68+ cmd = exec .CommandContext (ctx , parts [0 ], append (parts [1 :], args ... )... )
69+ cmd .Stdin = strings .NewReader (expr )
4470 }
4571
46- cmd := exec .CommandContext (ctx , e .cfg .Path , args ... )
47- cmd .Stdin = bytes .NewReader (raw )
72+ // Common output wiring
4873 cmd .Stdout = os .Stdout
4974 cmd .Stderr = os .Stderr
75+
5076 return cmd .Run ()
5177}
78+
79+ func renderTemplate (input string , data map [string ]any ) (string , error ) {
80+ tmpl , err := template .New ("inline" ).Funcs (funcMap ()).Parse (input )
81+ if err != nil {
82+ return "" , err
83+ }
84+ var buf bytes.Buffer
85+ err = tmpl .Execute (& buf , data )
86+ return buf .String (), err
87+ }
88+
89+ func splitRuntime (runtime string ) []string {
90+ return strings .Fields (runtime ) // basic split
91+ }
0 commit comments