Merge pull request #54 from Munsio/feature/gitlab-system-hooks

* Add gitlab system hook parsing

If you use gitlab system hooks you can now send push/tag_push/merge_request events

* Declare err and payload to avoid scope issues

* Refactor the whole event switch into own function

* Remove unnecessary comment

* Give the user the choice to parse SystemHooks
This commit is contained in:
Dean Karn
2019-03-31 04:54:13 -07:00
committed by GitHub
3 changed files with 117 additions and 27 deletions
+51 -23
View File
@@ -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"
SystemHookEvents 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
@@ -83,6 +89,14 @@ func (hook Webhook) Parse(r *http.Request, events ...Event) (interface{}, error)
return nil, ErrInvalidHTTPMethod
}
// If we have a Secret set, we should check the MAC
if len(hook.secret) > 0 {
signature := r.Header.Get("X-Gitlab-Token")
if signature != hook.secret {
return nil, ErrGitLabTokenVerificationFailed
}
}
event := r.Header.Get("X-Gitlab-Event")
if len(event) == 0 {
return nil, ErrMissingGitLabEventHeader
@@ -90,6 +104,16 @@ func (hook Webhook) Parse(r *http.Request, events ...Event) (interface{}, error)
gitLabEvent := Event(event)
payload, err := ioutil.ReadAll(r.Body)
if err != nil || len(payload) == 0 {
return nil, ErrParsingPayload
}
return eventParsing(gitLabEvent, events, payload)
}
func eventParsing(gitLabEvent Event, events []Event, payload []byte) (interface{}, error) {
var found bool
for _, evt := range events {
if evt == gitLabEvent {
@@ -102,64 +126,68 @@ 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
}
// If we have a Secret set, we should check the MAC
if len(hook.secret) > 0 {
signature := r.Header.Get("X-Gitlab-Token")
if signature != hook.secret {
return nil, ErrGitLabTokenVerificationFailed
}
}
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
case SystemHookEvents:
var pl SystemHookPayload
err := json.Unmarshal([]byte(payload), &pl)
if err != nil {
return nil, err
}
switch pl.ObjectKind {
case objectPush:
return eventParsing(PushEvents, events, payload)
case objectTag:
return eventParsing(TagEvents, events, payload)
case objectMergeRequest:
return eventParsing(MergeRequestEvents, events, payload)
default:
return nil, fmt.Errorf("unknown system hook event %s", gitLabEvent)
}
default:
return nil, fmt.Errorf("unknown event %s", gitLabEvent)
}
+61 -4
View File
@@ -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, SystemHookEvents, 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))
})
}
}
+5
View File
@@ -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"`