Skip to content

Commit

Permalink
Add demote commands (#635)
Browse files Browse the repository at this point in the history
* Update planetscale-go dependency.

* Fix up promote arg.

* Add Demote command.

* Update mocks.

* Add demotion tests.

* Handle when a demotion request is returned.
  • Loading branch information
iheanyi authored Mar 16, 2023
1 parent 8b3dd7b commit 8df764e
Show file tree
Hide file tree
Showing 9 changed files with 292 additions and 22 deletions.
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ require (
github.com/mitchellh/go-homedir v1.1.0
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
github.com/pkg/errors v0.9.1
github.com/planetscale/planetscale-go v0.82.0
github.com/planetscale/planetscale-go v0.83.0
github.com/planetscale/sql-proxy v0.13.0
github.com/spf13/cobra v1.6.1
github.com/spf13/pflag v1.0.5
Expand Down Expand Up @@ -68,9 +68,9 @@ require (
github.com/subosito/gotenv v1.4.2 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/oauth2 v0.5.0 // indirect
golang.org/x/term v0.5.0 // indirect
golang.org/x/net v0.8.0 // indirect
golang.org/x/oauth2 v0.6.0 // indirect
golang.org/x/term v0.6.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.28.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
Expand Down
16 changes: 8 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/planetscale/planetscale-go v0.51.0/go.mod h1:+rGpW2u7iQZZx4O/nFj4MZe4xIS22CVegEgl1IkTExQ=
github.com/planetscale/planetscale-go v0.82.0 h1:00FaB+K5DJ29MTHm0oM3MT1SPotj7RfO2Ew2Fi0tvqo=
github.com/planetscale/planetscale-go v0.82.0/go.mod h1:zCYbPZu3s99BEePAFortKfLJwxKd5HgP53wRFq5r0m4=
github.com/planetscale/planetscale-go v0.83.0 h1:gFB2zg8j3rsmFQOSi/kQ/JSYuBFwTa37XRShm60MrV4=
github.com/planetscale/planetscale-go v0.83.0/go.mod h1:Mnv8ntn4x1qO6f5FS+uMKZji4oWjG9PZqdTx/3uYAxM=
github.com/planetscale/sql-proxy v0.13.0 h1:NDjcdqgoNzwbZQTyoIDEoI+K7keC5RRKvdML2roAMn4=
github.com/planetscale/sql-proxy v0.13.0/go.mod h1:4Sk6JdoBqQhHv9V4FCOC27YIM3EjU8cLIsw5HqxN8x4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down Expand Up @@ -369,8 +369,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand All @@ -380,8 +380,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
Expand Down Expand Up @@ -438,8 +438,8 @@ golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
42 changes: 42 additions & 0 deletions internal/cmd/branch/branch.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ func BranchCmd(ch *cmdutil.Helper) *cobra.Command {
cmd.AddCommand(SchemaCmd(ch))
cmd.AddCommand(RefreshSchemaCmd(ch))
cmd.AddCommand(PromoteCmd(ch))
cmd.AddCommand(DemoteCmd(ch))
cmd.AddCommand(VSchemaCmd(ch))
cmd.AddCommand(KeyspaceCmd(ch))

Expand All @@ -55,6 +56,47 @@ func (d *DatabaseBranch) MarshalCSVValue() interface{} {
return []*DatabaseBranch{d}
}

// Actor represents an actor for an action
type Actor struct {
Name string `header:"name"`
}

type BranchDemotionRequest struct {
ID string `header:"id" json:"id"`
Actor *Actor `header:"actor" json:"actor"`
State string `header:"state" json:"state"`
CreatedAt int64 `header:"created_at,timestamp(ms|utc|human)" json:"created_at"`
UpdatedAt int64 `header:"updated_at,timestamp(ms|utc|human)" json:"updated_at"`

orig *ps.BranchDemotionRequest
}

func ToBranchDemotionRequest(dr *ps.BranchDemotionRequest) *BranchDemotionRequest {
newDR := &BranchDemotionRequest{
ID: dr.ID,
State: dr.State,
CreatedAt: cmdutil.TimeToMilliseconds(dr.CreatedAt),
UpdatedAt: cmdutil.TimeToMilliseconds(dr.UpdatedAt),
orig: dr,
}

if dr.Actor != nil {
newDR.Actor = &Actor{
Name: dr.Actor.Name,
}
}

return newDR
}

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

func (d *BranchDemotionRequest) MarshalCSVValue() interface{} {
return []*BranchDemotionRequest{d}
}

// ToDatabaseBranch returns a struct that prints out the various fields of a
// database model.
func ToDatabaseBranch(db *ps.DatabaseBranch) *DatabaseBranch {
Expand Down
78 changes: 78 additions & 0 deletions internal/cmd/branch/demote.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package branch

import (
"fmt"

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

ps "github.com/planetscale/planetscale-go/planetscale"
)

func DemoteCmd(ch *cmdutil.Helper) *cobra.Command {
demoteReq := &ps.DemoteRequest{}

cmd := &cobra.Command{
Use: "demote <database> <branch> [options]",
Short: "Demote a production branch to development",
Args: cmdutil.RequiredArgs("database", "branch"),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()

database := args[0]
branch := args[1]

demoteReq.Database = database
demoteReq.Branch = branch
demoteReq.Organization = ch.Config.Organization

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

end := ch.Printer.PrintProgress(fmt.Sprintf("Demoting %s branch in %s to development...", printer.BoldBlue(branch), printer.BoldBlue(database)))
defer end()

demotionRequest, err := client.DatabaseBranches.Demote(ctx, demoteReq)
if err != nil {
switch cmdutil.ErrCode(err) {
case ps.ErrNotFound:
return fmt.Errorf("branch %s does not exist in database %s",
printer.BoldBlue(branch), printer.BoldBlue(database))
default:
return cmdutil.HandleError(err)
}
}

end()

if demotionRequest == nil {
if ch.Printer.Format() == printer.Human {
ch.Printer.Printf("%s branch was successfully demoted to development.\n", printer.BoldBlue(branch))
return nil
} else {
dbBranch, err := client.DatabaseBranches.Get(cmd.Context(), &ps.GetDatabaseBranchRequest{
Organization: ch.Config.Organization,
Database: database,
Branch: branch,
})
if err != nil {
return cmdutil.HandleError(err)
}
return ch.Printer.PrintResource(ToDatabaseBranch(dbBranch))
}
}

if ch.Printer.Format() == printer.Human {
ch.Printer.Printf("Successfully requested to demote %s branch to development, requires admin approval.\n", printer.BoldBlue(branch))
return nil
} else {
return ch.Printer.PrintResource(ToBranchDemotionRequest(demotionRequest))
}
},
}

return cmd
}
122 changes: 122 additions & 0 deletions internal/cmd/branch/demote_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package branch

import (
"bytes"
"context"
"testing"

qt "github.com/frankban/quicktest"
"github.com/planetscale/cli/internal/cmdutil"
"github.com/planetscale/cli/internal/config"
"github.com/planetscale/cli/internal/mock"
"github.com/planetscale/cli/internal/printer"
ps "github.com/planetscale/planetscale-go/planetscale"
)

func TestBranch_DemoteCmd(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"
branch := "development"

res := &ps.DatabaseBranch{
Name: branch,
}

svc := &mock.DatabaseBranchesService{
DemoteFn: func(ctx context.Context, req *ps.DemoteRequest) (*ps.BranchDemotionRequest, error) {
c.Assert(req.Branch, qt.Equals, branch)
c.Assert(req.Database, qt.Equals, db)
c.Assert(req.Organization, qt.Equals, org)

return nil, nil
},
GetFn: func(ctx context.Context, req *ps.GetDatabaseBranchRequest) (*ps.DatabaseBranch, error) {
c.Assert(req.Branch, qt.Equals, branch)
c.Assert(req.Database, qt.Equals, db)
c.Assert(req.Organization, qt.Equals, org)

return res, nil
},
}

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

},
}

cmd := DemoteCmd(ch)
cmd.SetArgs([]string{db, branch})
err := cmd.Execute()

c.Assert(err, qt.IsNil)
c.Assert(svc.DemoteFnInvoked, qt.IsTrue)
c.Assert(svc.GetFnInvoked, qt.IsTrue)
c.Assert(buf.String(), qt.JSONEquals, res)
}

func TestBranch_DemoteCmdWithDemotionRequest(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"
branch := "development"

res := &ps.BranchDemotionRequest{
ID: "test",
State: "pending",
Actor: &ps.Actor{
Name: "Test User",
},
}

svc := &mock.DatabaseBranchesService{
DemoteFn: func(ctx context.Context, req *ps.DemoteRequest) (*ps.BranchDemotionRequest, error) {
c.Assert(req.Branch, qt.Equals, branch)
c.Assert(req.Database, qt.Equals, db)
c.Assert(req.Organization, qt.Equals, org)

return res, nil
},
}

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

},
}

cmd := DemoteCmd(ch)
cmd.SetArgs([]string{db, branch})
err := cmd.Execute()

c.Assert(err, qt.IsNil)
c.Assert(svc.DemoteFnInvoked, qt.IsTrue)
c.Assert(buf.String(), qt.JSONEquals, res)
}
7 changes: 3 additions & 4 deletions internal/cmd/branch/promote.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ func PromoteCmd(ch *cmdutil.Helper) *cobra.Command {
promoteReq := &ps.RequestPromotionRequest{}

cmd := &cobra.Command{
Use: "promote <database> <branch> [options]",
Short: "Promote a new branch from a database",
Args: cmdutil.RequiredArgs("source-database", "branch"),
Aliases: []string{"b"},
Use: "promote <database> <branch> [options]",
Short: "Promote a new branch from a database",
Args: cmdutil.RequiredArgs("database", "branch"),
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
if len(args) != 0 {
return nil, cobra.ShellCompDirectiveNoFileComp
Expand Down
4 changes: 2 additions & 2 deletions internal/cmd/branch/promote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func TestBranch_PromoteCmd(t *testing.T) {
}

svc := &mock.DatabaseBranchesService{
PromoteFn: func(ctx context.Context, req *ps.RequestPromotionRequest) (*ps.BranchPromotionRequest, error) {
RequestPromotionFn: func(ctx context.Context, req *ps.RequestPromotionRequest) (*ps.BranchPromotionRequest, error) {
c.Assert(req.Branch, qt.Equals, branch)
c.Assert(req.Database, qt.Equals, db)
c.Assert(req.Organization, qt.Equals, org)
Expand Down Expand Up @@ -73,7 +73,7 @@ func TestBranch_PromoteCmd(t *testing.T) {
err := cmd.Execute()

c.Assert(err, qt.IsNil)
c.Assert(svc.PromoteFnInvoked, qt.IsTrue)
c.Assert(svc.RequestPromotionFnInvoked, qt.IsTrue)
c.Assert(svc.GetFnInvoked, qt.IsTrue)
c.Assert(svc.GetPromotionRequestFnInvoked, qt.IsTrue)
c.Assert(buf.String(), qt.JSONEquals, res)
Expand Down
5 changes: 5 additions & 0 deletions internal/cmdutil/cmdutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"path/filepath"
"runtime"
"strings"
"time"

"github.com/planetscale/cli/internal/config"
"github.com/planetscale/cli/internal/printer"
Expand Down Expand Up @@ -231,3 +232,7 @@ func ParseSSLMode(sslMode string) ps.ExternalDataSourceSSLVerificationMode {
return ps.SSLModeVerifyIdentity
}
}

func TimeToMilliseconds(t time.Time) int64 {
return t.UTC().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
}
Loading

0 comments on commit 8df764e

Please sign in to comment.