Skip to content

Commit 41bbe94

Browse files
committed
Merge master into prod, release: v1.1.3
2 parents 1adb810 + dc4b46c commit 41bbe94

File tree

5 files changed

+423
-5
lines changed

5 files changed

+423
-5
lines changed

README.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,10 @@ For more information check the *How to add support for a new Provider* section.
1818
* handled on the path: `/h/bitbucket-v2/BITRISE-APP-SLUG/BITRISE-APP-API-TOKEN`
1919
* [Slack](https://slack.com) (both outgoing webhooks & slash commands)
2020
* handled on the path: `/h/slack/BITRISE-APP-SLUG/BITRISE-APP-API-TOKEN`
21-
22-
Work in progress:
23-
2421
* [Visual Studio Team Services](https://www.visualstudio.com/products/visual-studio-team-services-vs)
22+
* handled on the path: `/h/visualstudio/BITRISE-APP-SLUG/BITRISE-APP-API-TOKEN`
2523
* [GitLab](https://gitlab.com)
24+
* handled on the path: `/h/gitlab/BITRISE-APP-SLUG/BITRISE-APP-API-TOKEN`
2625

2726

2827
### GitHub - setup & usage:
@@ -62,6 +61,23 @@ a [Bitbucket](https://bitbucket.org) *repository*.
6261
That's all, the next time you push code (into your repository) a build will be triggered.
6362

6463

64+
### GitLab - setup & usage:
65+
66+
All you have to do is register your `bitrise-webhooks` URL for
67+
a [GitLab](https://gitlab.com) *project*.
68+
69+
1. Open your *project* on [GitLab.com](https://gitlab.com)
70+
2. Go to `Settings` of the *project*
71+
3. Select `Web Hooks`
72+
4. Specify the `bitrise-webhooks` URL (`.../h/gitlab/BITRISE-APP-SLUG/BITRISE-APP-API-TOKEN`) in the `URL` field
73+
5. In the *Triggers* section select `Push events`
74+
* Right now `bitrise-webhooks` only supports the *Push events* trigger for
75+
GitLab Webhooks.
76+
6. Click `Add Web Hook`
77+
78+
That's all, the next time you push code (into your repository) a build will be triggered.
79+
80+
6581
### Visual Studio Online / Visual Studio Team Services - setup & usage:
6682

6783
All you have to do is register your `bitrise-webhooks` URL for
@@ -316,4 +332,4 @@ response provider will be used.
316332

317333
* Re-try handling
318334
* Bitbucket V1 (aka "Services" on the Bitbucket web UI) - not sure whether we should support this,
319-
it'll be deprecated in the future, and we already support the newer, V2 webhooks.
335+
it's already kind of deprecated, and we already support the newer, V2 webhooks.

service/hook/endpoint.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/bitrise-io/bitrise-webhooks/service/hook/bitbucketv2"
1414
hookCommon "github.com/bitrise-io/bitrise-webhooks/service/hook/common"
1515
"github.com/bitrise-io/bitrise-webhooks/service/hook/github"
16+
"github.com/bitrise-io/bitrise-webhooks/service/hook/gitlab"
1617
"github.com/bitrise-io/bitrise-webhooks/service/hook/slack"
1718
"github.com/bitrise-io/bitrise-webhooks/service/hook/visualstudioteamservices"
1819
"github.com/gorilla/mux"
@@ -24,6 +25,7 @@ func supportedProviders() map[string]hookCommon.Provider {
2425
"bitbucket-v2": bitbucketv2.HookProvider{},
2526
"slack": slack.HookProvider{},
2627
"visualstudio": visualstudioteamservices.HookProvider{},
28+
"gitlab": gitlab.HookProvider{},
2729
}
2830
}
2931

service/hook/gitlab/gitlab.go

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
package gitlab
2+
3+
// # Infos / notes:
4+
//
5+
// ## Webhook calls
6+
//
7+
// Official API docs: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/web_hooks/web_hooks.md
8+
//
9+
// ### Code Push
10+
//
11+
// A code push webhook is sent with the header: `X-Gitlab-Event: Push Hook`.
12+
// Official docs: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/web_hooks/web_hooks.md#push-events
13+
//
14+
// GitLab sends push webhooks for every branch separately. Even if you
15+
// push to two different branches at the same time (git push --all) it'll
16+
// trigger two webhook calls, one for each branch.
17+
//
18+
// Commits are grouped in the webhook - if you push more than one commit
19+
// to a single branch it'll be included in a single webhook call, including
20+
// all of the commits.
21+
//
22+
// The latest commit's hash is included as the "checkout_sha" parameter
23+
// in the webhook. As we don't want to trigger build for every commit
24+
// which is related to a single branch we will only handle the commit
25+
// with the hash / id specified as the "checkout_sha".
26+
//
27+
28+
import (
29+
"encoding/json"
30+
"errors"
31+
"fmt"
32+
"net/http"
33+
"strings"
34+
35+
"github.com/bitrise-io/bitrise-webhooks/bitriseapi"
36+
hookCommon "github.com/bitrise-io/bitrise-webhooks/service/hook/common"
37+
"github.com/bitrise-io/go-utils/httputil"
38+
)
39+
40+
// --------------------------
41+
// --- Webhook Data Model ---
42+
43+
const (
44+
pushEventID = "Push Hook"
45+
)
46+
47+
// CommitModel ...
48+
type CommitModel struct {
49+
CommitHash string `json:"id"`
50+
CommitMessage string `json:"message"`
51+
}
52+
53+
// CodePushEventModel ...
54+
type CodePushEventModel struct {
55+
ObjectKind string `json:"object_kind"`
56+
Ref string `json:"ref"`
57+
CheckoutSHA string `json:"checkout_sha"`
58+
Commits []CommitModel `json:"commits"`
59+
}
60+
61+
// ---------------------------------------
62+
// --- Webhook Provider Implementation ---
63+
64+
// HookProvider ...
65+
type HookProvider struct{}
66+
67+
func detectContentTypeAndEventID(header http.Header) (string, string, error) {
68+
contentType, err := httputil.GetSingleValueFromHeader("Content-Type", header)
69+
if err != nil {
70+
return "", "", fmt.Errorf("Issue with Content-Type Header: %s", err)
71+
}
72+
73+
eventID, err := httputil.GetSingleValueFromHeader("X-Gitlab-Event", header)
74+
if err != nil {
75+
return "", "", fmt.Errorf("Issue with X-Gitlab-Event Header: %s", err)
76+
}
77+
78+
return contentType, eventID, nil
79+
}
80+
81+
func transformCodePushEvent(codePushEvent CodePushEventModel) hookCommon.TransformResultModel {
82+
if !strings.HasPrefix(codePushEvent.Ref, "refs/heads/") {
83+
return hookCommon.TransformResultModel{
84+
Error: fmt.Errorf("Ref (%s) is not a head ref", codePushEvent.Ref),
85+
ShouldSkip: true,
86+
}
87+
}
88+
branch := strings.TrimPrefix(codePushEvent.Ref, "refs/heads/")
89+
90+
lastCommit := CommitModel{}
91+
isLastCommitFound := false
92+
for _, aCommit := range codePushEvent.Commits {
93+
if aCommit.CommitHash == codePushEvent.CheckoutSHA {
94+
isLastCommitFound = true
95+
lastCommit = aCommit
96+
break
97+
}
98+
}
99+
100+
if !isLastCommitFound {
101+
return hookCommon.TransformResultModel{
102+
Error: errors.New("The commit specified by 'checkout_sha' was not included in the 'commits' array - no match found"),
103+
}
104+
}
105+
106+
return hookCommon.TransformResultModel{
107+
TriggerAPIParams: []bitriseapi.TriggerAPIParamsModel{
108+
{
109+
BuildParams: bitriseapi.BuildParamsModel{
110+
CommitHash: lastCommit.CommitHash,
111+
CommitMessage: lastCommit.CommitMessage,
112+
Branch: branch,
113+
},
114+
},
115+
},
116+
}
117+
}
118+
119+
// TransformRequest ...
120+
func (hp HookProvider) TransformRequest(r *http.Request) hookCommon.TransformResultModel {
121+
contentType, eventID, err := detectContentTypeAndEventID(r.Header)
122+
if err != nil {
123+
return hookCommon.TransformResultModel{
124+
Error: fmt.Errorf("Issue with Headers: %s", err),
125+
}
126+
}
127+
128+
if contentType != "application/json" {
129+
return hookCommon.TransformResultModel{
130+
Error: fmt.Errorf("Content-Type is not supported: %s", contentType),
131+
}
132+
}
133+
134+
if eventID != "Push Hook" {
135+
// Unsupported Event
136+
return hookCommon.TransformResultModel{
137+
Error: fmt.Errorf("Unsupported Webhook event: %s", eventID),
138+
}
139+
}
140+
141+
if r.Body == nil {
142+
return hookCommon.TransformResultModel{
143+
Error: fmt.Errorf("Failed to read content of request body: no or empty request body"),
144+
}
145+
}
146+
147+
// code push
148+
var codePushEvent CodePushEventModel
149+
if contentType == "application/json" {
150+
if err := json.NewDecoder(r.Body).Decode(&codePushEvent); err != nil {
151+
return hookCommon.TransformResultModel{Error: fmt.Errorf("Failed to parse request body: %s", err)}
152+
}
153+
}
154+
return transformCodePushEvent(codePushEvent)
155+
}

0 commit comments

Comments
 (0)