Skip to content

Commit

Permalink
feat(hooks): trigger on pull-request (#6819)
Browse files Browse the repository at this point in the history
  • Loading branch information
yesnault authored Feb 8, 2024
1 parent 7bfbe81 commit 308db5c
Show file tree
Hide file tree
Showing 21 changed files with 475 additions and 169 deletions.
12 changes: 12 additions & 0 deletions engine/api/v2_hooks.go
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,18 @@ func LoadWorkflowHooksWithRepositoryWebHooks(ctx context.Context, db gorp.SqlExe
filteredWorkflowHooks = append(filteredWorkflowHooks, w)
}
continue
case sdk.WorkflowHookEventPullRequest, sdk.WorkflowHookEventPullRequestComment:
validBranch := sdk.IsValidHookRefs(ctx, w.Data.BranchFilter, strings.TrimPrefix(hookRequest.Ref, sdk.GitRefBranchPrefix))
validTag := sdk.IsValidHookRefs(ctx, w.Data.TagFilter, strings.TrimPrefix(hookRequest.Ref, sdk.GitRefTagPrefix))
validPath := sdk.IsValidHookPath(ctx, w.Data.PathFilter, hookRequest.Paths)
validType := true
if len(w.Data.TypesFilter) > 0 {
validType = sdk.IsInArray(hookRequest.RepositoryEventType, w.Data.TypesFilter)
}
if validBranch && validPath && validTag && validType {
filteredWorkflowHooks = append(filteredWorkflowHooks, w)
}
continue
}
}
return filteredWorkflowHooks, nil
Expand Down
119 changes: 119 additions & 0 deletions engine/api/v2_hooks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,125 @@ func TestPostRetrieveWorkflowToTriggerHandler_RepositoryWebHooks(t *testing.T) {
require.Equal(t, wh1.ID, hs[0].ID)
}

func TestPostRetrieveWorkflowToTriggerHandler_RepositoryWebHooksPullRequest(t *testing.T) {
api, db, _ := newTestAPI(t)

_, err := db.Exec("DELETE FROM v2_workflow_hook")
require.NoError(t, err)

_, pwd := assets.InsertAdminUser(t, db)

p := assets.InsertTestProject(t, db, api.Cache, sdk.RandomString(10), sdk.RandomString(10))
vcs := assets.InsertTestVCSProject(t, db, p.ID, "github", sdk.VCSTypeGithub)
repo := assets.InsertTestProjectRepository(t, db, p.Key, vcs.ID, sdk.RandomString(10))
e := sdk.Entity{
Name: "MyWorkflow",
Type: sdk.EntityTypeWorkflow,
ProjectKey: p.Key,
ProjectRepositoryID: repo.ID,
Commit: "123456",
Ref: "refs/heads/master",
}
require.NoError(t, entity.Insert(context.TODO(), db, &e))

wh1 := sdk.V2WorkflowHook{
ProjectKey: p.Key,
VCSName: vcs.Name,
EntityID: e.ID,
WorkflowName: sdk.RandomString(10),
Commit: "123456",
Ref: "refs/heads/master",
Type: sdk.WorkflowHookTypeRepository,
Data: sdk.V2WorkflowHookData{
RepositoryName: repo.Name,
VCSServer: vcs.Name,
RepositoryEvent: sdk.WorkflowHookEventPullRequest,
TypesFilter: []string{"pr:opened"},
},
}
require.NoError(t, workflow_v2.InsertWorkflowHook(context.TODO(), db, &wh1))

r := sdk.HookListWorkflowRequest{
RepositoryName: repo.Name,
VCSName: vcs.Name,
RepositoryEventName: sdk.WorkflowHookEventPullRequest,
RepositoryEventType: "pr:opened",
AnayzedProjectKeys: []string{p.Key},
}

uri := api.Router.GetRouteV2("POST", api.postRetrieveWorkflowToTriggerHandler, nil)
test.NotEmpty(t, uri)
req := assets.NewAuthentifiedRequest(t, nil, pwd, "POST", uri, &r)
w := httptest.NewRecorder()
api.Router.Mux.ServeHTTP(w, req)
require.Equal(t, 200, w.Code)

var hs []sdk.V2WorkflowHook
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &hs))

require.Equal(t, 1, len(hs))
require.Equal(t, wh1.ID, hs[0].ID)
}

