Documentation
¶
Overview ¶
Package external provides three actions for calling code that lives outside the Flow process: exec (shell), http.request (HTTP client), and wasm (Wazero WASI modules). This is the polyglot escape hatch — any repository can expose a Bundle that wraps its own logic through one of these.
Typical use:
external.exec @{ cmd: "python3 enhancer.py", input: .payload }
http.request @{ url: "https://api.example/v1/items", method: "GET" }
external.wasm @{ path: "plugins/transform.wasm", input: .data }
Index ¶
Constants ¶
const ID = "external"
Variables ¶
var Exec = action.New("external.exec", func(ctx context.Context, in map[string]any) (ExecResult, error) { command := readStringField(in, "cmd", "command") if command == "" { return ExecResult{}, xerr.BadRequest("exec: 'cmd' parameter is required") } cmd := shellCommand(ctx, command) if input, ok := in["input"]; ok && input != nil { cmd.Stdin = bytes.NewReader(inputBytes(input)) } var stdout, stderr bytes.Buffer cmd.Stdout = &stdout cmd.Stderr = &stderr runErr := cmd.Run() result := ExecResult{ Stdout: stdout.String(), Stderr: stderr.String(), ExitCode: 0, OK: runErr == nil, Output: decodeJSON(stdout.String()), } if runErr == nil { return result, nil } var exitErr *exec.ExitError if !asExitError(runErr, &exitErr) { return result, xerr.Internal("exec " + command + ": " + runErr.Error()) } result.ExitCode = exitErr.ExitCode() return result, xerr.Internal(fmt.Sprintf( "exec %q failed with exit code %d: %s", command, result.ExitCode, strings.TrimSpace(result.Stderr))) }). Description("Execute external shell/CLI command"). Tag("external", "exec", "os"). Build()
var HTTPRequest = action.New("http.request", func(ctx context.Context, in map[string]any) (HTTPResult, error) { reqURL := readStringField(in, "url") if reqURL == "" { return HTTPResult{}, xerr.BadRequest("http.request: 'url' parameter is required") } method := strings.ToUpper(readStringField(in, "method")) if method == "" { method = http.MethodGet } body, err := buildBody(in["body"]) if err != nil { return HTTPResult{}, xerr.BadRequest("http.request: invalid body: " + err.Error()) } req, err := http.NewRequestWithContext(ctx, method, reqURL, body) if err != nil { return HTTPResult{}, xerr.Internal("http.request: build request", err) } if headers, ok := in["headers"].(map[string]any); ok { for k, v := range headers { req.Header.Set(k, fmt.Sprint(v)) } } resp, err := httpClient.Do(req) if err != nil { return HTTPResult{}, xerr.Unavailable(fmt.Sprintf("http.request: %s %s failed", method, reqURL), err) } defer func() { _ = resp.Body.Close() }() raw, err := io.ReadAll(io.LimitReader(resp.Body, 16<<20)) if err != nil { return HTTPResult{}, xerr.Internal("http.request: read body", err) } result := HTTPResult{Status: resp.StatusCode, Body: decodeBody(raw)} if resp.StatusCode >= 400 { return result, errorFromStatus(resp.StatusCode, method, reqURL) } return result, nil }). Description("Send HTTP request and decode result"). Tag("external", "http", "network"). Build()
HTTPRequest performs an HTTP request and maps the status to a classified error kind (429 → TooManyRequests, 5xx → Unavailable, …). The response body is always returned, even on error, so a fallback pipeline can inspect it.
var WASM = action.New("external.wasm", func(ctx context.Context, in map[string]any) (ExecResult, error) { path := readStringField(in, "path") if path == "" { return ExecResult{}, xerr.BadRequest("wasm: 'path' parameter is required") } entry, err := getOrCompileWASM(ctx, path) if err != nil { return ExecResult{}, err } var stdin []byte if input, ok := in["input"]; ok && input != nil { stdin = inputBytes(input) } var stdout, stderr bytes.Buffer moduleConfig := wazero.NewModuleConfig(). WithStdin(bytes.NewReader(stdin)). WithStdout(&stdout). WithStderr(&stderr). WithName("") mod, runErr := entry.runtime.InstantiateModule(ctx, entry.compiled, moduleConfig) if mod != nil { defer mod.Close(ctx) } result := ExecResult{ Stdout: stdout.String(), Stderr: stderr.String(), OK: runErr == nil, Output: decodeJSON(stdout.String()), } if runErr == nil { return result, nil } exitErr := &sys.ExitError{} if errors.As(runErr, &exitErr) { result.ExitCode = int(exitErr.ExitCode()) result.OK = false return result, xerr.Internal(fmt.Sprintf( "wasm %q exited with code %d: %s", path, result.ExitCode, strings.TrimSpace(result.Stderr))) } return result, xerr.Internal(fmt.Sprintf("wasm %q: %v", path, runErr)) }). Description("Execute sandboxed WebAssembly module with stdin/stdout JSON bridging"). Tag("external", "wasm", "sandbox"). Build()
WASM executes a WebAssembly module compiled with WASI. The first call compiles the module and caches it; every call instantiates a fresh instance so module state does not leak between invocations.
Functions ¶
Types ¶
type ExecResult ¶
type ExecResult struct {
Stdout string `json:"stdout"`
Stderr string `json:"stderr"`
ExitCode int `json:"exit_code"`
OK bool `json:"ok"`
Output any `json:"output,omitempty"`
}
ExecResult is the shape returned by Exec and WASM. It carries both the raw streams and, when stdout parses as JSON, the structured Output. Ok is a convenience flag for exit_code == 0.
type HTTPResult ¶
HTTPResult carries the status and the decoded body. JSON responses are decoded into any; other bodies are returned as a string.