Skip to content

Commit 8df764e

Browse files
authored
Add demote commands (#635)
* Update planetscale-go dependency. * Fix up promote arg. * Add Demote command. * Update mocks. * Add demotion tests. * Handle when a demotion request is returned.
1 parent 8b3dd7b commit 8df764e

File tree

9 files changed

+292
-22
lines changed

9 files changed

+292
-22
lines changed

go.mod

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ require (
2020
github.com/mitchellh/go-homedir v1.1.0
2121
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
2222
github.com/pkg/errors v0.9.1
23-
github.com/planetscale/planetscale-go v0.82.0
23+
github.com/planetscale/planetscale-go v0.83.0
2424
github.com/planetscale/sql-proxy v0.13.0
2525
github.com/spf13/cobra v1.6.1
2626
github.com/spf13/pflag v1.0.5
@@ -68,9 +68,9 @@ require (
6868
github.com/subosito/gotenv v1.4.2 // indirect
6969
go.uber.org/atomic v1.10.0 // indirect
7070
go.uber.org/multierr v1.9.0 // indirect
71-
golang.org/x/net v0.7.0 // indirect
72-
golang.org/x/oauth2 v0.5.0 // indirect
73-
golang.org/x/term v0.5.0 // indirect
71+
golang.org/x/net v0.8.0 // indirect
72+
golang.org/x/oauth2 v0.6.0 // indirect
73+
golang.org/x/term v0.6.0 // indirect
7474
google.golang.org/appengine v1.6.7 // indirect
7575
google.golang.org/protobuf v1.28.1 // indirect
7676
gopkg.in/ini.v1 v1.67.0 // indirect

go.sum

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,8 @@ github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
227227
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
228228
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
229229
github.com/planetscale/planetscale-go v0.51.0/go.mod h1:+rGpW2u7iQZZx4O/nFj4MZe4xIS22CVegEgl1IkTExQ=
230-
github.com/planetscale/planetscale-go v0.82.0 h1:00FaB+K5DJ29MTHm0oM3MT1SPotj7RfO2Ew2Fi0tvqo=
231-
github.com/planetscale/planetscale-go v0.82.0/go.mod h1:zCYbPZu3s99BEePAFortKfLJwxKd5HgP53wRFq5r0m4=
230+
github.com/planetscale/planetscale-go v0.83.0 h1:gFB2zg8j3rsmFQOSi/kQ/JSYuBFwTa37XRShm60MrV4=
231+
github.com/planetscale/planetscale-go v0.83.0/go.mod h1:Mnv8ntn4x1qO6f5FS+uMKZji4oWjG9PZqdTx/3uYAxM=
232232
github.com/planetscale/sql-proxy v0.13.0 h1:NDjcdqgoNzwbZQTyoIDEoI+K7keC5RRKvdML2roAMn4=
233233
github.com/planetscale/sql-proxy v0.13.0/go.mod h1:4Sk6JdoBqQhHv9V4FCOC27YIM3EjU8cLIsw5HqxN8x4=
234234
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -369,8 +369,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v
369369
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
370370
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
371371
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
372-
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
373-
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
372+
golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
373+
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
374374
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
375375
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
376376
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -380,8 +380,8 @@ golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ
380380
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
381381
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
382382
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
383-
golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s=
384-
golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I=
383+
golang.org/x/oauth2 v0.6.0 h1:Lh8GPgSKBfWSwFvtuWOfeI3aAAnbXTSutYxJiOJFgIw=
384+
golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw=
385385
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
386386
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
387387
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -438,8 +438,8 @@ golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
438438
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
439439
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
440440
golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY=
441-
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
442-
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
441+
golang.org/x/term v0.6.0 h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=
442+
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
443443
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
444444
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
445445
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=

internal/cmd/branch/branch.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func BranchCmd(ch *cmdutil.Helper) *cobra.Command {
3030
cmd.AddCommand(SchemaCmd(ch))
3131
cmd.AddCommand(RefreshSchemaCmd(ch))
3232
cmd.AddCommand(PromoteCmd(ch))
33+
cmd.AddCommand(DemoteCmd(ch))
3334
cmd.AddCommand(VSchemaCmd(ch))
3435
cmd.AddCommand(KeyspaceCmd(ch))
3536

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

59+
// Actor represents an actor for an action
60+
type Actor struct {
61+
Name string `header:"name"`
62+
}
63+
64+
type BranchDemotionRequest struct {
65+
ID string `header:"id" json:"id"`
66+
Actor *Actor `header:"actor" json:"actor"`
67+
State string `header:"state" json:"state"`
68+
CreatedAt int64 `header:"created_at,timestamp(ms|utc|human)" json:"created_at"`
69+
UpdatedAt int64 `header:"updated_at,timestamp(ms|utc|human)" json:"updated_at"`
70+
71+
orig *ps.BranchDemotionRequest
72+
}
73+
74+
func ToBranchDemotionRequest(dr *ps.BranchDemotionRequest) *BranchDemotionRequest {
75+
newDR := &BranchDemotionRequest{
76+
ID: dr.ID,
77+
State: dr.State,
78+
CreatedAt: cmdutil.TimeToMilliseconds(dr.CreatedAt),
79+
UpdatedAt: cmdutil.TimeToMilliseconds(dr.UpdatedAt),
80+
orig: dr,
81+
}
82+
83+
if dr.Actor != nil {
84+
newDR.Actor = &Actor{
85+
Name: dr.Actor.Name,
86+
}
87+
}
88+
89+
return newDR
90+
}
91+
92+
func (d *BranchDemotionRequest) MarshalJSON() ([]byte, error) {
93+
return json.MarshalIndent(d.orig, "", " ")
94+
}
95+
96+
func (d *BranchDemotionRequest) MarshalCSVValue() interface{} {
97+
return []*BranchDemotionRequest{d}
98+
}
99+
58100
// ToDatabaseBranch returns a struct that prints out the various fields of a
59101
// database model.
60102
func ToDatabaseBranch(db *ps.DatabaseBranch) *DatabaseBranch {

internal/cmd/branch/demote.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package branch
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/planetscale/cli/internal/cmdutil"
7+
"github.com/planetscale/cli/internal/printer"
8+
"github.com/spf13/cobra"
9+
10+
ps "github.com/planetscale/planetscale-go/planetscale"
11+
)
12+
13+
func DemoteCmd(ch *cmdutil.Helper) *cobra.Command {
14+
demoteReq := &ps.DemoteRequest{}
15+
16+
cmd := &cobra.Command{
17+
Use: "demote <database> <branch> [options]",
18+
Short: "Demote a production branch to development",
19+
Args: cmdutil.RequiredArgs("database", "branch"),
20+
RunE: func(cmd *cobra.Command, args []string) error {
21+
ctx := cmd.Context()
22+
23+
database := args[0]
24+
branch := args[1]
25+
26+
demoteReq.Database = database
27+
demoteReq.Branch = branch
28+
demoteReq.Organization = ch.Config.Organization
29+
30+
client, err := ch.Client()
31+
if err != nil {
32+
return err
33+
}
34+
35+
end := ch.Printer.PrintProgress(fmt.Sprintf("Demoting %s branch in %s to development...", printer.BoldBlue(branch), printer.BoldBlue(database)))
36+
defer end()
37+
38+
demotionRequest, err := client.DatabaseBranches.Demote(ctx, demoteReq)
39+
if err != nil {
40+
switch cmdutil.ErrCode(err) {
41+
case ps.ErrNotFound:
42+
return fmt.Errorf("branch %s does not exist in database %s",
43+
printer.BoldBlue(branch), printer.BoldBlue(database))
44+
default:
45+
return cmdutil.HandleError(err)
46+
}
47+
}
48+
49+
end()
50+
51+
if demotionRequest == nil {
52+
if ch.Printer.Format() == printer.Human {
53+
ch.Printer.Printf("%s branch was successfully demoted to development.\n", printer.BoldBlue(branch))
54+
return nil
55+
} else {
56+
dbBranch, err := client.DatabaseBranches.Get(cmd.Context(), &ps.GetDatabaseBranchRequest{
57+
Organization: ch.Config.Organization,
58+
Database: database,
59+
Branch: branch,
60+
})
61+
if err != nil {
62+
return cmdutil.HandleError(err)
63+
}
64+
return ch.Printer.PrintResource(ToDatabaseBranch(dbBranch))
65+
}
66+
}
67+
68+
if ch.Printer.Format() == printer.Human {
69+
ch.Printer.Printf("Successfully requested to demote %s branch to development, requires admin approval.\n", printer.BoldBlue(branch))
70+
return nil
71+
} else {
72+
return ch.Printer.PrintResource(ToBranchDemotionRequest(demotionRequest))
73+
}
74+
},
75+
}
76+
77+
return cmd
78+
}

internal/cmd/branch/demote_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package branch
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"testing"
7+
8+
qt "github.com/frankban/quicktest"
9+
"github.com/planetscale/cli/internal/cmdutil"
10+
"github.com/planetscale/cli/internal/config"
11+
"github.com/planetscale/cli/internal/mock"
12+
"github.com/planetscale/cli/internal/printer"
13+
ps "github.com/planetscale/planetscale-go/planetscale"
14+
)
15+
16+
func TestBranch_DemoteCmd(t *testing.T) {
17+
c := qt.New(t)
18+
19+
var buf bytes.Buffer
20+
format := printer.JSON
21+
p := printer.NewPrinter(&format)
22+
p.SetResourceOutput(&buf)
23+
24+
org := "planetscale"
25+
db := "planetscale"
26+
branch := "development"
27+
28+
res := &ps.DatabaseBranch{
29+
Name: branch,
30+
}
31+
32+
svc := &mock.DatabaseBranchesService{
33+
DemoteFn: func(ctx context.Context, req *ps.DemoteRequest) (*ps.BranchDemotionRequest, error) {
34+
c.Assert(req.Branch, qt.Equals, branch)
35+
c.Assert(req.Database, qt.Equals, db)
36+
c.Assert(req.Organization, qt.Equals, org)
37+
38+
return nil, nil
39+
},
40+
GetFn: func(ctx context.Context, req *ps.GetDatabaseBranchRequest) (*ps.DatabaseBranch, error) {
41+
c.Assert(req.Branch, qt.Equals, branch)
42+
c.Assert(req.Database, qt.Equals, db)
43+
c.Assert(req.Organization, qt.Equals, org)
44+
45+
return res, nil
46+
},
47+
}
48+
49+
ch := &cmdutil.Helper{
50+
Printer: p,
51+
Config: &config.Config{
52+
Organization: org,
53+
},
54+
Client: func() (*ps.Client, error) {
55+
return &ps.Client{
56+
DatabaseBranches: svc,
57+
}, nil
58+
59+
},
60+
}
61+
62+
cmd := DemoteCmd(ch)
63+
cmd.SetArgs([]string{db, branch})
64+
err := cmd.Execute()
65+
66+
c.Assert(err, qt.IsNil)
67+
c.Assert(svc.DemoteFnInvoked, qt.IsTrue)
68+
c.Assert(svc.GetFnInvoked, qt.IsTrue)
69+
c.Assert(buf.String(), qt.JSONEquals, res)
70+
}
71+
72+
func TestBranch_DemoteCmdWithDemotionRequest(t *testing.T) {
73+
c := qt.New(t)
74+
75+
var buf bytes.Buffer
76+
format := printer.JSON
77+
p := printer.NewPrinter(&format)
78+
p.SetResourceOutput(&buf)
79+
80+
org := "planetscale"
81+
db := "planetscale"
82+
branch := "development"
83+
84+
res := &ps.BranchDemotionRequest{
85+
ID: "test",
86+
State: "pending",
87+
Actor: &ps.Actor{
88+
Name: "Test User",
89+
},
90+
}
91+
92+
svc := &mock.DatabaseBranchesService{
93+
DemoteFn: func(ctx context.Context, req *ps.DemoteRequest) (*ps.BranchDemotionRequest, error) {
94+
c.Assert(req.Branch, qt.Equals, branch)
95+
c.Assert(req.Database, qt.Equals, db)
96+
c.Assert(req.Organization, qt.Equals, org)
97+
98+
return res, nil
99+
},
100+
}
101+
102+
ch := &cmdutil.Helper{
103+
Printer: p,
104+
Config: &config.Config{
105+
Organization: org,
106+
},
107+
Client: func() (*ps.Client, error) {
108+
return &ps.Client{
109+
DatabaseBranches: svc,
110+
}, nil
111+
112+
},
113+
}
114+
115+
cmd := DemoteCmd(ch)
116+
cmd.SetArgs([]string{db, branch})
117+
err := cmd.Execute()
118+
119+
c.Assert(err, qt.IsNil)
120+
c.Assert(svc.DemoteFnInvoked, qt.IsTrue)
121+
c.Assert(buf.String(), qt.JSONEquals, res)
122+
}

internal/cmd/branch/promote.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@ func PromoteCmd(ch *cmdutil.Helper) *cobra.Command {
1818
promoteReq := &ps.RequestPromotionRequest{}
1919

2020
cmd := &cobra.Command{
21-
Use: "promote <database> <branch> [options]",
22-
Short: "Promote a new branch from a database",
23-
Args: cmdutil.RequiredArgs("source-database", "branch"),
24-
Aliases: []string{"b"},
21+
Use: "promote <database> <branch> [options]",
22+
Short: "Promote a new branch from a database",
23+
Args: cmdutil.RequiredArgs("database", "branch"),
2524
ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
2625
if len(args) != 0 {
2726
return nil, cobra.ShellCompDirectiveNoFileComp

internal/cmd/branch/promote_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ func TestBranch_PromoteCmd(t *testing.T) {
3030
}
3131

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

7575
c.Assert(err, qt.IsNil)
76-
c.Assert(svc.PromoteFnInvoked, qt.IsTrue)
76+
c.Assert(svc.RequestPromotionFnInvoked, qt.IsTrue)
7777
c.Assert(svc.GetFnInvoked, qt.IsTrue)
7878
c.Assert(svc.GetPromotionRequestFnInvoked, qt.IsTrue)
7979
c.Assert(buf.String(), qt.JSONEquals, res)

internal/cmdutil/cmdutil.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"path/filepath"
77
"runtime"
88
"strings"
9+
"time"
910

1011
"github.com/planetscale/cli/internal/config"
1112
"github.com/planetscale/cli/internal/printer"
@@ -231,3 +232,7 @@ func ParseSSLMode(sslMode string) ps.ExternalDataSourceSSLVerificationMode {
231232
return ps.SSLModeVerifyIdentity
232233
}
233234
}
235+
236+
func TimeToMilliseconds(t time.Time) int64 {
237+
return t.UTC().UnixNano() / (int64(time.Millisecond) / int64(time.Nanosecond))
238+
}

0 commit comments

Comments
 (0)