Something went wrong. Try again.
forked from charmbracelet/soft-serve
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415package cmd
import ( "fmt" "strconv" "strings"
"charm.land/lipgloss/v2/table" "github.com/charmbracelet/soft-serve/pkg/access" "github.com/charmbracelet/soft-serve/pkg/backend" "github.com/charmbracelet/soft-serve/pkg/utils" "github.com/charmbracelet/soft-serve/pkg/webhook" "github.com/dustin/go-humanize" "github.com/google/uuid" "github.com/spf13/cobra")
func webhookCommand() *cobra.Command { cmd := &cobra.Command{ Use: "webhook", Aliases: []string{"webhooks"}, Short: "Manage repository webhooks", }
cmd.AddCommand( webhookListCommand(), webhookCreateCommand(), webhookDeleteCommand(), webhookUpdateCommand(), webhookDeliveriesCommand(), )
return cmd}
var webhookEvents []string
func init() { events := webhook.Events() webhookEvents = make([]string, len(events)) for i, e := range events { webhookEvents[i] = e.String() }}
func webhookListCommand() *cobra.Command { cmd := &cobra.Command{ Use: "list REPOSITORY", Short: "List repository webhooks", Args: cobra.ExactArgs(1), PersistentPreRunE: requireRepo(access.AdminAccess), RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() be := backend.FromContext(ctx) repo, _, err := repoFromContext(ctx) if err != nil { return err }
webhooks, err := be.ListWebhooks(ctx, repo) if err != nil { return err }
table := table.New().Headers("ID", "URL", "Events", "Active", "Created At", "Updated At") for _, h := range webhooks { events := make([]string, len(h.Events)) for i, e := range h.Events { events[i] = e.String() }
table = table.Row( strconv.FormatInt(h.ID, 10), utils.Sanitize(h.URL), strings.Join(events, ","), strconv.FormatBool(h.Active), humanize.Time(h.CreatedAt), humanize.Time(h.UpdatedAt), ) } cmd.Println(table) return nil }, }
return cmd}
func webhookCreateCommand() *cobra.Command { var events []string var secret string var active bool var contentType string cmd := &cobra.Command{ Use: "create REPOSITORY URL", Short: "Create a repository webhook", Args: cobra.ExactArgs(2), PersistentPreRunE: requireRepo(access.AdminAccess), RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() be := backend.FromContext(ctx) repo, _, err := repoFromContext(ctx) if err != nil { return err }
var evs []webhook.Event for _, e := range events { ev, err := webhook.ParseEvent(e) if err != nil { return fmt.Errorf("invalid event: %w", err) }
evs = append(evs, ev) }
var ct webhook.ContentType switch strings.ToLower(strings.TrimSpace(contentType)) { case "json": ct = webhook.ContentTypeJSON case "form": ct = webhook.ContentTypeForm default: return webhook.ErrInvalidContentType }
url := utils.Sanitize(args[1]) return be.CreateWebhook(ctx, repo, strings.TrimSpace(url), ct, secret, evs, active) }, }
cmd.Flags().StringSliceVarP(&events, "events", "e", nil, fmt.Sprintf("events to trigger the webhook, available events are (%s)", strings.Join(webhookEvents, ", "))) cmd.Flags().StringVarP(&secret, "secret", "s", "", "secret to sign the webhook payload") cmd.Flags().BoolVarP(&active, "active", "a", true, "whether the webhook is active") cmd.Flags().StringVarP(&contentType, "content-type", "c", "json", "content type of the webhook payload, can be either `json` or `form`")
return cmd}
func webhookDeleteCommand() *cobra.Command { cmd := &cobra.Command{ Use: "delete REPOSITORY WEBHOOK_ID", Short: "Delete a repository webhook", Args: cobra.ExactArgs(2), PersistentPreRunE: requireRepo(access.AdminAccess), RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() be := backend.FromContext(ctx) repo, _, err := repoFromContext(ctx) if err != nil { return err }
id, err := strconv.ParseInt(args[1], 10, 64) if err != nil { return fmt.Errorf("invalid webhook ID: %w", err) }
return be.DeleteWebhook(ctx, repo, id) }, }
return cmd}
func webhookUpdateCommand() *cobra.Command { var events []string var secret string var active string var contentType string var url string cmd := &cobra.Command{ Use: "update REPOSITORY WEBHOOK_ID", Short: "Update a repository webhook", Args: cobra.ExactArgs(2), PersistentPreRunE: requireRepo(access.AdminAccess), RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() be := backend.FromContext(ctx) repo, _, err := repoFromContext(ctx) if err != nil { return err }
id, err := strconv.ParseInt(args[1], 10, 64) if err != nil { return fmt.Errorf("invalid webhook ID: %w", err) }
wh, err := be.Webhook(ctx, repo, id) if err != nil { return err }
newURL := wh.URL if url != "" { newURL = url }
newSecret := wh.Secret if secret != "" { newSecret = secret }
newActive := wh.Active if active != "" { active, err := strconv.ParseBool(active) if err != nil { return fmt.Errorf("invalid active value: %w", err) }
newActive = active }
newContentType := wh.ContentType if contentType != "" { var ct webhook.ContentType switch strings.ToLower(strings.TrimSpace(contentType)) { case "json": ct = webhook.ContentTypeJSON case "form": ct = webhook.ContentTypeForm default: return webhook.ErrInvalidContentType } newContentType = ct }
newEvents := wh.Events if len(events) > 0 { var evs []webhook.Event for _, e := range events { ev, err := webhook.ParseEvent(e) if err != nil { return fmt.Errorf("invalid event: %w", err) }
evs = append(evs, ev) }
newEvents = evs }
return be.UpdateWebhook(ctx, repo, id, newURL, newContentType, newSecret, newEvents, newActive) }, }
cmd.Flags().StringSliceVarP(&events, "events", "e", nil, fmt.Sprintf("events to trigger the webhook, available events are (%s)", strings.Join(webhookEvents, ", "))) cmd.Flags().StringVarP(&secret, "secret", "s", "", "secret to sign the webhook payload") cmd.Flags().StringVarP(&active, "active", "a", "", "whether the webhook is active") cmd.Flags().StringVarP(&contentType, "content-type", "c", "", "content type of the webhook payload, can be either `json` or `form`") cmd.Flags().StringVarP(&url, "url", "u", "", "webhook URL")
return cmd}
func webhookDeliveriesCommand() *cobra.Command { cmd := &cobra.Command{ Use: "deliveries", Short: "Manage webhook deliveries", Aliases: []string{"delivery", "deliver"}, }
cmd.AddCommand( webhookDeliveriesListCommand(), webhookDeliveriesRedeliverCommand(), webhookDeliveriesGetCommand(), )
return cmd}
func webhookDeliveriesListCommand() *cobra.Command { cmd := &cobra.Command{ Use: "list REPOSITORY WEBHOOK_ID", Short: "List webhook deliveries", Args: cobra.ExactArgs(2), PersistentPreRunE: requireRepo(access.AdminAccess), RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() be := backend.FromContext(ctx) repo, _, err := repoFromContext(ctx) if err != nil { return err }
id, err := strconv.ParseInt(args[1], 10, 64) if err != nil { return fmt.Errorf("invalid webhook ID: %w", err) }
dels, err := be.ListWebhookDeliveries(ctx, repo, id) if err != nil { return err }
table := table.New().Headers("Status", "ID", "Event", "Created At") for _, d := range dels { status := "❌" if d.ResponseStatus >= 200 && d.ResponseStatus < 300 { status = "✅" } table = table.Row( status, d.ID.String(), d.Event.String(), humanize.Time(d.CreatedAt), ) } cmd.Println(table) return nil }, }
return cmd}
func webhookDeliveriesRedeliverCommand() *cobra.Command { cmd := &cobra.Command{ Use: "redeliver REPOSITORY WEBHOOK_ID DELIVERY_ID", Short: "Redeliver a webhook delivery", Args: cobra.ExactArgs(3), PersistentPreRunE: requireRepo(access.AdminAccess), RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() be := backend.FromContext(ctx) repo, _, err := repoFromContext(ctx) if err != nil { return err }
id, err := strconv.ParseInt(args[1], 10, 64) if err != nil { return fmt.Errorf("invalid webhook ID: %w", err) }
delID, err := uuid.Parse(args[2]) if err != nil { return fmt.Errorf("invalid delivery ID: %w", err) }
return be.RedeliverWebhookDelivery(ctx, repo, id, delID) }, }
return cmd}
func webhookDeliveriesGetCommand() *cobra.Command { cmd := &cobra.Command{ Use: "get REPOSITORY WEBHOOK_ID DELIVERY_ID", Short: "Get a webhook delivery", Args: cobra.ExactArgs(3), PersistentPreRunE: requireRepo(access.AdminAccess), RunE: func(cmd *cobra.Command, args []string) error { ctx := cmd.Context() be := backend.FromContext(ctx) repo, _, err := repoFromContext(ctx) if err != nil { return err }
id, err := strconv.ParseInt(args[1], 10, 64) if err != nil { return fmt.Errorf("invalid webhook ID: %w", err) }
delID, err := uuid.Parse(args[2]) if err != nil { return fmt.Errorf("invalid delivery ID: %w", err) }
del, err := be.WebhookDelivery(ctx, repo, id, delID) if err != nil { return err }
out := cmd.OutOrStdout() fmt.Fprintf(out, "ID: %s\n", del.ID) //nolint:errcheck fmt.Fprintf(out, "Event: %s\n", del.Event) //nolint:errcheck fmt.Fprintf(out, "Request URL: %s\n", del.RequestURL) //nolint:errcheck fmt.Fprintf(out, "Request Method: %s\n", del.RequestMethod) //nolint:errcheck fmt.Fprintf(out, "Request Error: %s\n", del.RequestError.String) //nolint:errcheck fmt.Fprintf(out, "Request Headers:\n") //nolint:errcheck reqHeaders := strings.Split(del.RequestHeaders, "\n") for _, h := range reqHeaders { fmt.Fprintf(out, " %s\n", h) //nolint:errcheck }
fmt.Fprintf(out, "Request Body:\n") //nolint:errcheck reqBody := strings.Split(del.RequestBody, "\n") for _, b := range reqBody { fmt.Fprintf(out, " %s\n", b) //nolint:errcheck }
fmt.Fprintf(out, "Response Status: %d\n", del.ResponseStatus) //nolint:errcheck fmt.Fprintf(out, "Response Headers:\n") //nolint:errcheck resHeaders := strings.Split(del.ResponseHeaders, "\n") for _, h := range resHeaders { fmt.Fprintf(out, " %s\n", h) //nolint:errcheck }
fmt.Fprintf(out, "Response Body:\n") //nolint:errcheck resBody := strings.Split(del.ResponseBody, "\n") for _, b := range resBody { fmt.Fprintf(out, " %s\n", b) //nolint:errcheck }
return nil }, }
return cmd}