Skip to content

Webhooks for repo creation/deletion #1663

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

Merged
merged 4 commits into from
Sep 3, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 31 additions & 11 deletions models/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -835,8 +835,8 @@ func wikiRemoteURL(remote string) string {
}

// MigrateRepository migrates a existing repository from other project hosting.
func MigrateRepository(u *User, opts MigrateRepoOptions) (*Repository, error) {
repo, err := CreateRepository(u, CreateRepoOptions{
func MigrateRepository(doer, u *User, opts MigrateRepoOptions) (*Repository, error) {
repo, err := CreateRepository(doer, u, CreateRepoOptions{
Name: opts.Name,
Description: opts.Description,
IsPrivate: opts.IsPrivate,
Expand Down Expand Up @@ -1202,7 +1202,7 @@ func IsUsableRepoName(name string) error {
return isUsableName(reservedRepoNames, reservedRepoPatterns, name)
}

func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) {
func createRepository(e *xorm.Session, doer, u *User, repo *Repository) (err error) {
if err = IsUsableRepoName(repo.Name); err != nil {
return err
}
Expand Down Expand Up @@ -1249,7 +1249,15 @@ func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) {
return fmt.Errorf("getOwnerTeam: %v", err)
} else if err = t.addRepository(e, repo); err != nil {
return fmt.Errorf("addRepository: %v", err)
} else if err = prepareWebhooks(e, repo, HookEventRepository, &api.RepositoryPayload{
Action: api.HookRepoCreated,
Repository: repo.APIFormat(AccessModeOwner),
Organization: u.APIFormat(),
Sender: doer.APIFormat(),
}); err != nil {
return fmt.Errorf("prepareWebhooks: %v", err)
}
go HookQueue.Add(repo.ID)
} else {
// Organization automatically called this in addRepository method.
if err = repo.recalculateAccesses(e); err != nil {
Expand All @@ -1266,8 +1274,8 @@ func createRepository(e *xorm.Session, u *User, repo *Repository) (err error) {
return nil
}

// CreateRepository creates a repository for given user or organization.
func CreateRepository(u *User, opts CreateRepoOptions) (_ *Repository, err error) {
// CreateRepository creates a repository for the user/organization u.
func CreateRepository(doer, u *User, opts CreateRepoOptions) (_ *Repository, err error) {
if !u.CanCreateRepo() {
return nil, ErrReachLimitOfRepo{u.MaxRepoCreation}
}
Expand All @@ -1287,7 +1295,7 @@ func CreateRepository(u *User, opts CreateRepoOptions) (_ *Repository, err error
return nil, err
}

if err = createRepository(sess, u, repo); err != nil {
if err = createRepository(sess, doer, u, repo); err != nil {
return nil, err
}

Expand Down Expand Up @@ -1623,7 +1631,7 @@ func UpdateRepositoryUnits(repo *Repository, units []RepoUnit) (err error) {
}

// DeleteRepository deletes a repository for a user or organization.
func DeleteRepository(uid, repoID int64) error {
func DeleteRepository(doer *User, uid, repoID int64) error {
// In case is a organization.
org, err := GetUserByID(uid)
if err != nil {
Expand Down Expand Up @@ -1781,6 +1789,18 @@ func DeleteRepository(uid, repoID int64) error {
return fmt.Errorf("Commit: %v", err)
}

if org.IsOrganization() {
if err = PrepareWebhooks(repo, HookEventRepository, &api.RepositoryPayload{
Action: api.HookRepoDeleted,
Repository: repo.APIFormat(AccessModeOwner),
Organization: org.APIFormat(),
Sender: doer.APIFormat(),
}); err != nil {
return err
}
go HookQueue.Add(repo.ID)
}

return nil
}

Expand Down Expand Up @@ -1974,7 +1994,7 @@ func gatherMissingRepoRecords() ([]*Repository, error) {
}

// DeleteMissingRepositories deletes all repository records that lost Git files.
func DeleteMissingRepositories() error {
func DeleteMissingRepositories(doer *User) error {
repos, err := gatherMissingRepoRecords()
if err != nil {
return fmt.Errorf("gatherMissingRepoRecords: %v", err)
Expand All @@ -1986,7 +2006,7 @@ func DeleteMissingRepositories() error {

for _, repo := range repos {
log.Trace("Deleting %d/%d...", repo.OwnerID, repo.ID)
if err := DeleteRepository(repo.OwnerID, repo.ID); err != nil {
if err := DeleteRepository(doer, repo.OwnerID, repo.ID); err != nil {
if err2 := CreateRepositoryNotice(fmt.Sprintf("DeleteRepository [%d]: %v", repo.ID, err)); err2 != nil {
return fmt.Errorf("CreateRepositoryNotice: %v", err)
}
Expand Down Expand Up @@ -2226,7 +2246,7 @@ func HasForkedRepo(ownerID, repoID int64) (*Repository, bool) {
}

// ForkRepository forks a repository
func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) {
func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *Repository, err error) {
forkedRepo, err := oldRepo.GetUserFork(u.ID)
if err != nil {
return nil, err
Expand Down Expand Up @@ -2256,7 +2276,7 @@ func ForkRepository(u *User, oldRepo *Repository, name, desc string) (_ *Reposit
return nil, err
}

if err = createRepository(sess, u, repo); err != nil {
if err = createRepository(sess, doer, u, repo); err != nil {
return nil, err
}

Expand Down
11 changes: 5 additions & 6 deletions models/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,13 +118,12 @@ func TestGetUserFork(t *testing.T) {
func TestForkRepository(t *testing.T) {
assert.NoError(t, PrepareTestDatabase())

// User13 has repo 11 forked from repo10
repo, err := GetRepositoryByID(10)
assert.NoError(t, err)
assert.NotNil(t, repo)
// user 13 has already forked repo10
user := AssertExistsAndLoadBean(t, &User{ID: 13}).(*User)
repo := AssertExistsAndLoadBean(t, &Repository{ID: 10}).(*Repository)

repo, err = ForkRepository(&User{ID: 13}, repo, "test", "test")
assert.Nil(t, repo)
fork, err := ForkRepository(user, user, repo, "test", "test")
assert.Nil(t, fork)
assert.Error(t, err)
assert.True(t, IsErrRepoAlreadyExist(err))
}
Expand Down
48 changes: 40 additions & 8 deletions models/webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ type HookEvents struct {
Create bool `json:"create"`
Push bool `json:"push"`
PullRequest bool `json:"pull_request"`
Repository bool `json:"repository"`
}

// HookEvent represents events that will delivery hook.
Expand Down Expand Up @@ -188,6 +189,12 @@ func (w *Webhook) HasPullRequestEvent() bool {
(w.ChooseEvents && w.HookEvents.PullRequest)
}

// HasRepositoryEvent returns if hook enabled repository event.
func (w *Webhook) HasRepositoryEvent() bool {
return w.SendEverything ||
(w.ChooseEvents && w.HookEvents.Repository)
}

// EventsArray returns an array of hook events
func (w *Webhook) EventsArray() []string {
events := make([]string, 0, 3)
Expand Down Expand Up @@ -239,8 +246,12 @@ func GetWebhookByOrgID(orgID, id int64) (*Webhook, error) {

// GetActiveWebhooksByRepoID returns all active webhooks of repository.
func GetActiveWebhooksByRepoID(repoID int64) ([]*Webhook, error) {
return getActiveWebhooksByRepoID(x, repoID)
}

func getActiveWebhooksByRepoID(e Engine, repoID int64) ([]*Webhook, error) {
webhooks := make([]*Webhook, 0, 5)
return webhooks, x.Where("is_active=?", true).
return webhooks, e.Where("is_active=?", true).
Find(&webhooks, &Webhook{RepoID: repoID})
}

Expand All @@ -252,7 +263,11 @@ func GetWebhooksByRepoID(repoID int64) ([]*Webhook, error) {

// GetActiveWebhooksByOrgID returns all active webhooks for an organization.
func GetActiveWebhooksByOrgID(orgID int64) (ws []*Webhook, err error) {
err = x.
return getActiveWebhooksByOrgID(x, orgID)
}

func getActiveWebhooksByOrgID(e Engine, orgID int64) (ws []*Webhook, err error) {
err = e.
Where("org_id=?", orgID).
And("is_active=?", true).
Find(&ws)
Expand Down Expand Up @@ -366,6 +381,7 @@ const (
HookEventCreate HookEventType = "create"
HookEventPush HookEventType = "push"
HookEventPullRequest HookEventType = "pull_request"
HookEventRepository HookEventType = "repository"
)

// HookRequest represents hook task request information.
Expand Down Expand Up @@ -466,13 +482,17 @@ 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(x, t)
}

func createHookTask(e Engine, t *HookTask) error {
data, err := t.Payloader.JSONPayload()
if err != nil {
return err
}
t.UUID = gouuid.NewV4().String()
t.PayloadContent = string(data)
_, err = x.Insert(t)
_, err = e.Insert(t)
return err
}

Expand All @@ -484,6 +504,10 @@ func UpdateHookTask(t *HookTask) error {

// PrepareWebhook adds special webhook to task queue for given payload.
func PrepareWebhook(w *Webhook, repo *Repository, event HookEventType, p api.Payloader) error {
return prepareWebhook(x, w, repo, event, p)
}

func prepareWebhook(e Engine, w *Webhook, repo *Repository, event HookEventType, p api.Payloader) error {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing HookEventRepository on switch case. And missing handle on GetSlackPayload and GetDiscordPayload.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added missing case to HookEventRepository. Will hopefully get change to add handles to GetSlackPayload and GetDiscordPayload tomorrow.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lunny Done

switch event {
case HookEventCreate:
if !w.HasCreateEvent() {
Expand All @@ -497,6 +521,10 @@ func PrepareWebhook(w *Webhook, repo *Repository, event HookEventType, p api.Pay
if !w.HasPullRequestEvent() {
return nil
}
case HookEventRepository:
if !w.HasRepositoryEvent() {
return nil
}
}

var payloader api.Payloader
Expand All @@ -518,7 +546,7 @@ func PrepareWebhook(w *Webhook, repo *Repository, event HookEventType, p api.Pay
payloader = p
}

if err = CreateHookTask(&HookTask{
if err = createHookTask(e, &HookTask{
RepoID: repo.ID,
HookID: w.ID,
Type: w.HookTaskType,
Expand All @@ -535,15 +563,19 @@ func PrepareWebhook(w *Webhook, repo *Repository, event HookEventType, p api.Pay

// PrepareWebhooks adds new webhooks to task queue for given payload.
func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) error {
ws, err := GetActiveWebhooksByRepoID(repo.ID)
return prepareWebhooks(x, repo, event, p)
}

func prepareWebhooks(e Engine, repo *Repository, event HookEventType, p api.Payloader) error {
ws, err := getActiveWebhooksByRepoID(e, repo.ID)
if err != nil {
return fmt.Errorf("GetActiveWebhooksByRepoID: %v", err)
}

// check if repo belongs to org and append additional webhooks
if repo.MustOwner().IsOrganization() {
if repo.mustOwner(e).IsOrganization() {
// get hooks for org
orgHooks, err := GetActiveWebhooksByOrgID(repo.OwnerID)
orgHooks, err := getActiveWebhooksByOrgID(e, repo.OwnerID)
if err != nil {
return fmt.Errorf("GetActiveWebhooksByOrgID: %v", err)
}
Expand All @@ -555,7 +587,7 @@ func PrepareWebhooks(repo *Repository, event HookEventType, p api.Payloader) err
}

for _, w := range ws {
if err = PrepareWebhook(w, repo, event, p); err != nil {
if err = prepareWebhook(e, w, repo, event, p); err != nil {
return err
}
}
Expand Down
33 changes: 33 additions & 0 deletions models/webhook_discord.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,37 @@ func getDiscordPullRequestPayload(p *api.PullRequestPayload, meta *DiscordMeta)
}, nil
}

func getDiscordRepositoryPayload(p *api.RepositoryPayload, meta *DiscordMeta) (*DiscordPayload, error) {
var title, url string
var color int
switch p.Action {
case api.HookRepoCreated:
title = fmt.Sprintf("[%s] Repository created", p.Repository.FullName)
url = p.Repository.HTMLURL
color = successColor
case api.HookRepoDeleted:
title = fmt.Sprintf("[%s] Repository deleted", p.Repository.FullName)
color = warnColor
}

return &DiscordPayload{
Username: meta.Username,
AvatarURL: meta.IconURL,
Embeds: []DiscordEmbed{
{
Title: title,
URL: url,
Color: color,
Author: DiscordEmbedAuthor{
Name: p.Sender.UserName,
URL: setting.AppURL + p.Sender.UserName,
IconURL: p.Sender.AvatarURL,
},
},
},
}, nil
}

// GetDiscordPayload converts a discord webhook into a DiscordPayload
func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*DiscordPayload, error) {
s := new(DiscordPayload)
Expand All @@ -246,6 +277,8 @@ func GetDiscordPayload(p api.Payloader, event HookEventType, meta string) (*Disc
return getDiscordPushPayload(p.(*api.PushPayload), discord)
case HookEventPullRequest:
return getDiscordPullRequestPayload(p.(*api.PullRequestPayload), discord)
case HookEventRepository:
return getDiscordRepositoryPayload(p.(*api.RepositoryPayload), discord)
}

return s, nil
Expand Down
26 changes: 26 additions & 0 deletions models/webhook_slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,30 @@ func getSlackPullRequestPayload(p *api.PullRequestPayload, slack *SlackMeta) (*S
}, nil
}

func getSlackRepositoryPayload(p *api.RepositoryPayload, slack *SlackMeta) (*SlackPayload, error) {
senderLink := SlackLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)
var text, title, attachmentText string
switch p.Action {
case api.HookRepoCreated:
text = fmt.Sprintf("[%s] Repository created by %s", p.Repository.FullName, senderLink)
title = p.Repository.HTMLURL
case api.HookRepoDeleted:
text = fmt.Sprintf("[%s] Repository deleted by %s", p.Repository.FullName, senderLink)
}

return &SlackPayload{
Channel: slack.Channel,
Text: text,
Username: slack.Username,
IconURL: slack.IconURL,
Attachments: []SlackAttachment{{
Color: slack.Color,
Title: title,
Text: attachmentText,
}},
}, nil
}

// GetSlackPayload converts a slack webhook into a SlackPayload
func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackPayload, error) {
s := new(SlackPayload)
Expand All @@ -205,6 +229,8 @@ func GetSlackPayload(p api.Payloader, event HookEventType, meta string) (*SlackP
return getSlackPushPayload(p.(*api.PushPayload), slack)
case HookEventPullRequest:
return getSlackPullRequestPayload(p.(*api.PullRequestPayload), slack)
case HookEventRepository:
return getSlackRepositoryPayload(p.(*api.RepositoryPayload), slack)
}

return s, nil
Expand Down
1 change: 1 addition & 0 deletions modules/auth/repo_form.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ type WebhookForm struct {
Create bool
Push bool
PullRequest bool
Repository bool
Active bool
}

Expand Down
2 changes: 2 additions & 0 deletions options/locale/locale_en-US.ini
Original file line number Diff line number Diff line change
Expand Up @@ -892,6 +892,8 @@ settings.event_pull_request = Pull Request
settings.event_pull_request_desc = Pull request opened, closed, reopened, edited, assigned, unassigned, label updated, label cleared, or synchronized.
settings.event_push = Push
settings.event_push_desc = Git push to a repository
settings.event_repository = Repository
settings.event_repository_desc = Repository created or deleted
settings.active = Active
settings.active_helper = Information about the event which triggered the hook will be sent as well.
settings.add_hook_success = New webhook has been added.
Expand Down
2 changes: 1 addition & 1 deletion routers/admin/admin.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ func Dashboard(ctx *context.Context) {
err = models.DeleteRepositoryArchives()
case cleanMissingRepos:
success = ctx.Tr("admin.dashboard.delete_missing_repos_success")
err = models.DeleteMissingRepositories()
err = models.DeleteMissingRepositories(ctx.User)
case gitGCRepos:
success = ctx.Tr("admin.dashboard.git_gc_repos_success")
err = models.GitGcRepos()
Expand Down
2 changes: 1 addition & 1 deletion routers/admin/repos.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ func DeleteRepo(ctx *context.Context) {
return
}

if err := models.DeleteRepository(repo.MustOwner().ID, repo.ID); err != nil {
if err := models.DeleteRepository(ctx.User, repo.MustOwner().ID, repo.ID); err != nil {
ctx.Handle(500, "DeleteRepository", err)
return
}
Expand Down
Loading