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
15 changes: 15 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,21 @@ pscale keyspace resize status <database> <branch> <keyspace> --org <org> --forma

External create required flags: `--host`, `--source-database`, `--username`, `--password`, `--ssl-mode` (`disabled`, `preferred`, `required`, `verify_ca`, `verify_identity`). Default `--port` is `3306`.

Keyspace VTTablet and MySQL parameters: list them with current and default values, then change them with `parameters set`. Prefix each parameter with its namespace (`vttablet.` or `mysqld.`); `--reset` sets a parameter back to its default. Changes for both namespaces are submitted together and roll out to the keyspace; follow them with `parameters changes list`. Only one unfinished change per namespace can exist on a keyspace at a time.

```bash
pscale keyspace parameters list <database> <branch> <keyspace> --org <org> --format json
pscale keyspace parameters list <database> <branch> <keyspace> --org <org> --format json --namespace vttablet
pscale keyspace parameters set <database> <branch> <keyspace> --org <org> --format json \
--parameters vttablet.vreplication-parallel-insert-workers=4 \
--parameters vttablet.vreplication_max_time_to_retry_on_error=720h
pscale keyspace parameters set <database> <branch> <keyspace> --org <org> --format json \
--reset vttablet.vreplication-parallel-insert-workers
pscale keyspace parameters changes list <database> <branch> <keyspace> --org <org> --format json
pscale keyspace parameters changes show <database> <branch> <keyspace> <change-id> --org <org> --format json
pscale keyspace parameters changes cancel <database> <branch> <keyspace> <change-id> --org <org> --format json
```

## Vitess MoveTables

Copy tables between keyspaces with `pscale branch vtctld move-tables`. `pscale workflow` is deprecated; use `move-tables` instead. JSON output includes `next_steps` — follow those commands. Typical order: create the target keyspace (`keyspace create` or `keyspace create-external`), create the workflow, poll `status`, switch replica traffic, then primary traffic (ask the user first), then `complete --dry-run` and `complete` after approval. Create with `--auto-start=false` to leave streams stopped, then `start` when you are ready.
Expand Down
1 change: 1 addition & 0 deletions internal/cmd/keyspace/keyspace.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func KeyspaceCmd(ch *cmdutil.Helper) *cobra.Command {
cmd.AddCommand(ReadOnlyRegionsCmd(ch))
cmd.AddCommand(UpdateSettingsCmd(ch))
cmd.AddCommand(SettingsCmd(ch))
cmd.AddCommand(ParametersCmd(ch))

return cmd
}
Expand Down
144 changes: 144 additions & 0 deletions internal/cmd/keyspace/parameters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package keyspace

import (
"encoding/json"
"fmt"
"slices"
"strings"

"github.com/planetscale/cli/internal/cmdutil"
ps "github.com/planetscale/cli/internal/planetscale"
"github.com/planetscale/cli/internal/printer"
"github.com/spf13/cobra"
)

var parameterNamespaces = []string{"vttablet", "mysqld"}

// ParametersCmd lists and changes the VTTablet and MySQL parameters of a keyspace.
func ParametersCmd(ch *cmdutil.Helper) *cobra.Command {
var flags struct {
namespace string
}

long := `List the VTTablet and MySQL parameters of a keyspace, including their current and default values.

To change parameters, use 'pscale keyspace parameters set <database> <branch> <keyspace> --parameters namespace.name=value'.`

run := func(cmd *cobra.Command, args []string) error {
database, branch, keyspace := args[0], args[1], args[2]

if flags.namespace != "" && !slices.Contains(parameterNamespaces, flags.namespace) {
return fmt.Errorf("invalid --namespace %q, must be one of: %s", flags.namespace, strings.Join(parameterNamespaces, ", "))
}

client, err := ch.Client()
if err != nil {
return err
}

end := ch.Printer.PrintProgress(fmt.Sprintf("Fetching parameters for keyspace %s in %s/%s...", printer.BoldBlue(keyspace), printer.BoldBlue(database), printer.BoldBlue(branch)))
defer end()

parameters, err := client.Keyspaces.ListParameters(cmd.Context(), &ps.ListKeyspaceParametersRequest{
Organization: ch.Config.Organization,
Database: database,
Branch: branch,
Keyspace: keyspace,
})
if err != nil {
switch cmdutil.ErrCode(err) {
case ps.ErrNotFound:
return fmt.Errorf("database %s or branch %s does not exist in organization %s", printer.BoldBlue(database), printer.BoldBlue(branch), printer.BoldBlue(ch.Config.Organization))
default:
return cmdutil.HandleError(err)
}
}
end()

if parameters == nil {
return keyspaceNotFoundError(ch, database, branch, keyspace)
}

var selected []*ps.VitessParameter
if flags.namespace == "" || flags.namespace == "vttablet" {
selected = append(selected, parameters.VTTablet...)
}
if flags.namespace == "" || flags.namespace == "mysqld" {
selected = append(selected, parameters.MySQL...)
}

return ch.Printer.PrintResource(toKeyspaceParameters(selected))
}

registerFlags := func(cmd *cobra.Command) {
cmd.Flags().StringVar(&flags.namespace, "namespace", "", "Only show parameters in this namespace: vttablet or mysqld.")
}

cmd := &cobra.Command{
Use: "parameters <database> <branch> <keyspace>",
Aliases: []string{"params"},
Short: "List and change the VTTablet and MySQL parameters of a keyspace",
Long: long,
Args: cmdutil.RequiredArgs("database", "branch", "keyspace"),
RunE: run,
}
registerFlags(cmd)

listCmd := &cobra.Command{
Use: "list <database> <branch> <keyspace>",
Short: "List the VTTablet and MySQL parameters of a keyspace",
Long: long,
Args: cmdutil.RequiredArgs("database", "branch", "keyspace"),
RunE: run,
}
registerFlags(listCmd)

cmd.AddCommand(listCmd, parametersSetCmd(ch), parametersChangesCmd(ch))
return cmd
}

