Skip to content
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

feat: implement gitlab release webhook #174

Merged
merged 3 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
79 changes: 42 additions & 37 deletions gitlab/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,43 +25,44 @@ var (

// GitLab hook types
const (
PushEvents Event = "Push Hook"
TagEvents Event = "Tag Push Hook"
IssuesEvents Event = "Issue Hook"
ConfidentialIssuesEvents Event = "Confidential Issue Hook"
CommentEvents Event = "Note Hook"
ConfidentialCommentEvents Event = "Confidential Note Hook"
MergeRequestEvents Event = "Merge Request Hook"
WikiPageEvents Event = "Wiki Page Hook"
PipelineEvents Event = "Pipeline Hook"
BuildEvents Event = "Build Hook"
JobEvents Event = "Job Hook"
DeploymentEvents Event = "Deployment Hook"
SystemHookEvents Event = "System Hook"
objectPush string = "push"
objectTag string = "tag_push"
objectMergeRequest string = "merge_request"
objectBuild string = "build"
eventProjectCreate string = "project_create"
eventProjectDestroy string = "project_destroy"
eventProjectRename string = "project_rename"
eventProjectTransfer string = "project_transfer"
eventProjectUpdate string = "project_update"
eventUserAddToTeam string = "user_add_to_team"
eventUserRemoveFromTeam string = "user_remove_from_team"
eventUserUpdateForTeam string = "user_update_for_team"
eventUserCreate string = "user_create"
eventUserDestroy string = "user_destroy"
eventUserFailedLogin string = "user_failed_login"
eventUserRename string = "user_rename"
eventKeyCreate string = "key_create"
eventKeyDestroy string = "key_destroy"
eventGroupCreate string = "group_create"
eventGroupDestroy string = "group_destroy"
eventGroupRename string = "group_rename"
eventUserAddToGroup string = "user_add_to_group"
eventUserRemoveFromGroup string = "user_remove_from_group"
eventUserUpdateForGroup string = "user_update_for_group"
PushEvents Event = "Push Hook"
TagEvents Event = "Tag Push Hook"
IssuesEvents Event = "Issue Hook"
ConfidentialIssuesEvents Event = "Confidential Issue Hook"
CommentEvents Event = "Note Hook"
ConfidentialCommentEvents Event = "Confidential Note Hook"
MergeRequestEvents Event = "Merge Request Hook"
WikiPageEvents Event = "Wiki Page Hook"
PipelineEvents Event = "Pipeline Hook"
BuildEvents Event = "Build Hook"
JobEvents Event = "Job Hook"
DeploymentEvents Event = "Deployment Hook"
ReleaseEvents Event = "Release Hook"
SystemHookEvents Event = "System Hook"
objectPush string = "push"
objectTag string = "tag_push"
objectMergeRequest string = "merge_request"
objectBuild string = "build"
eventProjectCreate string = "project_create"
eventProjectDestroy string = "project_destroy"
eventProjectRename string = "project_rename"
eventProjectTransfer string = "project_transfer"
eventProjectUpdate string = "project_update"
eventUserAddToTeam string = "user_add_to_team"
eventUserRemoveFromTeam string = "user_remove_from_team"
eventUserUpdateForTeam string = "user_update_for_team"
eventUserCreate string = "user_create"
eventUserDestroy string = "user_destroy"
eventUserFailedLogin string = "user_failed_login"
eventUserRename string = "user_rename"
eventKeyCreate string = "key_create"
eventKeyDestroy string = "key_destroy"
eventGroupCreate string = "group_create"
eventGroupDestroy string = "group_destroy"
eventGroupRename string = "group_rename"
eventUserAddToGroup string = "user_add_to_group"
eventUserRemoveFromGroup string = "user_remove_from_group"
eventUserUpdateForGroup string = "user_update_for_group"
)

// Option is a configuration option for the webhook
Expand Down Expand Up @@ -354,6 +355,10 @@ func eventParsing(gitLabEvent Event, events []Event, payload []byte) (interface{
return nil, fmt.Errorf("unknown system hook event %s", gitLabEvent)
}
}
case ReleaseEvents:
var pl ReleaseEventPayload
err := json.Unmarshal([]byte(payload), &pl)
return pl, err
default:
return nil, fmt.Errorf("unknown event %s", gitLabEvent)
}
Expand Down
201 changes: 113 additions & 88 deletions gitlab/gitlab_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,23 +94,27 @@ func TestBadRequests(t *testing.T) {
for _, tt := range tests {
tc := tt
client := &http.Client{}
t.Run(tt.name, func(t *testing.T) {
xNok marked this conversation as resolved.
Show resolved Hide resolved
t.Parallel()
var parseError error
server := newServer(func(w http.ResponseWriter, r *http.Request) {
_, parseError = hook.Parse(r, tc.event)
})
defer server.Close()
req, err := http.NewRequest(http.MethodPost, server.URL+path, tc.payload)
assert.NoError(err)
req.Header = tc.headers
req.Header.Set("Content-Type", "application/json")
t.Run(
tt.name, func(t *testing.T) {
t.Parallel()
var parseError error
server := newServer(
func(w http.ResponseWriter, r *http.Request) {
_, parseError = hook.Parse(r, tc.event)
},
)
defer server.Close()
req, err := http.NewRequest(http.MethodPost, server.URL+path, tc.payload)
assert.NoError(err)
req.Header = tc.headers
req.Header.Set("Content-Type", "application/json")

resp, err := client.Do(req)
assert.NoError(err)
assert.Equal(http.StatusOK, resp.StatusCode)
assert.Error(parseError)
})
resp, err := client.Do(req)
assert.NoError(err)
assert.Equal(http.StatusOK, resp.StatusCode)
assert.Error(parseError)
},
)
}
}