func TestPostRetrieveWorkflowToTriggerHandler_RepositoryWebHooksPullRequestFiltered(t *testing.T) {
api, db, _ := newTestAPI(t)

_, err := db.Exec("DELETE FROM v2_workflow_hook")
require.NoError(t, err)

_, pwd := assets.InsertAdminUser(t, db)

p := assets.InsertTestProject(t, db, api.Cache, sdk.RandomString(10), sdk.RandomString(10))
vcs := assets.InsertTestVCSProject(t, db, p.ID, "github", sdk.VCSTypeGithub)
repo := assets.InsertTestProjectRepository(t, db, p.Key, vcs.ID, sdk.RandomString(10))
e := sdk.Entity{
Name: "MyWorkflow",
Type: sdk.EntityTypeWorkflow,
ProjectKey: p.Key,
ProjectRepositoryID: repo.ID,
Commit: "123456",
Ref: "refs/heads/master",
}
require.NoError(t, entity.Insert(context.TODO(), db, &e))

wh1 := sdk.V2WorkflowHook{
ProjectKey: p.Key,
VCSName: vcs.Name,
EntityID: e.ID,
WorkflowName: sdk.RandomString(10),
Commit: "123456",
Ref: "refs/heads/master",
Type: sdk.WorkflowHookTypeRepository,
Data: sdk.V2WorkflowHookData{
RepositoryName: repo.Name,
VCSServer: vcs.Name,
RepositoryEvent: sdk.WorkflowHookEventPullRequest,
TypesFilter: []string{"pr:opened"},
},
}
require.NoError(t, workflow_v2.InsertWorkflowHook(context.TODO(), db, &wh1))

r := sdk.HookListWorkflowRequest{
RepositoryName: repo.Name,
VCSName: vcs.Name,
RepositoryEventName: sdk.WorkflowHookEventPullRequest,
RepositoryEventType: "pr:foo",
AnayzedProjectKeys: []string{p.Key},
}

uri := api.Router.GetRouteV2("POST", api.postRetrieveWorkflowToTriggerHandler, nil)
test.NotEmpty(t, uri)
req := assets.NewAuthentifiedRequest(t, nil, pwd, "POST", uri, &r)
w := httptest.NewRecorder()
api.Router.Mux.ServeHTTP(w, req)
require.Equal(t, 200, w.Code)

var hs []sdk.V2WorkflowHook
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &hs))

require.Equal(t, 0, len(hs))
}

func TestPostRetrieveWorkflowToTriggerHandler_WorkerModels(t *testing.T) {
api, db, _ := newTestAPI(t)

Expand Down
52 changes: 52 additions & 0 deletions engine/api/v2_repository_analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,58 @@ func manageWorkflowHooks(ctx context.Context, db gorpmapper.SqlExecutorWithTx, e
}
}

if e.Workflow.On.PullRequest != nil {
if workflowSameRepo || e.Ref == ref {
wh := sdk.V2WorkflowHook{
EntityID: e.ID,
ProjectKey: e.ProjectKey,
Type: sdk.WorkflowHookTypeRepository,
Ref: e.Ref,
Commit: e.Commit,
WorkflowName: e.Name,
VCSName: workflowDefVCSName,
RepositoryName: workflowDefRepositoryName,
Data: sdk.V2WorkflowHookData{
RepositoryEvent: sdk.WorkflowHookEventPullRequest,
VCSServer: targetVCS,
RepositoryName: targetRepository,
BranchFilter: e.Workflow.On.PullRequest.Branches,
PathFilter: e.Workflow.On.PullRequest.Paths,
TypesFilter: e.Workflow.On.PullRequest.Types,
},
}
if err := workflow_v2.InsertWorkflowHook(ctx, db, &wh); err != nil {
return err
}
}
}

if e.Workflow.On.PullRequestComment != nil {
if workflowSameRepo || e.Ref == ref {
wh := sdk.V2WorkflowHook{
EntityID: e.ID,
ProjectKey: e.ProjectKey,
Type: sdk.WorkflowHookTypeRepository,
Ref: e.Ref,
Commit: e.Commit,
WorkflowName: e.Name,
VCSName: workflowDefVCSName,
RepositoryName: workflowDefRepositoryName,
Data: sdk.V2WorkflowHookData{
RepositoryEvent: sdk.WorkflowHookEventPullRequestComment,
VCSServer: targetVCS,
RepositoryName: targetRepository,
BranchFilter: e.Workflow.On.PullRequest.Branches,
PathFilter: e.Workflow.On.PullRequest.Paths,
TypesFilter: e.Workflow.On.PullRequest.Types,
},
}
if err := workflow_v2.InsertWorkflowHook(ctx, db, &wh); err != nil {
return err
}
}
}

