From d811d343e28f3f876e2ddfa618e56037b2b64964 Mon Sep 17 00:00:00 2001 From: Ayrton Date: Mon, 5 Oct 2026 09:36:39 +0200 Subject: [PATCH 1/2] Stop relying on immutable and extension on Postgres parameters The API is dropping both fields from Postgres parameters, as it already does for Neki. resize asks the catalog for changeable parameters only, so internal ones fail the same way unknown ones do, and parameters list drops the immutable column; --internal still filters on the server. Co-Authored-By: Claude Opus 5.5 (1M context) --- AGENTS.md | 4 ++-- internal/cmd/branch/parameters.go | 4 +--- internal/cmd/branch/resize.go | 7 +++---- internal/cmd/branch/resize_test.go | 11 +++++++---- internal/planetscale/postgres_bouncers.go | 1 - internal/planetscale/postgres_bouncers_test.go | 2 +- internal/planetscale/postgres_branches.go | 4 +--- internal/planetscale/postgres_branches_test.go | 2 +- 8 files changed, 16 insertions(+), 19 deletions(-) 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..ff6e8024 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 --internal=false' to see the parameters you can change", printer.BoldBlue(key), database, branch) } 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..4641b180 100644 --- a/internal/cmd/branch/resize_test.go +++ b/internal/cmd/branch/resize_test.go @@ -116,11 +116,11 @@ 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(err.Error(), qt.Contains, "does not exist or cannot be changed") c.Assert(pgSvc.ResizeFnInvoked, qt.IsFalse) } -func TestBranch_ResizeCmd_ImmutableParameter(t *testing.T) { +func TestBranch_ResizeCmd_InternalParameter(t *testing.T) { c := qt.New(t) var buf bytes.Buffer @@ -136,8 +136,11 @@ func TestBranch_ResizeCmd_ImmutableParameter(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: "wal_level", Immutable: true}, + {Namespace: "pgconf", Name: "max_connections"}, }, nil }, ResizeFn: func(ctx context.Context, req *ps.ResizePostgresBranchRequest) (*ps.PostgresBranchClusterResizeRequest, error) { @@ -152,7 +155,7 @@ func TestBranch_ResizeCmd_ImmutableParameter(t *testing.T) { 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(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) })) From 33573ae5b12d1f4ae5c586dc6270bf300950219a Mon Sep 17 00:00:00 2001 From: Ayrton Date: Mon, 5 Oct 2026 12:28:54 +0200 Subject: [PATCH 2/2] Fold internal filter check into the resize parameters test Also include --org in the hint printed for unknown parameters. Co-Authored-By: Claude Opus 5.5 (1M context) --- internal/cmd/branch/resize.go | 2 +- internal/cmd/branch/resize_test.go | 43 +++--------------------------- 2 files changed, 5 insertions(+), 40 deletions(-) diff --git a/internal/cmd/branch/resize.go b/internal/cmd/branch/resize.go index ff6e8024..b7bf079c 100644 --- a/internal/cmd/branch/resize.go +++ b/internal/cmd/branch/resize.go @@ -223,7 +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("parameter %s does not exist or cannot be changed. Run 'pscale branch parameters list %s %s --internal=false' to see the parameters you can change", printer.BoldBlue(key), database, branch) + 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 4641b180..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 @@ -117,45 +120,7 @@ func TestBranch_ResizeCmd_UnknownParameter(t *testing.T) { c.Assert(err, qt.IsNotNil) c.Assert(err.Error(), qt.Contains, "does not exist or cannot be changed") - c.Assert(pgSvc.ResizeFnInvoked, qt.IsFalse) -} - -func TestBranch_ResizeCmd_InternalParameter(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) { - c.Assert(req.Internal, qt.IsNotNil) - c.Assert(*req.Internal, qt.IsFalse) - - return []*ps.PostgresParameter{ - {Namespace: "pgconf", Name: "max_connections"}, - }, 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, "does not exist or cannot be changed") + c.Assert(err.Error(), qt.Contains, "--org planetscale") c.Assert(pgSvc.ResizeFnInvoked, qt.IsFalse) }