diff options
author | Lunny Xiao <xiaolunwen@gmail.com> | 2021-11-10 13:13:16 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-10 13:13:16 +0800 |
commit | 33fca2b537d36cf998dd27425b2bb8ed5b0965f3 (patch) | |
tree | 817f392502e1c176a5cd7e80290520cb940a8416 /models | |
parent | edbaa5d3f05b5ca397524587ba9db15edd61bc29 (diff) | |
download | gitea-33fca2b537d36cf998dd27425b2bb8ed5b0965f3.tar.gz gitea-33fca2b537d36cf998dd27425b2bb8ed5b0965f3.zip |
Move webhook into models/webhook/ (#17579)
Diffstat (limited to 'models')
-rw-r--r-- | models/db/error.go | 29 | ||||
-rw-r--r-- | models/error.go | 44 | ||||
-rw-r--r-- | models/repo.go | 15 | ||||
-rw-r--r-- | models/repo_avatar.go | 2 | ||||
-rw-r--r-- | models/repo_generate.go | 7 | ||||
-rw-r--r-- | models/statistic.go | 5 | ||||
-rw-r--r-- | models/user.go | 2 | ||||
-rw-r--r-- | models/webhook/hooktask.go | 280 | ||||
-rw-r--r-- | models/webhook/main_test.go | 16 | ||||
-rw-r--r-- | models/webhook/webhook.go (renamed from models/webhook.go) | 310 | ||||
-rw-r--r-- | models/webhook/webhook_test.go (renamed from models/webhook_test.go) | 4 |
11 files changed, 376 insertions, 338 deletions
diff --git a/models/db/error.go b/models/db/error.go new file mode 100644 index 0000000000..675247ed87 --- /dev/null +++ b/models/db/error.go @@ -0,0 +1,29 @@ +// Copyright 2021 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package db + +import "fmt" + +// ErrCancelled represents an error due to context cancellation +type ErrCancelled struct { + Message string +} + +// IsErrCancelled checks if an error is a ErrCancelled. +func IsErrCancelled(err error) bool { + _, ok := err.(ErrCancelled) + return ok +} + +func (err ErrCancelled) Error() string { + return "Cancelled: " + err.Message +} + +// ErrCancelledf returns an ErrCancelled for the provided format and args +func ErrCancelledf(format string, args ...interface{}) error { + return ErrCancelled{ + fmt.Sprintf(format, args...), + } +} diff --git a/models/error.go b/models/error.go index 1179fa6eb7..b365a67b73 100644 --- a/models/error.go +++ b/models/error.go @@ -84,28 +84,6 @@ func (err ErrSSHDisabled) Error() string { return "SSH is disabled" } -// ErrCancelled represents an error due to context cancellation -type ErrCancelled struct { - Message string -} - -// IsErrCancelled checks if an error is a ErrCancelled. -func IsErrCancelled(err error) bool { - _, ok := err.(ErrCancelled) - return ok -} - -func (err ErrCancelled) Error() string { - return "Cancelled: " + err.Message -} - -// ErrCancelledf returns an ErrCancelled for the provided format and args -func ErrCancelledf(format string, args ...interface{}) error { - return ErrCancelled{ - fmt.Sprintf(format, args...), - } -} - // ____ ___ // | | \______ ___________ // | | / ___// __ \_ __ \ @@ -1309,28 +1287,6 @@ func (err ErrSHAOrCommitIDNotProvided) Error() string { return "a SHA or commit ID must be proved when updating a file" } -// __ __ ___. .__ __ -// / \ / \ ____\_ |__ | |__ ____ ____ | | __ -// \ \/\/ // __ \| __ \| | \ / _ \ / _ \| |/ / -// \ /\ ___/| \_\ \ Y ( <_> | <_> ) < -// \__/\ / \___ >___ /___| /\____/ \____/|__|_ \ -// \/ \/ \/ \/ \/ - -// ErrWebhookNotExist represents a "WebhookNotExist" kind of error. -type ErrWebhookNotExist struct { - ID int64 -} - -// IsErrWebhookNotExist checks if an error is a ErrWebhookNotExist. -func IsErrWebhookNotExist(err error) bool { - _, ok := err.(ErrWebhookNotExist) - return ok -} - -func (err ErrWebhookNotExist) Error() string { - return fmt.Sprintf("webhook does not exist [id: %d]", err.ID) -} - // .___ // | | ______ ________ __ ____ // | |/ ___// ___/ | \_/ __ \ diff --git a/models/repo.go b/models/repo.go index 10dc8d9b09..f44fc763a5 100644 --- a/models/repo.go +++ b/models/repo.go @@ -24,6 +24,7 @@ import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/unit" + "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/lfs" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/markup" @@ -1153,7 +1154,7 @@ func CreateRepository(ctx context.Context, doer, u *User, repo *Repository, over } } - if err = copyDefaultWebhooksToRepo(db.GetEngine(ctx), repo.ID); err != nil { + if err = webhook.CopyDefaultWebhooksToRepo(ctx, repo.ID); err != nil { return fmt.Errorf("copyDefaultWebhooksToRepo: %v", err) } @@ -1509,7 +1510,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error { &Comment{RefRepoID: repoID}, &CommitStatus{RepoID: repoID}, &DeletedBranch{RepoID: repoID}, - &HookTask{RepoID: repoID}, + &webhook.HookTask{RepoID: repoID}, &LFSLock{RepoID: repoID}, &LanguageStat{RepoID: repoID}, &Milestone{RepoID: repoID}, @@ -1526,7 +1527,7 @@ func DeleteRepository(doer *User, uid, repoID int64) error { &Star{RepoID: repoID}, &Task{RepoID: repoID}, &Watch{RepoID: repoID}, - &Webhook{RepoID: repoID}, + &webhook.Webhook{RepoID: repoID}, ); err != nil { return fmt.Errorf("deleteBeans: %v", err) } @@ -1932,7 +1933,7 @@ func CheckRepoStats(ctx context.Context) error { select { case <-ctx.Done(): log.Warn("CheckRepoStats: Cancelled before %s", checker.desc) - return ErrCancelledf("before checking %s", checker.desc) + return db.ErrCancelledf("before checking %s", checker.desc) default: repoStatsCheck(ctx, checker) } @@ -1949,7 +1950,7 @@ func CheckRepoStats(ctx context.Context) error { select { case <-ctx.Done(): log.Warn("CheckRepoStats: Cancelled during %s for repo ID %d", desc, id) - return ErrCancelledf("during %s for repo ID %d", desc, id) + return db.ErrCancelledf("during %s for repo ID %d", desc, id) default: } log.Trace("Updating %s: %d", desc, id) @@ -1972,7 +1973,7 @@ func CheckRepoStats(ctx context.Context) error { select { case <-ctx.Done(): log.Warn("CheckRepoStats: Cancelled") - return ErrCancelledf("during %s for repo ID %d", desc, id) + return db.ErrCancelledf("during %s for repo ID %d", desc, id) default: } log.Trace("Updating %s: %d", desc, id) @@ -1995,7 +1996,7 @@ func CheckRepoStats(ctx context.Context) error { select { case <-ctx.Done(): log.Warn("CheckRepoStats: Cancelled") - return ErrCancelledf("during %s for repo ID %d", desc, id) + return db.ErrCancelledf("during %s for repo ID %d", desc, id) default: } log.Trace("Updating repository count 'num_forks': %d", id) diff --git a/models/repo_avatar.go b/models/repo_avatar.go index bb5f083dd5..6c5e03c0d0 100644 --- a/models/repo_avatar.go +++ b/models/repo_avatar.go @@ -64,7 +64,7 @@ func RemoveRandomAvatars(ctx context.Context) error { repository := bean.(*Repository) select { case <-ctx.Done(): - return ErrCancelledf("before random avatars removed for %s", repository.FullName()) + return db.ErrCancelledf("before random avatars removed for %s", repository.FullName()) default: } stringifiedID := strconv.FormatInt(repository.ID, 10) diff --git a/models/repo_generate.go b/models/repo_generate.go index 650da711a3..cef5fa7928 100644 --- a/models/repo_generate.go +++ b/models/repo_generate.go @@ -12,6 +12,7 @@ import ( "strings" "code.gitea.io/gitea/models/db" + "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/storage" @@ -113,13 +114,13 @@ func GenerateGitHooks(ctx context.Context, templateRepo, generateRepo *Repositor // GenerateWebhooks generates webhooks from a template repository func GenerateWebhooks(ctx context.Context, templateRepo, generateRepo *Repository) error { - templateWebhooks, err := ListWebhooksByOpts(&ListWebhookOptions{RepoID: templateRepo.ID}) + templateWebhooks, err := webhook.ListWebhooksByOpts(&webhook.ListWebhookOptions{RepoID: templateRepo.ID}) if err != nil { return err } for _, templateWebhook := range templateWebhooks { - generateWebhook := &Webhook{ + generateWebhook := &webhook.Webhook{ RepoID: generateRepo.ID, URL: templateWebhook.URL, HTTPMethod: templateWebhook.HTTPMethod, @@ -132,7 +133,7 @@ func GenerateWebhooks(ctx context.Context, templateRepo, generateRepo *Repositor Events: templateWebhook.Events, Meta: templateWebhook.Meta, } - if err := createWebhook(db.GetEngine(ctx), generateWebhook); err != nil { + if err := webhook.CreateWebhook(ctx, generateWebhook); err != nil { return err } } diff --git a/models/statistic.go b/models/statistic.go index 5e72dc713d..fab35e62dc 100644 --- a/models/statistic.go +++ b/models/statistic.go @@ -7,6 +7,7 @@ package models import ( "code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/login" + "code.gitea.io/gitea/models/webhook" "code.gitea.io/gitea/modules/setting" ) @@ -95,10 +96,10 @@ func GetStatistic() (stats Statistic) { stats.Counter.Mirror, _ = e.Count(new(Mirror)) stats.Counter.Release, _ = e.Count(new(Release)) stats.Counter.LoginSource = login.CountSources() - stats.Counter.Webhook, _ = e.Count(new(Webhook)) + stats.Counter.Webhook, _ = e.Count(new(webhook.Webhook)) stats.Counter.Milestone, _ = e.Count(new(Milestone)) stats.Counter.Label, _ = e.Count(new(Label)) - stats.Counter.HookTask, _ = e.Count(new(HookTask)) + stats.Counter.HookTask, _ = e.Count(new(webhook.HookTask)) stats.Counter.Team, _ = e.Count(new(Team)) stats.Counter.Attachment, _ = e.Count(new(Attachment)) stats.Counter.Project, _ = e.Count(new(Project)) diff --git a/models/user.go b/models/user.go index d27c581bab..13347a46b7 100644 --- a/models/user.go +++ b/models/user.go @@ -1356,7 +1356,7 @@ func DeleteInactiveUsers(ctx context.Context, olderThan time.Duration) (err erro for _, u := range users { select { case <-ctx.Done(): - return ErrCancelledf("Before delete inactive user %s", u.Name) + return db.ErrCancelledf("Before delete inactive user %s", u.Name) default: } if err = DeleteUser(u); err != nil { diff --git a/models/webhook/hooktask.go b/models/webhook/hooktask.go new file mode 100644 index 0000000000..1967ded298 --- /dev/null +++ b/models/webhook/hooktask.go @@ -0,0 +1,280 @@ +// Copyright 2017 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package webhook + +import ( + "context" + "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" + + gouuid "github.com/google/uuid" +) + +// ___ ___ __ ___________ __ +// / | \ ____ ____ | | _\__ ___/____ _____| | __ +// / ~ \/ _ \ / _ \| |/ / | | \__ \ / ___/ |/ / +// \ Y ( <_> | <_> ) < | | / __ \_\___ \| < +// \___|_ / \____/ \____/|__|_ \ |____| (____ /____ >__|_ \ +// \/ \/ \/ \/ \/ + +// HookEventType is the type of an hook event +type HookEventType string + +// Types of hook events +const ( + HookEventCreate HookEventType = "create" + HookEventDelete HookEventType = "delete" + HookEventFork HookEventType = "fork" + HookEventPush HookEventType = "push" + HookEventIssues HookEventType = "issues" + HookEventIssueAssign HookEventType = "issue_assign" + HookEventIssueLabel HookEventType = "issue_label" + HookEventIssueMilestone HookEventType = "issue_milestone" + HookEventIssueComment HookEventType = "issue_comment" + HookEventPullRequest HookEventType = "pull_request" + HookEventPullRequestAssign HookEventType = "pull_request_assign" + HookEventPullRequestLabel HookEventType = "pull_request_label" + HookEventPullRequestMilestone HookEventType = "pull_request_milestone" + HookEventPullRequestComment HookEventType = "pull_request_comment" + HookEventPullRequestReviewApproved HookEventType = "pull_request_review_approved" + HookEventPullRequestReviewRejected HookEventType = "pull_request_review_rejected" + HookEventPullRequestReviewComment HookEventType = "pull_request_review_comment" + HookEventPullRequestSync HookEventType = "pull_request_sync" + HookEventRepository HookEventType = "repository" + HookEventRelease HookEventType = "release" +) + +// Event returns the HookEventType as an event string +func (h HookEventType) Event() string { + switch h { + case HookEventCreate: + return "create" + case HookEventDelete: + return "delete" + case HookEventFork: + return "fork" + case HookEventPush: + return "push" + case HookEventIssues, HookEventIssueAssign, HookEventIssueLabel, HookEventIssueMilestone: + return "issues" + case HookEventPullRequest, HookEventPullRequestAssign, HookEventPullRequestLabel, HookEventPullRequestMilestone, + HookEventPullRequestSync: + return "pull_request" + case HookEventIssueComment, HookEventPullRequestComment: + return "issue_comment" + case HookEventPullRequestReviewApproved: + return "pull_request_approved" + case HookEventPullRequestReviewRejected: + return "pull_request_rejected" + case HookEventPullRequestReviewComment: + return "pull_request_comment" + case HookEventRepository: + return "repository" + case HookEventRelease: + return "release" + } + return "" +} + +// HookRequest represents hook task request information. +type HookRequest struct { + URL string `json:"url"` + HTTPMethod string `json:"http_method"` + Headers map[string]string `json:"headers"` +} + +// HookResponse represents hook task response information. +type HookResponse struct { + Status int `json:"status"` + Headers map[string]string `json:"headers"` + Body string `json:"body"` +} + +// HookTask represents a hook task. +type HookTask struct { + ID int64 `xorm:"pk autoincr"` + RepoID int64 `xorm:"INDEX"` + HookID int64 + UUID string + api.Payloader `xorm:"-"` + PayloadContent string `xorm:"TEXT"` + EventType HookEventType + IsDelivered bool + Delivered int64 + DeliveredString string `xorm:"-"` + + // History info. + IsSucceed bool + RequestContent string `xorm:"TEXT"` + RequestInfo *HookRequest `xorm:"-"` + ResponseContent string `xorm:"TEXT"` + ResponseInfo *HookResponse `xorm:"-"` +} + +func init() { + db.RegisterModel(new(HookTask)) +} + +// BeforeUpdate will be invoked by XORM before updating a record +// representing this object +func (t *HookTask) BeforeUpdate() { + if t.RequestInfo != nil { + t.RequestContent = t.simpleMarshalJSON(t.RequestInfo) + } + if t.ResponseInfo != nil { + t.ResponseContent = t.simpleMarshalJSON(t.ResponseInfo) + } +} + +// AfterLoad updates the webhook object upon setting a column +func (t *HookTask) AfterLoad() { + t.DeliveredString = time.Unix(0, t.Delivered).Format("2006-01-02 15:04:05 MST") + + if len(t.RequestContent) == 0 { + return + } + + t.RequestInfo = &HookRequest{} + if err := json.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil { + log.Error("Unmarshal RequestContent[%d]: %v", t.ID, err) + } + + if len(t.ResponseContent) > 0 { + t.ResponseInfo = &HookResponse{} + if err := json.Unmarshal([]byte(t.ResponseContent), t.ResponseInfo); err != nil { + log.Error("Unmarshal ResponseContent[%d]: %v", t.ID, err) + } + } +} + +func (t *HookTask) simpleMarshalJSON(v interface{}) string { + p, err := json.Marshal(v) + if err != nil { + log.Error("Marshal [%d]: %v", t.ID, err) + } + return string(p) +} + +// HookTasks returns a list of hook tasks by given conditions. +func HookTasks(hookID int64, page int) ([]*HookTask, error) { + tasks := make([]*HookTask, 0, setting.Webhook.PagingNum) + return tasks, db.GetEngine(db.DefaultContext). + Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum). + Where("hook_id=?", hookID). + Desc("id"). + Find(&tasks) +} + +// 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 +} + +// UpdateHookTask updates information of hook task. +func UpdateHookTask(t *HookTask) error { + _, err := db.GetEngine(db.DefaultContext).ID(t.ID).AllCols().Update(t) + return err +} + +// FindUndeliveredHookTasks represents find the undelivered hook tasks +func FindUndeliveredHookTasks() ([]*HookTask, error) { + tasks := make([]*HookTask, 0, 10) + if err := db.GetEngine(db.DefaultContext).Where("is_delivered=?", false).Find(&tasks); err != nil { + return nil, err + } + return tasks, nil +} + +// FindRepoUndeliveredHookTasks represents find the undelivered hook tasks of one repository +func FindRepoUndeliveredHookTasks(repoID int64) ([]*HookTask, error) { + tasks := make([]*HookTask, 0, 5) + if err := db.GetEngine(db.DefaultContext).Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil { + return nil, err + } + return tasks, nil +} + +// CleanupHookTaskTable deletes rows from hook_task as needed. +func CleanupHookTaskTable(ctx context.Context, cleanupType HookTaskCleanupType, olderThan time.Duration, numberToKeep int) error { + log.Trace("Doing: CleanupHookTaskTable") + + if cleanupType == OlderThan { + deleteOlderThan := time.Now().Add(-olderThan).UnixNano() + deletes, err := db.GetEngine(db.DefaultContext). + Where("is_delivered = ? and delivered < ?", true, deleteOlderThan). + Delete(new(HookTask)) + if err != nil { + return err + } + log.Trace("Deleted %d rows from hook_task", deletes) + } else if cleanupType == PerWebhook { + hookIDs := make([]int64, 0, 10) + err := db.GetEngine(db.DefaultContext).Table("webhook"). + Where("id > 0"). + Cols("id"). + Find(&hookIDs) + if err != nil { + return err + } + for _, hookID := range hookIDs { + select { + case <-ctx.Done(): + return db.ErrCancelledf("Before deleting hook_task records for hook id %d", hookID) + default: + } + if err = deleteDeliveredHookTasksByWebhook(hookID, numberToKeep); err != nil { + return err + } + } + } + log.Trace("Finished: CleanupHookTaskTable") + return nil +} + +func deleteDeliveredHookTasksByWebhook(hookID int64, numberDeliveriesToKeep int) error { + log.Trace("Deleting hook_task rows for webhook %d, keeping the most recent %d deliveries", hookID, numberDeliveriesToKeep) + deliveryDates := make([]int64, 0, 10) + err := db.GetEngine(db.DefaultContext).Table("hook_task"). + Where("hook_task.hook_id = ? AND hook_task.is_delivered = ? AND hook_task.delivered is not null", hookID, true). + Cols("hook_task.delivered"). + Join("INNER", "webhook", "hook_task.hook_id = webhook.id"). + OrderBy("hook_task.delivered desc"). + Limit(1, int(numberDeliveriesToKeep)). + Find(&deliveryDates) + if err != nil { + return err + } + + if len(deliveryDates) > 0 { + deletes, err := db.GetEngine(db.DefaultContext). + Where("hook_id = ? and is_delivered = ? and delivered <= ?", hookID, true, deliveryDates[0]). + Delete(new(HookTask)) + if err != nil { + return err + } + log.Trace("Deleted %d hook_task rows for webhook %d", deletes, hookID) + } else { + log.Trace("No hook_task rows to delete for webhook %d", hookID) + } + + return nil +} diff --git a/models/webhook/main_test.go b/models/webhook/main_test.go new file mode 100644 index 0000000000..f94612a755 --- /dev/null +++ b/models/webhook/main_test.go @@ -0,0 +1,16 @@ +// Copyright 2020 The Gitea Authors. All rights reserved. +// Use of this source code is governed by a MIT-style +// license that can be found in the LICENSE file. + +package webhook + +import ( + "path/filepath" + "testing" + + "code.gitea.io/gitea/models/db" +) + +func TestMain(m *testing.M) { + db.MainTest(m, filepath.Join("..", ".."), "webhook.yml", "hook_task.yml") +} diff --git a/models/webhook.go b/models/webhook/webhook.go index 9d04f8f5e4..de8bd5e338 100644 --- a/models/webhook.go +++ b/models/webhook/webhook.go @@ -3,26 +3,44 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package webhook import ( "context" "fmt" "strings" - "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" "code.gitea.io/gitea/modules/util" - gouuid "github.com/google/uuid" "xorm.io/builder" ) +// __ __ ___. .__ __ +// / \ / \ ____\_ |__ | |__ ____ ____ | | __ +// \ \/\/ // __ \| __ \| | \ / _ \ / _ \| |/ / +// \ /\ ___/| \_\ \ Y ( <_> | <_> ) < +// \__/\ / \___ >___ /___| /\____/ \____/|__|_ \ +// \/ \/ \/ \/ \/ + +// ErrWebhookNotExist represents a "WebhookNotExist" kind of error. +type ErrWebhookNotExist struct { + ID int64 +} + +// IsErrWebhookNotExist checks if an error is a ErrWebhookNotExist. +func IsErrWebhookNotExist(err error) bool { + _, ok := err.(ErrWebhookNotExist) + return ok +} + +func (err ErrWebhookNotExist) Error() string { + return fmt.Sprintf("webhook does not exist [id: %d]", err.ID) +} + // HookContentType is the content type of a web hook type HookContentType int @@ -162,7 +180,6 @@ type Webhook struct { func init() { db.RegisterModel(new(Webhook)) - db.RegisterModel(new(HookTask)) } // AfterLoad updates the webhook object upon setting a column @@ -350,14 +367,9 @@ func (w *Webhook) EventsArray() []string { } // CreateWebhook creates a new web hook. -func CreateWebhook(w *Webhook) error { - return createWebhook(db.GetEngine(db.DefaultContext), w) -} - -func createWebhook(e db.Engine, w *Webhook) error { +func CreateWebhook(ctx context.Context, w *Webhook) error { w.Type = strings.TrimSpace(w.Type) - _, err := e.Insert(w) - return err + return db.Insert(ctx, w) } // getWebhook uses argument bean as query condition, @@ -444,12 +456,12 @@ func CountWebhooksByOpts(opts *ListWebhookOptions) (int64, error) { // GetDefaultWebhooks returns all admin-default webhooks. func GetDefaultWebhooks() ([]*Webhook, error) { - return getDefaultWebhooks(db.GetEngine(db.DefaultContext)) + return getDefaultWebhooks(db.DefaultContext) } -func getDefaultWebhooks(e db.Engine) ([]*Webhook, error) { +func getDefaultWebhooks(ctx context.Context) ([]*Webhook, error) { webhooks := make([]*Webhook, 0, 5) - return webhooks, e. + return webhooks, db.GetEngine(ctx). Where("repo_id=? AND org_id=? AND is_system_webhook=?", 0, 0, false). Find(&webhooks) } @@ -552,9 +564,9 @@ func DeleteDefaultSystemWebhook(id int64) error { return sess.Commit() } -// copyDefaultWebhooksToRepo creates copies of the default webhooks in a new repo -func copyDefaultWebhooksToRepo(e db.Engine, repoID int64) error { - ws, err := getDefaultWebhooks(e) +// CopyDefaultWebhooksToRepo creates copies of the default webhooks in a new repo +func CopyDefaultWebhooksToRepo(ctx context.Context, repoID int64) error { + ws, err := getDefaultWebhooks(ctx) if err != nil { return fmt.Errorf("GetDefaultWebhooks: %v", err) } @@ -562,267 +574,9 @@ func copyDefaultWebhooksToRepo(e db.Engine, repoID int64) error { for _, w := range ws { w.ID = 0 w.RepoID = repoID - if err := createWebhook(e, w); err != nil { + if err := CreateWebhook(ctx, w); err != nil { return fmt.Errorf("CreateWebhook: %v", err) } } return nil } - -// ___ ___ __ ___________ __ -// / | \ ____ ____ | | _\__ ___/____ _____| | __ -// / ~ \/ _ \ / _ \| |/ / | | \__ \ / ___/ |/ / -// \ Y ( <_> | <_> ) < | | / __ \_\___ \| < -// \___|_ / \____/ \____/|__|_ \ |____| (____ /____ >__|_ \ -// \/ \/ \/ \/ \/ - -// HookEventType is the type of an hook event -type HookEventType string - -// Types of hook events -const ( - HookEventCreate HookEventType = "create" - HookEventDelete HookEventType = "delete" - HookEventFork HookEventType = "fork" - HookEventPush HookEventType = "push" - HookEventIssues HookEventType = "issues" - HookEventIssueAssign HookEventType = "issue_assign" - HookEventIssueLabel HookEventType = "issue_label" - HookEventIssueMilestone HookEventType = "issue_milestone" - HookEventIssueComment HookEventType = "issue_comment" - HookEventPullRequest HookEventType = "pull_request" - HookEventPullRequestAssign HookEventType = "pull_request_assign" - HookEventPullRequestLabel HookEventType = "pull_request_label" - HookEventPullRequestMilestone HookEventType = "pull_request_milestone" - HookEventPullRequestComment HookEventType = "pull_request_comment" - HookEventPullRequestReviewApproved HookEventType = "pull_request_review_approved" - HookEventPullRequestReviewRejected HookEventType = "pull_request_review_rejected" - HookEventPullRequestReviewComment HookEventType = "pull_request_review_comment" - HookEventPullRequestSync HookEventType = "pull_request_sync" - HookEventRepository HookEventType = "repository" - HookEventRelease HookEventType = "release" -) - -// Event returns the HookEventType as an event string -func (h HookEventType) Event() string { - switch h { - case HookEventCreate: - return "create" - case HookEventDelete: - return "delete" - case HookEventFork: - return "fork" - case HookEventPush: - return "push" - case HookEventIssues, HookEventIssueAssign, HookEventIssueLabel, HookEventIssueMilestone: - return "issues" - case HookEventPullRequest, HookEventPullRequestAssign, HookEventPullRequestLabel, HookEventPullRequestMilestone, - HookEventPullRequestSync: - return "pull_request" - case HookEventIssueComment, HookEventPullRequestComment: - return "issue_comment" - case HookEventPullRequestReviewApproved: - return "pull_request_approved" - case HookEventPullRequestReviewRejected: - return "pull_request_rejected" - case HookEventPullRequestReviewComment: - return "pull_request_comment" - case HookEventRepository: - return "repository" - case HookEventRelease: - return "release" - } - return "" -} - -// HookRequest represents hook task request information. -type HookRequest struct { - URL string `json:"url"` - HTTPMethod string `json:"http_method"` - Headers map[string]string `json:"headers"` -} - -// HookResponse represents hook task response information. -type HookResponse struct { - Status int `json:"status"` - Headers map[string]string `json:"headers"` - Body string `json:"body"` -} - -// HookTask represents a hook task. -type HookTask struct { - ID int64 `xorm:"pk autoincr"` - RepoID int64 `xorm:"INDEX"` - HookID int64 - UUID string - api.Payloader `xorm:"-"` - PayloadContent string `xorm:"TEXT"` - EventType HookEventType - IsDelivered bool - Delivered int64 - DeliveredString string `xorm:"-"` - - // History info. - IsSucceed bool - RequestContent string `xorm:"TEXT"` - RequestInfo *HookRequest `xorm:"-"` - ResponseContent string `xorm:"TEXT"` - ResponseInfo *HookResponse `xorm:"-"` -} - -// BeforeUpdate will be invoked by XORM before updating a record -// representing this object -func (t *HookTask) BeforeUpdate() { - if t.RequestInfo != nil { - t.RequestContent = t.simpleMarshalJSON(t.RequestInfo) - } - if t.ResponseInfo != nil { - t.ResponseContent = t.simpleMarshalJSON(t.ResponseInfo) - } -} - -// AfterLoad updates the webhook object upon setting a column -func (t *HookTask) AfterLoad() { - t.DeliveredString = time.Unix(0, t.Delivered).Format("2006-01-02 15:04:05 MST") - - if len(t.RequestContent) == 0 { - return - } - - t.RequestInfo = &HookRequest{} - if err := json.Unmarshal([]byte(t.RequestContent), t.RequestInfo); err != nil { - log.Error("Unmarshal RequestContent[%d]: %v", t.ID, err) - } - - if len(t.ResponseContent) > 0 { - t.ResponseInfo = &HookResponse{} - if err := json.Unmarshal([]byte(t.ResponseContent), t.ResponseInfo); err != nil { - log.Error("Unmarshal ResponseContent[%d]: %v", t.ID, err) - } - } -} - -func (t *HookTask) simpleMarshalJSON(v interface{}) string { - p, err := json.Marshal(v) - if err != nil { - log.Error("Marshal [%d]: %v", t.ID, err) - } - return string(p) -} - -// HookTasks returns a list of hook tasks by given conditions. -func HookTasks(hookID int64, page int) ([]*HookTask, error) { - tasks := make([]*HookTask, 0, setting.Webhook.PagingNum) - return tasks, db.GetEngine(db.DefaultContext). - Limit(setting.Webhook.PagingNum, (page-1)*setting.Webhook.PagingNum). - Where("hook_id=?", hookID). - Desc("id"). - Find(&tasks) -} - -// 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 -} - -// UpdateHookTask updates information of hook task. -func UpdateHookTask(t *HookTask) error { - _, err := db.GetEngine(db.DefaultContext).ID(t.ID).AllCols().Update(t) - return err -} - -// FindUndeliveredHookTasks represents find the undelivered hook tasks -func FindUndeliveredHookTasks() ([]*HookTask, error) { - tasks := make([]*HookTask, 0, 10) - if err := db.GetEngine(db.DefaultContext).Where("is_delivered=?", false).Find(&tasks); err != nil { - return nil, err - } - return tasks, nil -} - -// FindRepoUndeliveredHookTasks represents find the undelivered hook tasks of one repository -func FindRepoUndeliveredHookTasks(repoID int64) ([]*HookTask, error) { - tasks := make([]*HookTask, 0, 5) - if err := db.GetEngine(db.DefaultContext).Where("repo_id=? AND is_delivered=?", repoID, false).Find(&tasks); err != nil { - return nil, err - } - return tasks, nil -} - -// CleanupHookTaskTable deletes rows from hook_task as needed. -func CleanupHookTaskTable(ctx context.Context, cleanupType HookTaskCleanupType, olderThan time.Duration, numberToKeep int) error { - log.Trace("Doing: CleanupHookTaskTable") - - if cleanupType == OlderThan { - deleteOlderThan := time.Now().Add(-olderThan).UnixNano() - deletes, err := db.GetEngine(db.DefaultContext). - Where("is_delivered = ? and delivered < ?", true, deleteOlderThan). - Delete(new(HookTask)) - if err != nil { - return err - } - log.Trace("Deleted %d rows from hook_task", deletes) - } else if cleanupType == PerWebhook { - hookIDs := make([]int64, 0, 10) - err := db.GetEngine(db.DefaultContext).Table("webhook"). - Where("id > 0"). - Cols("id"). - Find(&hookIDs) - if err != nil { - return err - } - for _, hookID := range hookIDs { - select { - case <-ctx.Done(): - return ErrCancelledf("Before deleting hook_task records for hook id %d", hookID) - default: - } - if err = deleteDeliveredHookTasksByWebhook(hookID, numberToKeep); err != nil { - return err - } - } - } - log.Trace("Finished: CleanupHookTaskTable") - return nil -} - -func deleteDeliveredHookTasksByWebhook(hookID int64, numberDeliveriesToKeep int) error { - log.Trace("Deleting hook_task rows for webhook %d, keeping the most recent %d deliveries", hookID, numberDeliveriesToKeep) - deliveryDates := make([]int64, 0, 10) - err := db.GetEngine(db.DefaultContext).Table("hook_task"). - Where("hook_task.hook_id = ? AND hook_task.is_delivered = ? AND hook_task.delivered is not null", hookID, true). - Cols("hook_task.delivered"). - Join("INNER", "webhook", "hook_task.hook_id = webhook.id"). - OrderBy("hook_task.delivered desc"). - Limit(1, int(numberDeliveriesToKeep)). - Find(&deliveryDates) - if err != nil { - return err - } - - if len(deliveryDates) > 0 { - deletes, err := db.GetEngine(db.DefaultContext). - Where("hook_id = ? and is_delivered = ? and delivered <= ?", hookID, true, deliveryDates[0]). - Delete(new(HookTask)) - if err != nil { - return err - } - log.Trace("Deleted %d hook_task rows for webhook %d", deletes, hookID) - } else { - log.Trace("No hook_task rows to delete for webhook %d", hookID) - } - - return nil -} diff --git a/models/webhook_test.go b/models/webhook/webhook_test.go index d48fa365be..df2c37b355 100644 --- a/models/webhook_test.go +++ b/models/webhook/webhook_test.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a MIT-style // license that can be found in the LICENSE file. -package models +package webhook import ( "context" @@ -92,7 +92,7 @@ func TestCreateWebhook(t *testing.T) { Events: `{"push_only":false,"send_everything":false,"choose_events":false,"events":{"create":false,"push":true,"pull_request":true}}`, } db.AssertNotExistsBean(t, hook) - assert.NoError(t, CreateWebhook(hook)) + assert.NoError(t, CreateWebhook(db.DefaultContext, hook)) db.AssertExistsAndLoadBean(t, hook) } |