Skip to content

Commit 92e9281

Browse files
committed
First implementation
Signed-off-by: jolheiser <[email protected]>
1 parent eeae1f4 commit 92e9281

File tree

20 files changed

+495
-41
lines changed

20 files changed

+495
-41
lines changed

cmd/web.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package cmd
66

77
import (
8-
"code.gitea.io/gitea/modules/webhook"
98
"context"
109
"fmt"
1110
"net"
@@ -20,6 +19,7 @@ import (
2019
"code.gitea.io/gitea/modules/log"
2120
"code.gitea.io/gitea/modules/process"
2221
"code.gitea.io/gitea/modules/setting"
22+
"code.gitea.io/gitea/modules/webhook"
2323
"code.gitea.io/gitea/routers"
2424
"code.gitea.io/gitea/routers/install"
2525

@@ -163,8 +163,6 @@ func runWeb(ctx *cli.Context) error {
163163
log.Fatal("Could not load custom webhooks: %v", err)
164164
}
165165

166-
log.Info("%#v\n", webhook.Webhooks)
167-
168166
// We check that AppDataPath exists here (it should have been created during installation)
169167
// We can't check it in `GlobalInitInstalled`, because some integration tests
170168
// use cmd -> GlobalInitInstalled, but the AppDataPath doesn't exist during those tests.

models/migrations/migrations.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -387,6 +387,8 @@ var migrations = []Migration{
387387
NewMigration("Add auto merge table", addAutoMergeTable),
388388
// v215 -> v216
389389
NewMigration("allow to view files in PRs", addReviewViewedFiles),
390+
// v216 -> v217
391+
NewMigration("Add custom webhooks", addCustomWebhooks),
390392
}
391393

392394
// GetCurrentDBVersion returns the current db version

models/migrations/v217.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Copyright 2022 The Gitea Authors. All rights reserved.
2+
// Use of this source code is governed by a MIT-style
3+
// license that can be found in the LICENSE file.
4+
5+
package migrations
6+
7+
import (
8+
"fmt"
9+
10+
"code.gitea.io/gitea/modules/timeutil"
11+
12+
"xorm.io/xorm"
13+
)
14+
15+
func addCustomWebhooks(x *xorm.Engine) error {
16+
type HookContentType int
17+
18+
type HookEvents struct {
19+
Create bool `json:"create"`
20+
Delete bool `json:"delete"`
21+
Fork bool `json:"fork"`
22+
Issues bool `json:"issues"`
23+
IssueAssign bool `json:"issue_assign"`
24+
IssueLabel bool `json:"issue_label"`
25+
IssueMilestone bool `json:"issue_milestone"`
26+
IssueComment bool `json:"issue_comment"`
27+
Push bool `json:"push"`
28+
PullRequest bool `json:"pull_request"`
29+
PullRequestAssign bool `json:"pull_request_assign"`
30+
PullRequestLabel bool `json:"pull_request_label"`
31+
PullRequestMilestone bool `json:"pull_request_milestone"`
32+
PullRequestComment bool `json:"pull_request_comment"`
33+
PullRequestReview bool `json:"pull_request_review"`
34+
PullRequestSync bool `json:"pull_request_sync"`
35+
Repository bool `json:"repository"`
36+
Release bool `json:"release"`
37+
}
38+
39+
type HookEvent struct {
40+
PushOnly bool `json:"push_only"`
41+
SendEverything bool `json:"send_everything"`
42+
ChooseEvents bool `json:"choose_events"`
43+
BranchFilter string `json:"branch_filter"`
44+
45+
HookEvents `json:"events"`
46+
}
47+
48+
type HookType = string
49+
50+
type HookStatus int
51+
52+
type Webhook struct {
53+
ID int64 `xorm:"pk autoincr"`
54+
CustomID string `xorm:"VARCHAR(20) 'custom_id'"`
55+
RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook
56+
OrgID int64 `xorm:"INDEX"`
57+
IsSystemWebhook bool
58+
URL string `xorm:"url TEXT"`
59+
HTTPMethod string `xorm:"http_method"`
60+
ContentType HookContentType
61+
Secret string `xorm:"TEXT"`
62+
Events string `xorm:"TEXT"`
63+
*HookEvent `xorm:"-"`
64+
IsActive bool `xorm:"INDEX"`
65+
Type HookType `xorm:"VARCHAR(16) 'type'"`
66+
Meta string `xorm:"TEXT"` // store hook-specific attributes
67+
LastStatus HookStatus // Last delivery status
68+
69+
CreatedUnix timeutil.TimeStamp `xorm:"INDEX created"`
70+
UpdatedUnix timeutil.TimeStamp `xorm:"INDEX updated"`
71+
}
72+
73+
if err := x.Sync2(new(Webhook)); err != nil {
74+
return fmt.Errorf("Sync2: %v", err)
75+
}
76+
return nil
77+
}

models/webhook/webhook.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ const (
163163
MATRIX HookType = "matrix"
164164
WECHATWORK HookType = "wechatwork"
165165
PACKAGIST HookType = "packagist"
166+
CUSTOM HookType = "custom"
166167
)
167168

168169
// HookStatus is the status of a web hook
@@ -177,9 +178,10 @@ const (
177178

178179
// Webhook represents a web hook object.
179180
type Webhook struct {
180-
ID int64 `xorm:"pk autoincr"`
181-
RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook
182-
OrgID int64 `xorm:"INDEX"`
181+
ID int64 `xorm:"pk autoincr"`
182+
CustomID string `xorm:"VARCHAR(20) 'custom_id'"`
183+
RepoID int64 `xorm:"INDEX"` // An ID of 0 indicates either a default or system webhook
184+
OrgID int64 `xorm:"INDEX"`
183185
IsSystemWebhook bool
184186
URL string `xorm:"url TEXT"`
185187
HTTPMethod string `xorm:"http_method"`

modules/webhook/webhook.go

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,32 +8,35 @@ import (
88
"errors"
99
"fmt"
1010
"io"
11+
"io/fs"
1112
"os"
1213
"path/filepath"
1314

1415
"gopkg.in/yaml.v2"
1516
)
1617

17-
var Webhooks map[string]*Webhook
18+
var Webhooks = make(map[string]*Webhook)
1819

1920
// Webhook is a custom webhook
2021
type Webhook struct {
21-
ID string `yaml:"id"`
22-
HTTP string `yaml:"http"`
23-
Exec []string `yaml:"exec"`
24-
Form []Form `yaml:"form"`
25-
Path string `yaml:"-"`
22+
ID string `yaml:"id"`
23+
Label string `yaml:"label"`
24+
URL string `yaml:"url"`
25+
HTTP string `yaml:"http"`
26+
Exec []string `yaml:"exec"`
27+
Form []Form `yaml:"form"`
28+
Path string `yaml:"-"`
2629
}
2730

2831
// Image returns a custom webhook image if it exists, else the default image
29-
func (w *Webhook) Image() ([]byte, error) {
32+
// Image needs to be CLOSED
33+
func (w *Webhook) Image() (io.ReadCloser, error) {
3034
img, err := os.Open(filepath.Join(w.Path, "image.png"))
3135
if err != nil {
3236
return nil, fmt.Errorf("could not open custom webhook image: %w", err)
3337
}
34-
defer img.Close()
3538

36-
return io.ReadAll(img)
39+
return img, nil
3740
}
3841

3942
// Form is a webhook form
@@ -45,6 +48,22 @@ type Form struct {
4548
Default string `yaml:"default"`
4649
}
4750

51+
// InputType returns the HTML input type of a Form.Type
52+
func (f Form) InputType() string {
53+
switch f.Type {
54+
case "text":
55+
return "text"
56+
case "secret":
57+
return "password"
58+
case "number":
59+
return "number"
60+
case "bool":
61+
return "checkbox"
62+
default:
63+
return "text"
64+
}
65+
}
66+
4867
func (w *Webhook) validate() error {
4968
if w.ID == "" {
5069
return errors.New("webhook id is required")
@@ -94,6 +113,9 @@ func Parse(r io.Reader) (*Webhook, error) {
94113
func Init(path string) error {
95114
dir, err := os.ReadDir(path)
96115
if err != nil {
116+
if errors.Is(err, fs.ErrNotExist) {
117+
return nil
118+
}
97119
return fmt.Errorf("could not read dir %q: %w", path, err)
98120
}
99121

modules/webhook/webhook_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package webhook
22

33
import (
44
"bytes"
5+
"testing"
6+
57
"code.gitea.io/gitea/testdata"
8+
69
"github.com/stretchr/testify/assert"
7-
"testing"
810
)
911

1012
func TestWebhook(t *testing.T) {

options/locale/locale_en-US.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,6 +1921,7 @@ settings.webhook.payload = Content
19211921
settings.webhook.body = Body
19221922
settings.webhook.replay.description = Replay this webhook.
19231923
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.
1924+
settings.webhook.display_name = Display Name
19241925
settings.githooks_desc = "Git Hooks are powered by Git itself. You can edit hook files below to set up custom operations."
19251926
settings.githook_edit_desc = If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook.
19261927
settings.githook_name = Hook Name

routers/web/admin/hooks.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"code.gitea.io/gitea/modules/context"
1313
"code.gitea.io/gitea/modules/setting"
1414
"code.gitea.io/gitea/modules/util"
15+
cwebhook "code.gitea.io/gitea/modules/webhook"
1516
)
1617

1718
const (
@@ -25,6 +26,7 @@ func DefaultOrSystemWebhooks(ctx *context.Context) {
2526

2627
ctx.Data["PageIsAdminSystemHooks"] = true
2728
ctx.Data["PageIsAdminDefaultHooks"] = true
29+
ctx.Data["CustomWebhooks"] = cwebhook.Webhooks
2830

2931
def := make(map[string]interface{}, len(ctx.Data))
3032
sys := make(map[string]interface{}, len(ctx.Data))

routers/web/org/setting.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
repo_module "code.gitea.io/gitea/modules/repository"
2222
"code.gitea.io/gitea/modules/setting"
2323
"code.gitea.io/gitea/modules/web"
24+
cwebhook "code.gitea.io/gitea/modules/webhook"
2425
user_setting "code.gitea.io/gitea/routers/web/user/setting"
2526
"code.gitea.io/gitea/services/forms"
2627
"code.gitea.io/gitea/services/org"
@@ -206,6 +207,7 @@ func Webhooks(ctx *context.Context) {
206207
ctx.Data["BaseLink"] = ctx.Org.OrgLink + "/settings/hooks"
207208
ctx.Data["BaseLinkNew"] = ctx.Org.OrgLink + "/settings/hooks"
208209
ctx.Data["Description"] = ctx.Tr("org.settings.hooks_desc")
210+
ctx.Data["CustomWebhooks"] = cwebhook.Webhooks
209211

210212
ws, err := webhook.ListWebhooksByOpts(&webhook.ListWebhookOptions{OrgID: ctx.Org.Organization.ID})
211213
if err != nil {

routers/web/repo/webhook.go

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
api "code.gitea.io/gitea/modules/structs"
2626
"code.gitea.io/gitea/modules/util"
2727
"code.gitea.io/gitea/modules/web"
28+
cwebhook "code.gitea.io/gitea/modules/webhook"
2829
"code.gitea.io/gitea/services/forms"
2930
webhook_service "code.gitea.io/gitea/services/webhook"
3031
)
@@ -50,6 +51,7 @@ func Webhooks(ctx *context.Context) {
5051
return
5152
}
5253
ctx.Data["Webhooks"] = ws
54+
ctx.Data["CustomWebhooks"] = cwebhook.Webhooks
5355

5456
ctx.HTML(http.StatusOK, tplHooks)
5557
}
@@ -108,19 +110,21 @@ func getOrgRepoCtx(ctx *context.Context) (*orgRepoCtx, error) {
108110
return nil, errors.New("unable to set OrgRepo context")
109111
}
110112

111-
func checkHookType(ctx *context.Context) string {
113+
func checkHookType(ctx *context.Context) (string, bool) {
112114
hookType := strings.ToLower(ctx.Params(":type"))
113-
if !util.IsStringInSlice(hookType, setting.Webhook.Types, true) {
115+
_, isCustom := cwebhook.Webhooks[hookType]
116+
if !util.IsStringInSlice(hookType, setting.Webhook.Types, true) && !isCustom {
114117
ctx.NotFound("checkHookType", nil)
115-
return ""
118+
return "", false
116119
}
117-
return hookType
120+
return hookType, isCustom
118121
}
119122

120123
// WebhooksNew render creating webhook page
121124
func WebhooksNew(ctx *context.Context) {
122125
ctx.Data["Title"] = ctx.Tr("repo.settings.add_webhook")
123126
ctx.Data["Webhook"] = webhook.Webhook{HookEvent: &webhook.HookEvent{}}
127+
ctx.Data["CustomWebhooks"] = cwebhook.Webhooks
124128

125129
orCtx, err := getOrgRepoCtx(ctx)
126130
if err != nil {
@@ -139,7 +143,7 @@ func WebhooksNew(ctx *context.Context) {
139143
ctx.Data["PageIsSettingsHooksNew"] = true
140144
}
141145

142-
hookType := checkHookType(ctx)
146+
hookType, isCustom := checkHookType(ctx)
143147
ctx.Data["HookType"] = hookType
144148
if ctx.Written() {
145149
return
@@ -149,6 +153,9 @@ func WebhooksNew(ctx *context.Context) {
149153
"Username": "Gitea",
150154
}
151155
}
156+
if isCustom {
157+
ctx.Data["CustomHook"] = cwebhook.Webhooks[hookType]
158+
}
152159
ctx.Data["BaseLink"] = orCtx.LinkNew
153160

154161
ctx.HTML(http.StatusOK, orCtx.NewTemplate)
@@ -771,6 +778,13 @@ func checkWebhook(ctx *context.Context) (*orgRepoCtx, *webhook.Webhook) {
771778
ctx.Data["MatrixHook"] = webhook_service.GetMatrixHook(w)
772779
case webhook.PACKAGIST:
773780
ctx.Data["PackagistHook"] = webhook_service.GetPackagistHook(w)
781+
case webhook.CUSTOM:
782+
ctx.Data["CustomHook"] = cwebhook.Webhooks[w.CustomID]
783+
hook := webhook_service.GetCustomHook(w)
784+
ctx.Data["Webhook"] = hook
785+
for key, val := range hook.Form {
786+
ctx.Data["CustomHook_"+key] = val
787+
}
774788
}
775789

776790
ctx.Data["History"], err = w.History(1)

0 commit comments

Comments
 (0)