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
8 changes: 7 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ Vitess only. See https://planetscale.com/docs/vitess/schema-changes/aggressive-c

## Vitess deploy requests (inspect + throttler)

Core lifecycle is already covered (`list/create/show/diff/review/deploy/apply/unblock/edit/cancel/close/revert/skip-revert`). `unblock` clears the queue after a failed deploy or revert (dashboard “Unblock deploy queue”); it is not `apply`. These inspect commands are read-only:
Core lifecycle is already covered (`list/create/show/diff/review/deploy/apply/unblock/update/cancel/close/revert/skip-revert`). `update` (`edit` is an alias) sets auto-apply and auto-delete-branch. `unblock` clears the queue after a failed deploy or revert (dashboard “Unblock deploy queue”); it is not `apply`. These inspect commands are read-only:

```bash
pscale deploy-request queue <database> --org <org> --format json # database deploy queue (first page)
Expand All @@ -313,12 +313,18 @@ pscale deploy-request throttler update <database> <number> --org <org> --format

Alias: `pscale dr …` works the same. Vitess only. `--ratio` is 0–95 (0 disables throttling; 95 is slowest). Use either `--ratio` or `--configuration keyspace=ratio`, not both.

```bash
pscale deploy-request update <database> <number> --org <org> --format json --enable-auto-apply
pscale deploy-request update <database> <number> --org <org> --format json --auto-delete-branch=false
```

After a failed deploy or revert (`complete_error` / `complete_revert_error`), unblock the queue. This is not `apply` (gated cutover) and it cannot fix a deploy-check `error`:

```bash
pscale deploy-request unblock <database> <number> --org <org> --format json
```


## Maintenance schedules (Vitess Enterprise)

Read-only visibility into planned maintenance windows for a Vitess database (Enterprise plans):
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/deployrequest/dr.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func DeployRequestCmd(ch *cmdutil.Helper) *cobra.Command {
cmd.AddCommand(DeployCmd(ch))
cmd.AddCommand(DeploymentCmd(ch))
cmd.AddCommand(DiffCmd(ch))
cmd.AddCommand(EditCmd(ch))
cmd.AddCommand(UpdateCmd(ch))
cmd.AddCommand(ForceCutoverCmd(ch))
cmd.AddCommand(ListCmd(ch))
cmd.AddCommand(OperationsCmd(ch))
Expand Down
115 changes: 114 additions & 1 deletion internal/cmd/deployrequest/edit_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ func TestDeployRequest_EditCmdNoFlags(t *testing.T) {
cmd.SetArgs([]string{db, strconv.FormatUint(number, 10)})
err := cmd.Execute()

c.Assert(err, qt.ErrorMatches, "must specify either --enable-auto-apply, --disable-auto-apply, or --auto-apply")
c.Assert(err, qt.ErrorMatches, "must specify at least one of --enable-auto-apply, --disable-auto-apply, --auto-delete-branch, or --auto-apply")
}

func TestDeployRequest_EditCmdBothFlags(t *testing.T) {
Expand Down Expand Up @@ -307,3 +307,116 @@ func TestDeployRequest_EditCmdMixedFlags(t *testing.T) {
res := &ps.DeployRequest{Number: number}
c.Assert(buf.String(), qt.JSONEquals, res)
}

func TestDeployRequest_UpdateCmdEnableAutoDeleteBranch(t *testing.T) {
c := qt.New(t)

var buf bytes.Buffer
format := printer.JSON
p := printer.NewPrinter(&format)
p.SetResourceOutput(&buf)

org := "planetscale"
db := "planetscale"
number := uint64(10)

svc := &mock.DeployRequestsService{
AutoDeleteBranchFn: func(ctx context.Context, req *ps.AutoDeleteBranchRequest) (*ps.DeployRequest, error) {
c.Assert(req.Number, qt.Equals, number)
c.Assert(req.Database, qt.Equals, db)
c.Assert(req.Organization, qt.Equals, org)
c.Assert(req.Enable, qt.IsTrue)
return &ps.DeployRequest{Number: number}, nil
},
}

ch := &cmdutil.Helper{
Printer: p,
Config: &config.Config{Organization: org},
Client: func() (*ps.Client, error) {
return &ps.Client{DeployRequests: svc}, nil
},
}

cmd := UpdateCmd(ch)
cmd.SetArgs([]string{db, strconv.FormatUint(number, 10), "--auto-delete-branch"})
err := cmd.Execute()

c.Assert(err, qt.IsNil)
c.Assert(svc.AutoDeleteBranchFnInvoked, qt.IsTrue)
c.Assert(svc.AutoApplyFnInvoked, qt.IsFalse)
c.Assert(buf.String(), qt.JSONEquals, &ps.DeployRequest{Number: number})
}

func TestDeployRequest_UpdateCmdBothSettings(t *testing.T) {
c := qt.New(t)

var buf bytes.Buffer
format := printer.JSON
p := printer.NewPrinter(&format)
p.SetResourceOutput(&buf)

org := "planetscale"
db := "planetscale"
number := uint64(10)

svc := &mock.DeployRequestsService{
AutoApplyFn: func(ctx context.Context, req *ps.AutoApplyDeployRequestRequest) (*ps.DeployRequest, error) {
c.Assert(req.Enable, qt.IsTrue)
return &ps.DeployRequest{Number: number}, nil
},
AutoDeleteBranchFn: func(ctx context.Context, req *ps.AutoDeleteBranchRequest) (*ps.DeployRequest, error) {
c.Assert(req.Enable, qt.IsFalse)
return &ps.DeployRequest{Number: number}, nil
},
}

ch := &cmdutil.Helper{
Printer: p,
Config: &config.Config{Organization: org},
Client: func() (*ps.Client, error) {
return &ps.Client{DeployRequests: svc}, nil
},
}

cmd := UpdateCmd(ch)
cmd.SetArgs([]string{db, strconv.FormatUint(number, 10), "--enable-auto-apply", "--auto-delete-branch=false"})
err := cmd.Execute()

c.Assert(err, qt.IsNil)
c.Assert(svc.AutoApplyFnInvoked, qt.IsTrue)
c.Assert(svc.AutoDeleteBranchFnInvoked, qt.IsTrue)
c.Assert(buf.String(), qt.JSONEquals, &ps.DeployRequest{Number: number})
}

func TestDeployRequest_UpdateCmdDisableAutoDeleteBranch(t *testing.T) {
c := qt.New(t)

var buf bytes.Buffer
format := printer.JSON
p := printer.NewPrinter(&format)
p.SetResourceOutput(&buf)

svc := &mock.DeployRequestsService{
AutoDeleteBranchFn: func(ctx context.Context, req *ps.AutoDeleteBranchRequest) (*ps.DeployRequest, error) {
c.Assert(req.Enable, qt.IsFalse)
return &ps.DeployRequest{Number: 10}, nil
},
}

ch := &cmdutil.Helper{
Printer: p,
Config: &config.Config{Organization: "planetscale"},
Client: func() (*ps.Client, error) {
return &ps.Client{DeployRequests: svc}, nil
},
}

cmd := UpdateCmd(ch)
cmd.SetArgs([]string{"planetscale", "10", "--auto-delete-branch=false"})
err := cmd.Execute()

c.Assert(err, qt.IsNil)
c.Assert(svc.AutoDeleteBranchFnInvoked, qt.IsTrue)
c.Assert(buf.String(), qt.JSONEquals, &ps.DeployRequest{Number: 10})
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package deployrequest
import (
"fmt"
"strconv"
"strings"

"github.com/planetscale/cli/internal/cmdutil"
"github.com/planetscale/cli/internal/planetscale"
Expand All @@ -11,18 +12,26 @@ import (
"github.com/spf13/cobra"
)

// EditCmd is the command for editing preferences on deploy requests.
func EditCmd(ch *cmdutil.Helper) *cobra.Command {
// UpdateCmd updates deploy-request settings. `edit` is kept as an alias.
func UpdateCmd(ch *cmdutil.Helper) *cobra.Command {
var flags struct {
enable_auto_apply bool
disable_auto_apply bool
autoApply string // deprecated
auto_delete_branch bool
}

cmd := &cobra.Command{
Use: "edit <database> <number> [flags]",
Short: "Edit a deploy request",
Args: cmdutil.RequiredArgs("database", "number"),
Use: "update <database> <number> [flags]",
Aliases: []string{"edit"},
Short: "Update a deploy request",
Long: `Update settings on a deploy request.

