From 8b0426106535eea0a45b9d456bf4b861d89c80d8 Mon Sep 17 00:00:00 2001 From: ryank90 Date: Tue, 22 Sep 2026 17:35:43 +0100 Subject: [PATCH 1/3] fix(serverless): set several environment variables in one rollout apps env set wrote one key per request, so a second set during that rollout returned 409 and stored nothing. --env and --env-file now merge into the current set and write it once. --- docs/runware_serverless_apps_env.md | 2 +- docs/runware_serverless_apps_env_set.md | 36 ++++-- internal/cmd/serverless/env.go | 160 ++++++++++++++++++++---- internal/cmd/serverless/env_test.go | 80 ++++++++++++ 4 files changed, 244 insertions(+), 34 deletions(-) diff --git a/docs/runware_serverless_apps_env.md b/docs/runware_serverless_apps_env.md index 6a7f417..b2b7545 100644 --- a/docs/runware_serverless_apps_env.md +++ b/docs/runware_serverless_apps_env.md @@ -32,6 +32,6 @@ runware serverless apps env [flags] * [runware serverless apps](runware_serverless_apps.md) - Manage deployed serverless applications * [runware serverless apps env list](runware_serverless_apps_env_list.md) - List environment variables for a serverless application -* [runware serverless apps env set](runware_serverless_apps_env_set.md) - Create or update an environment variable +* [runware serverless apps env set](runware_serverless_apps_env_set.md) - Create or update environment variables * [runware serverless apps env unset](runware_serverless_apps_env_unset.md) - Remove an environment variable diff --git a/docs/runware_serverless_apps_env_set.md b/docs/runware_serverless_apps_env_set.md index 5ebf5d6..103a99e 100644 --- a/docs/runware_serverless_apps_env_set.md +++ b/docs/runware_serverless_apps_env_set.md @@ -1,13 +1,19 @@ ## runware serverless apps env set -Create or update an environment variable +Create or update environment variables ### Synopsis -Create or update one plain-text environment variable. +Create or update plain-text environment variables. -Prefer --value-file so the value is not visible in process lists; use ---value-file - to read from stdin. +A single key is the [key] argument with --value or --value-file. Prefer +--value-file so the value is not visible in process lists; use --value-file - +to read from stdin. + +Several keys are repeatable --env KEY=VALUE, or --env-file. The command reads +the current set, merges these keys in, and writes the set once, so one rollout +carries all of them. A key you do not mention is left in place. A write during +an in-flight rollout returns 409 and does not store the change. A change records a new version with the same image and rolls the workload when the app is active, initializing, or failed and its image is deployable. A @@ -21,28 +27,36 @@ with an attached secret's injected env var, and adding a binding past the allowed. ``` -runware serverless apps env set [flags] +runware serverless apps env set [key] [flags] ``` ### Examples ``` - # set an environment variable + # set one environment variable runware serverless apps env set my-app MY_KEY --value hello - # read the value from a file + # read one value from a file runware serverless apps env set my-app MY_KEY --value-file ./value.txt - # read the value from stdin + # read one value from stdin printf '%s' "$MY_VALUE" | runware serverless apps env set my-app MY_KEY --value-file - + + # set several keys in one rollout + runware serverless apps env set my-app --env FOO=bar --env BAZ=qux + + # set several keys from a file, in one rollout + runware serverless apps env set my-app --env-file .env.deploy ``` ### Options ``` - -h, --help help for set - --value string Variable value (visible in process lists; prefer --value-file) - --value-file string Read variable value from a file, or - for stdin + --env stringArray Environment variable as KEY=VALUE, merged and written once (repeatable) + --env-file stringArray File of KEY=VALUE lines to merge and write once (repeatable) + -h, --help help for set + --value string Variable value for a single (visible in process lists; prefer --value-file) + --value-file string Read one value from a file, or - for stdin ``` ### Options inherited from parent commands diff --git a/internal/cmd/serverless/env.go b/internal/cmd/serverless/env.go index 127ddb5..dce3f10 100644 --- a/internal/cmd/serverless/env.go +++ b/internal/cmd/serverless/env.go @@ -1,10 +1,12 @@ package serverless import ( + "context" "fmt" "log/slog" "os" "regexp" + "slices" "strings" "unicode/utf8" @@ -83,15 +85,23 @@ func newAppsEnvSetCmd(logger *log.Logger) *cobra.Command { var ( value string valueFile string + envPairs []string + envFiles []string ) cmd := &cobra.Command{ - Use: "set ", - Short: "Create or update an environment variable", - Long: `Create or update one plain-text environment variable. + Use: "set [key]", + Short: "Create or update environment variables", + Long: `Create or update plain-text environment variables. -Prefer --value-file so the value is not visible in process lists; use ---value-file - to read from stdin. +A single key is the [key] argument with --value or --value-file. Prefer +--value-file so the value is not visible in process lists; use --value-file - +to read from stdin. + +Several keys are repeatable --env KEY=VALUE, or --env-file. The command reads +the current set, merges these keys in, and writes the set once, so one rollout +carries all of them. A key you do not mention is left in place. A write during +an in-flight rollout returns 409 and does not store the change. A change records a new version with the same image and rolls the workload when the app is active, initializing, or failed and its image is deployable. A @@ -103,47 +113,153 @@ The server rejects (HTTP 422) reserved platform names, names that collide with an attached secret's injected env var, and adding a binding past the 100-variable-plus-secret ceiling. Overwriting an existing key is always allowed.`, - Example: ` # set an environment variable + Example: ` # set one environment variable runware serverless apps env set my-app MY_KEY --value hello - # read the value from a file + # read one value from a file runware serverless apps env set my-app MY_KEY --value-file ./value.txt - # read the value from stdin - printf '%s' "$MY_VALUE" | runware serverless apps env set my-app MY_KEY --value-file -`, - Args: cobra.ExactArgs(2), + # read one value from stdin + printf '%s' "$MY_VALUE" | runware serverless apps env set my-app MY_KEY --value-file - + + # set several keys in one rollout + runware serverless apps env set my-app --env FOO=bar --env BAZ=qux + + # set several keys from a file, in one rollout + runware serverless apps env set my-app --env-file .env.deploy`, + Args: cobra.RangeArgs(1, 2), RunE: func(cmd *cobra.Command, args []string) error { - app := args[0] - key := args[1] - v, err := readValueFlag(value, valueFile, cmd.InOrStdin()) + appID := args[0] + bulk := len(envPairs) > 0 || len(envFiles) > 0 + if len(args) == 2 { + if bulk { + return fmt.Errorf("pass either with --value, or --env/--env-file") + } + return setOneEnvironmentVariable(cmd, logger, appID, args[1], value, valueFile) + } + if value != "" || valueFile != "" { + return fmt.Errorf("--value and --value-file apply to a single ") + } + updates, err := buildEnvironmentVariables(envFiles, envPairs) if err != nil { return err } + if updates == nil || len(*updates) == 0 { + return fmt.Errorf("pass with --value or --value-file, or use --env / --env-file") + } - spin := cmdutil.NewSpinner(fmt.Sprintf("Saving environment variable %s...", key)) + spin := cmdutil.NewSpinner(fmt.Sprintf("Saving %d environment variables...", len(*updates))) spin.Start() client := serverlessapi.NewClient(config.GetAPIKey(), config.GetServerlessBaseURL(), slog.New(logger)) - ev, err := client.UpdateAppEnvironmentVariable(cmd.Context(), app, key, serverlessapi.EnvironmentVariableUpdate{ - Value: v, - }) - if err != nil { + if err := applyEnvUpdates(cmd.Context(), client, appID, *updates); err != nil { spin.Stop() return err } spin.Stop() - return output.Print(cmdutil.FormatFor(cmd), envVarResult(*ev)) + return output.Print(cmdutil.FormatFor(cmd), envVarsResult(envVarsFromMap(*updates))) }, } - cmd.Flags().StringVar(&value, "value", "", "Variable value (visible in process lists; prefer --value-file)") - cmd.Flags().StringVar(&valueFile, "value-file", "", "Read variable value from a file, or - for stdin") + cmd.Flags().StringVar(&value, "value", "", "Variable value for a single (visible in process lists; prefer --value-file)") + cmd.Flags().StringVar(&valueFile, "value-file", "", "Read one value from a file, or - for stdin") + cmd.Flags().StringArrayVar(&envPairs, "env", nil, "Environment variable as KEY=VALUE, merged and written once (repeatable)") + cmd.Flags().StringArrayVar(&envFiles, "env-file", nil, "File of KEY=VALUE lines to merge and write once (repeatable)") cmd.MarkFlagsMutuallyExclusive("value", "value-file") - cmd.MarkFlagsOneRequired("value", "value-file") return cmd } +func setOneEnvironmentVariable(cmd *cobra.Command, logger *log.Logger, appID, key, value, valueFile string) error { + if value == "" && valueFile == "" { + return fmt.Errorf("--value or --value-file is required") + } + v, err := readValueFlag(value, valueFile, cmd.InOrStdin()) + if err != nil { + return err + } + + spin := cmdutil.NewSpinner(fmt.Sprintf("Saving environment variable %s...", key)) + spin.Start() + + client := serverlessapi.NewClient(config.GetAPIKey(), config.GetServerlessBaseURL(), slog.New(logger)) + ev, err := client.UpdateAppEnvironmentVariable(cmd.Context(), appID, key, serverlessapi.EnvironmentVariableUpdate{ + Value: v, + }) + if err != nil { + spin.Stop() + return err + } + spin.Stop() + + return output.Print(cmdutil.FormatFor(cmd), envVarResult(*ev)) +} + +// applyEnvUpdates merges updates into the app's current variables and writes +// the whole set in one request. A key absent from updates is kept. The API +// treats the map as a replacement, so sending only the new keys would delete +// the rest. +func applyEnvUpdates(ctx context.Context, client *serverlessapi.Client, appID string, updates map[string]string) error { + existing, err := listEnvironmentVariables(ctx, client, appID) + if err != nil { + return err + } + merged := envReplacement(existing, updates) + _, err = client.UpdateApp(ctx, appID, serverlessapi.AppUpdate{ + EnvironmentVariables: &merged, + }) + return err +} + +func listEnvironmentVariables(ctx context.Context, client *serverlessapi.Client, appID string) (map[string]string, error) { + out := map[string]string{} + var cursor string + for { + params := &serverlessapi.ListAppEnvironmentVariablesParams{} + params.Limit, params.Cursor = listPageParams(maxEnvVars, cursor) + page, err := client.ListAppEnvironmentVariables(ctx, appID, params) + if err != nil { + return nil, err + } + for i := range page.Data { + out[page.Data[i].Key] = page.Data[i].Value + } + if page.NextCursor == nil || *page.NextCursor == "" { + return out, nil + } + cursor = *page.NextCursor + } +} + +func envReplacement(existing, updates map[string]string) map[string]*string { + out := make(map[string]*string, len(existing)+len(updates)) + for key, value := range existing { + val := value + out[key] = &val + } + for key, value := range updates { + val := value + out[key] = &val + } + return out +} + +func envVarsFromMap(updates map[string]string) []serverlessapi.EnvironmentVariable { + keys := make([]string, 0, len(updates)) + for key := range updates { + keys = append(keys, key) + } + slices.Sort(keys) + out := make([]serverlessapi.EnvironmentVariable, len(keys)) + for i, key := range keys { + out[i] = serverlessapi.EnvironmentVariable{ + Key: key, + Value: updates[key], + } + } + return out +} + func newAppsEnvUnsetCmd(logger *log.Logger) *cobra.Command { return &cobra.Command{ Use: "unset ", diff --git a/internal/cmd/serverless/env_test.go b/internal/cmd/serverless/env_test.go index c59fc43..9b9a3a2 100644 --- a/internal/cmd/serverless/env_test.go +++ b/internal/cmd/serverless/env_test.go @@ -1,10 +1,18 @@ package serverless import ( + "bytes" + "context" + "encoding/json" + "log/slog" + "net/http" + "net/http/httptest" "os" "path/filepath" "strings" "testing" + + serverlessapi "github.com/runware/runware-cli/internal/api/serverless" ) func TestBuildEnvironmentVariables(t *testing.T) { @@ -241,3 +249,75 @@ func TestBuildEnvironmentVariables_LimitIsCountedInRunes(t *testing.T) { t.Error("expected a rejection one character past the limit") } } + +func TestEnvReplacement_KeepsUnmentionedKeys(t *testing.T) { + got := envReplacement( + map[string]string{"KEEP": "old"}, + map[string]string{"NEW_KEY": "n"}, + ) + if got["KEEP"] == nil || *got["KEEP"] != "old" || got["NEW_KEY"] == nil || *got["NEW_KEY"] != "n" { + t.Fatalf("replacement = %#v", derefEnv(got)) + } +} + +func TestApplyEnvUpdates_MergesIntoOneRequest(t *testing.T) { + var patches int + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if strings.Contains(r.URL.Path, "environment-variables") { + if r.URL.Query().Get("cursor") == "" { + _, _ = w.Write([]byte(`{"data":[{"key":"KEEP","value":"old"}],"nextCursor":"page2"}`)) + return + } + _, _ = w.Write([]byte(`{"data":[{"key":"KEEP_TOO","value":"also"}]}`)) + return + } + if r.Method != http.MethodPatch { + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + return + } + patches++ + var body struct { + EnvironmentVariables map[string]string `json:"environmentVariables"` + } + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + t.Errorf("decode: %v", err) + return + } + if body.EnvironmentVariables["KEEP"] != "old" || body.EnvironmentVariables["KEEP_TOO"] != "also" || body.EnvironmentVariables["NEW_KEY"] != "n" { + t.Errorf("environmentVariables = %#v", body.EnvironmentVariables) + } + _, _ = w.Write([]byte(activeAppBody(""))) + })) + defer srv.Close() + + client := serverlessapi.NewClient("test-key", srv.URL, slog.Default()) + err := applyEnvUpdates(context.Background(), client, testAppID, map[string]string{"NEW_KEY": "n"}) + if err != nil { + t.Fatalf("applyEnvUpdates: %v", err) + } + if patches != 1 { + t.Fatalf("patches = %d, want 1", patches) + } +} + +func TestEnvSet_RejectsMixedForms(t *testing.T) { + cmd := newAppsEnvSetCmd(nil) + cmd.SetOut(&bytes.Buffer{}) + cmd.SetErr(&bytes.Buffer{}) + cmd.SetArgs([]string{testAppID, testEnvKey, "--value", testEnvValue, "--env", "NEW_KEY=n"}) + err := cmd.Execute() + if err == nil || !strings.Contains(err.Error(), "--env") { + t.Fatalf("err = %v", err) + } +} + +func derefEnv(in map[string]*string) map[string]string { + out := make(map[string]string, len(in)) + for key, value := range in { + if value != nil { + out[key] = *value + } + } + return out +} From ed3321a40c1143b52e7359396831c2bdac74907f Mon Sep 17 00:00:00 2001 From: ryank90 Date: Tue, 22 Sep 2026 20:01:27 +0100 Subject: [PATCH 2/3] fix(serverless): keep an empty env --value and scope the 409 help An empty --value is a real assignment. The bulk write 409s only on a create or resume rollout, and keys you omit stay only when no other writer races the replace. --- docs/runware_serverless_apps_env_set.md | 8 ++-- internal/cmd/serverless/env.go | 36 ++++++++-------- internal/cmd/serverless/env_test.go | 56 ++++++++++++++++++++++--- 3 files changed, 73 insertions(+), 27 deletions(-) diff --git a/docs/runware_serverless_apps_env_set.md b/docs/runware_serverless_apps_env_set.md index 103a99e..8f653b7 100644 --- a/docs/runware_serverless_apps_env_set.md +++ b/docs/runware_serverless_apps_env_set.md @@ -8,12 +8,14 @@ Create or update plain-text environment variables. A single key is the [key] argument with --value or --value-file. Prefer --value-file so the value is not visible in process lists; use --value-file - -to read from stdin. +to read from stdin. A single-key write during an in-flight rollout returns +409 and does not store the change. Several keys are repeatable --env KEY=VALUE, or --env-file. The command reads the current set, merges these keys in, and writes the set once, so one rollout -carries all of them. A key you do not mention is left in place. A write during -an in-flight rollout returns 409 and does not store the change. +carries all of them. Keys you do not mention stay when no other writer changes +the set between the read and the write. That write returns 409 while a create +or resume rollout is already in progress, and does not store the change. A change records a new version with the same image and rolls the workload when the app is active, initializing, or failed and its image is deployable. A diff --git a/internal/cmd/serverless/env.go b/internal/cmd/serverless/env.go index dce3f10..5159f6d 100644 --- a/internal/cmd/serverless/env.go +++ b/internal/cmd/serverless/env.go @@ -96,18 +96,19 @@ func newAppsEnvSetCmd(logger *log.Logger) *cobra.Command { A single key is the [key] argument with --value or --value-file. Prefer --value-file so the value is not visible in process lists; use --value-file - -to read from stdin. +to read from stdin. A single-key write during an in-flight rollout returns +409 and does not store the change. Several keys are repeatable --env KEY=VALUE, or --env-file. The command reads the current set, merges these keys in, and writes the set once, so one rollout -carries all of them. A key you do not mention is left in place. A write during -an in-flight rollout returns 409 and does not store the change. +carries all of them. Keys you do not mention stay when no other writer changes +the set between the read and the write. That write returns 409 while a create +or resume rollout is already in progress, and does not store the change. A change records a new version with the same image and rolls the workload when the app is active, initializing, or failed and its image is deployable. A stopped or stopping app applies it on resume. An unchanged value records no -version. A write during an in-flight rollout returns 409 and does not store -the value. +version. The server rejects (HTTP 422) reserved platform names, names that collide with an attached secret's injected env var, and adding a binding past the @@ -130,22 +131,23 @@ allowed.`, Args: cobra.RangeArgs(1, 2), RunE: func(cmd *cobra.Command, args []string) error { appID := args[0] + hasSingleValue := cmd.Flags().Changed("value") || cmd.Flags().Changed("value-file") bulk := len(envPairs) > 0 || len(envFiles) > 0 if len(args) == 2 { if bulk { - return fmt.Errorf("pass either with --value, or --env/--env-file") + return fmt.Errorf("%s", envSetUsage) } return setOneEnvironmentVariable(cmd, logger, appID, args[1], value, valueFile) } - if value != "" || valueFile != "" { - return fmt.Errorf("--value and --value-file apply to a single ") + if hasSingleValue { + return fmt.Errorf("%s", envSetUsage) } updates, err := buildEnvironmentVariables(envFiles, envPairs) if err != nil { return err } if updates == nil || len(*updates) == 0 { - return fmt.Errorf("pass with --value or --value-file, or use --env / --env-file") + return fmt.Errorf("%s", envSetUsage) } spin := cmdutil.NewSpinner(fmt.Sprintf("Saving %d environment variables...", len(*updates))) @@ -170,9 +172,11 @@ allowed.`, return cmd } +const envSetUsage = "pass with --value or --value-file, or use --env / --env-file" + func setOneEnvironmentVariable(cmd *cobra.Command, logger *log.Logger, appID, key, value, valueFile string) error { - if value == "" && valueFile == "" { - return fmt.Errorf("--value or --value-file is required") + if !cmd.Flags().Changed("value") && !cmd.Flags().Changed("value-file") { + return fmt.Errorf("%s", envSetUsage) } v, err := readValueFlag(value, valueFile, cmd.InOrStdin()) if err != nil { @@ -296,7 +300,7 @@ rollout returns 409 and does not remove the value.`, } // --------------------------------------------------------------------------- -// Create-time environment variables, for `deploy --env` / `--env-file`. +// --env / --env-file parsing, shared by deploy and apps env set. // --------------------------------------------------------------------------- // Environment variable limits, mirrored from the server's EnvironmentVariableName @@ -313,12 +317,8 @@ const ( // the archive has already been uploaded. var envNamePattern = regexp.MustCompile(`^[A-Za-z_][A-Za-z0-9_]{0,127}$`) -// buildEnvironmentVariables turns --env KEY=VALUE pairs and --env-file paths into -// the create request's map. After the app exists, 'apps env set' and 'apps env -// unset' record a new version with the same image and roll the workload; this -// helper only builds the create-time map. -// -// Files are read before the inline pairs are applied, so an explicit --env wins +// buildEnvironmentVariables turns --env KEY=VALUE pairs and --env-file paths +// into a name-to-value map. Files are read first, so an explicit --env wins // over a file entry with the same name. func buildEnvironmentVariables(files, pairs []string) (*map[string]string, error) { if len(files) == 0 && len(pairs) == 0 { diff --git a/internal/cmd/serverless/env_test.go b/internal/cmd/serverless/env_test.go index 9b9a3a2..15b25e7 100644 --- a/internal/cmd/serverless/env_test.go +++ b/internal/cmd/serverless/env_test.go @@ -4,6 +4,7 @@ import ( "bytes" "context" "encoding/json" + "io" "log/slog" "net/http" "net/http/httptest" @@ -12,6 +13,7 @@ import ( "strings" "testing" + "github.com/charmbracelet/log" serverlessapi "github.com/runware/runware-cli/internal/api/serverless" ) @@ -277,15 +279,18 @@ func TestApplyEnvUpdates_MergesIntoOneRequest(t *testing.T) { return } patches++ - var body struct { - EnvironmentVariables map[string]string `json:"environmentVariables"` - } + var body serverlessapi.AppUpdate if err := json.NewDecoder(r.Body).Decode(&body); err != nil { t.Errorf("decode: %v", err) return } - if body.EnvironmentVariables["KEEP"] != "old" || body.EnvironmentVariables["KEEP_TOO"] != "also" || body.EnvironmentVariables["NEW_KEY"] != "n" { - t.Errorf("environmentVariables = %#v", body.EnvironmentVariables) + if body.EnvironmentVariables == nil { + t.Error("missing environmentVariables") + return + } + got := derefEnv(*body.EnvironmentVariables) + if got["KEEP"] != "old" || got["KEEP_TOO"] != "also" || got["NEW_KEY"] != "n" { + t.Errorf("environmentVariables = %#v", got) } _, _ = w.Write([]byte(activeAppBody(""))) })) @@ -307,11 +312,50 @@ func TestEnvSet_RejectsMixedForms(t *testing.T) { cmd.SetErr(&bytes.Buffer{}) cmd.SetArgs([]string{testAppID, testEnvKey, "--value", testEnvValue, "--env", "NEW_KEY=n"}) err := cmd.Execute() - if err == nil || !strings.Contains(err.Error(), "--env") { + if err == nil || err.Error() != envSetUsage { t.Fatalf("err = %v", err) } } +func TestEnvSet_EmptyValueStillSets(t *testing.T) { + var puts int + var gotValue *string + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json") + if r.Method != http.MethodPut { + t.Errorf("unexpected %s %s", r.Method, r.URL.Path) + return + } + puts++ + var body serverlessapi.EnvironmentVariableUpdate + if err := json.NewDecoder(r.Body).Decode(&body); err != nil { + t.Errorf("decode: %v", err) + return + } + value := body.Value + gotValue = &value + _, _ = w.Write([]byte(`{"key":"` + testEnvKey + `","value":""}`)) + })) + defer srv.Close() + + t.Setenv("RUNWARE_API_KEY", "test-key") + t.Setenv("RUNWARE_SERVERLESS_BASE_URL", srv.URL) + + cmd := newAppsEnvSetCmd(log.New(io.Discard)) + cmd.SetOut(&bytes.Buffer{}) + cmd.SetErr(&bytes.Buffer{}) + cmd.SetArgs([]string{testAppID, testEnvKey, "--value", ""}) + if err := cmd.Execute(); err != nil { + t.Fatalf("env set: %v", err) + } + if puts != 1 { + t.Fatalf("puts = %d, want 1", puts) + } + if gotValue == nil || *gotValue != "" { + t.Fatalf("value = %v, want empty", gotValue) + } +} + func derefEnv(in map[string]*string) map[string]string { out := make(map[string]string, len(in)) for key, value := range in { From 1c043ea2622d0c1bd6e15147486cb82202e92aa9 Mon Sep 17 00:00:00 2001 From: ryank90 Date: Thu, 24 Sep 2026 16:42:09 +0100 Subject: [PATCH 3/3] docs: drop the duplicate env-set 409 sentence after rebase --- docs/runware_serverless_apps_env_set.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/runware_serverless_apps_env_set.md b/docs/runware_serverless_apps_env_set.md index 8f653b7..36c5a64 100644 --- a/docs/runware_serverless_apps_env_set.md +++ b/docs/runware_serverless_apps_env_set.md @@ -20,8 +20,7 @@ or resume rollout is already in progress, and does not store the change. A change records a new version with the same image and rolls the workload when the app is active, initializing, or failed and its image is deployable. A stopped or stopping app applies it on resume. An unchanged value records no -version. A write during an in-flight rollout returns 409 and does not store -the value. +version. The server rejects (HTTP 422) reserved platform names, names that collide with an attached secret's injected env var, and adding a binding past the