Skip to content

Commit f59203c

Browse files
committed
Merge origin/main into proxies branch
2 parents bbfbc96 + 86424ea commit f59203c

File tree

5 files changed

+202
-6
lines changed

5 files changed

+202
-6
lines changed

DEVELOPMENT.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ brew install onkernel/tap/kernel
1313
Install the following tools:
1414

1515
- Go 1.22+ ( https://go.dev/doc/install )
16-
- [Goreleaser](https://goreleaser.com/install/)
16+
- [Goreleaser Pro](https://goreleaser.com/install/#pro) - **IMPORTANT: You must install goreleaser-pro, not the standard version, as this is required for our release process**
1717
- [chglog](https://github.com/goreleaser/chglog)
1818

1919
Compile the CLI:
@@ -60,7 +60,7 @@ A typical workflow we encounter is updating the API and integrating those change
6060

6161
Prerequisites:
6262

63-
- Make sure you have `goreleaser` _pro_ installed via `brew install --cask goreleaser/tap/goreleaser-pro`. You will need a license key (in 1pw), and then `export GORELEASER_KEY=<the key>`.
63+
- Make sure you have **goreleaser-pro** installed via `brew install --cask goreleaser/tap/goreleaser-pro`. You will need a license key (in 1pw), and then `export GORELEASER_KEY=<the key>`. **Note: goreleaser-pro is required, not the standard goreleaser version.**
6464

6565
- Grab the NPM token for our org (in 1pw) and run `npm config set '//registry.npmjs.org/:_authToken'=<the token>`
6666

cmd/invoke.go

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"syscall"
1313
"time"
1414

15+
"github.com/onkernel/cli/pkg/util"
1516
"github.com/onkernel/kernel-go-sdk"
1617
"github.com/onkernel/kernel-go-sdk/option"
1718
"github.com/pterm/pterm"
@@ -21,17 +22,31 @@ import (
2122
var invokeCmd = &cobra.Command{
2223
Use: "invoke <app_name> <action_name> [flags]",
2324
Short: "Invoke a deployed Kernel application",
24-
Args: cobra.ExactArgs(2),
2525
RunE: runInvoke,
2626
}
2727

28+
var invocationHistoryCmd = &cobra.Command{
29+
Use: "history",
30+
Short: "Show invocation history",
31+
Args: cobra.NoArgs,
32+
RunE: runInvocationHistory,
33+
}
34+
2835
func init() {
2936
invokeCmd.Flags().StringP("version", "v", "latest", "Specify a version of the app to invoke (optional, defaults to 'latest')")
3037
invokeCmd.Flags().StringP("payload", "p", "", "JSON payload for the invocation (optional)")
3138
invokeCmd.Flags().BoolP("sync", "s", false, "Invoke synchronously (default false). A synchronous invocation will open a long-lived HTTP POST to the Kernel API to wait for the invocation to complete. This will time out after 60 seconds, so only use this option if you expect your invocation to complete in less than 60 seconds. The default is to invoke asynchronously, in which case the CLI will open an SSE connection to the Kernel API after submitting the invocation and wait for the invocation to complete.")
39+
40+
invocationHistoryCmd.Flags().Int("limit", 100, "Max invocations to return (default 100)")
41+
invocationHistoryCmd.Flags().StringP("app", "a", "", "Filter by app name")
42+
invocationHistoryCmd.Flags().String("version", "", "Filter by invocation version")
43+
invokeCmd.AddCommand(invocationHistoryCmd)
3244
}
3345

3446
func runInvoke(cmd *cobra.Command, args []string) error {
47+
if len(args) != 2 {
48+
return fmt.Errorf("requires exactly 2 arguments: <app_name> <action_name>")
49+
}
3550
startTime := time.Now()
3651
client := getKernelClient(cmd)
3752
appName := args[0]
@@ -70,6 +85,8 @@ func runInvoke(cmd *cobra.Command, args []string) error {
7085
if err != nil {
7186
return handleSdkError(err)
7287
}
88+
// Log the invocation ID for user reference
89+
pterm.Info.Printfln("Invocation ID: %s", resp.ID)
7390
// coordinate the cleanup with the polling loop to ensure this is given enough time to run
7491
// before this function returns
7592
cleanupDone := make(chan struct{})
@@ -187,3 +204,90 @@ func printResult(success bool, output string) {
187204
pterm.Error.Printf("Result:\n%s\n", output)
188205
}
189206
}
207+
208+
func runInvocationHistory(cmd *cobra.Command, args []string) error {
209+
client := getKernelClient(cmd)
210+
211+
lim, _ := cmd.Flags().GetInt("limit")
212+
appFilter, _ := cmd.Flags().GetString("app")
213+
versionFilter, _ := cmd.Flags().GetString("version")
214+
215+
// Build parameters for the API call
216+
params := kernel.InvocationListParams{
217+
Limit: kernel.Opt(int64(lim)),
218+
}
219+
220+
// Only add app filter if specified
221+
if appFilter != "" {
222+
params.AppName = kernel.Opt(appFilter)
223+
}
224+
225+
// Only add version filter if specified
226+
if versionFilter != "" {
227+
params.Version = kernel.Opt(versionFilter)
228+
}
229+
230+
// Build debug message based on filters
231+
if appFilter != "" && versionFilter != "" {
232+
pterm.Debug.Printf("Listing invocations for app '%s' version '%s'...\n", appFilter, versionFilter)
233+
} else if appFilter != "" {
234+
pterm.Debug.Printf("Listing invocations for app '%s'...\n", appFilter)
235+
} else if versionFilter != "" {
236+
pterm.Debug.Printf("Listing invocations for version '%s'...\n", versionFilter)
237+
} else {
238+
pterm.Debug.Printf("Listing all invocations...\n")
239+
}
240+
241+
// Make a single API call to get invocations
242+
invocations, err := client.Invocations.List(cmd.Context(), params)
243+
if err != nil {
244+
pterm.Error.Printf("Failed to list invocations: %v\n", err)
245+
return nil
246+
}
247+
248+
table := pterm.TableData{{"Invocation ID", "App Name", "Action", "Version", "Status", "Started At", "Duration", "Output"}}
249+
250+
for _, inv := range invocations.Items {
251+
started := util.FormatLocal(inv.StartedAt)
252+
status := string(inv.Status)
253+
254+
// Calculate duration
255+
var duration string
256+
if !inv.FinishedAt.IsZero() {
257+
dur := inv.FinishedAt.Sub(inv.StartedAt)
258+
duration = dur.Round(time.Millisecond).String()
259+
} else if status == "running" {
260+
dur := time.Since(inv.StartedAt)
261+
duration = dur.Round(time.Second).String() + " (running)"
262+
} else {
263+
duration = "-"
264+
}
265+
266+
// Truncate output for display
267+
output := inv.Output
268+
if len(output) > 50 {
269+
output = output[:47] + "..."
270+
}
271+
if output == "" {
272+
output = "-"
273+
}
274+
275+
table = append(table, []string{
276+
inv.ID,
277+
inv.AppName,
278+
inv.ActionName,
279+
inv.Version,
280+
status,
281+
started,
282+
duration,
283+
output,
284+
})
285+
}
286+
287+
if len(table) == 1 {
288+
pterm.Info.Println("No invocations found.")
289+
} else {
290+
pterm.DefaultTable.WithHasHeader().WithData(table).Render()
291+
}
292+
return nil
293+
}

cmd/logs.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ func init() {
2323
logsCmd.Flags().BoolP("follow", "f", false, "Follow logs in real-time (stream continuously)")
2424
logsCmd.Flags().String("since", "s", "How far back to retrieve logs. Supports duration formats: ns, us, ms, s, m, h (e.g., 5m, 2h, 1h30m). Note: 'd' for days is NOT supported - use hours instead. Can also specify timestamps: 2006-01-02 (day), 2006-01-02T15:04 (minute), 2006-01-02T15:04:05 (second), 2006-01-02T15:04:05.000 (ms). Maximum lookback is 167h (just under 7 days). Defaults to 5m if not following, 5s if following.")
2525
logsCmd.Flags().Bool("with-timestamps", false, "Include timestamps in each log line")
26+
logsCmd.Flags().StringP("invocation", "i", "", "Show logs for a specific invocation/run of the app. Accepts full ID or unambiguous prefix. If the invocation is still running, streaming respects --follow.")
2627
rootCmd.AddCommand(logsCmd)
2728
}
2829

@@ -34,6 +35,7 @@ func runLogs(cmd *cobra.Command, args []string) error {
3435
follow, _ := cmd.Flags().GetBool("follow")
3536
since, _ := cmd.Flags().GetString("since")
3637
timestamps, _ := cmd.Flags().GetBool("with-timestamps")
38+
invocationRef, _ := cmd.Flags().GetString("invocation")
3739
if version == "" {
3840
version = "latest"
3941
}
@@ -45,6 +47,90 @@ func runLogs(cmd *cobra.Command, args []string) error {
4547
}
4648
}
4749

50+
// If an invocation is specified, stream invocation-specific logs and return
51+
if invocationRef != "" {
52+
inv, err := client.Invocations.Get(cmd.Context(), invocationRef)
53+
if err != nil {
54+
return fmt.Errorf("failed to get invocation: %w", err)
55+
}
56+
if inv.AppName != appName {
57+
return fmt.Errorf("invocation %s does not belong to app \"%s\" (found app: %s)", inv.ID, appName, inv.AppName)
58+
}
59+
60+
pterm.Info.Printf("Streaming logs for invocation \"%s\" of app \"%s\" (action: %s, status: %s)...\n", inv.ID, inv.AppName, inv.ActionName, inv.Status)
61+
if follow {
62+
pterm.Info.Println("Press Ctrl+C to exit")
63+
} else {
64+
pterm.Info.Println("Showing recent logs (timeout after 3s with no events)")
65+
}
66+
67+
stream := client.Invocations.FollowStreaming(cmd.Context(), inv.ID, kernel.InvocationFollowParams{}, option.WithMaxRetries(0))
68+
if stream.Err() != nil {
69+
return fmt.Errorf("failed to follow streaming: %w", stream.Err())
70+
}
71+
72+
if follow {
73+
for stream.Next() {
74+
data := stream.Current()
75+
switch data.Event {
76+
case "log":
77+
logEntry := data.AsLog()
78+
if timestamps {
79+
fmt.Printf("%s %s\n", util.FormatLocal(logEntry.Timestamp), logEntry.Message)
80+
} else {
81+
fmt.Println(logEntry.Message)
82+
}
83+
case "error":
84+
errEv := data.AsError()
85+
pterm.Error.Printfln("%s: %s", errEv.Error.Code, errEv.Error.Message)
86+
}
87+
}
88+
} else {
89+
timeout := time.NewTimer(3 * time.Second)
90+
defer timeout.Stop()
91+
92+
done := false
93+
for !done {
94+
nextCh := make(chan bool, 1)
95+
go func() {
96+
hasNext := stream.Next()
97+
nextCh <- hasNext
98+
}()
99+
100+
select {
101+
case hasNext := <-nextCh:
102+
if !hasNext {
103+
done = true
104+
} else {
105+
data := stream.Current()
106+
switch data.Event {
107+
case "log":
108+
logEntry := data.AsLog()
109+
if timestamps {
110+
fmt.Printf("%s %s\n", util.FormatLocal(logEntry.Timestamp), logEntry.Message)
111+
} else {
112+
fmt.Println(logEntry.Message)
113+
}
114+
case "error":
115+
errEv := data.AsError()
116+
pterm.Error.Printfln("%s: %s", errEv.Error.Code, errEv.Error.Message)
117+
}
118+
timeout.Reset(3 * time.Second)
119+
}
120+
case <-timeout.C:
121+
done = true
122+
stream.Close()
123+
return nil
124+
}
125+
}
126+
}
127+
128+
if stream.Err() != nil {
129+
return fmt.Errorf("failed to follow streaming: %w", stream.Err())
130+
}
131+
return nil
132+
}
133+
48134
params := kernel.AppListParams{
49135
AppName: kernel.Opt(appName),
50136
Version: kernel.Opt(version),
@@ -88,6 +174,9 @@ func runLogs(cmd *cobra.Command, args []string) error {
88174
} else {
89175
fmt.Println(logEntry.Message)
90176
}
177+
case "error":
178+
errEv := data.AsErrorEvent()
179+
pterm.Error.Printfln("%s: %s", errEv.Error.Code, errEv.Error.Message)
91180
}
92181
}
93182
} else {
@@ -122,6 +211,9 @@ func runLogs(cmd *cobra.Command, args []string) error {
122211
} else {
123212
fmt.Println(logEntry.Message)
124213
}
214+
case "error":
215+
errEv := data.AsErrorEvent()
216+
pterm.Error.Printfln("%s: %s", errEv.Error.Code, errEv.Error.Message)
125217
}
126218
timeout.Reset(3 * time.Second)
127219
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/charmbracelet/fang v0.2.0
99
github.com/golang-jwt/jwt/v5 v5.2.2
1010
github.com/joho/godotenv v1.5.1
11-
github.com/onkernel/kernel-go-sdk v0.11.4
11+
github.com/onkernel/kernel-go-sdk v0.11.5
1212
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
1313
github.com/pterm/pterm v0.12.80
1414
github.com/samber/lo v1.51.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ github.com/muesli/mango-pflag v0.1.0 h1:UADqbYgpUyRoBja3g6LUL+3LErjpsOwaC9ywvBWe
9191
github.com/muesli/mango-pflag v0.1.0/go.mod h1:YEQomTxaCUp8PrbhFh10UfbhbQrM/xJ4i2PB8VTLLW0=
9292
github.com/muesli/roff v0.1.0 h1:YD0lalCotmYuF5HhZliKWlIx7IEhiXeSfq7hNjFqGF8=
9393
github.com/muesli/roff v0.1.0/go.mod h1:pjAHQM9hdUUwm/krAfrLGgJkXJ+YuhtsfZ42kieB2Ig=
94-
github.com/onkernel/kernel-go-sdk v0.11.4 h1:vgDcPtldfEcRh+a1wlOSOY2bBWjxLFUwHqeXHHQ4OjM=
95-
github.com/onkernel/kernel-go-sdk v0.11.4/go.mod h1:MjUR92i8UPqjrmneyVykae6GuB3GGSmnQtnjf1v74Dc=
94+
github.com/onkernel/kernel-go-sdk v0.11.5 h1:LApX5A/Ful62LwNTW+srhi/3cx3W04pzgZ361PlDEAc=
95+
github.com/onkernel/kernel-go-sdk v0.11.5/go.mod h1:MjUR92i8UPqjrmneyVykae6GuB3GGSmnQtnjf1v74Dc=
9696
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
9797
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
9898
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=

0 commit comments

Comments
 (0)