Skip to content
Open
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
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -163,19 +163,20 @@ runware serverless apps events my-app
runware serverless usage --for this-month --group-by app,day
runware serverless apps usage my-app --for this-month

# Environment, secrets, and scale
# Environment, secrets, scale, and rename
runware serverless apps env set my-app MY_KEY --value-file ./value.txt
runware serverless secrets set FOO --value-file ./foo.txt
runware serverless secrets attach my-app FOO
runware serverless apps scale my-app --max-workers 2
runware serverless apps rename my-app "Image generator"

# Lifecycle
runware serverless apps stop my-app
runware serverless apps resume my-app
runware serverless open my-app
```

`--volume`, `--env`, `--env-file`, and `--name` apply on create only. Change `--gpu-type` and other worker settings with `apps scale`, and environment with `apps env`. An environment change or a secret attach, detach, or rotation records a new version (or rolls live workers) — you do not need to redeploy source to apply them.
`--volume`, `--env`, `--env-file`, and `--name` apply on create only. Change `--gpu-type` and other worker settings with `apps scale`, environment with `apps env`, and the display name with `apps rename`. An environment change or a secret attach, detach, or rotation records a new version (or rolls live workers) — you do not need to redeploy source to apply them.

`.env` files are never packed into the source archive. Prefer `--env-file` and `secrets set --value-file` so values stay out of the process list.

Expand Down
1 change: 1 addition & 0 deletions docs/runware_serverless_apps.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ runware serverless apps [flags]
* [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 or follow logs for a serverless application
* [runware serverless apps rename](runware_serverless_apps_rename.md) - Rename 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
41 changes: 41 additions & 0 deletions docs/runware_serverless_apps_rename.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
## runware serverless apps rename

Rename a serverless application

### Synopsis

Change the display name of a serverless application.

The application ID is immutable. A name-only update records a version and does
not pin or roll workers. deploy --name still applies on create only.

```
runware serverless apps rename <appId> <name> [flags]
```

### Examples

```
# rename an application
runware serverless apps rename my-app "Image generator"
```

### Options

```
-h, --help help for rename
```

### Options inherited from parent commands

```
--debug Show full debug output
-F, --format string CLI output format: table, json, yaml (default "table")
--transport string Transport protocol: ws (WebSocket) or http (REST) (default "ws")
-v, --verbose Show request/response details
```

### SEE ALSO

* [runware serverless apps](runware_serverless_apps.md) - Manage deployed serverless applications

5 changes: 3 additions & 2 deletions docs/runware_serverless_deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ same --id uploads a new source, records version N+1, and rolls it when the
build is ready. Create-only flags (--gpu-type, worker settings, --volume,
--secret, --env, --env-file, --name) apply only to create; passing them when
the application already exists is an error. Change workers with 'apps scale',
attach a secret later with 'secrets attach', and change environment with
'apps env'. A source update on a stopped application is 409.
attach a secret later with 'secrets attach', change environment with
'apps env', and the display name with 'apps rename'. A source update on a
stopped application is 409.

A code deploy takes a Python entry file. The whole source directory is zipped
and submitted as the application source, so the entry file can import its own
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/serverless/apps.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func newAppsCmd(logger *log.Logger) *cobra.Command {
newAppsEventsCmd(logger),
newAppsWorkersCmd(logger),
newAppsScaleCmd(logger),
newAppsRenameCmd(logger),
newAppsUsageCmd(logger),
newAppsStopCmd(logger),
newAppsResumeCmd(logger),
Expand Down
57 changes: 57 additions & 0 deletions internal/cmd/serverless/apps_rename.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package serverless

import (
"fmt"
"log/slog"
"strings"

"github.com/charmbracelet/log"
serverlessapi "github.com/runware/runware-cli/internal/api/serverless"
"github.com/runware/runware-cli/internal/cmdutil"
"github.com/runware/runware-cli/internal/config"
"github.com/runware/runware-cli/internal/output"
"github.com/spf13/cobra"
)

func newAppsRenameCmd(logger *log.Logger) *cobra.Command {
return &cobra.Command{
Use: "rename <appId> <name>",
Short: "Rename a serverless application",
Long: `Change the display name of a serverless application.

