diff --git a/AGENTS.md b/AGENTS.md index eb27eda2..498fe8fc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -544,7 +544,7 @@ pscale maintenance windows --org --format json `pscale branch resize` queues a single asynchronous **change request** for a Postgres branch covering cluster size, replica count, and configuration parameters in any combination. Track it with `resize status`; cancel it with `resize cancel` while queued. ```bash -# Read the parameter catalog first (names, current/default values, restart/immutable flags) +# Read the parameter catalog first (names, current/default values, restart flags) pscale branch parameters list --org --format json pscale branch parameters list --org --format json --namespace pgconf @@ -589,7 +589,7 @@ pscale branch resize cancel --org --format json - At least one of `--cluster-size`, `--replicas`, or `--parameters` is required. - Neki `--inherited-roles` may include `neki_viewer` (must also include `pg_read_all_data`) and `neki_operator` (must also include `postgres`). The API rejects those pairings if the required role is missing. - `--replica`, `--dedicated-read-replica`, and `--bouncer` are mutually exclusive. `--dedicated-read-replica` and `--bouncer` are Postgres-only and cannot combine with `--router` or `--shard`. The deprecated `--read-only-replica` flag is a compatibility alias for `--dedicated-read-replica`. On Neki, `--replica`, `--shard`, and `--router` can be combined. `--router` rewrites `username` to `user|`. `--replica` and `--shard` set `options` (`-c __neki.target=REPLICA`, `-c __neki.shard=…`) and add them to `database_url`. List names with `pscale branch router list` and `pscale branch shard list` (use the shard name, not the API id). -- `--parameters` values are validated against the catalog before submission; unknown or immutable parameters fail fast. Parameters with `"restart": true` in the catalog restart the database when applied — surface this to the user before changing them. +- `--parameters` values are validated against the changeable parameters in the catalog before submission; parameters that don't exist or can't be changed fail fast. Parameters with `"restart": true` in the catalog restart the database when applied — surface this to the user before changing them. - Change request `state` is one of `queued`, `pending`, `resizing`, `completed`, `canceled`. Only `completed` and `canceled` are terminal. Without `--wait`, poll `resize status` instead of assuming completion. - A no-op (branch already matches the requested configuration) prints `{"result": "no_change", "branch": ""}` in JSON mode instead of a change request. - `resize cancel` prints `{"result": "canceled", "branch": ""}` in JSON mode. diff --git a/internal/cmd/branch/parameters.go b/internal/cmd/branch/parameters.go index 719d5ad2..c86bf41e 100644 --- a/internal/cmd/branch/parameters.go +++ b/internal/cmd/branch/parameters.go @@ -76,7 +76,7 @@ To change parameters, use 'pscale branch resize --parameters registerFlags := func(cmd *cobra.Command) { cmd.Flags().StringVar(&flags.namespace, "namespace", "", "Only show parameters in this namespace (e.g. pgconf, pgbouncer, patroni).") cmd.Flags().BoolVar(&flags.extension, "extension", false, "Only show parameters that configure an extension (--extension=false hides them).") - cmd.Flags().BoolVar(&flags.internal, "internal", false, "Only show internal (immutable) parameters (--internal=false hides them).") + cmd.Flags().BoolVar(&flags.internal, "internal", false, "Only show internal parameters, which cannot be changed (--internal=false hides them).") } // The bare 'parameters ' invocation is an alias for @@ -111,7 +111,6 @@ type postgresParameter struct { Default string `header:"default" json:"default_value"` Type string `header:"type" json:"parameter_type"` Restart bool `header:"restart" json:"restart"` - Immutable bool `header:"immutable" json:"immutable"` orig *ps.PostgresParameter } @@ -126,7 +125,6 @@ func toPostgresParameters(parameters []*ps.PostgresParameter) []*postgresParamet Default: formatParameterValue(param.DefaultValue), Type: param.ParameterType, Restart: param.Restart, - Immutable: param.Immutable, orig: param, }) } diff --git a/internal/cmd/branch/resize.go b/internal/cmd/branch/resize.go index d79ff019..b7bf079c 100644 --- a/internal/cmd/branch/resize.go +++ b/internal/cmd/branch/resize.go @@ -193,10 +193,12 @@ func preflightParameters(ctx context.Context, client *ps.Client, organization, d return nil, nil } + internal := false catalog, err := client.PostgresBranches.ListParameters(ctx, &ps.ListPostgresParametersRequest{ Organization: organization, Database: database, Branch: branch, + Internal: &internal, }) if err != nil { return nil, nil @@ -221,10 +223,7 @@ func preflightParameters(ctx context.Context, client *ps.Client, organization, d for _, key := range keys { param, ok := known[key] if !ok { - return nil, fmt.Errorf("unknown parameter %s. Run 'pscale branch parameters list %s %s' to see available parameters", printer.BoldBlue(key), database, branch) - } - if param.Immutable { - return nil, fmt.Errorf("parameter %s cannot be changed", printer.BoldBlue(key)) + return nil, fmt.Errorf("parameter %s does not exist or cannot be changed. Run 'pscale branch parameters list %s %s --org %s --internal=false' to see the parameters you can change", printer.BoldBlue(key), database, branch, organization) } if param.Restart { restartParams = append(restartParams, key) diff --git a/internal/cmd/branch/resize_test.go b/internal/cmd/branch/resize_test.go index a70384e4..9ba798ba 100644 --- a/internal/cmd/branch/resize_test.go +++ b/internal/cmd/branch/resize_test.go @@ -48,6 +48,9 @@ func TestBranch_ResizeCmd_WithParameters(t *testing.T) { pgSvc := &mock.PostgresBranchesService{ ListParametersFn: func(ctx context.Context, req *ps.ListPostgresParametersRequest) ([]*ps.PostgresParameter, error) { + c.Assert(req.Internal, qt.IsNotNil) + c.Assert(*req.Internal, qt.IsFalse) + return []*ps.PostgresParameter{ {Namespace: "pgconf", Name: "max_connections", ParameterType: "integer", Restart: true}, }, nil @@ -116,43 +119,8 @@ func TestBranch_ResizeCmd_UnknownParameter(t *testing.T) { err := cmd.Execute() c.Assert(err, qt.IsNotNil) - c.Assert(err.Error(), qt.Contains, "unknown parameter") - c.Assert(pgSvc.ResizeFnInvoked, qt.IsFalse) -} - -func TestBranch_ResizeCmd_ImmutableParameter(t *testing.T) { - c := qt.New(t) - - var buf bytes.Buffer - format := printer.JSON - p := printer.NewPrinter(&format) - p.SetResourceOutput(&buf) - - dbSvc := &mock.DatabaseService{ - GetFn: func(ctx context.Context, req *ps.GetDatabaseRequest) (*ps.Database, error) { - return &ps.Database{Name: "postgres-db", Kind: "postgresql"}, nil - }, - } - - pgSvc := &mock.PostgresBranchesService{ - ListParametersFn: func(ctx context.Context, req *ps.ListPostgresParametersRequest) ([]*ps.PostgresParameter, error) { - return []*ps.PostgresParameter{ - {Namespace: "pgconf", Name: "wal_level", Immutable: true}, - }, nil - }, - ResizeFn: func(ctx context.Context, req *ps.ResizePostgresBranchRequest) (*ps.PostgresBranchClusterResizeRequest, error) { - return nil, nil - }, - } - - ch := testResizeHelper(p, "planetscale", dbSvc, pgSvc) - - cmd := ResizeCmd(ch) - cmd.SetArgs([]string{"postgres-db", "main", "--parameters", "pgconf.wal_level=logical"}) - err := cmd.Execute() - - c.Assert(err, qt.IsNotNil) - c.Assert(err.Error(), qt.Contains, "cannot be changed") + c.Assert(err.Error(), qt.Contains, "does not exist or cannot be changed") + c.Assert(err.Error(), qt.Contains, "--org planetscale") c.Assert(pgSvc.ResizeFnInvoked, qt.IsFalse) } diff --git a/internal/planetscale/postgres_bouncers.go b/internal/planetscale/postgres_bouncers.go index 391e8419..e953b4d7 100644 --- a/internal/planetscale/postgres_bouncers.go +++ b/internal/planetscale/postgres_bouncers.go @@ -34,7 +34,6 @@ type PostgresBouncerParameter struct { DisplayName string `json:"display_name"` Category string `json:"category"` Description string `json:"description"` - Immutable bool `json:"immutable"` ParameterType string `json:"parameter_type"` DefaultValue string `json:"default_value"` Value string `json:"value"` diff --git a/internal/planetscale/postgres_bouncers_test.go b/internal/planetscale/postgres_bouncers_test.go index a03e0179..ef6072d9 100644 --- a/internal/planetscale/postgres_bouncers_test.go +++ b/internal/planetscale/postgres_bouncers_test.go @@ -71,7 +71,7 @@ func TestPostgresBouncers_Get(t *testing.T) { "deleted_at":null, "actor":{"id":"user-1","display_name":"Alice","avatar_url":"https://example.com/a.png"}, "branch":{"id":"branch-1","name":"main","created_at":"2021-01-01T00:00:00.000Z","updated_at":"2021-01-01T00:00:00.000Z","deleted_at":null}, - "parameters":[{"id":"p1","namespace":"pgbouncer","name":"default_pool_size","display_name":"Default pool size","category":"","description":"","immutable":false,"parameter_type":"integer","default_value":"20","value":"50","required":false,"restart":false,"created_at":"2021-01-14T10:19:23.000Z","updated_at":"2021-01-14T10:19:23.000Z"}] + "parameters":[{"id":"p1","namespace":"pgbouncer","name":"default_pool_size","display_name":"Default pool size","category":"","description":"","parameter_type":"integer","default_value":"20","value":"50","required":false,"restart":false,"created_at":"2021-01-14T10:19:23.000Z","updated_at":"2021-01-14T10:19:23.000Z"}] }` _, err := w.Write([]byte(out)) c.Assert(err, qt.IsNil) diff --git a/internal/planetscale/postgres_branches.go b/internal/planetscale/postgres_branches.go index d2580c1b..5762bd09 100644 --- a/internal/planetscale/postgres_branches.go +++ b/internal/planetscale/postgres_branches.go @@ -196,7 +196,7 @@ type ListPostgresParametersRequest struct { // Extension filters parameters by whether they configure an extension. // Nil returns both. Extension *bool - // Internal filters parameters by whether they are internal (immutable). + // Internal filters parameters by whether they are internal and cannot be changed. // Nil returns both. Internal *bool } @@ -209,8 +209,6 @@ type PostgresParameter struct { Namespace string `json:"namespace"` Category string `json:"category"` Description string `json:"description"` - Extension bool `json:"extension"` - Immutable bool `json:"immutable"` ParameterType string `json:"parameter_type"` DefaultValue any `json:"default_value"` Value any `json:"value"` diff --git a/internal/planetscale/postgres_branches_test.go b/internal/planetscale/postgres_branches_test.go index ee578501..fb9dd30f 100644 --- a/internal/planetscale/postgres_branches_test.go +++ b/internal/planetscale/postgres_branches_test.go @@ -564,7 +564,7 @@ func TestPostgresBranches_ListParameters(t *testing.T) { w.WriteHeader(200) // The parameters endpoint returns a bare array, not a {"data": [...]} envelope. - out := `[{"id":"param-1","name":"max_connections","display_name":"Max connections","namespace":"pgconf","parameter_type":"integer","default_value":"100","value":"200","restart":true,"immutable":false,"min":25,"max":5000}]` + out := `[{"id":"param-1","name":"max_connections","display_name":"Max connections","namespace":"pgconf","parameter_type":"integer","default_value":"100","value":"200","restart":true,"min":25,"max":5000}]` _, err := w.Write([]byte(out)) c.Assert(err, qt.IsNil) }))