Add replay of webhooks. (#18191)
This commit is contained in:
		
							parent
							
								
									a38ba634a4
								
							
						
					
					
						commit
						bf7b083cfe
					
				
					 8 changed files with 108 additions and 13 deletions
				
			
		| 
						 | 
				
			
			@ -175,18 +175,13 @@ func HookTasks(hookID int64, page int) ([]*HookTask, error) {
 | 
			
		|||
// CreateHookTask creates a new hook task,
 | 
			
		||||
// it handles conversion from Payload to PayloadContent.
 | 
			
		||||
func CreateHookTask(t *HookTask) error {
 | 
			
		||||
	return createHookTask(db.GetEngine(db.DefaultContext), t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createHookTask(e db.Engine, t *HookTask) error {
 | 
			
		||||
	data, err := t.Payloader.JSONPayload()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
	t.UUID = gouuid.New().String()
 | 
			
		||||
	t.PayloadContent = string(data)
 | 
			
		||||
	_, err = e.Insert(t)
 | 
			
		||||
	return err
 | 
			
		||||
	return db.Insert(db.DefaultContext, t)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UpdateHookTask updates information of hook task.
 | 
			
		||||
| 
						 | 
				
			
			@ -195,6 +190,38 @@ func UpdateHookTask(t *HookTask) error {
 | 
			
		|||
	return err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReplayHookTask copies a hook task to get re-delivered
 | 
			
		||||
func ReplayHookTask(hookID int64, uuid string) (*HookTask, error) {
 | 
			
		||||
	var newTask *HookTask
 | 
			
		||||
 | 
			
		||||
	err := db.WithTx(func(ctx context.Context) error {
 | 
			
		||||
		task := &HookTask{
 | 
			
		||||
			HookID: hookID,
 | 
			
		||||
			UUID:   uuid,
 | 
			
		||||
		}
 | 
			
		||||
		has, err := db.GetByBean(ctx, task)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		} else if !has {
 | 
			
		||||
			return ErrHookTaskNotExist{
 | 
			
		||||
				HookID: hookID,
 | 
			
		||||
				UUID:   uuid,
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		newTask = &HookTask{
 | 
			
		||||
			UUID:           gouuid.New().String(),
 | 
			
		||||
			RepoID:         task.RepoID,
 | 
			
		||||
			HookID:         task.HookID,
 | 
			
		||||
			PayloadContent: task.PayloadContent,
 | 
			
		||||
			EventType:      task.EventType,
 | 
			
		||||
		}
 | 
			
		||||
		return db.Insert(ctx, newTask)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return newTask, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// FindUndeliveredHookTasks represents find the undelivered hook tasks
 | 
			
		||||
func FindUndeliveredHookTasks() ([]*HookTask, error) {
 | 
			
		||||
	tasks := make([]*HookTask, 0, 10)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -41,6 +41,22 @@ func (err ErrWebhookNotExist) Error() string {
 | 
			
		|||
	return fmt.Sprintf("webhook does not exist [id: %d]", err.ID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ErrHookTaskNotExist represents a "HookTaskNotExist" kind of error.
 | 
			
		||||
type ErrHookTaskNotExist struct {
 | 
			
		||||
	HookID int64
 | 
			
		||||
	UUID   string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// IsErrWebhookNotExist checks if an error is a ErrWebhookNotExist.
 | 
			
		||||
func IsErrHookTaskNotExist(err error) bool {
 | 
			
		||||
	_, ok := err.(ErrHookTaskNotExist)
 | 
			
		||||
	return ok
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (err ErrHookTaskNotExist) Error() string {
 | 
			
		||||
	return fmt.Sprintf("hook task does not exist [hook: %d, uuid: %s]", err.HookID, err.UUID)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// HookContentType is the content type of a web hook
 | 
			
		||||
type HookContentType int
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1831,12 +1831,13 @@ settings.webhook_deletion_desc = Removing a webhook deletes its settings and del
 | 
			
		|||
settings.webhook_deletion_success = The webhook has been removed.
 | 
			
		||||
settings.webhook.test_delivery = Test Delivery
 | 
			
		||||
settings.webhook.test_delivery_desc = Test this webhook with a fake event.
 | 
			
		||||
settings.webhook.test_delivery_success = A fake event has been added to the delivery queue. It may take few seconds before it shows up in the delivery history.
 | 
			
		||||
settings.webhook.request = Request
 | 
			
		||||
settings.webhook.response = Response
 | 
			
		||||
settings.webhook.headers = Headers
 | 
			
		||||
settings.webhook.payload = Content
 | 
			
		||||
settings.webhook.body = Body
 | 
			
		||||
settings.webhook.replay.description = Replay this webhook.
 | 
			
		||||
settings.webhook.delivery.success = An event has been added to the delivery queue. It may take few seconds before it shows up in the delivery history.
 | 
			
		||||
settings.githooks_desc = "Git Hooks are powered by Git itself. You can edit hook files below to set up custom operations."
 | 
			
		||||
settings.githook_edit_desc = If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook.
 | 
			
		||||
settings.githook_name = Hook Name
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1189,11 +1189,33 @@ func TestWebhook(ctx *context.Context) {
 | 
			
		|||
		ctx.Flash.Error("PrepareWebhook: " + err.Error())
 | 
			
		||||
		ctx.Status(500)
 | 
			
		||||
	} else {
 | 
			
		||||
		ctx.Flash.Info(ctx.Tr("repo.settings.webhook.test_delivery_success"))
 | 
			
		||||
		ctx.Flash.Info(ctx.Tr("repo.settings.webhook.delivery.success"))
 | 
			
		||||
		ctx.Status(200)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReplayWebhook replays a webhook
 | 
			
		||||
func ReplayWebhook(ctx *context.Context) {
 | 
			
		||||
	hookTaskUUID := ctx.Params(":uuid")
 | 
			
		||||
 | 
			
		||||
	orCtx, w := checkWebhook(ctx)
 | 
			
		||||
	if ctx.Written() {
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := webhook_service.ReplayHookTask(w, hookTaskUUID); err != nil {
 | 
			
		||||
		if webhook.IsErrHookTaskNotExist(err) {
 | 
			
		||||
			ctx.NotFound("ReplayHookTask", nil)
 | 
			
		||||
		} else {
 | 
			
		||||
			ctx.ServerError("ReplayHookTask", err)
 | 
			
		||||
		}
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ctx.Flash.Success(ctx.Tr("repo.settings.webhook.delivery.success"))
 | 
			
		||||
	ctx.Redirect(fmt.Sprintf("%s/%d", orCtx.Link, w.ID))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DeleteWebhook delete a webhook
 | 
			
		||||
func DeleteWebhook(ctx *context.Context) {
 | 
			
		||||
	if err := webhook.DeleteWebhookByRepoID(ctx.Repo.Repository.ID, ctx.FormInt64("id")); err != nil {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -435,7 +435,10 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		|||
		m.Group("/hooks", func() {
 | 
			
		||||
			m.Get("", admin.DefaultOrSystemWebhooks)
 | 
			
		||||
			m.Post("/delete", admin.DeleteDefaultOrSystemWebhook)
 | 
			
		||||
			m.Get("/{id}", repo.WebHooksEdit)
 | 
			
		||||
			m.Group("/{id}", func() {
 | 
			
		||||
				m.Get("", repo.WebHooksEdit)
 | 
			
		||||
				m.Post("/replay/{uuid}", repo.ReplayWebhook)
 | 
			
		||||
			})
 | 
			
		||||
			m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost)
 | 
			
		||||
			m.Post("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost)
 | 
			
		||||
			m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost)
 | 
			
		||||
| 
						 | 
				
			
			@ -559,7 +562,10 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		|||
					m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
 | 
			
		||||
					m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
 | 
			
		||||
					m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
 | 
			
		||||
					m.Get("/{id}", repo.WebHooksEdit)
 | 
			
		||||
					m.Group("/{id}", func() {
 | 
			
		||||
						m.Get("", repo.WebHooksEdit)
 | 
			
		||||
						m.Post("/replay/{uuid}", repo.ReplayWebhook)
 | 
			
		||||
					})
 | 
			
		||||
					m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost)
 | 
			
		||||
					m.Post("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost)
 | 
			
		||||
					m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost)
 | 
			
		||||
| 
						 | 
				
			
			@ -653,8 +659,11 @@ func RegisterRoutes(m *web.Route) {
 | 
			
		|||
				m.Post("/msteams/new", bindIgnErr(forms.NewMSTeamsHookForm{}), repo.MSTeamsHooksNewPost)
 | 
			
		||||
				m.Post("/feishu/new", bindIgnErr(forms.NewFeishuHookForm{}), repo.FeishuHooksNewPost)
 | 
			
		||||
				m.Post("/wechatwork/new", bindIgnErr(forms.NewWechatWorkHookForm{}), repo.WechatworkHooksNewPost)
 | 
			
		||||
				m.Get("/{id}", repo.WebHooksEdit)
 | 
			
		||||
				m.Post("/{id}/test", repo.TestWebhook)
 | 
			
		||||
				m.Group("/{id}", func() {
 | 
			
		||||
					m.Get("", repo.WebHooksEdit)
 | 
			
		||||
					m.Post("/test", repo.TestWebhook)
 | 
			
		||||
					m.Post("/replay/{uuid}", repo.ReplayWebhook)
 | 
			
		||||
				})
 | 
			
		||||
				m.Post("/gitea/{id}", bindIgnErr(forms.NewWebhookForm{}), repo.WebHooksEditPost)
 | 
			
		||||
				m.Post("/gogs/{id}", bindIgnErr(forms.NewGogshookForm{}), repo.GogsHooksEditPost)
 | 
			
		||||
				m.Post("/slack/{id}", bindIgnErr(forms.NewSlackHookForm{}), repo.SlackHooksEditPost)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -229,3 +229,15 @@ func prepareWebhooks(repo *repo_model.Repository, event webhook_model.HookEventT
 | 
			
		|||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ReplayHookTask replays a webhook task
 | 
			
		||||
func ReplayHookTask(w *webhook_model.Webhook, uuid string) error {
 | 
			
		||||
	t, err := webhook_model.ReplayHookTask(w.ID, uuid)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	go hookQueue.Add(t.RepoID)
 | 
			
		||||
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,5 +1,5 @@
 | 
			
		|||
{{template "base/head" .}}
 | 
			
		||||
<div class="page-content admin new webhook">
 | 
			
		||||
<div class="page-content admin settings new webhook">
 | 
			
		||||
	{{template "admin/navbar" .}}
 | 
			
		||||
	<div class="ui container">
 | 
			
		||||
		{{template "base/alert" .}}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -40,6 +40,14 @@
 | 
			
		|||
									<span class="ui label">N/A</span>
 | 
			
		||||
								{{end}}
 | 
			
		||||
							</a>
 | 
			
		||||
							{{if or $.Permission.IsAdmin $.IsOrganizationOwner $.PageIsAdmin}}
 | 
			
		||||
							<div class="right menu">
 | 
			
		||||
								<form class="item" action="{{$.Link}}/replay/{{.UUID}}" method="post">
 | 
			
		||||
									{{$.CsrfTokenHtml}}
 | 
			
		||||
									<button class="ui tiny button tooltip" data-content="{{$.i18n.Tr "repo.settings.webhook.replay.description"}}" data-variation="inverted tiny">{{svg "octicon-sync"}}</button>
 | 
			
		||||
								</form>
 | 
			
		||||
							</div>
 | 
			
		||||
							{{end}}
 | 
			
		||||
						</div>
 | 
			
		||||
						<div class="ui bottom attached tab segment active" data-tab="request-{{.ID}}">
 | 
			
		||||
							{{if .RequestInfo}}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue