Skip to content

Commit 58dc759

Browse files
authored
Merge pull request #1153 from planetscale/list-webhooks
Add `webhook list` command
2 parents 0504c16 + d8d3bb2 commit 58dc759

File tree

7 files changed

+292
-3
lines changed

7 files changed

+292
-3
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ require (
2525
github.com/mattn/go-shellwords v1.0.12
2626
github.com/mitchellh/go-homedir v1.1.0
2727
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c
28-
github.com/planetscale/planetscale-go v0.145.0
28+
github.com/planetscale/planetscale-go v0.146.0
2929
github.com/planetscale/psdb v0.0.0-20250717190954-65c6661ab6e4
3030
github.com/planetscale/psdbproxy v0.0.0-20250728082226-3f4ea3a74ec7
3131
github.com/spf13/cobra v1.10.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -175,8 +175,8 @@ github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjL
175175
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
176176
github.com/planetscale/noglog v0.2.1-0.20210421230640-bea75fcd2e8e h1:MZ8D+Z3m2vvqGZLvoQfpaGg/j1fNDr4j03s3PRz4rVY=
177177
github.com/planetscale/noglog v0.2.1-0.20210421230640-bea75fcd2e8e/go.mod h1:hwAsSPQdvPa3WcfKfzTXxtEq/HlqwLjQasfO6QbGo4Q=
178-
github.com/planetscale/planetscale-go v0.145.0 h1:jdmAzU5sfdBZxVGMQXkT+BBxvOcND7cakCIQc0vdeVg=
179-
github.com/planetscale/planetscale-go v0.145.0/go.mod h1:PheYDHAwF14wfCBak1M0J64AdPW8NUeyvgPgWqe7zpI=
178+
github.com/planetscale/planetscale-go v0.146.0 h1:cc65OzW4hkbhNLDApQlVAFG1680obE/OvQzEKPPsT+U=
179+
github.com/planetscale/planetscale-go v0.146.0/go.mod h1:PheYDHAwF14wfCBak1M0J64AdPW8NUeyvgPgWqe7zpI=
180180
github.com/planetscale/psdb v0.0.0-20250717190954-65c6661ab6e4 h1:Xv5pj20Rhfty1Tv0OVcidg4ez4PvGrpKvb6rvUwQgDs=
181181
github.com/planetscale/psdb v0.0.0-20250717190954-65c6661ab6e4/go.mod h1:M52h5IWxAcbdQ1hSZrLAGQC4ZXslxEsK/Wh9nu3wdWs=
182182
github.com/planetscale/psdbproxy v0.0.0-20250728082226-3f4ea3a74ec7 h1:aRd6vdE1fyuSI4RVj7oCr8lFmgqXvpnPUmN85VbZCp8=

internal/cmd/root.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import (
4949
"github.com/planetscale/cli/internal/cmd/signup"
5050
"github.com/planetscale/cli/internal/cmd/token"
5151
"github.com/planetscale/cli/internal/cmd/version"
52+
"github.com/planetscale/cli/internal/cmd/webhook"
5253
"github.com/planetscale/cli/internal/cmdutil"
5354
"github.com/planetscale/cli/internal/config"
5455
"github.com/planetscale/cli/internal/printer"
@@ -281,6 +282,10 @@ func runCmd(ctx context.Context, ver, commit, buildDate string, format *printer.
281282
databaseCmd.GroupID = "database"
282283
rootCmd.AddCommand(databaseCmd)
283284

285+
webhookCmd := webhook.WebhookCmd(ch)
286+
webhookCmd.GroupID = "database"
287+
rootCmd.AddCommand(webhookCmd)
288+
284289
// Vitess-specific commands
285290
connectCmd := connect.ConnectCmd(ch)
286291
connectCmd.GroupID = "vitess"

internal/cmd/webhook/list.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package webhook
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/planetscale/cli/internal/cmdutil"
7+
"github.com/planetscale/cli/internal/printer"
8+
"github.com/planetscale/planetscale-go/planetscale"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
func ListCmd(ch *cmdutil.Helper) *cobra.Command {
13+
cmd := &cobra.Command{
14+
Use: "list <database>",
15+
Short: "List webhooks for a database",
16+
Args: cmdutil.RequiredArgs("database"),
17+
RunE: func(cmd *cobra.Command, args []string) error {
18+
ctx := cmd.Context()
19+
database := args[0]
20+
21+
client, err := ch.Client()
22+
if err != nil {
23+
return err
24+
}
25+
26+
end := ch.Printer.PrintProgress(fmt.Sprintf("Fetching webhooks for %s", printer.BoldBlue(database)))
27+
defer end()
28+
29+
webhooks, err := client.Webhooks.List(ctx, &planetscale.ListWebhooksRequest{
30+
Organization: ch.Config.Organization,
31+
Database: database,
32+
})
33+
if err != nil {
34+
switch cmdutil.ErrCode(err) {
35+
case planetscale.ErrNotFound:
36+
return fmt.Errorf("database %s does not exist in organization %s",
37+
printer.BoldBlue(database), printer.BoldBlue(ch.Config.Organization))
38+
default:
39+
return cmdutil.HandleError(err)
40+
}
41+
}
42+
43+
end()
44+
45+
if len(webhooks) == 0 && ch.Printer.Format() == printer.Human {
46+
ch.Printer.Printf("No webhooks exist in database %s.\n", printer.BoldBlue(database))
47+
return nil
48+
}
49+
50+
return ch.Printer.PrintResource(toWebhooks(webhooks))
51+
},
52+
}
53+
54+
return cmd
55+
}

internal/cmd/webhook/list_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package webhook
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"testing"
7+
"time"
8+
9+
qt "github.com/frankban/quicktest"
10+
"github.com/planetscale/cli/internal/cmdutil"
11+
"github.com/planetscale/cli/internal/config"
12+
"github.com/planetscale/cli/internal/mock"
13+
"github.com/planetscale/cli/internal/printer"
14+
ps "github.com/planetscale/planetscale-go/planetscale"
15+
)
16+
17+
func TestWebhook_ListCmd(t *testing.T) {
18+
c := qt.New(t)
19+
20+
var buf bytes.Buffer
21+
format := printer.JSON
22+
p := printer.NewPrinter(&format)
23+
p.SetResourceOutput(&buf)
24+
25+
org := "planetscale"
26+
db := "mydb"
27+
createdAt := time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC)
28+
29+
webhooks := []*ps.Webhook{
30+
{
31+
ID: "webhook-123",
32+
URL: "https://example.com/webhook",
33+
Enabled: true,
34+
Events: []string{"branch.created", "branch.deleted"},
35+
CreatedAt: createdAt,
36+
},
37+
}
38+
39+
svc := &mock.WebhooksService{
40+
ListFn: func(ctx context.Context, req *ps.ListWebhooksRequest, opts ...ps.ListOption) ([]*ps.Webhook, error) {
41+
c.Assert(req.Organization, qt.Equals, org)
42+
c.Assert(req.Database, qt.Equals, db)
43+
return webhooks, nil
44+
},
45+
}
46+
47+
ch := &cmdutil.Helper{
48+
Printer: p,
49+
Config: &config.Config{
50+
Organization: org,
51+
},
52+
Client: func() (*ps.Client, error) {
53+
return &ps.Client{
54+
Webhooks: svc,
55+
}, nil
56+
},
57+
}
58+
59+
cmd := ListCmd(ch)
60+
cmd.SetArgs([]string{db})
61+
err := cmd.Execute()
62+
63+
c.Assert(err, qt.IsNil)
64+
c.Assert(svc.ListFnInvoked, qt.IsTrue)
65+
66+
res := []*Webhook{
67+
{orig: webhooks[0]},
68+
}
69+
c.Assert(buf.String(), qt.JSONEquals, res)
70+
}
71+
72+
func TestWebhook_ListCmd_Empty(t *testing.T) {
73+
c := qt.New(t)
74+
75+
var buf bytes.Buffer
76+
format := printer.Human
77+
p := printer.NewPrinter(&format)
78+
p.SetHumanOutput(&buf)
79+
80+
org := "planetscale"
81+
db := "mydb"
82+
83+
svc := &mock.WebhooksService{
84+
ListFn: func(ctx context.Context, req *ps.ListWebhooksRequest, opts ...ps.ListOption) ([]*ps.Webhook, error) {
85+
c.Assert(req.Organization, qt.Equals, org)
86+
c.Assert(req.Database, qt.Equals, db)
87+
return []*ps.Webhook{}, nil
88+
},
89+
}
90+
91+
ch := &cmdutil.Helper{
92+
Printer: p,
93+
Config: &config.Config{
94+
Organization: org,
95+
},
96+
Client: func() (*ps.Client, error) {
97+
return &ps.Client{
98+
Webhooks: svc,
99+
}, nil
100+
},
101+
}
102+
103+
cmd := ListCmd(ch)
104+
cmd.SetArgs([]string{db})
105+
err := cmd.Execute()
106+
107+
c.Assert(err, qt.IsNil)
108+
c.Assert(svc.ListFnInvoked, qt.IsTrue)
109+
c.Assert(buf.String(), qt.Contains, "No webhooks exist")
110+
}

internal/cmd/webhook/webhook.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package webhook
2+
3+
import (
4+
"encoding/json"
5+
"strings"
6+
7+
"github.com/planetscale/cli/internal/cmdutil"
8+
"github.com/planetscale/cli/internal/printer"
9+
ps "github.com/planetscale/planetscale-go/planetscale"
10+
"github.com/spf13/cobra"
11+
)
12+
13+
// WebhookCmd encapsulates the command for managing webhooks.
14+
func WebhookCmd(ch *cmdutil.Helper) *cobra.Command {
15+
cmd := &cobra.Command{
16+
Use: "webhook <command>",
17+
Short: "List webhooks",
18+
PersistentPreRunE: cmdutil.CheckAuthentication(ch.Config),
19+
}
20+
21+
cmd.PersistentFlags().StringVar(&ch.Config.Organization, "org", ch.Config.Organization, "The organization for the current user")
22+
cmd.MarkPersistentFlagRequired("org") // nolint:errcheck
23+
24+
cmd.AddCommand(ListCmd(ch))
25+
26+
return cmd
27+
}
28+
29+
// Webhook returns a table and json serializable webhook for printing.
30+
type Webhook struct {
31+
ID string `header:"id" json:"id"`
32+
URL string `header:"url" json:"url"`
33+
Events string `header:"events" json:"events"`
34+
Enabled bool `header:"enabled" json:"enabled"`
35+
CreatedAt int64 `header:"created_at,timestamp(ms|utc|human)" json:"created_at"`
36+
37+
orig *ps.Webhook
38+
}
39+
40+
func (w *Webhook) MarshalJSON() ([]byte, error) {
41+
return json.MarshalIndent(w.orig, "", " ")
42+
}
43+
44+
// toWebhook returns a struct that prints out the various fields of a webhook model.
45+
func toWebhook(webhook *ps.Webhook) *Webhook {
46+
return &Webhook{
47+
ID: webhook.ID,
48+
URL: webhook.URL,
49+
Events: strings.Join(webhook.Events, ", "),
50+
Enabled: webhook.Enabled,
51+
CreatedAt: printer.GetMilliseconds(webhook.CreatedAt),
52+
orig: webhook,
53+
}
54+
}
55+
56+
func toWebhooks(webhooks []*ps.Webhook) []*Webhook {
57+
results := make([]*Webhook, 0, len(webhooks))
58+
for _, webhook := range webhooks {
59+
results = append(results, toWebhook(webhook))
60+
}
61+
return results
62+
}

internal/mock/webhook.go

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package mock
2+
3+
import (
4+
"context"
5+
6+
ps "github.com/planetscale/planetscale-go/planetscale"
7+
)
8+
9+
type WebhooksService struct {
10+
ListFn func(context.Context, *ps.ListWebhooksRequest, ...ps.ListOption) ([]*ps.Webhook, error)
11+
ListFnInvoked bool
12+
13+
CreateFn func(context.Context, *ps.CreateWebhookRequest) (*ps.Webhook, error)
14+
CreateFnInvoked bool
15+
16+
GetFn func(context.Context, *ps.GetWebhookRequest) (*ps.Webhook, error)
17+
GetFnInvoked bool
18+
19+
UpdateFn func(context.Context, *ps.UpdateWebhookRequest) (*ps.Webhook, error)
20+
UpdateFnInvoked bool
21+
22+
DeleteFn func(context.Context, *ps.DeleteWebhookRequest) error
23+
DeleteFnInvoked bool
24+
25+
TestFn func(context.Context, *ps.TestWebhookRequest) error
26+
TestFnInvoked bool
27+
}
28+
29+
func (w *WebhooksService) List(ctx context.Context, req *ps.ListWebhooksRequest, opts ...ps.ListOption) ([]*ps.Webhook, error) {
30+
w.ListFnInvoked = true
31+
return w.ListFn(ctx, req, opts...)
32+
}
33+
34+
func (w *WebhooksService) Create(ctx context.Context, req *ps.CreateWebhookRequest) (*ps.Webhook, error) {
35+
w.CreateFnInvoked = true
36+
return w.CreateFn(ctx, req)
37+
}
38+
39+
func (w *WebhooksService) Get(ctx context.Context, req *ps.GetWebhookRequest) (*ps.Webhook, error) {
40+
w.GetFnInvoked = true
41+
return w.GetFn(ctx, req)
42+
}
43+
44+
func (w *WebhooksService) Update(ctx context.Context, req *ps.UpdateWebhookRequest) (*ps.Webhook, error) {
45+
w.UpdateFnInvoked = true
46+
return w.UpdateFn(ctx, req)
47+
}
48+
49+
func (w *WebhooksService) Delete(ctx context.Context, req *ps.DeleteWebhookRequest) error {
50+
w.DeleteFnInvoked = true
51+
return w.DeleteFn(ctx, req)
52+
}
53+
54+
func (w *WebhooksService) Test(ctx context.Context, req *ps.TestWebhookRequest) error {
55+
w.TestFnInvoked = true
56+
return w.TestFn(ctx, req)
57+
}

0 commit comments

Comments
 (0)