Exec is the main handler for executing simple commands. It is called for all [syntax.CallExpr] nodes where the first argument is neither a declared shell function nor a builtin. This handler is responsible to interpreting functions and module references as commands that can be executed, and wraps
(next interp.ExecHandlerFunc)
| 150 | // This handler is responsible to interpreting functions and module references |
| 151 | // as commands that can be executed, and wraps any returned errors. |
| 152 | func (h *shellCallHandler) Exec(next interp.ExecHandlerFunc) interp.ExecHandlerFunc { |
| 153 | return func(ctx context.Context, args []string) (rerr error) { |
| 154 | // This avoids interpreter builtins running first, which would make it |
| 155 | // impossible to have a function named "echo", for example. We can |
| 156 | // remove `__dag` from this point onward. |
| 157 | if args[0] == shellInternalCmd { |
| 158 | args = args[1:] |
| 159 | } |
| 160 | |
| 161 | // If argument is a state value, just pass it on to stdout directly. |
| 162 | // Example: `$FOO` or `$FOO | bar` |
| 163 | if GetStateKey(args[0]) != "" { |
| 164 | hctx := interp.HandlerCtx(ctx) |
| 165 | fmt.Fprint(hctx.Stdout, args[0]) |
| 166 | return nil |
| 167 | } |
| 168 | |
| 169 | // It's a cascading error if the state from the previous handler in a |
| 170 | // pipeline failed. |
| 171 | var cascadingErr bool |
| 172 | |
| 173 | // Read stdin. If not nil this will block until the previous handler |
| 174 | // has finished. If state is nil here it means it's the first command |
| 175 | // in a pipeline (foo | bar). |
| 176 | st, err := h.loadInput(ctx, args) |
| 177 | if st != nil { |
| 178 | cascadingErr = st.IsHandlerError() |
| 179 | } |
| 180 | |
| 181 | // Having a span for each handler makes it much easier to debug what |
| 182 | // the shell is doing. |
| 183 | opts := make([]trace.SpanStartOption, 0, 2) |
| 184 | opts = append(opts, trace.WithAttributes(attribute.StringSlice("dagger.io/shell.handler.args", args))) |
| 185 | // Don't show span by default unless there's an error or we're debugging |
| 186 | // (with `.debug`). |
| 187 | if !h.Debug() { |
| 188 | opts = append(opts, telemetry.Passthrough()) |
| 189 | } |
| 190 | ctx, span := Tracer().Start(ctx, args[0], opts...) |
| 191 | defer telemetry.EndWithCause(span, &rerr) |
| 192 | |
| 193 | defer func() { |
| 194 | if cascadingErr { |
| 195 | // Early exit if an error is passed through stdin. |
| 196 | span.SetAttributes( |
| 197 | attribute.Bool(telemetry.CanceledAttr, true), |
| 198 | ) |
| 199 | } else if rerr != nil { |
| 200 | // TODO: it's helpful to show the span on error when it's a usage |
| 201 | // issue, but if it's from resolving a query it shows the error |
| 202 | // twice. Could still be useful though, to pinpoint exactly which |
| 203 | // part of the script triggered it. |
| 204 | attrs := []attribute.KeyValue{ |
| 205 | attribute.Bool(telemetry.UIPassthroughAttr, false), |
| 206 | } |
| 207 | var he *HandlerError |
| 208 | if errors.As(rerr, &he) { |
| 209 | attrs = append(attrs, attribute.Int("dagger.io/shell.handler.exit", he.ExitCode)) |
nothing calls this directly
no test coverage detected