func keyspaceNotFoundError(ch *cmdutil.Helper, database, branch, keyspace string) error {
return fmt.Errorf("keyspace %s does not exist in branch %s (database: %s, organization: %s)", printer.BoldBlue(keyspace), printer.BoldBlue(branch), printer.BoldBlue(database), printer.BoldBlue(ch.Config.Organization))
}

type keyspaceParameter struct {
Namespace string `header:"namespace" json:"namespace"`
Name string `header:"name" json:"name"`
Value string `header:"value" json:"value"`
Default string `header:"default" json:"default_value"`
Type string `header:"type" json:"parameter_type"`
Override bool `header:"override" json:"override"`

orig *ps.VitessParameter
}

func toKeyspaceParameters(parameters []*ps.VitessParameter) []*keyspaceParameter {
out := make([]*keyspaceParameter, 0, len(parameters))
for _, param := range parameters {
out = append(out, &keyspaceParameter{
Namespace: param.Component,
Name: param.Name,
Value: stringValue(param.Value),
Default: stringValue(param.DefaultValue),
Type: param.ParameterType,
Override: param.Override,
orig: param,
})
}
return out
}

func (p *keyspaceParameter) MarshalJSON() ([]byte, error) {
return json.MarshalIndent(p.orig, "", " ")
}

func (p *keyspaceParameter) MarshalCSVValue() interface{} {
return []*keyspaceParameter{p}
}

func stringValue(value *string) string {
if value == nil {
return ""
}
return *value
}
222 changes: 222 additions & 0 deletions internal/cmd/keyspace/parameters_changes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
package keyspace

import (
"encoding/json"
"fmt"
"sort"
"strings"

"github.com/planetscale/cli/internal/cmdutil"
ps "github.com/planetscale/cli/internal/planetscale"
"github.com/planetscale/cli/internal/printer"
"github.com/spf13/cobra"
)

func parametersChangesCmd(ch *cmdutil.Helper) *cobra.Command {
cmd := &cobra.Command{
Use: "changes <command>",
Short: "Manage parameter changes to a keyspace",
}
cmd.AddCommand(parametersChangesListCmd(ch), parametersChangesShowCmd(ch), parametersChangesCancelCmd(ch))
return cmd
}

func parametersChangesListCmd(ch *cmdutil.Helper) *cobra.Command {
var flags struct {
page int
perPage int
}

cmd := &cobra.Command{
Use: "list <database> <branch> <keyspace>",
Short: "List parameter changes to a keyspace",
Args: cmdutil.RequiredArgs("database", "branch", "keyspace"),
Aliases: []string{"ls"},
RunE: func(cmd *cobra.Command, args []string) error {
database, branch, keyspace := args[0], args[1], args[2]

client, err := ch.Client()
if err != nil {
return err
}

end := ch.Printer.PrintProgress(fmt.Sprintf("Fetching parameter changes for keyspace %s in %s/%s...", printer.BoldBlue(keyspace), printer.BoldBlue(database), printer.BoldBlue(branch)))
defer end()

changes, err := client.Keyspaces.ListConfigChanges(cmd.Context(), &ps.ListKeyspaceConfigChangesRequest{
Organization: ch.Config.Organization,
Database: database,
Branch: branch,
Keyspace: keyspace,
Page: flags.page,
PerPage: flags.perPage,
})
if err != nil {
return parameterChangeError(ch, err, database, branch, keyspace)
}
end()

if len(changes) == 0 && ch.Printer.Format() == printer.Human {
ch.Printer.Printf("No parameter changes found for keyspace %s in %s/%s.\n", printer.BoldBlue(keyspace), printer.BoldBlue(database), printer.BoldBlue(branch))
return nil
}

return ch.Printer.PrintResource(toKeyspaceConfigChanges(changes))
},
}

cmd.Flags().IntVar(&flags.page, "page", 0, "Page number to fetch")
cmd.Flags().IntVar(&flags.perPage, "per-page", 25, "Number of results per page")
return cmd
}

