294 lines
8.4 KiB
Go
294 lines
8.4 KiB
Go
|
|
package cmd
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"strconv"
|
||
|
|
"strings"
|
||
|
|
|
||
|
|
"code.gitea.io/sdk/gitea"
|
||
|
|
"forgejo.zerova.net/public/fgj-sid/internal/api"
|
||
|
|
"forgejo.zerova.net/public/fgj-sid/internal/config"
|
||
|
|
"github.com/spf13/cobra"
|
||
|
|
)
|
||
|
|
|
||
|
|
var webhookCmd = &cobra.Command{
|
||
|
|
Use: "webhook",
|
||
|
|
Aliases: []string{"webhooks", "hook"},
|
||
|
|
Short: "Manage repository webhooks",
|
||
|
|
Long: "List, create, update, and delete webhooks attached to a repository.",
|
||
|
|
}
|
||
|
|
|
||
|
|
var webhookListCmd = &cobra.Command{
|
||
|
|
Use: "list",
|
||
|
|
Aliases: []string{"ls"},
|
||
|
|
Short: "List webhooks for a repository",
|
||
|
|
Example: ` # List webhooks on the current repository
|
||
|
|
fgj webhook list
|
||
|
|
|
||
|
|
# List with JSON output
|
||
|
|
fgj webhook list --json`,
|
||
|
|
RunE: runWebhookList,
|
||
|
|
}
|
||
|
|
|
||
|
|
var webhookCreateCmd = &cobra.Command{
|
||
|
|
Use: "create <url>",
|
||
|
|
Short: "Create a repository webhook",
|
||
|
|
Long: `Create a webhook that delivers events to <url>.
|
||
|
|
|
||
|
|
Event names follow the Gitea/Forgejo webhook event model: push, pull_request,
|
||
|
|
issues, issue_comment, release, create, delete, fork, wiki, repository, and others.
|
||
|
|
Omit --events to deliver only the default (push).`,
|
||
|
|
Example: ` # Create a Gitea-format push webhook
|
||
|
|
fgj webhook create https://example.com/hook
|
||
|
|
|
||
|
|
# Multiple events and a content type
|
||
|
|
fgj webhook create https://ci.example.com/hook \
|
||
|
|
--events push,pull_request,release \
|
||
|
|
--content-type application/json \
|
||
|
|
--secret "$HOOK_SECRET"
|
||
|
|
|
||
|
|
# Slack-style webhook
|
||
|
|
fgj webhook create https://hooks.slack.com/services/XXX --type slack`,
|
||
|
|
Args: cobra.ExactArgs(1),
|
||
|
|
RunE: runWebhookCreate,
|
||
|
|
}
|
||
|
|
|
||
|
|
var webhookUpdateCmd = &cobra.Command{
|
||
|
|
Use: "update <id>",
|
||
|
|
Aliases: []string{"edit"},
|
||
|
|
Short: "Update a repository webhook",
|
||
|
|
Long: "Update an existing webhook. Flags you omit are left unchanged.",
|
||
|
|
Args: cobra.ExactArgs(1),
|
||
|
|
Example: ` # Disable a webhook
|
||
|
|
fgj webhook update 12 --active=false
|
||
|
|
|
||
|
|
# Change events and URL
|
||
|
|
fgj webhook update 12 --url https://new.example.com/hook --events push,release`,
|
||
|
|
RunE: runWebhookUpdate,
|
||
|
|
}
|
||
|
|
|
||
|
|
var webhookDeleteCmd = &cobra.Command{
|
||
|
|
Use: "delete <id>",
|
||
|
|
Aliases: []string{"rm"},
|
||
|
|
Short: "Delete a repository webhook",
|
||
|
|
Args: cobra.ExactArgs(1),
|
||
|
|
RunE: runWebhookDelete,
|
||
|
|
}
|
||
|
|
|
||
|
|
func init() {
|
||
|
|
rootCmd.AddCommand(webhookCmd)
|
||
|
|
webhookCmd.AddCommand(webhookListCmd)
|
||
|
|
webhookCmd.AddCommand(webhookCreateCmd)
|
||
|
|
webhookCmd.AddCommand(webhookUpdateCmd)
|
||
|
|
webhookCmd.AddCommand(webhookDeleteCmd)
|
||
|
|
|
||
|
|
addRepoFlags(webhookListCmd)
|
||
|
|
addJSONFlags(webhookListCmd, "Output as JSON")
|
||
|
|
|
||
|
|
addRepoFlags(webhookCreateCmd)
|
||
|
|
webhookCreateCmd.Flags().String("type", "gitea", "Hook type (gitea, slack, discord, msteams, telegram, feishu, gogs)")
|
||
|
|
webhookCreateCmd.Flags().StringSlice("events", []string{"push"}, "Events to deliver (comma-separated)")
|
||
|
|
webhookCreateCmd.Flags().String("content-type", "application/json", "Content type (application/json or application/x-www-form-urlencoded)")
|
||
|
|
webhookCreateCmd.Flags().String("secret", "", "HMAC secret used to sign payloads")
|
||
|
|
webhookCreateCmd.Flags().String("branch-filter", "", "Glob filter for branches that trigger the hook")
|
||
|
|
webhookCreateCmd.Flags().String("authorization-header", "", "Authorization header value sent with each delivery")
|
||
|
|
webhookCreateCmd.Flags().Bool("active", true, "Whether the hook is active on creation")
|
||
|
|
|
||
|
|
addRepoFlags(webhookUpdateCmd)
|
||
|
|
webhookUpdateCmd.Flags().String("url", "", "New target URL")
|
||
|
|
webhookUpdateCmd.Flags().StringSlice("events", nil, "New event list (replaces existing)")
|
||
|
|
webhookUpdateCmd.Flags().String("content-type", "", "New content type")
|
||
|
|
webhookUpdateCmd.Flags().String("secret", "", "New HMAC secret")
|
||
|
|
webhookUpdateCmd.Flags().String("branch-filter", "", "New branch filter")
|
||
|
|
webhookUpdateCmd.Flags().String("authorization-header", "", "New authorization header")
|
||
|
|
webhookUpdateCmd.Flags().Bool("active", true, "Enable or disable the hook")
|
||
|
|
|
||
|
|
addRepoFlags(webhookDeleteCmd)
|
||
|
|
webhookDeleteCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt")
|
||
|
|
}
|
||
|
|
|
||
|
|
func runWebhookList(cmd *cobra.Command, args []string) error {
|
||
|
|
client, owner, name, err := newWebhookClient(cmd)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
hooks, _, err := client.ListRepoHooks(owner, name, gitea.ListHooksOptions{})
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to list webhooks: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if wantJSON(cmd) {
|
||
|
|
return outputJSON(cmd, hooks)
|
||
|
|
}
|
||
|
|
|
||
|
|
if len(hooks) == 0 {
|
||
|
|
fmt.Fprintln(ios.Out, "No webhooks.")
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
tp := ios.NewTablePrinter()
|
||
|
|
tp.AddHeader("ID", "TYPE", "URL", "EVENTS", "ACTIVE")
|
||
|
|
for _, h := range hooks {
|
||
|
|
url := h.Config["url"]
|
||
|
|
active := "no"
|
||
|
|
if h.Active {
|
||
|
|
active = "yes"
|
||
|
|
}
|
||
|
|
tp.AddRow(
|
||
|
|
strconv.FormatInt(h.ID, 10),
|
||
|
|
h.Type,
|
||
|
|
url,
|
||
|
|
strings.Join(h.Events, ","),
|
||
|
|
active,
|
||
|
|
)
|
||
|
|
}
|
||
|
|
return tp.Render()
|
||
|
|
}
|
||
|
|
|
||
|
|
func runWebhookCreate(cmd *cobra.Command, args []string) error {
|
||
|
|
client, owner, name, err := newWebhookClient(cmd)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
url := args[0]
|
||
|
|
hookType, _ := cmd.Flags().GetString("type")
|
||
|
|
events, _ := cmd.Flags().GetStringSlice("events")
|
||
|
|
contentType, _ := cmd.Flags().GetString("content-type")
|
||
|
|
secret, _ := cmd.Flags().GetString("secret")
|
||
|
|
branchFilter, _ := cmd.Flags().GetString("branch-filter")
|
||
|
|
authHeader, _ := cmd.Flags().GetString("authorization-header")
|
||
|
|
active, _ := cmd.Flags().GetBool("active")
|
||
|
|
|
||
|
|
opt := gitea.CreateHookOption{
|
||
|
|
Type: gitea.HookType(hookType),
|
||
|
|
Config: map[string]string{
|
||
|
|
"url": url,
|
||
|
|
"content_type": contentType,
|
||
|
|
},
|
||
|
|
Events: events,
|
||
|
|
BranchFilter: branchFilter,
|
||
|
|
Active: active,
|
||
|
|
AuthorizationHeader: authHeader,
|
||
|
|
}
|
||
|
|
if secret != "" {
|
||
|
|
opt.Config["secret"] = secret
|
||
|
|
}
|
||
|
|
|
||
|
|
hook, _, err := client.CreateRepoHook(owner, name, opt)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("failed to create webhook: %w", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
cs := ios.ColorScheme()
|
||
|
|
fmt.Fprintf(ios.Out, "%s Created webhook %d (%s → %s)\n", cs.SuccessIcon(), hook.ID, hook.Type, url)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func runWebhookUpdate(cmd *cobra.Command, args []string) error {
|
||
|
|
client, owner, name, err := newWebhookClient(cmd)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
id, err := strconv.ParseInt(args[0], 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("invalid webhook id %q: %w", args[0], err)
|
||
|
|
}
|
||
|
|
|
||
|
|
opt := gitea.EditHookOption{}
|
||
|
|
|
||
|
|
// Only set fields the user explicitly provided.
|
||
|
|
cfg := map[string]string{}
|
||
|
|
if url, _ := cmd.Flags().GetString("url"); url != "" {
|
||
|
|
cfg["url"] = url
|
||
|
|
}
|
||
|
|
if ct, _ := cmd.Flags().GetString("content-type"); ct != "" {
|
||
|
|
cfg["content_type"] = ct
|
||
|
|
}
|
||
|
|
if secret, _ := cmd.Flags().GetString("secret"); secret != "" {
|
||
|
|
cfg["secret"] = secret
|
||
|
|
}
|
||
|
|
if len(cfg) > 0 {
|
||
|
|
opt.Config = cfg
|
||
|
|
}
|
||
|
|
|
||
|
|
if cmd.Flags().Changed("events") {
|
||
|
|
events, _ := cmd.Flags().GetStringSlice("events")
|
||
|
|
opt.Events = events
|
||
|
|
}
|
||
|
|
if cmd.Flags().Changed("branch-filter") {
|
||
|
|
bf, _ := cmd.Flags().GetString("branch-filter")
|
||
|
|
opt.BranchFilter = bf
|
||
|
|
}
|
||
|
|
if cmd.Flags().Changed("authorization-header") {
|
||
|
|
auth, _ := cmd.Flags().GetString("authorization-header")
|
||
|
|
opt.AuthorizationHeader = auth
|
||
|
|
}
|
||
|
|
if cmd.Flags().Changed("active") {
|
||
|
|
active, _ := cmd.Flags().GetBool("active")
|
||
|
|
opt.Active = &active
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, err := client.EditRepoHook(owner, name, id, opt); err != nil {
|
||
|
|
return fmt.Errorf("failed to update webhook %d: %w", id, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
cs := ios.ColorScheme()
|
||
|
|
fmt.Fprintf(ios.Out, "%s Updated webhook %d\n", cs.SuccessIcon(), id)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func runWebhookDelete(cmd *cobra.Command, args []string) error {
|
||
|
|
client, owner, name, err := newWebhookClient(cmd)
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
|
||
|
|
id, err := strconv.ParseInt(args[0], 10, 64)
|
||
|
|
if err != nil {
|
||
|
|
return fmt.Errorf("invalid webhook id %q: %w", args[0], err)
|
||
|
|
}
|
||
|
|
|
||
|
|
skipConfirm, _ := cmd.Flags().GetBool("yes")
|
||
|
|
if !skipConfirm && ios.IsStdinTTY() {
|
||
|
|
answer, err := promptLine(fmt.Sprintf("Delete webhook %d in %s/%s? [y/N]: ", id, owner, name))
|
||
|
|
if err != nil {
|
||
|
|
return err
|
||
|
|
}
|
||
|
|
if answer != "y" && answer != "Y" && answer != "yes" {
|
||
|
|
fmt.Fprintln(ios.ErrOut, "Cancelled.")
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if _, err := client.DeleteRepoHook(owner, name, id); err != nil {
|
||
|
|
return fmt.Errorf("failed to delete webhook %d: %w", id, err)
|
||
|
|
}
|
||
|
|
|
||
|
|
cs := ios.ColorScheme()
|
||
|
|
fmt.Fprintf(ios.Out, "%s Deleted webhook %d\n", cs.SuccessIcon(), id)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
func newWebhookClient(cmd *cobra.Command) (*api.Client, string, string, error) {
|
||
|
|
repo, _ := cmd.Flags().GetString("repo")
|
||
|
|
owner, name, err := parseRepo(repo)
|
||
|
|
if err != nil {
|
||
|
|
return nil, "", "", err
|
||
|
|
}
|
||
|
|
|
||
|
|
cfg, err := config.Load()
|
||
|
|
if err != nil {
|
||
|
|
return nil, "", "", err
|
||
|
|
}
|
||
|
|
|
||
|
|
client, err := api.NewClientFromConfig(cfg, "", getDetectedHost(), getCwd())
|
||
|
|
if err != nil {
|
||
|
|
return nil, "", "", err
|
||
|
|
}
|
||
|
|
|
||
|
|
return client, owner, name, nil
|
||
|
|
}
|