Use --enable-auto-apply / --disable-auto-apply to control gated cutover, and
--auto-delete-branch to control whether the source branch is deleted after a
successful deploy (same flag as 'deploy-request create'). At least one setting
must be passed; unset flags are not sent.`,
Args: cmdutil.RequiredArgs("database", "number"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
database := args[0]
Expand All @@ -42,11 +51,13 @@ func EditCmd(ch *cmdutil.Helper) *cobra.Command {
return fmt.Errorf("cannot use both --enable-auto-apply and --disable-auto-apply flags together")
}

hasNewFlags := flags.enable_auto_apply || flags.disable_auto_apply
hasNewAutoApply := flags.enable_auto_apply || flags.disable_auto_apply
hasDeprecatedFlag := flags.autoApply != ""
hasAutoApply := hasNewAutoApply || hasDeprecatedFlag
hasAutoDelete := cmd.Flags().Changed("auto-delete-branch")

if !hasNewFlags && !hasDeprecatedFlag {
return fmt.Errorf("must specify either --enable-auto-apply, --disable-auto-apply, or --auto-apply")
if !hasAutoApply && !hasAutoDelete {
return fmt.Errorf("must specify at least one of --enable-auto-apply, --disable-auto-apply, --auto-delete-branch, or --auto-apply")
}

if hasDeprecatedFlag {
Expand All @@ -57,20 +68,7 @@ func EditCmd(ch *cmdutil.Helper) *cobra.Command {
}
}

var enable bool
if hasNewFlags {
enable = flags.enable_auto_apply
} else {
enable = flags.autoApply == "enable"
}

dr, err := client.DeployRequests.AutoApplyDeploy(ctx, &planetscale.AutoApplyDeployRequestRequest{
Organization: ch.Config.Organization,
Database: database,
Number: n,
Enable: enable,
})
if err != nil {
handleDRErr := func(err error) error {
switch cmdutil.ErrCode(err) {
case planetscale.ErrNotFound:
return fmt.Errorf("deploy request '%s/%s' does not exist in organization %s",
Expand All @@ -80,8 +78,49 @@ func EditCmd(ch *cmdutil.Helper) *cobra.Command {
}
}

var dr *planetscale.DeployRequest

if hasAutoApply {
var enable bool
if hasNewAutoApply {
enable = flags.enable_auto_apply
} else {
enable = flags.autoApply == "enable"
}

dr, err = client.DeployRequests.AutoApplyDeploy(ctx, &planetscale.AutoApplyDeployRequestRequest{
Organization: ch.Config.Organization,
Database: database,
Number: n,
Enable: enable,
})
if err != nil {
return handleDRErr(err)
}
}

if hasAutoDelete {
dr, err = client.DeployRequests.AutoDeleteBranch(ctx, &planetscale.AutoDeleteBranchRequest{
Organization: ch.Config.Organization,
Database: database,
Number: n,
Enable: flags.auto_delete_branch,
})
if err != nil {
return handleDRErr(err)
}
}

if ch.Printer.Format() == printer.Human {
ch.Printer.Printf("Successfully updated auto-apply changes for '%s/%s'.\n",
updated := make([]string, 0, 2)
if hasAutoApply {
updated = append(updated, "auto-apply")
}
if hasAutoDelete {
updated = append(updated, "auto-delete-branch")
}
ch.Printer.Printf("Successfully updated %s for '%s/%s'.\n",
strings.Join(updated, " and "),
printer.BoldBlue(database),
printer.BoldBlue(dr.Number))
return nil
Expand All @@ -93,9 +132,15 @@ func EditCmd(ch *cmdutil.Helper) *cobra.Command {

cmd.Flags().BoolVar(&flags.enable_auto_apply, "enable-auto-apply", false, "Enable auto-apply. The deploy request will automatically swap over to the new schema once ready.")
cmd.Flags().BoolVar(&flags.disable_auto_apply, "disable-auto-apply", false, "Disable auto-apply. The deploy request will wait for your confirmation before swapping to the new schema. Use 'deploy-request apply' to apply the changes manually.")
cmd.Flags().BoolVar(&flags.auto_delete_branch, "auto-delete-branch", false, "Delete the branch after the deploy request completes. Pass --auto-delete-branch=false to keep it.")

cmd.Flags().StringVar(&flags.autoApply, "auto-apply", "", "Update the auto apply setting for a deploy request. Possible values: [enable,disable]")
cmd.Flags().MarkDeprecated("auto-apply", "use --enable-auto-apply or --disable-auto-apply instead")

return cmd
}

// EditCmd is an alias of UpdateCmd so existing tests keep compiling.
func EditCmd(ch *cmdutil.Helper) *cobra.Command {
return UpdateCmd(ch)
}
8 changes: 8 additions & 0 deletions internal/mock/dr.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ type DeployRequestsService struct {
AutoApplyFn func(context.Context, *ps.AutoApplyDeployRequestRequest) (*ps.DeployRequest, error)
AutoApplyFnInvoked bool

AutoDeleteBranchFn func(context.Context, *ps.AutoDeleteBranchRequest) (*ps.DeployRequest, error)
AutoDeleteBranchFnInvoked bool

CancelFn func(context.Context, *ps.CancelDeployRequestRequest) (*ps.DeployRequest, error)
CancelFnInvoked bool

Expand Down Expand Up @@ -94,6 +97,11 @@ func (d *DeployRequestsService) AutoApplyDeploy(ctx context.Context, req *ps.Aut
return d.AutoApplyFn(ctx, req)
}

func (d *DeployRequestsService) AutoDeleteBranch(ctx context.Context, req *ps.AutoDeleteBranchRequest) (*ps.DeployRequest, error) {
d.AutoDeleteBranchFnInvoked = true
return d.AutoDeleteBranchFn(ctx, req)
}

func (d *DeployRequestsService) CancelDeploy(ctx context.Context, req *ps.CancelDeployRequestRequest) (*ps.DeployRequest, error) {
d.CancelFnInvoked = true
return d.CancelFn(ctx, req)
Expand Down
29 changes: 29 additions & 0 deletions internal/planetscale/deploy_requests.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ var _ DeployRequestsService = (*deployRequestsService)(nil)
type DeployRequestsService interface {
ApplyDeploy(context.Context, *ApplyDeployRequestRequest) (*DeployRequest, error)
AutoApplyDeploy(context.Context, *AutoApplyDeployRequestRequest) (*DeployRequest, error)
AutoDeleteBranch(context.Context, *AutoDeleteBranchRequest) (*DeployRequest, error)
CancelDeploy(context.Context, *CancelDeployRequestRequest) (*DeployRequest, error)
CloseDeploy(context.Context, *CloseDeployRequestRequest) (*DeployRequest, error)
Create(context.Context, *CreateDeployRequestRequest) (*DeployRequest, error)
Expand Down Expand Up @@ -306,6 +307,13 @@ type AutoApplyDeployRequestRequest struct {
Enable bool `json:"-"`
}

type AutoDeleteBranchRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
Number uint64 `json:"-"`
Enable bool `json:"-"`
}

type CancelDeployRequestRequest struct {
Organization string `json:"-"`
Database string `json:"-"`
Expand Down Expand Up @@ -536,6 +544,27 @@ func (d *deployRequestsService) AutoApplyDeploy(ctx context.Context, autoApplyRe
return drr, nil
}

func (d *deployRequestsService) AutoDeleteBranch(ctx context.Context, autoDeleteReq *AutoDeleteBranchRequest) (*DeployRequest, error) {
reqBody := struct {
Enable bool `json:"enable"`
}{
Enable: autoDeleteReq.Enable,
}

path := deployRequestActionAPIPath(autoDeleteReq.Organization, autoDeleteReq.Database, autoDeleteReq.Number, "auto-delete-branch")
req, err := d.client.newRequest(http.MethodPut, path, reqBody)
if err != nil {
return nil, fmt.Errorf("error creating http request: %w", err)
}

drr := &DeployRequest{}
if err := d.client.do(ctx, req, &drr); err != nil {
return nil, err
}

return drr, nil
}

// SkipRevert skips a pending revert of a completed deploy request
func (d *deployRequestsService) SkipRevertDeploy(ctx context.Context, deployReq *SkipRevertDeployRequestRequest) (*DeployRequest, error) {
path := deployRequestActionAPIPath(deployReq.Organization, deployReq.Database, deployReq.Number, "skip-revert")
Expand Down
Loading