Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line numberDiff line numberDiff line change
Expand Up@@ -24,7 +24,7 @@ jobs:
- name: Lint
uses: golangci/golangci-lint-action@v9
with:
version: v2.12
version: v2.13

docs:
runs-on: ubuntu-latest
Expand Down
421 changes: 334 additions & 87 deletions api/serverless/openapi.yaml

Large diffs are not rendered by default.

33 changes: 30 additions & 3 deletions cmd/runware/main.go
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
package main

import (
"context"
"errors"
"os"
"os/signal"
"syscall"

"github.com/runware/runware-cli/internal/buildinfo"
"github.com/runware/runware-cli/internal/cmd"
Expand All@@ -14,11 +18,34 @@ var (
date = "unknown"
)

// exitInterrupted is the conventional status for a run stopped by SIGINT.
const exitInterrupted = 130

func main() {
buildinfo.Set(version, commit, date)
os.Exit(run())
}

// run executes the root command under a context that SIGINT and SIGTERM
// cancel, so long-running commands stop cleanly, and returns the exit status.
func run() int {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
// Once the first signal has cancelled the context, hand the signals back so
// a second Ctrl-C kills a command that does not stop on its own.
go func() {
<-ctx.Done()
stop()
}()

rootCmd, logger := cmd.NewRoot()
if err := rootCmd.Execute(); err != nil {
cmdutil.PrintError(logger, cmdutil.FormatFor(rootCmd), err)
os.Exit(1)
err := rootCmd.ExecuteContext(ctx)
if err == nil {
return 0
}
if errors.Is(err, context.Canceled) && ctx.Err() != nil {
return exitInterrupted
}
cmdutil.PrintError(logger, cmdutil.FormatFor(rootCmd), err)
return 1
}
2 changes: 1 addition & 1 deletion docs/runware_serverless_apps.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,7 +35,7 @@ runware serverless apps [flags]
* [runware serverless apps events](runware_serverless_apps_events.md) - List events for a serverless application
* [runware serverless apps invoke](runware_serverless_apps_invoke.md) - Invoke an application endpoint
* [runware serverless apps list](runware_serverless_apps_list.md) - List serverless applications
* [runware serverless apps logs](runware_serverless_apps_logs.md) - Show logs for a serverless application
* [runware serverless apps logs](runware_serverless_apps_logs.md) - Show or follow logs for a serverless application
* [runware serverless apps resume](runware_serverless_apps_resume.md) - Resume a stopped serverless application
* [runware serverless apps scale](runware_serverless_apps_scale.md) - Scale a serverless application
* [runware serverless apps show](runware_serverless_apps_show.md) - Show details for a serverless application
Expand Down
4 changes: 2 additions & 2 deletions docs/runware_serverless_apps_events.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,8 +6,8 @@ List events for a serverless application

List deploy, scaling, audit, and error events for an application.

Events are the control-plane audit trail, not worker stdout. Live log
streaming is not available (apps logs is not implemented).
Events are the control-plane audit trail, not worker stdout; use apps logs
for worker output.

```
runware serverless apps events <appId> [flags]
Expand Down
34 changes: 28 additions & 6 deletions docs/runware_serverless_apps_logs.md
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,22 @@
## runware serverless apps logs

Show logs for a serverless application
Show or follow logs for a serverless application

### Synopsis

Show application logs.
Show recent application logs, oldest first, and optionally follow new ones.

This command is not implemented yet. The log-query route exists but currently
answers 404 until a follow-up ADR; live tail is not supported.
The recent page is read from the runtime log query over --window (default 1h),
and --limit and --cursor page through it. With --follow the command prints the
recent page, then streams new entries until interrupted; the stream reconnects
when the server ends it. The live stream has no window, so --window, --limit
and --cursor apply to the recent page only, and --cursor cannot be combined
with --follow. Entries written between the recent page and the start of the
stream, or while the stream reconnects, can be missed or repeated.

In table format each entry is one line: time, level and message. In json or
yaml format the recent page is printed as one document; with --follow every
entry is printed as one JSON object per line.

```
runware serverless apps logs <appId> [flags]
Expand All@@ -16,14 +25,27 @@ runware serverless apps logs <appId> [flags]
### Examples

```
# show application logs (not available yet)
# show the last hour of logs
runware serverless apps logs my-app

# show the last six hours
runware serverless apps logs my-app --window 6h

# follow new log entries until Ctrl-C
runware serverless apps logs my-app --follow

# page through older entries
runware serverless apps logs my-app --limit 50 --cursor <nextCursor>
```

### Options

```
-h, --help help for logs
--cursor string Pagination cursor from a previous nextCursor
-f, --follow Stream new log entries until interrupted
-h, --help help for logs
--limit int Maximum number of entries on the recent page (1-100, default 20)
--window string Time window for the recent page (1h, 6h, 24h, 7d, or 30d) (default "1h")
```

### Options inherited from parent commands
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
module github.com/runware/runware-cli

go 1.26.4
go 1.27.1

require (
github.com/briandowns/spinner v1.23.2
Expand Down
5 changes: 2 additions & 3 deletions internal/api/client_test.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"log/slog"
"maps"
"net/http"
"net/http/httptest"
"strings"
Expand DownExpand Up@@ -165,9 +166,7 @@ func TestSend_NoAPIKey(t *testing.T) {
func successItem(t *testing.T, extra map[string]any) json.RawMessage {
t.Helper()
m := map[string]any{fieldStatus: "success"}
for k, v := range extra {
m[k] = v
}
maps.Copy(m, extra)
return rawJSON(t, m)
}

Expand Down
15 changes: 11 additions & 4 deletions internal/api/serverless/client.go
Original file line numberDiff line numberDiff line change
Expand Up@@ -114,7 +114,7 @@ type TaskStatus = gen.TaskStatus

// TaskPayload is the JSON object forwarded to an endpoint handler.
// It is the TaskInvocation.payload member, not the request body itself.
type TaskPayload = map[string]interface{}
type TaskPayload = map[string]any

// ListTasksParams are optional filters for ListTasks.
type ListTasksParams = gen.ListTasksParams
Expand DownExpand Up@@ -252,14 +252,21 @@ func (c *Client) createInner() *gen.ClientWithResponses {
// unchanged.
func (c *Client) innerWithMinTimeout(minTimeout time.Duration) *gen.ClientWithResponses {
hc, ok := c.doer.(*http.Client)
if !ok {
if !ok || hc.Timeout == 0 || hc.Timeout >= minTimeout {
return c.inner
}
if hc.Timeout == 0 || hc.Timeout >= minTimeout {
return c.innerWithTimeout(minTimeout)
}

// innerWithTimeout returns a generated client over a clone of the HTTP client
// with the given whole-request timeout; zero removes the deadline.
func (c *Client) innerWithTimeout(timeout time.Duration) *gen.ClientWithResponses {
hc, ok := c.doer.(*http.Client)
if !ok {
return c.inner
}
cloned := *hc
cloned.Timeout = minTimeout
cloned.Timeout = timeout
return newGeneratedClient(c.apiKey, c.baseURL, &cloned)
}

Expand Down
Loading