From 1b9fe11c1f6efba93c0bac860e4e677c147e6896 Mon Sep 17 00:00:00 2001 From: Martin Treml Date: Thu, 20 Dec 2018 17:09:12 +0100 Subject: [PATCH] Add gitlab system hook parsing If you use gitlab system hooks you can now send push/tag_push/merge_request events --- gitlab/gitlab.go | 60 ++++++++++++++++++++++++++++++--------- gitlab/gitlab_test.go | 65 ++++++++++++++++++++++++++++++++++++++++--- gitlab/payload.go | 5 ++++ 3 files changed, 113 insertions(+), 17 deletions(-) diff --git a/gitlab/gitlab.go b/gitlab/gitlab.go index e603868..48879ed 100644 --- a/gitlab/gitlab.go +++ b/gitlab/gitlab.go @@ -17,6 +17,7 @@ var ( ErrGitLabTokenVerificationFailed = errors.New("X-Gitlab-Token validation failed") ErrEventNotFound = errors.New("event not defined to be parsed") ErrParsingPayload = errors.New("error parsing payload") + ErrParsingSystemPayload = errors.New("error parsing system payload") // ErrHMACVerificationFailed = errors.New("HMAC verification failed") ) @@ -31,6 +32,11 @@ const ( WikiPageEvents Event = "Wiki Page Hook" PipelineEvents Event = "Pipeline Hook" BuildEvents Event = "Build Hook" + SystemHooks Event = "System Hook" + + objectPush string = "push" + objectTag string = "tag_push" + objectMergeRequest string = "merge_request" ) // Option is a configuration option for the webhook @@ -55,7 +61,7 @@ type Webhook struct { secret string } -// Event defines a GitHub hook event type +// Event defines a GitLab hook event type by the X-Gitlab-Event Header type Event string // New creates and returns a WebHook instance denoted by the Provider type @@ -89,6 +95,31 @@ func (hook Webhook) Parse(r *http.Request, events ...Event) (interface{}, error) } gitLabEvent := Event(event) + payload := []byte{} + + if gitLabEvent == SystemHooks { + + sysPayload, err := ioutil.ReadAll(r.Body) + if err != nil || len(sysPayload) == 0 { + return nil, ErrParsingSystemPayload + } + payload = sysPayload + + var sysPl SystemHookPayload + err = json.Unmarshal([]byte(payload), &sysPl) + if err != nil { + return nil, err + } + + switch sysPl.ObjectKind { + case objectPush: + gitLabEvent = PushEvents + case objectTag: + gitLabEvent = TagEvents + case objectMergeRequest: + gitLabEvent = MergeRequestEvents + } + } var found bool for _, evt := range events { @@ -102,9 +133,12 @@ func (hook Webhook) Parse(r *http.Request, events ...Event) (interface{}, error) return nil, ErrEventNotFound } - payload, err := ioutil.ReadAll(r.Body) - if err != nil || len(payload) == 0 { - return nil, ErrParsingPayload + // check if payload is empty - that means it was no system hook call + if len(payload) == 0 { + payload, err := ioutil.ReadAll(r.Body) + if err != nil || len(payload) == 0 { + return nil, ErrParsingPayload + } } // If we have a Secret set, we should check the MAC @@ -118,47 +152,47 @@ func (hook Webhook) Parse(r *http.Request, events ...Event) (interface{}, error) switch gitLabEvent { case PushEvents: var pl PushEventPayload - err = json.Unmarshal([]byte(payload), &pl) + err := json.Unmarshal([]byte(payload), &pl) return pl, err case TagEvents: var pl TagEventPayload - err = json.Unmarshal([]byte(payload), &pl) + err := json.Unmarshal([]byte(payload), &pl) return pl, err case ConfidentialIssuesEvents: var pl ConfidentialIssueEventPayload - err = json.Unmarshal([]byte(payload), &pl) + err := json.Unmarshal([]byte(payload), &pl) return pl, err case IssuesEvents: var pl IssueEventPayload - err = json.Unmarshal([]byte(payload), &pl) + err := json.Unmarshal([]byte(payload), &pl) return pl, err case CommentEvents: var pl CommentEventPayload - err = json.Unmarshal([]byte(payload), &pl) + err := json.Unmarshal([]byte(payload), &pl) return pl, err case MergeRequestEvents: var pl MergeRequestEventPayload - err = json.Unmarshal([]byte(payload), &pl) + err := json.Unmarshal([]byte(payload), &pl) return pl, err case WikiPageEvents: var pl WikiPageEventPayload - err = json.Unmarshal([]byte(payload), &pl) + err := json.Unmarshal([]byte(payload), &pl) return pl, err case PipelineEvents: var pl PipelineEventPayload - err = json.Unmarshal([]byte(payload), &pl) + err := json.Unmarshal([]byte(payload), &pl) return pl, err case BuildEvents: var pl BuildEventPayload - err = json.Unmarshal([]byte(payload), &pl) + err := json.Unmarshal([]byte(payload), &pl) return pl, err default: return nil, fmt.Errorf("unknown event %s", gitLabEvent) diff --git a/gitlab/gitlab_test.go b/gitlab/gitlab_test.go index 2d9c256..da5cd11 100644 --- a/gitlab/gitlab_test.go +++ b/gitlab/gitlab_test.go @@ -2,15 +2,13 @@ package gitlab import ( "bytes" + "io" "log" "net/http" "net/http/httptest" "os" - "testing" - - "io" - "reflect" + "testing" "github.com/stretchr/testify/require" ) @@ -266,3 +264,62 @@ func TestWebhooks(t *testing.T) { }) } } + +func TestSystemHooks(t *testing.T) { + assert := require.New(t) + tests := []struct { + name string + event Event + typ interface{} + filename string + }{ + { + name: "PushEvent", + event: PushEvents, + typ: PushEventPayload{}, + filename: "../testdata/gitlab/push-event.json", + }, + { + name: "TagEvent", + event: TagEvents, + typ: TagEventPayload{}, + filename: "../testdata/gitlab/tag-event.json", + }, + { + name: "MergeRequestEvent", + event: MergeRequestEvents, + typ: MergeRequestEventPayload{}, + filename: "../testdata/gitlab/merge-request-event.json", + }, + } + for _, tt := range tests { + tc := tt + client := &http.Client{} + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + payload, err := os.Open(tc.filename) + assert.NoError(err) + defer func() { + _ = payload.Close() + }() + + var parseError error + var results interface{} + server := newServer(func(w http.ResponseWriter, r *http.Request) { + results, parseError = hook.Parse(r, tc.event) + }) + defer server.Close() + req, err := http.NewRequest(http.MethodPost, server.URL+path, payload) + assert.NoError(err) + req.Header.Set("Content-Type", "application/json") + req.Header.Set("X-Gitlab-Token", "sampleToken!") + req.Header.Set("X-Gitlab-Event", "System Hook") + + resp, err := client.Do(req) + assert.NoError(err) + assert.Equal(http.StatusOK, resp.StatusCode) + assert.NoError(parseError) + assert.Equal(reflect.TypeOf(tc.typ), reflect.TypeOf(results)) + }) + } +} diff --git a/gitlab/payload.go b/gitlab/payload.go index c4b9238..f5a1baa 100644 --- a/gitlab/payload.go +++ b/gitlab/payload.go @@ -148,6 +148,11 @@ type BuildEventPayload struct { Repository Repository `json:"repository"` } +// SystemHookPayload contains the ObjectKind to match with real hook events +type SystemHookPayload struct { + ObjectKind string `json:"object_kind"` +} + // Issue contains all of the GitLab issue information type Issue struct { ID int64 `json:"id"`