Expand Down Expand Up @@ -249,37 +253,50 @@ func TestWebhooks(t *testing.T) {
"X-Gitlab-Event": []string{"Deployment Hook"},
},
},
{
name: "ReleaseEvent",
event: ReleaseEvents,
typ: ReleaseEventPayload{},
filename: "../testdata/gitlab/release-event.json",
headers: http.Header{
"X-Gitlab-Event": []string{"Release Hook"},
},
},
}

for _, tt := range tests {
tc := tt
client := &http.Client{}
t.Run(tt.name, func(t *testing.T) {
xNok marked this conversation as resolved.
Show resolved Hide resolved
t.Parallel()
payload, err := os.Open(tc.filename)
assert.NoError(err)
defer func() {
_ = payload.Close()
}()
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 = tc.headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Gitlab-Token", "sampleToken!")
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 = tc.headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Gitlab-Token", "sampleToken!")

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))
})
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))
},
)
}
}

Expand All @@ -306,32 +323,36 @@ func TestJobHooks(t *testing.T) {
for _, tt := range tests {
tc := tt
client := &http.Client{}
t.Run(tt.name, func(t *testing.T) {
xNok marked this conversation as resolved.
Show resolved Hide resolved
t.Parallel()
payload, err := os.Open(tc.filename)
assert.NoError(err)
defer func() {
_ = payload.Close()
}()
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.events...)
})
defer server.Close()
req, err := http.NewRequest(http.MethodPost, server.URL+path, payload)
assert.NoError(err)
req.Header = tc.headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Gitlab-Token", "sampleToken!")
var parseError error
var results interface{}
server := newServer(
func(w http.ResponseWriter, r *http.Request) {
results, parseError = hook.Parse(r, tc.events...)
},
)
defer server.Close()
req, err := http.NewRequest(http.MethodPost, server.URL+path, payload)
assert.NoError(err)
req.Header = tc.headers
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-Gitlab-Token", "sampleToken!")

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))
})
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))
},
)
}
}

Expand Down Expand Up @@ -485,31 +506,35 @@ func TestSystemHooks(t *testing.T) {
for _, tt := range tests {
tc := tt
client := &http.Client{}
t.Run(tt.name, func(t *testing.T) {
xNok marked this conversation as resolved.
Show resolved Hide resolved
t.Parallel()
payload, err := os.Open(tc.filename)
assert.NoError(err)
defer func() {
_ = payload.Close()
}()
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")
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))
})
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))
},
)
}
}
37 changes: 37 additions & 0 deletions gitlab/payload.go
Original file line number Diff line number Diff line change
Expand Up @@ -485,6 +485,43 @@ type GroupMemberUpdatedEventPayload struct {
UserID int64 `json:"user_id"`
}

// ReleaseEventPayload contains the information about GitLab's release event
type ReleaseEventPayload struct {
ID int `json:"id"`
CreatedAt customTime `json:"created_at"`
Description string `json:"description"`
Name string `json:"name"`
ReleasedAt customTime `json:"released_at"`
Tag string `json:"tag"`
ObjectKind string `json:"object_kind"`
Project Project `json:"project"`
URL string `json:"url"`
Action string `json:"action"`
Assets Assets `json:"assets"`
}

// Assets represent artefacts and links associated to a release
type Assets struct {
Count int `json:"count"`
Links []Link `json:"links"`
Sources []AssetSource `json:"sources"`
}

// Link represent a generic html link
type Link struct {
ID int `json:"id"`
External bool `json:"external"`
LinkType string `json:"link_type"`
Name string `json:"name"`
URL string `json:"url"`
}

// AssetSource represent the download url for an asset
type AssetSource struct {
Format string `json:"format"`
URL string `json:"url"`
}

// Issue contains all of the GitLab issue information
type Issue struct {
ID int64 `json:"id"`
Expand Down
Loading