forked from gitea/gitea
		
	Add replay of webhooks. (#18191)
This commit is contained in:
		
							parent
							
								
									a38ba634a4
								
							
						
					
					
						commit
						bf7b083cfe
					
				| @ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 KN4CK3R
						KN4CK3R