From c7e8020b9dede4af6f3c13a23621be1e01d1ecba Mon Sep 17 00:00:00 2001 From: saify fairoz khan Date: Sat, 24 Jan 2026 18:24:31 -0600 Subject: [PATCH 1/2] ol(cli): minimal 'invoke' subcommand (POST-only JSON, cmdline JSON, -p project) --- .gitignore | 6 +++ go/invoke.go | 117 +++++++++++++++++++++++++++++++++++++++++++++++++++ go/main.go | 2 + 3 files changed, 125 insertions(+) create mode 100644 go/invoke.go diff --git a/.gitignore b/.gitignore index 6d1251cba..1f91dca72 100644 --- a/.gitignore +++ b/.gitignore @@ -96,3 +96,9 @@ azure.json boss.json cluster.log tasks.log + +# build artifacts +ol-linux +input.bin +payload.json +main.go.bak diff --git a/go/invoke.go b/go/invoke.go new file mode 100644 index 000000000..2434b9d56 --- /dev/null +++ b/go/invoke.go @@ -0,0 +1,117 @@ +// Minimal ol invoke (POST JSON only) +package main + +import ( + "bytes" + "path/filepath" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "strings" + "time" + + "github.com/urfave/cli/v2" + "github.com/open-lambda/open-lambda/go/common" +) + +func invokeAction(ctx *cli.Context) error { + // Usage: ol invoke [json] [-p PROJECT] + if ctx.NArg() < 1 || ctx.NArg() > 2 { + return cli.Exit("usage: ol invoke [json] [-p PROJECT]", 2) + } + + funcName := ctx.Args().Get(0) + jsonArg := "" + if ctx.NArg() == 2 { + jsonArg = ctx.Args().Get(1) + } + + // Resolve worker URL like `ol worker up` (stub for now; wired below) + project := ctx.String("project") + baseURL, err := resolveWorkerURL(ctx, project) + if err != nil { + return cli.Exit(fmt.Sprintf("failed to resolve worker URL: %v", err), 1) + } + + url := strings.TrimRight(baseURL, "/") + "/invoke/" + funcName + + // Body: no arg → "null"; with arg → must be valid JSON + var body []byte + if jsonArg == "" { + body = []byte("null") + } else { + var tmp any + if err := json.Unmarshal([]byte(jsonArg), &tmp); err != nil { + return cli.Exit(fmt.Sprintf("invalid JSON: %v", err), 2) + } + body = []byte(jsonArg) + } + + req, err := http.NewRequest(http.MethodPost, url, bytes.NewReader(body)) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{Timeout: time.Duration(ctx.Int("timeout")) * time.Second} + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + out, _ := io.ReadAll(resp.Body) + + if ctx.Bool("pretty") { + var js any + if json.Unmarshal(out, &js) == nil { + b, _ := json.MarshalIndent(js, "", " ") + fmt.Println(string(b)) + } else { + fmt.Println(string(out)) + } + } else { + fmt.Println(string(out)) + } + + if resp.StatusCode < 200 || resp.StatusCode >= 300 { + return fmt.Errorf("HTTP %d", resp.StatusCode) + } + return nil +} + +// TODO: wire to the same project->URL resolver used by `ol worker up` +func resolveWorkerURL(ctx *cli.Context, project string) (string, error) { + // If -p/--project is set (or defaults), reuse the same deploy path logic as `ol worker up`: + // 1) Resolve deploy dir via common.GetOlPath(ctx) + // 2) Load its config.json -> common.Conf.Worker_port + // 3) Return http://localhost: + olPath, err := common.GetOlPath(ctx) + if err == nil { + confPath := filepath.Join(olPath, "config.json") + if err2 := common.LoadGlobalConfig(confPath); err2 == nil && common.Conf.Worker_port != "" { + return "http://localhost:" + common.Conf.Worker_port, nil + } + } + // Fallbacks: OL_URL env, else 127.0.0.1:5000 (useful for local/mock testing) + if v := os.Getenv("OL_URL"); v != "" { + return v, nil + } + return "http://127.0.0.1:5000", nil +} + +func invokeCommand() *cli.Command { + return &cli.Command{ + Name: "invoke", + Usage: "Invoke a function on an OL worker (minimal: POST JSON only)", + UsageText: "ol invoke [json] [-p PROJECT]", + Description: "POSTs JSON (or null) to /invoke/. Will resolve worker URL same as 'ol worker up'.", + Flags: []cli.Flag{ + &cli.StringFlag{Name: "project", Aliases: []string{"p"}, Usage: "Project/deploy name (like 'ol worker up')"}, + &cli.IntFlag{Name: "timeout", Value: 15, Usage: "HTTP timeout seconds"}, + &cli.BoolFlag{Name: "pretty", Usage: "Pretty-print JSON responses"}, + }, + Action: invokeAction, + } +} diff --git a/go/main.go b/go/main.go index fbe83c7d2..01419efc5 100644 --- a/go/main.go +++ b/go/main.go @@ -359,6 +359,8 @@ OPTIONS: UsageText: "ol bench ", Subcommands: bench.BenchCommands(), }, + + invokeCommand(), &cli.Command{ Name: "pprof", Usage: "Profile OL worker", From 6ac8362fc9e4ad860960f00f99b89bbc8a128313 Mon Sep 17 00:00:00 2001 From: saify fairoz khan Date: Sun, 25 Jan 2026 23:28:13 -0600 Subject: [PATCH 2/2] feat(cli): verify and finalize invoke command on ubuntu --- go/go.mod | 2 +- go/invoke.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go/go.mod b/go/go.mod index 21a70ee32..181c95025 100644 --- a/go/go.mod +++ b/go/go.mod @@ -13,6 +13,7 @@ require ( github.com/twmb/franz-go v1.19.0 github.com/urfave/cli/v2 v2.25.3 gocloud.dev v0.42.0 + golang.org/x/sys v0.33.0 gopkg.in/yaml.v3 v3.0.1 ) @@ -115,7 +116,6 @@ require ( golang.org/x/net v0.41.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sync v0.15.0 // indirect - golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.26.0 // indirect golang.org/x/time v0.11.0 // indirect golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da // indirect diff --git a/go/invoke.go b/go/invoke.go index 2434b9d56..66287164a 100644 --- a/go/invoke.go +++ b/go/invoke.go @@ -3,17 +3,17 @@ package main import ( "bytes" - "path/filepath" "encoding/json" "fmt" "io" "net/http" "os" + "path/filepath" "strings" "time" - "github.com/urfave/cli/v2" "github.com/open-lambda/open-lambda/go/common" + "github.com/urfave/cli/v2" ) func invokeAction(ctx *cli.Context) error { @@ -35,7 +35,7 @@ func invokeAction(ctx *cli.Context) error { return cli.Exit(fmt.Sprintf("failed to resolve worker URL: %v", err), 1) } - url := strings.TrimRight(baseURL, "/") + "/invoke/" + funcName + url := strings.TrimRight(baseURL, "/") + "/run/" + funcName // Body: no arg → "null"; with arg → must be valid JSON var body []byte @@ -106,7 +106,7 @@ func invokeCommand() *cli.Command { Name: "invoke", Usage: "Invoke a function on an OL worker (minimal: POST JSON only)", UsageText: "ol invoke [json] [-p PROJECT]", - Description: "POSTs JSON (or null) to /invoke/. Will resolve worker URL same as 'ol worker up'.", + Description: "POSTs JSON (or null) to /run/. Will resolve worker URL same as 'ol worker up'.", Flags: []cli.Flag{ &cli.StringFlag{Name: "project", Aliases: []string{"p"}, Usage: "Project/deploy name (like 'ol worker up')"}, &cli.IntFlag{Name: "timeout", Value: 15, Usage: "HTTP timeout seconds"},