Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[REFACTOR] store webhook event in database #29145

Merged
merged 10 commits into from
Mar 7, 2024
32 changes: 32 additions & 0 deletions models/fixtures/hook_task.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,35 @@
hook_id: 1
uuid: uuid1
is_delivered: true
is_succeed: false
request_content: >
{
"url": "/matrix-delivered",
"http_method":"PUT",
"headers": {
"X-Head": "42"
},
"body": "{}"
}

-
id: 2
hook_id: 1
uuid: uuid2
is_delivered: false

-
id: 3
hook_id: 1
uuid: uuid3
is_delivered: true
is_succeed: true
payload_content: '{"key":"value"}' # legacy task, payload saved in payload_content (and not in request_content)
request_content: >
{
"url": "/matrix-success",
"http_method":"PUT",
"headers": {
"X-Head": "42"
}
}
44 changes: 44 additions & 0 deletions models/migrations/v1_22/v287.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2024 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package v1_22 //nolint

import (
"code.gitea.io/gitea/modules/timeutil"
webhook_module "code.gitea.io/gitea/modules/webhook"

"xorm.io/xorm"
)

// HookTask represents a hook task.
// exact copy of models/webhook/hooktask.go when this migration was created
// - xorm:"-" fields deleted
type HookTask struct {
ID int64 `xorm:"pk autoincr"`
HookID int64 `xorm:"index"`
UUID string `xorm:"unique"`
PayloadContent string `xorm:"LONGTEXT"`
EventType webhook_module.HookEventType
IsDelivered bool
Delivered timeutil.TimeStampNano

// History info.
IsSucceed bool
RequestContent string `xorm:"LONGTEXT"`
ResponseContent string `xorm:"LONGTEXT"`

6543 marked this conversation as resolved.
Show resolved Hide resolved
// Version number to allow for smooth version upgrades:
// - Version 1: PayloadContent contains the JSON as send to the URL
// - Version 2: PayloadContent contains the original event
Version int
}

func AddVersionToHookTaskTable(x *xorm.Engine) error {
// create missing column
if err := x.Sync(new(HookTask)); err != nil {
return err
}
// set version to 1
_, err := x.Cols("version").Update(HookTask{Version: 1})
return err
}
20 changes: 11 additions & 9 deletions models/webhook/hooktask.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ package webhook

import (
"context"
"errors"
"time"

"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
webhook_module "code.gitea.io/gitea/modules/webhook"

Expand All @@ -31,6 +31,7 @@ type HookRequest struct {
URL string `json:"url"`
HTTPMethod string `json:"http_method"`
Headers map[string]string `json:"headers"`
Body string `json:"body"`
}

// HookResponse represents hook task response information.
Expand All @@ -45,7 +46,6 @@ type HookTask struct {
ID int64 `xorm:"pk autoincr"`
HookID int64 `xorm:"index"`
UUID string `xorm:"unique"`
api.Payloader `xorm:"-"`
PayloadContent string `xorm:"LONGTEXT"`
EventType webhook_module.HookEventType
IsDelivered bool
Expand All @@ -57,6 +57,11 @@ type HookTask struct {
RequestInfo *HookRequest `xorm:"-"`
ResponseContent string `xorm:"LONGTEXT"`
ResponseInfo *HookResponse `xorm:"-"`

// Version number to allow for smooth version upgrades:
// - Version 1: PayloadContent contains the JSON as send to the URL
// - Version 2: PayloadContent contains the original event
Version int
6543 marked this conversation as resolved.
Show resolved Hide resolved
}

func init() {
Expand Down Expand Up @@ -115,16 +120,12 @@ func HookTasks(ctx context.Context, hookID int64, page int) ([]*HookTask, error)
// it handles conversion from Payload to PayloadContent.
func CreateHookTask(ctx context.Context, t *HookTask) (*HookTask, error) {
t.UUID = gouuid.New().String()
if t.Payloader != nil {
data, err := t.Payloader.JSONPayload()
if err != nil {
return nil, err
}
t.PayloadContent = string(data)
}
if t.Delivered == 0 {
t.Delivered = timeutil.TimeStampNanoNow()
}
if t.Version == 0 {
return nil, errors.New("missing HookTask.Version")
}
return t, db.Insert(ctx, t)
}

Expand Down Expand Up @@ -165,6 +166,7 @@ func ReplayHookTask(ctx context.Context, hookID int64, uuid string) (*HookTask,
HookID: task.HookID,
PayloadContent: task.PayloadContent,
EventType: task.EventType,
Version: task.Version,
})
}

Expand Down
29 changes: 16 additions & 13 deletions models/webhook/webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
webhook_module "code.gitea.io/gitea/modules/webhook"
Expand All @@ -35,8 +34,10 @@ func TestWebhook_History(t *testing.T) {
webhook := unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 1})
tasks, err := webhook.History(db.DefaultContext, 0)
assert.NoError(t, err)
if assert.Len(t, tasks, 1) {
assert.Equal(t, int64(1), tasks[0].ID)
if assert.Len(t, tasks, 3) {
assert.Equal(t, int64(3), tasks[0].ID)
assert.Equal(t, int64(2), tasks[1].ID)
assert.Equal(t, int64(1), tasks[2].ID)
}

webhook = unittest.AssertExistsAndLoadBean(t, &Webhook{ID: 2})
Expand Down Expand Up @@ -197,8 +198,10 @@ func TestHookTasks(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hookTasks, err := HookTasks(db.DefaultContext, 1, 1)
assert.NoError(t, err)
if assert.Len(t, hookTasks, 1) {
assert.Equal(t, int64(1), hookTasks[0].ID)
if assert.Len(t, hookTasks, 3) {
assert.Equal(t, int64(3), hookTasks[0].ID)
assert.Equal(t, int64(2), hookTasks[1].ID)
assert.Equal(t, int64(1), hookTasks[2].ID)
}

hookTasks, err = HookTasks(db.DefaultContext, unittest.NonexistentID, 1)
Expand All @@ -209,8 +212,8 @@ func TestHookTasks(t *testing.T) {
func TestCreateHookTask(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{
HookID: 3,
Payloader: &api.PushPayload{},
HookID: 3,
Version: 2,
}
unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask)
Expand All @@ -233,9 +236,9 @@ func TestCleanupHookTaskTable_PerWebhook_DeletesDelivered(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{
HookID: 3,
Payloader: &api.PushPayload{},
IsDelivered: true,
Delivered: timeutil.TimeStampNanoNow(),
Version: 2,
}
unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask)
Expand All @@ -250,8 +253,8 @@ func TestCleanupHookTaskTable_PerWebhook_LeavesUndelivered(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{
HookID: 4,
Payloader: &api.PushPayload{},
IsDelivered: false,
Version: 2,
}
unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask)
Expand All @@ -266,9 +269,9 @@ func TestCleanupHookTaskTable_PerWebhook_LeavesMostRecentTask(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{
HookID: 4,
Payloader: &api.PushPayload{},
IsDelivered: true,
Delivered: timeutil.TimeStampNanoNow(),
Version: 2,
}
unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask)
Expand All @@ -283,9 +286,9 @@ func TestCleanupHookTaskTable_OlderThan_DeletesDelivered(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{
HookID: 3,
Payloader: &api.PushPayload{},
IsDelivered: true,
Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -8).UnixNano()),
Version: 2,
}
unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask)
Expand All @@ -300,8 +303,8 @@ func TestCleanupHookTaskTable_OlderThan_LeavesUndelivered(t *testing.T) {
assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{
HookID: 4,
Payloader: &api.PushPayload{},
IsDelivered: false,
Version: 2,
}
unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask)
Expand All @@ -316,9 +319,9 @@ func TestCleanupHookTaskTable_OlderThan_LeavesTaskEarlierThanAgeToDelete(t *test
assert.NoError(t, unittest.PrepareTestDatabase())
hookTask := &HookTask{
HookID: 4,
Payloader: &api.PushPayload{},
IsDelivered: true,
Delivered: timeutil.TimeStampNano(time.Now().AddDate(0, 0, -6).UnixNano()),
Version: 2,
}
unittest.AssertNotExistsBean(t, hookTask)
_, err := CreateHookTask(db.DefaultContext, hookTask)
Expand Down
Loading
Loading