-
Notifications
You must be signed in to change notification settings - Fork 4
feat(execbroker): propagate middleware errors #167
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -26,8 +26,9 @@ type Request struct { | |
| Stderr io.Writer | ||
| } | ||
|
|
||
| // Middleware may rewrite a command before execution. | ||
| type Middleware func(Request) Request | ||
| // Middleware may rewrite a command before execution. Returning an error | ||
| // prevents the command from starting. | ||
| type Middleware func(Request) (Request, error) | ||
|
|
||
| // Scope supplies process-independent defaults for commands created while fn | ||
| // runs. Custom fields already set on a command take precedence. | ||
|
|
@@ -78,16 +79,22 @@ func Println(a ...any) (int, error) { | |
|
|
||
| // Command is the brokered equivalent of exec.Command. | ||
| func Command(name string, args ...string) *exec.Cmd { | ||
| req := rewrite(Request{Name: name, Args: clone(args)}) | ||
| req, err := rewrite(Request{Name: name, Args: clone(args)}) | ||
| cmd := exec.Command(req.Name, req.Args...) | ||
| if err != nil { | ||
| cmd.Err = err | ||
| } | ||
| apply(cmd, req) | ||
| return cmd | ||
| } | ||
|
|
||
| // CommandContext is the brokered equivalent of exec.CommandContext. | ||
| func CommandContext(ctx context.Context, name string, args ...string) *exec.Cmd { | ||
| req := rewrite(Request{Name: name, Args: clone(args)}) | ||
| req, err := rewrite(Request{Name: name, Args: clone(args)}) | ||
| cmd := exec.CommandContext(ctx, req.Name, req.Args...) | ||
| if err != nil { | ||
| cmd.Err = err | ||
| } | ||
| apply(cmd, req) | ||
| return cmd | ||
| } | ||
|
|
@@ -101,7 +108,7 @@ func Run(cmd *exec.Cmd) error { | |
| name = cmd.Args[0] | ||
| args = cmd.Args[1:] | ||
| } | ||
| req := rewrite(Request{ | ||
| req, err := rewrite(Request{ | ||
| Name: name, | ||
| Args: clone(args), | ||
| Env: clone(cmd.Env), | ||
|
|
@@ -110,6 +117,9 @@ func Run(cmd *exec.Cmd) error { | |
| Stdout: cmd.Stdout, | ||
| Stderr: cmd.Stderr, | ||
| }) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| resolved := exec.Command(req.Name, req.Args...) | ||
| cmd.Path = resolved.Path | ||
| cmd.Args = resolved.Args | ||
|
|
@@ -118,7 +128,7 @@ func Run(cmd *exec.Cmd) error { | |
| return cmd.Run() | ||
| } | ||
|
|
||
| func rewrite(req Request) Request { | ||
| func rewrite(req Request) (Request, error) { | ||
| req.Args = clone(req.Args) | ||
| req.Env = clone(req.Env) | ||
|
|
||
|
|
@@ -143,14 +153,19 @@ func rewrite(req Request) Request { | |
| req.Stderr = scope.Stderr | ||
| } | ||
| if scope.Middleware != nil { | ||
| req = scope.Middleware(req) | ||
| var err error | ||
| req, err = scope.Middleware(req) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The user-supplied middleware now runs while |
||
| if err != nil { | ||
| scopeMu.RUnlock() | ||
| return req, err | ||
| } | ||
| } | ||
| } | ||
| scopeMu.RUnlock() | ||
|
|
||
| req.Args = clone(req.Args) | ||
| req.Env = clone(req.Env) | ||
| return req | ||
| return req, nil | ||
| } | ||
|
|
||
| func apply(cmd *exec.Cmd, req Request) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the error path,
Command/CommandContextstill build the cmd from the middleware-mutatedreq.Name(running aLookPath) and then runapply(), copying the mutated env/dir/streams onto a command that was rejected. This is functionally safe becausecmd.Errshort-circuitsStart()/Run(), but it's asymmetric withRun(which returns early at line 121 without mutating the caller's cmd) and exposes rejected-command mutations to any caller that inspectscmdbefore running it. Consider short-circuiting on error to keep the three paths symmetric, e.g.