// Only save workflow_update hook if :
// * workflow.repository declaration != workflow.repository && default branch
if e.Workflow.On.WorkflowUpdate != nil {
Expand Down
117 changes: 96 additions & 21 deletions engine/hooks/hook_repository_event.go
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
package hooks

import (
"net/http"
"strings"

"github.com/ovh/cds/sdk"
)

func (s *Service) extractDataFromPayload(vcsServerType string, body []byte) (string, sdk.HookRepositoryEventExtractData, error) {
func (s *Service) extractDataFromPayload(headers http.Header, vcsServerType string, body []byte, eventName string) (string, sdk.HookRepositoryEventExtractData, error) {
switch vcsServerType {
case sdk.VCSTypeBitbucketServer:
return s.extractDataFromBitbucketRequest(body)
case sdk.VCSTypeGithub:
return s.extractDataFromGithubRequest(body)
return s.extractDataFromGithubRequest(body, eventName)
case sdk.VCSTypeGitlab:
return s.extractDataFromGitlabRequest(body)
return s.extractDataFromGitlabRequest(body, eventName)
case sdk.VCSTypeGitea:
return s.extractDataFromGiteaRequest(body)
return s.extractDataFromGiteaRequest(headers, body, eventName)
default:
return "", sdk.HookRepositoryEventExtractData{}, sdk.WithStack(sdk.ErrNotImplemented)
}
}

// Update file paths are not is gitea payload
func (s *Service) extractDataFromGiteaRequest(body []byte) (string, sdk.HookRepositoryEventExtractData, error) {
func (s *Service) extractDataFromGiteaRequest(headers http.Header, body []byte, eventName string) (string, sdk.HookRepositoryEventExtractData, error) {
extractedData := sdk.HookRepositoryEventExtractData{}
var request GiteaEventPayload
if err := sdk.JSONUnmarshal(body, &request); err != nil {
Expand All @@ -34,10 +35,25 @@ func (s *Service) extractDataFromGiteaRequest(body []byte) (string, sdk.HookRepo

extractedData.Commit = request.After

// https://github.com/go-gitea/gitea/blob/main/modules/webhook/type.go
// https://github.com/go-gitea/gitea/blob/main/services/webhook/deliver.go#L128

switch eventName {
case "push":
extractedData.CDSEventName = sdk.WorkflowHookEventPush
extractedData.CDSEventType = "" // nothing here
case "pull_request":
extractedData.CDSEventName = sdk.WorkflowHookEventPullRequest
extractedData.CDSEventType = "" // nothing here
case "pull_request_comment":
extractedData.CDSEventName = sdk.WorkflowHookEventPullRequestComment
extractedData.CDSEventType = "" // nothing here
}

return repoName, extractedData, nil
}

func (s *Service) extractDataFromGitlabRequest(body []byte) (string, sdk.HookRepositoryEventExtractData, error) {
func (s *Service) extractDataFromGitlabRequest(body []byte, eventName string) (string, sdk.HookRepositoryEventExtractData, error) {
extractedData := sdk.HookRepositoryEventExtractData{
Paths: make([]string, 0),
}
Expand All @@ -53,21 +69,26 @@ func (s *Service) extractDataFromGitlabRequest(body []byte) (string, sdk.HookRep
extractedData.Commit = request.After

for _, c := range request.Commits {
for _, p := range c.Added {
extractedData.Paths = append(extractedData.Paths, p)
}
for _, p := range c.Modified {
extractedData.Paths = append(extractedData.Paths, p)
}
for _, p := range c.Removed {
extractedData.Paths = append(extractedData.Paths, p)
}
extractedData.Paths = append(extractedData.Paths, c.Added...)
extractedData.Paths = append(extractedData.Paths, c.Modified...)
extractedData.Paths = append(extractedData.Paths, c.Removed...)
}

switch eventName {
case "Push Hook":
extractedData.CDSEventName = sdk.WorkflowHookEventPush
extractedData.CDSEventType = "" // nothing here
case "Merge Request Hook":
extractedData.CDSEventName = sdk.WorkflowHookEventPullRequest
extractedData.CDSEventType = "" // nothing here
case "Note Hook":
extractedData.CDSEventName = sdk.WorkflowHookEventPullRequestComment
extractedData.CDSEventType = "" // nothing here
}
return repoName, extractedData, nil
}

func (s *Service) extractDataFromGithubRequest(body []byte) (string, sdk.HookRepositoryEventExtractData, error) {
func (s *Service) extractDataFromGithubRequest(body []byte, eventName string) (string, sdk.HookRepositoryEventExtractData, error) {
extractedData := sdk.HookRepositoryEventExtractData{
Paths: make([]string, 0),
}
Expand All @@ -88,10 +109,21 @@ func (s *Service) extractDataFromGithubRequest(body []byte) (string, sdk.HookRep
extractedData.Paths = append(extractedData.Paths, c.Modified...)
extractedData.Paths = append(extractedData.Paths, c.Removed...)
}

switch eventName {
case "push":
extractedData.CDSEventName = sdk.WorkflowHookEventPush
extractedData.CDSEventType = "" // nothing here
case "pull_request":
extractedData.CDSEventName = sdk.WorkflowHookEventPullRequest
extractedData.CDSEventType = request.Action
case "pull_request_comment":
extractedData.CDSEventName = sdk.WorkflowHookEventPullRequestComment
extractedData.CDSEventType = request.Action
}
return repoName, extractedData, nil
}

// Update file paths are not is gitea payload
func (s *Service) extractDataFromBitbucketRequest(body []byte) (string, sdk.HookRepositoryEventExtractData, error) {
extractedData := sdk.HookRepositoryEventExtractData{}
var request sdk.BitbucketServerWebhookEvent
Expand All @@ -102,10 +134,53 @@ func (s *Service) extractDataFromBitbucketRequest(body []byte) (string, sdk.Hook
if request.Repository != nil {
repoName = strings.ToLower(request.Repository.Project.Key) + "/" + request.Repository.Slug
}
if len(request.Changes) == 0 {
return "", extractedData, sdk.NewErrorFrom(sdk.ErrInvalidData, "unable to know branch and commit: %s", string(body))
switch request.EventKey {
case "repo:refs_changed":
extractedData.Ref = request.Changes[0].RefID
extractedData.Commit = request.Changes[0].ToHash
extractedData.CDSEventName = sdk.WorkflowHookEventPush
extractedData.CDSEventType = "" // no type here
case "pr:opened":
extractedData.Ref = request.PullRequest.FromRef.ID
extractedData.Commit = request.PullRequest.FromRef.LatestCommit
extractedData.CDSEventName = sdk.WorkflowHookEventPullRequest
extractedData.CDSEventType = sdk.WorkflowHookEventPullRequestTypeOpened
case "pr:reopened":
extractedData.Ref = request.PullRequest.FromRef.ID
extractedData.Commit = request.PullRequest.FromRef.LatestCommit
extractedData.CDSEventName = sdk.WorkflowHookEventPullRequest
extractedData.CDSEventType = sdk.WorkflowHookEventPullRequestTypeReopened
case "pr:declined":
extractedData.Ref = request.PullRequest.FromRef.ID
extractedData.Commit = request.PullRequest.FromRef.LatestCommit
extractedData.CDSEventName = sdk.WorkflowHookEventPullRequest
extractedData.CDSEventType = sdk.WorkflowHookEventPullRequestTypeClosed
case "pr:from_ref_updated":
extractedData.Ref = request.PullRequest.FromRef.ID
extractedData.Commit = request.PullRequest.FromRef.LatestCommit
extractedData.CDSEventName = sdk.WorkflowHookEventPullRequest
extractedData.CDSEventName = sdk.WorkflowHookEventPullRequest
extractedData.CDSEventType = sdk.WorkflowHookEventPullRequestTypeEdited
case "pr:comment:added":
extractedData.Ref = request.PullRequest.FromRef.ID
extractedData.Commit = request.PullRequest.FromRef.LatestCommit
extractedData.CDSEventName = sdk.WorkflowHookEventPullRequestComment
extractedData.CDSEventType = sdk.WorkflowHookEventPullRequestCommentTypeCreated
case "pr:comment:edited":
extractedData.Ref = request.PullRequest.FromRef.ID
extractedData.Commit = request.PullRequest.FromRef.LatestCommit
extractedData.CDSEventName = sdk.WorkflowHookEventPullRequestComment
extractedData.CDSEventType = sdk.WorkflowHookEventPullRequestCommentTypeEdited
case "pr:comment:deleted":
extractedData.Ref = request.PullRequest.FromRef.ID
extractedData.Commit = request.PullRequest.FromRef.LatestCommit
extractedData.CDSEventName = sdk.WorkflowHookEventPullRequestComment
extractedData.CDSEventType = sdk.WorkflowHookEventPullRequestCommentTypeDeleted
}
extractedData.Ref = request.Changes[0].RefID
extractedData.Commit = request.Changes[0].ToHash

if extractedData.Ref == "" {
return "", extractedData, sdk.NewErrorFrom(sdk.ErrInvalidData, "repoName: %v unable to extract data %s", repoName, string(body))
}

return repoName, extractedData, nil
}
Loading

0 comments on commit 308db5c

Please sign in to comment.