1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package exec
16
17 import (
18 "fmt"
19 "os/exec"
20 "strings"
21
22 "cuelang.org/go/cue"
23 "cuelang.org/go/cue/errors"
24 "cuelang.org/go/internal/task"
25 )
26
27 func init() {
28 task.Register("tool/exec.Run", newExecCmd)
29
30
31 task.Register("exec", newExecCmd)
32 }
33
34 type execCmd struct{}
35
36 func newExecCmd(v cue.Value) (task.Runner, error) {
37 return &execCmd{}, nil
38 }
39
40 func (c *execCmd) Run(ctx *task.Context) (res interface{}, err error) {
41 cmd, doc, err := mkCommand(ctx)
42 if err != nil {
43 return cue.Value{}, err
44 }
45
46
47 stream := func(name string) (stream cue.Value, ok bool) {
48 c := ctx.Obj.LookupPath(cue.ParsePath(name))
49 if err := c.Null(); c.Err() != nil || err == nil {
50 return
51 }
52 return c, true
53 }
54
55 if v, ok := stream("stdin"); !ok {
56 cmd.Stdin = ctx.Stdin
57 } else if cmd.Stdin, err = v.Reader(); err != nil {
58 return nil, errors.Wrapf(err, v.Pos(), "invalid input")
59 }
60 _, captureOut := stream("stdout")
61 if !captureOut {
62 cmd.Stdout = ctx.Stdout
63 }
64 _, captureErr := stream("stderr")
65 if !captureErr {
66 cmd.Stderr = ctx.Stderr
67 }
68
69 v := ctx.Obj.LookupPath(cue.ParsePath("mustSucceed"))
70 mustSucceed, err := v.Bool()
71 if err != nil {
72 return nil, errors.Wrapf(err, v.Pos(), "invalid bool value")
73 }
74
75 update := map[string]interface{}{}
76 if captureOut {
77 var stdout []byte
78 stdout, err = cmd.Output()
79 update["stdout"] = string(stdout)
80 } else {
81 err = cmd.Run()
82 }
83 update["success"] = err == nil
84
85 if err == nil {
86 return update, nil
87 }
88
89 if captureErr {
90 if exit := (*exec.ExitError)(nil); errors.As(err, &exit) {
91 update["stderr"] = string(exit.Stderr)
92 } else {
93 update["stderr"] = err.Error()
94 }
95 }
96
97 if !mustSucceed {
98 return update, nil
99 }
100
101 return nil, fmt.Errorf("command %q failed: %v", doc, err)
102 }
103
104 func mkCommand(ctx *task.Context) (c *exec.Cmd, doc string, err error) {
105 var bin string
106 var args []string
107
108 v := ctx.Lookup("cmd")
109 if ctx.Err != nil {
110 return nil, "", ctx.Err
111 }
112
113 switch v.Kind() {
114 case cue.StringKind:
115 str := ctx.String("cmd")
116 doc = str
117 list := strings.Fields(str)
118 bin = list[0]
119 args = append(args, list[1:]...)
120
121 case cue.ListKind:
122 list, _ := v.List()
123 if !list.Next() {
124 return nil, "", errors.New("empty command list")
125 }
126 bin, err = list.Value().String()
127 if err != nil {
128 return nil, "", err
129 }
130 doc += bin
131 for list.Next() {
132 str, err := list.Value().String()
133 if err != nil {
134 return nil, "", err
135 }
136 args = append(args, str)
137 doc += " " + str
138 }
139 }
140
141 if bin == "" {
142 return nil, "", errors.New("empty command")
143 }
144
145 cmd := exec.CommandContext(ctx.Context, bin, args...)
146
147 cmd.Dir, _ = ctx.Obj.LookupPath(cue.ParsePath("dir")).String()
148
149 env := ctx.Obj.LookupPath(cue.ParsePath("env"))
150
151
152 for iter, _ := env.List(); iter.Next(); {
153 v, _ := iter.Value().Default()
154 str, err := v.String()
155 if err != nil {
156 return nil, "", errors.Wrapf(err, v.Pos(),
157 "invalid environment variable value %q", v)
158 }
159 cmd.Env = append(cmd.Env, str)
160 }
161
162
163 for iter, _ := env.Fields(); iter.Next(); {
164 label := iter.Label()
165 v, _ := iter.Value().Default()
166 var str string
167 switch v.Kind() {
168 case cue.StringKind:
169 str, _ = v.String()
170 case cue.IntKind, cue.FloatKind, cue.NumberKind:
171 str = fmt.Sprint(v)
172 default:
173 return nil, "", errors.Newf(v.Pos(),
174 "invalid environment variable value %q", v)
175 }
176 cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", label, str))
177 }
178
179 return cmd, doc, nil
180 }
181
View as plain text