The application ID is immutable. A name-only update records a version and does
not pin or roll workers. deploy --name still applies on create only.`,
Example: ` # rename an application
runware serverless apps rename my-app "Image generator"`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
id := args[0]
name := args[1]
if err := validateAppName(name); err != nil {
return err
}

spin := cmdutil.NewSpinner(fmt.Sprintf("Renaming application %s...", id))
spin.Start()

client := serverlessapi.NewClient(config.GetAPIKey(), config.GetServerlessBaseURL(), slog.New(logger))
app, err := client.UpdateApp(cmd.Context(), id, serverlessapi.AppUpdate{
AppName: &name,
})
if err != nil {
spin.Stop()
return err
}
spin.Stop()

return output.Print(cmdutil.FormatFor(cmd), appResult(*app))
},
}
}

func validateAppName(name string) error {
if name == "" || strings.TrimSpace(name) != name {
return fmt.Errorf("name must start and end with a non-whitespace character")
}
return nil
}
89 changes: 89 additions & 0 deletions internal/cmd/serverless/apps_rename_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package serverless

import (
"bytes"
"encoding/json"
"io"
"net/http"
"net/http/httptest"
"strings"
"testing"

"github.com/charmbracelet/log"
serverlessapi "github.com/runware/runware-cli/internal/api/serverless"
)

func TestValidateAppName(t *testing.T) {
cases := []struct {
name string
wantErr bool
}{
{name: "Image generator"},
{name: "A"},
{name: "", wantErr: true},
{name: " padded", wantErr: true},
{name: "padded ", wantErr: true},
{name: " ", wantErr: true},
}
for _, tc := range cases {
err := validateAppName(tc.name)
if tc.wantErr && err == nil {
t.Errorf("%q: expected error", tc.name)
}
if !tc.wantErr && err != nil {
t.Errorf("%q: %v", tc.name, err)
}
}
}

func TestAppsRename_PatchesNameOnly(t *testing.T) {
const wantName = "Image generator"
var patches int
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPatch || r.URL.Path != "/v1/apps/"+testAppID {
t.Errorf("unexpected %s %s", r.Method, r.URL.Path)
w.WriteHeader(http.StatusNotFound)
return
}
patches++
var body serverlessapi.AppUpdate
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
t.Errorf("decode: %v", err)
return
}
if body.AppName == nil || *body.AppName != wantName {
t.Errorf("appName = %v", body.AppName)
}
if body.AppSource != nil || body.Configuration != nil || body.Secrets != nil || body.EnvironmentVariables != nil {
t.Errorf("patch included out-of-scope fields")
}
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(strings.Replace(activeAppBody(""), `"appName":"My App"`, `"appName":"`+wantName+`"`, 1)))
}))
defer srv.Close()

t.Setenv("RUNWARE_API_KEY", "test-key")
t.Setenv("RUNWARE_SERVERLESS_BASE_URL", srv.URL)

cmd := newAppsRenameCmd(log.New(io.Discard))
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{})
cmd.SetArgs([]string{testAppID, wantName})
if err := cmd.Execute(); err != nil {
t.Fatalf("rename: %v", err)
}
if patches != 1 {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: output is discarded, so dropping the output.Print in apps_rename.go still passes. Keep the stdout buffer and assert it contains wantName.

t.Fatalf("patches = %d, want 1", patches)
}
}

func TestAppsRename_RejectsPaddedName(t *testing.T) {
cmd := newAppsRenameCmd(nil)
cmd.SetOut(&bytes.Buffer{})
cmd.SetErr(&bytes.Buffer{})
cmd.SetArgs([]string{testAppID, " padded"})
err := cmd.Execute()
if err == nil || !strings.Contains(err.Error(), "non-whitespace") {
t.Fatalf("err = %v", err)
}
}
7 changes: 5 additions & 2 deletions internal/cmd/serverless/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,8 +150,9 @@ same --id uploads a new source, records version N+1, and rolls it when the
build is ready. Create-only flags (--gpu-type, worker settings, --volume,
--secret, --env, --env-file, --name) apply only to create; passing them when
the application already exists is an error. Change workers with 'apps scale',
attach a secret later with 'secrets attach', and change environment with
'apps env'. A source update on a stopped application is 409.
attach a secret later with 'secrets attach', change environment with
'apps env', and the display name with 'apps rename'. A source update on a
stopped application is 409.

A code deploy takes a Python entry file. The whole source directory is zipped
and submitted as the application source, so the entry file can import its own
Expand Down Expand Up @@ -522,6 +523,8 @@ func createOnlyDeployHint(name string) string {
return "use 'runware serverless secrets attach' to attach a secret"
case "volume":
return "volumes are immutable after create"
case "name":
return "use 'runware serverless apps rename' to change the display name"
default:
return "omit it when updating an existing application"
}
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/serverless/deploy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ func TestValidateUpdateDeployFlags(t *testing.T) {
{flags: []string{"--fallback-gpu-type", "l40s"}, wantErr: scaleHint},
{flags: []string{"--min-available-workers", "1"}, wantErr: scaleHint},
{flags: []string{"--available-workers-pct", "10"}, wantErr: scaleHint},
{flags: []string{"--name", "My App"}, wantErr: "omit it"},
{flags: []string{"--name", "My App"}, wantErr: "apps rename"},
{flags: []string{"--requirement", testPipPackage}},
{flags: []string{"--base-image", "python:3.12-slim"}},
{flags: []string{testSrcDirFlag, "."}},
Expand Down
Loading