func parametersChangesShowCmd(ch *cmdutil.Helper) *cobra.Command {
return &cobra.Command{
Use: "show <database> <branch> <keyspace> <change-id>",
Short: "Show a parameter change to a keyspace",
Args: cmdutil.RequiredArgs("database", "branch", "keyspace", "change-id"),
Aliases: []string{"get"},
RunE: func(cmd *cobra.Command, args []string) error {
database, branch, keyspace, changeID := args[0], args[1], args[2], args[3]

client, err := ch.Client()
if err != nil {
return err
}

end := ch.Printer.PrintProgress(fmt.Sprintf("Fetching parameter change %s for keyspace %s...", printer.BoldBlue(changeID), printer.BoldBlue(keyspace)))
defer end()

change, err := client.Keyspaces.GetConfigChange(cmd.Context(), &ps.GetKeyspaceConfigChangeRequest{
Organization: ch.Config.Organization,
Database: database,
Branch: branch,
Keyspace: keyspace,
ID: changeID,
})
if err != nil {
if cmdutil.ErrCode(err) == ps.ErrNotFound {
return fmt.Errorf("parameter change %s does not exist for keyspace %s in %s/%s", printer.BoldBlue(changeID), printer.BoldBlue(keyspace), printer.BoldBlue(database), printer.BoldBlue(branch))
}
return cmdutil.HandleError(err)
}
end()

return ch.Printer.PrintResource(toKeyspaceConfigChange(change))
},
}
}

func parametersChangesCancelCmd(ch *cmdutil.Helper) *cobra.Command {
return &cobra.Command{
Use: "cancel <database> <branch> <keyspace> <change-id>",
Short: "Cancel a pending parameter change to a keyspace",
Args: cmdutil.RequiredArgs("database", "branch", "keyspace", "change-id"),
RunE: func(cmd *cobra.Command, args []string) error {
database, branch, keyspace, changeID := args[0], args[1], args[2], args[3]

client, err := ch.Client()
if err != nil {
return err
}

end := ch.Printer.PrintProgress(fmt.Sprintf("Canceling parameter change %s for keyspace %s...", printer.BoldBlue(changeID), printer.BoldBlue(keyspace)))
defer end()

if err := client.Keyspaces.CancelConfigChange(cmd.Context(), &ps.CancelKeyspaceConfigChangeRequest{
Organization: ch.Config.Organization,
Database: database,
Branch: branch,
Keyspace: keyspace,
ID: changeID,
}); err != nil {
if cmdutil.ErrCode(err) == ps.ErrNotFound {
return fmt.Errorf("parameter change %s does not exist for keyspace %s in %s/%s", printer.BoldBlue(changeID), printer.BoldBlue(keyspace), printer.BoldBlue(database), printer.BoldBlue(branch))
}
return cmdutil.HandleError(err)
}
end()

if ch.Printer.Format() == printer.Human {
ch.Printer.Printf("Canceled parameter change %s for keyspace %s in %s/%s.\n", printer.BoldBlue(changeID), printer.BoldBlue(keyspace), printer.BoldBlue(database), printer.BoldBlue(branch))
return nil
}

return ch.Printer.PrintResource(map[string]string{
"result": "change canceled",
"change_id": changeID,
})
},
}
}

type keyspaceConfigChange struct {
ID string `header:"id" json:"id"`
Namespace string `header:"namespace" json:"change_type"`
State string `header:"state" json:"state"`
Changes string `header:"changes" json:"changes"`
CreatedAt string `header:"created at" json:"created_at"`

orig *ps.KeyspaceConfigChange
}

func toKeyspaceConfigChange(change *ps.KeyspaceConfigChange) *keyspaceConfigChange {
return &keyspaceConfigChange{
ID: change.ID,
Namespace: change.ChangeType,
State: change.State,
Changes: formatParameterChanges(change),
CreatedAt: change.CreatedAt.UTC().Format("2006-01-02 15:04:05"),
orig: change,
}
}

func toKeyspaceConfigChanges(changes []*ps.KeyspaceConfigChange) []*keyspaceConfigChange {
out := make([]*keyspaceConfigChange, 0, len(changes))
for _, change := range changes {
out = append(out, toKeyspaceConfigChange(change))
}
return out
}

func (c *keyspaceConfigChange) MarshalJSON() ([]byte, error) {
return json.MarshalIndent(c.orig, "", " ")
}

func (c *keyspaceConfigChange) MarshalCSVValue() interface{} {
return []*keyspaceConfigChange{c}
}

func formatParameterChanges(change *ps.KeyspaceConfigChange) string {
seen := make(map[string]struct{})
for name := range change.PreviousOptions {
seen[name] = struct{}{}
}
for name := range change.NewOptions {
seen[name] = struct{}{}
}

names := make([]string, 0, len(seen))
for name := range seen {
names = append(names, name)
}
sort.Strings(names)

parts := make([]string, 0, len(names))
for _, name := range names {
before := optionDisplayValue(change.PreviousOptions[name])
after := optionDisplayValue(change.NewOptions[name])
if before == after {
continue
}
parts = append(parts, fmt.Sprintf("%s: %s → %s", name, before, after))
}
return strings.Join(parts, ", ")
}

func optionDisplayValue(value *string) string {
if value == nil {
return "(default)"
}
return *value
}
Loading
Loading