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:
+51
-23
@@ -17,6 +17,7 @@ var (
|
|||||||
ErrGitLabTokenVerificationFailed = errors.New("X-Gitlab-Token validation failed")
|
ErrGitLabTokenVerificationFailed = errors.New("X-Gitlab-Token validation failed")
|
||||||
ErrEventNotFound = errors.New("event not defined to be parsed")
|
ErrEventNotFound = errors.New("event not defined to be parsed")
|
||||||
ErrParsingPayload = errors.New("error parsing payload")
|
ErrParsingPayload = errors.New("error parsing payload")
|
||||||
|
ErrParsingSystemPayload = errors.New("error parsing system payload")
|
||||||
// ErrHMACVerificationFailed = errors.New("HMAC verification failed")
|
// ErrHMACVerificationFailed = errors.New("HMAC verification failed")
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,6 +32,11 @@ const (
|
|||||||
WikiPageEvents Event = "Wiki Page Hook"
|
WikiPageEvents Event = "Wiki Page Hook"
|
||||||
PipelineEvents Event = "Pipeline Hook"
|
PipelineEvents Event = "Pipeline Hook"
|
||||||
BuildEvents Event = "Build 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
|
// Option is a configuration option for the webhook
|
||||||
@@ -55,7 +61,7 @@ type Webhook struct {
|
|||||||
secret string
|
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
|
type Event string
|
||||||
|
|
||||||
// New creates and returns a WebHook instance denoted by the Provider type
|
// 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
|
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")
|
event := r.Header.Get("X-Gitlab-Event")
|
||||||
if len(event) == 0 {
|
if len(event) == 0 {
|
||||||
return nil, ErrMissingGitLabEventHeader
|
return nil, ErrMissingGitLabEventHeader
|
||||||
@@ -90,6 +104,16 @@ func (hook Webhook) Parse(r *http.Request, events ...Event) (interface{}, error)
|
|||||||
|
|
||||||
gitLabEvent := Event(event)
|
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
|
var found bool
|
||||||
for _, evt := range events {
|
for _, evt := range events {
|
||||||
if evt == gitLabEvent {
|
if evt == gitLabEvent {
|
||||||
@@ -102,64 +126,68 @@ func (hook Webhook) Parse(r *http.Request, events ...Event) (interface{}, error)
|
|||||||
return nil, ErrEventNotFound
|
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 {
|
switch gitLabEvent {
|
||||||
case PushEvents:
|
case PushEvents:
|
||||||
var pl PushEventPayload
|
var pl PushEventPayload
|
||||||
err = json.Unmarshal([]byte(payload), &pl)
|
err := json.Unmarshal([]byte(payload), &pl)
|
||||||
return pl, err
|
return pl, err
|
||||||
|
|
||||||
case TagEvents:
|
case TagEvents:
|
||||||
var pl TagEventPayload
|
var pl TagEventPayload
|
||||||
err = json.Unmarshal([]byte(payload), &pl)
|
err := json.Unmarshal([]byte(payload), &pl)
|
||||||
return pl, err
|
return pl, err
|
||||||
|
|
||||||
case ConfidentialIssuesEvents:
|
case ConfidentialIssuesEvents:
|
||||||
var pl ConfidentialIssueEventPayload
|
var pl ConfidentialIssueEventPayload
|
||||||
err = json.Unmarshal([]byte(payload), &pl)
|
err := json.Unmarshal([]byte(payload), &pl)
|
||||||
return pl, err
|
return pl, err
|
||||||
|
|
||||||
case IssuesEvents:
|
case IssuesEvents:
|
||||||
var pl IssueEventPayload
|
var pl IssueEventPayload
|
||||||
err = json.Unmarshal([]byte(payload), &pl)
|
err := json.Unmarshal([]byte(payload), &pl)
|
||||||
return pl, err
|
return pl, err
|
||||||
|
|
||||||
case CommentEvents:
|
case CommentEvents:
|
||||||
var pl CommentEventPayload
|
var pl CommentEventPayload
|
||||||
err = json.Unmarshal([]byte(payload), &pl)
|
err := json.Unmarshal([]byte(payload), &pl)
|
||||||
return pl, err
|
return pl, err
|
||||||
|
|
||||||
case MergeRequestEvents:
|
case MergeRequestEvents:
|
||||||
var pl MergeRequestEventPayload
|
var pl MergeRequestEventPayload
|
||||||
err = json.Unmarshal([]byte(payload), &pl)
|
err := json.Unmarshal([]byte(payload), &pl)
|
||||||
return pl, err
|
return pl, err
|
||||||
|
|
||||||
case WikiPageEvents:
|
case WikiPageEvents:
|
||||||
var pl WikiPageEventPayload
|
var pl WikiPageEventPayload
|
||||||
err = json.Unmarshal([]byte(payload), &pl)
|
err := json.Unmarshal([]byte(payload), &pl)
|
||||||
return pl, err
|
return pl, err
|
||||||
|
|
||||||
case PipelineEvents:
|
case PipelineEvents:
|
||||||
var pl PipelineEventPayload
|
var pl PipelineEventPayload
|
||||||
err = json.Unmarshal([]byte(payload), &pl)
|
err := json.Unmarshal([]byte(payload), &pl)
|
||||||
return pl, err
|
return pl, err
|
||||||
|
|
||||||
case BuildEvents:
|
case BuildEvents:
|
||||||
var pl BuildEventPayload
|
var pl BuildEventPayload
|
||||||
err = json.Unmarshal([]byte(payload), &pl)
|
err := json.Unmarshal([]byte(payload), &pl)
|
||||||
return pl, err
|
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:
|
default:
|
||||||
return nil, fmt.Errorf("unknown event %s", gitLabEvent)
|
return nil, fmt.Errorf("unknown event %s", gitLabEvent)
|
||||||
}
|
}
|
||||||
|
|||||||
+61
-4
@@ -2,15 +2,13 @@ package gitlab
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
|
||||||
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"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))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -148,6 +148,11 @@ type BuildEventPayload struct {
|
|||||||
Repository Repository `json:"repository"`
|
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
|
// Issue contains all of the GitLab issue information
|
||||||
type Issue struct {
|
type Issue struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
|
|||||||
Reference in New Issue
Block a user