Skip to content

Commit fe79259

Browse files
committed
feat: added forgebit webhooks command group
1 parent 9a714fe commit fe79259

4 files changed

Lines changed: 818 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ Pre-release — nothing has shipped yet.
1616
- `--vendor <id|name>` on any command to target one vendor for a single call without changing the active default
1717
- `forgebit licenses issue|list|show|verify|revoke|renew` against the Forgebit license API
1818
- `forgebit products list|show|create|update|archive|restore` against the Forgebit products API
19+
- `forgebit webhooks list|show|create|update|delete|rotate-secret|test` and `forgebit webhooks logs list|replay` against the Forgebit webhooks API
1920
- `forgebit licenses public-key` — fetch a vendor's Ed25519 public key
2021
- Fully offline license verification (`forgebit licenses verify --offline`) for `jwt` and `forgebit`-type keys, checked locally with no network call
2122
- `forgebit status` — reports whether the CLI is running against the API or offline data

cmd/webhooks.go

Lines changed: 381 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,381 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/boone-studios/forgebit-cli/internal/forgebit"
7+
"github.com/spf13/cobra"
8+
)
9+
10+
var webhooksCmd = &cobra.Command{
11+
Use: "webhooks",
12+
Short: "Create, inspect, and manage Forgebit webhook endpoints",
13+
}
14+
15+
func init() {
16+
rootCmd.AddCommand(webhooksCmd)
17+
}
18+
19+
func formatWebhookName(name *string) string {
20+
if name == nil || *name == "" {
21+
return "-"
22+
}
23+
return *name
24+
}
25+
26+
func formatHTTPCode(code *int) string {
27+
if code == nil {
28+
return "-"
29+
}
30+
return fmt.Sprintf("%d", *code)
31+
}
32+
33+
func printRevealedSecret(secret *string) {
34+
if secret == nil {
35+
return
36+
}
37+
fmt.Printf("secret: %s\n", *secret)
38+
fmt.Println("This secret won't be shown again — store it now.")
39+
}
40+
41+
var webhooksListCmd = &cobra.Command{
42+
Use: "list",
43+
Short: "List webhook endpoints",
44+
RunE: func(c *cobra.Command, args []string) error {
45+
client, err := requireAuth()
46+
if err != nil {
47+
return err
48+
}
49+
50+
perPage, _ := c.Flags().GetInt("per-page")
51+
asJSON, _ := c.Flags().GetBool("json")
52+
53+
result, err := client.ListWebhooks(c.Context(), forgebit.ListWebhooksParams{PerPage: perPage})
54+
if err != nil {
55+
return err
56+
}
57+
58+
if asJSON {
59+
return printJSON(result)
60+
}
61+
62+
if len(result.Data) == 0 {
63+
fmt.Println("No webhook endpoints found.")
64+
return nil
65+
}
66+
for _, endpoint := range result.Data {
67+
fmt.Printf("%s %s %s enabled:%v\n", endpoint.ID, formatWebhookName(endpoint.Name), endpoint.URL, endpoint.IsEnabled)
68+
}
69+
if result.Meta.Pagination.NextCursor != nil {
70+
fmt.Printf("(more results — next cursor: %s)\n", *result.Meta.Pagination.NextCursor)
71+
}
72+
return nil
73+
},
74+
}
75+
76+
var webhooksShowCmd = &cobra.Command{
77+
Use: "show <webhook-id>",
78+
Short: "Show a single webhook endpoint",
79+
Args: cobra.ExactArgs(1),
80+
RunE: func(c *cobra.Command, args []string) error {
81+
client, err := requireAuth()
82+
if err != nil {
83+
return err
84+
}
85+
86+
asJSON, _ := c.Flags().GetBool("json")
87+
88+
result, err := client.ShowWebhook(c.Context(), args[0])
89+
if err != nil {
90+
return err
91+
}
92+
93+
if asJSON {
94+
return printJSON(result)
95+
}
96+
97+
endpoint := result.Data
98+
fmt.Printf("%s %s %s enabled:%v events:%v has_secret:%v\n",
99+
endpoint.ID, formatWebhookName(endpoint.Name), endpoint.URL, endpoint.IsEnabled, endpoint.Events, endpoint.HasSecret)
100+
return nil
101+
},
102+
}
103+
104+
var webhooksCreateCmd = &cobra.Command{
105+
Use: "create",
106+
Short: "Create a new webhook endpoint",
107+
RunE: func(c *cobra.Command, args []string) error {
108+
client, err := requireAuth()
109+
if err != nil {
110+
return err
111+
}
112+
113+
flags := c.Flags()
114+
name, _ := flags.GetString("name")
115+
url, _ := flags.GetString("url")
116+
events, _ := flags.GetStringArray("event")
117+
disabled, _ := flags.GetBool("disabled")
118+
asJSON, _ := flags.GetBool("json")
119+
120+
if len(events) == 0 {
121+
return fmt.Errorf("at least one --event is required")
122+
}
123+
124+
result, err := client.CreateWebhook(c.Context(), forgebit.CreateWebhookParams{
125+
Name: name,
126+
URL: url,
127+
Events: events,
128+
IsEnabled: !disabled,
129+
})
130+
if err != nil {
131+
return err
132+
}
133+
134+
if asJSON {
135+
return printJSON(result)
136+
}
137+
138+
fmt.Printf("Created %s %s\n", result.Data.ID, result.Data.URL)
139+
printRevealedSecret(result.Data.Secret)
140+
return nil
141+
},
142+
}
143+
144+
var webhooksUpdateCmd = &cobra.Command{
145+
Use: "update <webhook-id>",
146+
Short: "Update a webhook endpoint",
147+
Args: cobra.ExactArgs(1),
148+
RunE: func(c *cobra.Command, args []string) error {
149+
client, err := requireAuth()
150+
if err != nil {
151+
return err
152+
}
153+
154+
flags := c.Flags()
155+
name, _ := flags.GetString("name")
156+
url, _ := flags.GetString("url")
157+
events, _ := flags.GetStringArray("event")
158+
enabled, _ := flags.GetBool("enabled")
159+
disabled, _ := flags.GetBool("disabled")
160+
asJSON, _ := flags.GetBool("json")
161+
162+
if enabled && disabled {
163+
return fmt.Errorf("--enabled and --disabled are mutually exclusive")
164+
}
165+
166+
params := forgebit.UpdateWebhookParams{URL: url, Events: events}
167+
if flags.Changed("name") {
168+
params.Name = &name
169+
}
170+
if enabled {
171+
t := true
172+
params.IsEnabled = &t
173+
} else if disabled {
174+
f := false
175+
params.IsEnabled = &f
176+
}
177+
178+
result, err := client.UpdateWebhook(c.Context(), args[0], params)
179+
if err != nil {
180+
return err
181+
}
182+
183+
if asJSON {
184+
return printJSON(result)
185+
}
186+
187+
fmt.Printf("Updated %s %s\n", result.Data.ID, result.Data.URL)
188+
return nil
189+
},
190+
}
191+
192+
var webhooksDeleteCmd = &cobra.Command{
193+
Use: "delete <webhook-id>",
194+
Short: "Delete a webhook endpoint",
195+
Args: cobra.ExactArgs(1),
196+
RunE: func(c *cobra.Command, args []string) error {
197+
client, err := requireAuth()
198+
if err != nil {
199+
return err
200+
}
201+
202+
asJSON, _ := c.Flags().GetBool("json")
203+
204+
result, err := client.DeleteWebhook(c.Context(), args[0])
205+
if err != nil {
206+
return err
207+
}
208+
209+
if asJSON {
210+
return printJSON(result)
211+
}
212+
213+
fmt.Println(result.Message)
214+
return nil
215+
},
216+
}
217+
218+
var webhooksRotateSecretCmd = &cobra.Command{
219+
Use: "rotate-secret <webhook-id>",
220+
Short: "Rotate a webhook endpoint's signing secret",
221+
Args: cobra.ExactArgs(1),
222+
RunE: func(c *cobra.Command, args []string) error {
223+
client, err := requireAuth()
224+
if err != nil {
225+
return err
226+
}
227+
228+
asJSON, _ := c.Flags().GetBool("json")
229+
230+
result, err := client.RotateWebhookSecret(c.Context(), args[0])
231+
if err != nil {
232+
return err
233+
}
234+
235+
if asJSON {
236+
return printJSON(result)
237+
}
238+
239+
fmt.Printf("Rotated secret for %s\n", result.Data.ID)
240+
printRevealedSecret(result.Data.Secret)
241+
return nil
242+
},
243+
}
244+
245+
var webhooksTestCmd = &cobra.Command{
246+
Use: "test <webhook-id>",
247+
Short: "Send a test event to a webhook endpoint",
248+
Args: cobra.ExactArgs(1),
249+
RunE: func(c *cobra.Command, args []string) error {
250+
client, err := requireAuth()
251+
if err != nil {
252+
return err
253+
}
254+
255+
event, _ := c.Flags().GetString("event")
256+
asJSON, _ := c.Flags().GetBool("json")
257+
258+
result, err := client.TestWebhook(c.Context(), args[0], event)
259+
if err != nil {
260+
return err
261+
}
262+
263+
if asJSON {
264+
return printJSON(result)
265+
}
266+
267+
fmt.Printf("%s http_code:%s %s\n", result.Data.Indicator, formatHTTPCode(result.Data.HTTPCode), result.Data.Message)
268+
return nil
269+
},
270+
}
271+
272+
var webhooksLogsCmd = &cobra.Command{
273+
Use: "logs",
274+
Short: "Inspect and replay webhook delivery logs",
275+
}
276+
277+
var webhooksLogsListCmd = &cobra.Command{
278+
Use: "list",
279+
Short: "List webhook delivery logs",
280+
RunE: func(c *cobra.Command, args []string) error {
281+
client, err := requireAuth()
282+
if err != nil {
283+
return err
284+
}
285+
286+
flags := c.Flags()
287+
endpointID, _ := flags.GetString("endpoint")
288+
status, _ := flags.GetString("status")
289+
perPage, _ := flags.GetInt("per-page")
290+
asJSON, _ := flags.GetBool("json")
291+
292+
result, err := client.ListWebhookLogs(c.Context(), forgebit.ListWebhookLogsParams{
293+
EndpointID: endpointID,
294+
Status: status,
295+
PerPage: perPage,
296+
})
297+
if err != nil {
298+
return err
299+
}
300+
301+
if asJSON {
302+
return printJSON(result)
303+
}
304+
305+
if len(result.Data) == 0 {
306+
fmt.Println("No webhook logs found.")
307+
return nil
308+
}
309+
for _, log := range result.Data {
310+
fmt.Printf("%s event:%s status:%s http_code:%s retries:%d\n", log.ID, log.Event, log.Status, formatHTTPCode(log.HTTPCode), log.RetryCount)
311+
}
312+
if result.Meta.Pagination.NextCursor != nil {
313+
fmt.Printf("(more results — next cursor: %s)\n", *result.Meta.Pagination.NextCursor)
314+
}
315+
return nil
316+
},
317+
}
318+
319+
var webhooksLogsReplayCmd = &cobra.Command{
320+
Use: "replay <log-id>",
321+
Short: "Replay a webhook delivery",
322+
Args: cobra.ExactArgs(1),
323+
RunE: func(c *cobra.Command, args []string) error {
324+
client, err := requireAuth()
325+
if err != nil {
326+
return err
327+
}
328+
329+
asJSON, _ := c.Flags().GetBool("json")
330+
331+
result, err := client.ReplayWebhookLog(c.Context(), args[0])
332+
if err != nil {
333+
return err
334+
}
335+
336+
if asJSON {
337+
return printJSON(result)
338+
}
339+
340+
fmt.Println(result.Message)
341+
return nil
342+
},
343+
}
344+
345+
func init() {
346+
webhooksListCmd.Flags().Int("per-page", 15, "results per page (max 100)")
347+
webhooksListCmd.Flags().Bool("json", false, "print the raw API response as JSON")
348+
349+
webhooksShowCmd.Flags().Bool("json", false, "print the raw API response as JSON")
350+
351+
webhooksCreateCmd.Flags().String("name", "", "webhook endpoint name")
352+
webhooksCreateCmd.Flags().String("url", "", "webhook endpoint URL (required)")
353+
webhooksCreateCmd.Flags().StringArray("event", nil, "event to subscribe to, repeatable (required)")
354+
webhooksCreateCmd.Flags().Bool("disabled", false, "create the endpoint disabled")
355+
webhooksCreateCmd.Flags().Bool("json", false, "print the raw API response as JSON")
356+
_ = webhooksCreateCmd.MarkFlagRequired("url")
357+
358+
webhooksUpdateCmd.Flags().String("name", "", "webhook endpoint name")
359+
webhooksUpdateCmd.Flags().String("url", "", "webhook endpoint URL")
360+
webhooksUpdateCmd.Flags().StringArray("event", nil, "event to subscribe to, repeatable")
361+
webhooksUpdateCmd.Flags().Bool("enabled", false, "enable the endpoint")
362+
webhooksUpdateCmd.Flags().Bool("disabled", false, "disable the endpoint")
363+
webhooksUpdateCmd.Flags().Bool("json", false, "print the raw API response as JSON")
364+
365+
webhooksDeleteCmd.Flags().Bool("json", false, "print the raw API response as JSON")
366+
webhooksRotateSecretCmd.Flags().Bool("json", false, "print the raw API response as JSON")
367+
368+
webhooksTestCmd.Flags().String("event", "webhook.test", "event to simulate")
369+
webhooksTestCmd.Flags().Bool("json", false, "print the raw API response as JSON")
370+
371+
webhooksLogsListCmd.Flags().String("endpoint", "", "filter by webhook endpoint ID")
372+
webhooksLogsListCmd.Flags().String("status", "", "filter by status (pending, success, failed, permanent_failure)")
373+
webhooksLogsListCmd.Flags().Int("per-page", 15, "results per page (max 100)")
374+
webhooksLogsListCmd.Flags().Bool("json", false, "print the raw API response as JSON")
375+
376+
webhooksLogsReplayCmd.Flags().Bool("json", false, "print the raw API response as JSON")
377+
378+
webhooksLogsCmd.AddCommand(webhooksLogsListCmd, webhooksLogsReplayCmd)
379+
380+
webhooksCmd.AddCommand(webhooksListCmd, webhooksShowCmd, webhooksCreateCmd, webhooksUpdateCmd, webhooksDeleteCmd, webhooksRotateSecretCmd, webhooksTestCmd, webhooksLogsCmd)
381+
}

0 commit comments

Comments
 (0)