Skip to content

Commit 1cc7206

Browse files
authored
Merge pull request #45 from fabiang/bitbucket-server
Added support for Bitbucket Server
2 parents 577ab2b + cf0b8bb commit 1cc7206

File tree

7 files changed

+353
-2
lines changed

7 files changed

+353
-2
lines changed

docker/config.docker.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,12 @@ integrations:
8686
https_format: https://bitbucket.org/{$full_name}.git
8787
# SSH URL format, Full name will be something like Clivern/Rabbit
8888
ssh_format: [email protected]:{$full_name}.git
89+
bitbucket_server:
90+
# Webhook URI (Full URL will be app.domain + webhook_uri)
91+
webhook_uri: /webhook/bitbucket-server
92+
# whether to use ssh or https to clone
93+
clone_with: ${RABBIT_INTEGRATION_BITBUCKET_SERVER_CLONE_WITH:-https}
94+
# HTTPS URL format, Full name will be something like Clivern/Rabbit
95+
https_format: https://${RABBIT_INTEGRATION_BITBUCKET_SERVER_USER:-git}@${RABBIT_INTEGRATION_BITBUCKET_SERVER_HOSTNAME}/scm/{$project.key}/{$repository.slug}.git
96+
# SSH URL format, Full name will be something like Clivern/Rabbit
97+
ssh_format: ssh://${RABBIT_INTEGRATION_BITBUCKET_SERVER_USER:-git}@${RABBIT_INTEGRATION_BITBUCKET_SERVER_HOSTNAME}:${RABBIT_INTEGRATION_BITBUCKET_SERVER_SSH_PORT:-2200}/{$project.key}/{$repository.slug}.git

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
1010
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
1111
github.com/clivern/hippo v1.5.1 h1:O7AOJ3smbff9BqGA1Hypanu2aUEw7RHOEHgu1i6pMYM=
1212
github.com/clivern/hippo v1.5.1/go.mod h1:aGxjMpUlaGixQodYdrmKQMjpoItItFUjT/VILUETQG4=
13+
github.com/clivern/rabbit v0.0.0-20190619212833-cf34bb09610e h1:P3h9/hJYERiqY9Ly8sgt4cIBAmQ37yvkXWojEQBsvJ4=
14+
github.com/clivern/rabbit v0.0.0-20190619212833-cf34bb09610e/go.mod h1:ndBCWwd7/iTMzRd/PxPeM2D7njAP2yjj2vwRzk2muGo=
1315
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
1416
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
1517
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=

internal/app/controller/bitbucket_listener.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ package controller
66

77
import (
88
"fmt"
9+
"net/http"
10+
"strings"
11+
912
"github.com/clivern/hippo"
1013
"github.com/clivern/rabbit/internal/app/model"
1114
"github.com/clivern/rabbit/pkg"
1215
"github.com/gin-gonic/gin"
1316
"github.com/spf13/viper"
1417
"go.uber.org/zap"
15-
"net/http"
16-
"strings"
1718
)
1819

1920
// BitbucketListener controller
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package controller
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"strings"
7+
8+
"github.com/clivern/hippo"
9+
"github.com/clivern/rabbit/internal/app/model"
10+
"github.com/clivern/rabbit/pkg"
11+
"github.com/gin-gonic/gin"
12+
"github.com/spf13/viper"
13+
"go.uber.org/zap"
14+
)
15+
16+
func BitbucketServerListener(c *gin.Context, messages chan<- string) {
17+
rawBody, _ := c.GetRawData()
18+
body := string(rawBody)
19+
20+
logger, _ := hippo.NewLogger(
21+
viper.GetString("log.level"),
22+
viper.GetString("log.format"),
23+
[]string{viper.GetString("log.output")},
24+
)
25+
26+
defer logger.Sync()
27+
28+
pushEvent := pkg.BitbucketServerPushEvent{}
29+
ok, err := pushEvent.LoadFromJSON(rawBody)
30+
31+
if err != nil {
32+
logger.Info(
33+
fmt.Sprintf(
34+
`Invalid event received %s, error: %s`,
35+
body,
36+
err,
37+
),
38+
zap.String("CorrelationID", c.Request.Header.Get("X-Correlation-ID")),
39+
)
40+
41+
c.JSON(http.StatusForbidden, gin.H{
42+
"status": "Oops!",
43+
})
44+
return
45+
}
46+
47+
if !ok {
48+
c.JSON(http.StatusForbidden, gin.H{
49+
"status": "Oops!",
50+
})
51+
return
52+
}
53+
54+
if len(pushEvent.Changes) <= 0 {
55+
c.JSON(http.StatusOK, gin.H{
56+
"status": "Nice, Skip!",
57+
})
58+
return
59+
}
60+
61+
var cloneFormat string
62+
var href string
63+
64+
// Push event received
65+
if viper.GetString("integrations.bitbucket_server.clone_with") == "ssh" {
66+
cloneFormat = viper.GetString("integrations.bitbucket_server.ssh_format")
67+
} else {
68+
cloneFormat = viper.GetString("integrations.bitbucket_server.https_format")
69+
}
70+
71+
href = strings.ReplaceAll(
72+
cloneFormat,
73+
"{$project.key}",
74+
strings.ToLower(pushEvent.Repository.Project.Key),
75+
)
76+
77+
href = strings.ReplaceAll(
78+
href,
79+
"{$repository.slug}",
80+
strings.ToLower(pushEvent.Repository.Slug),
81+
)
82+
83+
logger.Info(fmt.Sprintf("Clone URL is: %s", href))
84+
85+
for _, changes := range pushEvent.Changes {
86+
ref := changes.Ref
87+
if ref.Type == pkg.BitbucketServerChangeTypeTag {
88+
releaseRequest := model.ReleaseRequest{
89+
Name: pushEvent.Repository.Name,
90+
URL: href,
91+
Version: ref.DisplayID,
92+
}
93+
94+
passToWorker(
95+
c,
96+
messages,
97+
logger,
98+
releaseRequest,
99+
)
100+
}
101+
}
102+
}
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package controller
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
7+
"github.com/clivern/hippo"
8+
"github.com/clivern/rabbit/internal/app/model"
9+
"github.com/clivern/rabbit/pkg"
10+
"github.com/gin-gonic/gin"
11+
"github.com/spf13/viper"
12+
"go.uber.org/zap"
13+
)
14+
15+
func passToWorker(c *gin.Context, messages chan<- string, logger *zap.Logger, releaseRequest model.ReleaseRequest) {
16+
validate := pkg.Validator{}
17+
18+
if validate.IsEmpty(releaseRequest.Name) {
19+
c.JSON(http.StatusBadRequest, gin.H{
20+
"status": "error",
21+
"error": "Repository name is required",
22+
})
23+
return
24+
}
25+
26+
if validate.IsEmpty(releaseRequest.URL) {
27+
c.JSON(http.StatusBadRequest, gin.H{
28+
"status": "error",
29+
"error": "Repository url is required",
30+
})
31+
return
32+
}
33+
34+
if validate.IsEmpty(releaseRequest.Version) {
35+
c.JSON(http.StatusBadRequest, gin.H{
36+
"status": "error",
37+
"error": "Repository version is required",
38+
})
39+
return
40+
}
41+
42+
requestBody, err := releaseRequest.ConvertToJSON()
43+
44+
if err != nil {
45+
logger.Error(fmt.Sprintf(
46+
`Error while converting request body to json %s`,
47+
err.Error(),
48+
), zap.String("CorrelationID", c.Request.Header.Get("X-Correlation-ID")))
49+
50+
c.JSON(http.StatusBadRequest, gin.H{
51+
"status": "error",
52+
"error": "Invalid request",
53+
})
54+
return
55+
}
56+
57+
if viper.GetString("broker.driver") == "redis" {
58+
driver := hippo.NewRedisDriver(
59+
viper.GetString("redis.addr"),
60+
viper.GetString("redis.password"),
61+
viper.GetInt("redis.db"),
62+
)
63+
64+
// connect to redis server
65+
ok, err := driver.Connect()
66+
67+
if err != nil {
68+
logger.Error(fmt.Sprintf(
69+
`Unable to connect to redis server %s`,
70+
err.Error(),
71+
), zap.String("CorrelationID", c.Request.Header.Get("X-Correlation-ID")))
72+
73+
c.JSON(http.StatusServiceUnavailable, gin.H{
74+
"status": "error",
75+
"error": "Internal server error",
76+
})
77+
return
78+
}
79+
80+
if !ok {
81+
logger.Error(
82+
`Unable to connect to redis server`,
83+
zap.String("CorrelationID", c.Request.Header.Get("X-Correlation-ID")),
84+
)
85+
86+
c.JSON(http.StatusServiceUnavailable, gin.H{
87+
"status": "error",
88+
"error": "Internal server error",
89+
})
90+
return
91+
}
92+
93+
// ping check
94+
ok, err = driver.Ping()
95+
96+
if err != nil {
97+
logger.Error(fmt.Sprintf(
98+
`Unable to ping redis server %s`,
99+
err.Error(),
100+
), zap.String("CorrelationID", c.Request.Header.Get("X-Correlation-ID")))
101+
102+
c.JSON(http.StatusServiceUnavailable, gin.H{
103+
"status": "error",
104+
"error": "Internal server error",
105+
})
106+
return
107+
}
108+
109+
if !ok {
110+
logger.Error(
111+
`Unable to ping redis server`,
112+
zap.String("CorrelationID", c.Request.Header.Get("X-Correlation-ID")),
113+
)
114+
115+
c.JSON(http.StatusServiceUnavailable, gin.H{
116+
"status": "error",
117+
"error": "Internal server error",
118+
})
119+
return
120+
}
121+
122+
logger.Info(fmt.Sprintf(
123+
`Send request [%s] to workers`,
124+
requestBody,
125+
), zap.String("CorrelationID", c.Request.Header.Get("X-Correlation-ID")))
126+
127+
driver.Publish(
128+
viper.GetString("broker.redis.channel"),
129+
requestBody,
130+
)
131+
} else {
132+
logger.Info(fmt.Sprintf(
133+
`Send request [%s] to workers`,
134+
requestBody,
135+
), zap.String("CorrelationID", c.Request.Header.Get("X-Correlation-ID")))
136+
137+
messages <- requestBody
138+
}
139+
140+
c.JSON(http.StatusOK, gin.H{
141+
"status": "Nice!",
142+
})
143+
}

pkg/bitbucket_server_events.go

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package pkg
2+
3+
import (
4+
"encoding/json"
5+
"strings"
6+
"time"
7+
)
8+
9+
const (
10+
bitbucketDateFormat string = "2006-01-02T15:04:05+0000"
11+
BitbucketServerChangeTypeTag = "TAG"
12+
)
13+
14+
type BitbucketServerPushEvent struct {
15+
EventKey string `json:"eventKey"`
16+
Date bitbucketServerDate `json:"date"`
17+
Actor bitbucketUser `json:"actor"`
18+
Repository struct {
19+
Slug string `json:"slug"`
20+
ID int `json:"id"`
21+
Name string `json:"name"`
22+
SCMID string `json:"scmId"`
23+
State string `json:"state"`
24+
StateMessage string `json:"statusMessage"`
25+
Forkable bool `json:"forkable"`
26+
Project struct {
27+
Key string `json:"key"`
28+
ID int `json:"id"`
29+
Name string `json:"name"`
30+
Type string `json:"type"`
31+
Owner bitbucketUser `json:"owner"`
32+
} `json:"project"`
33+
Public bool `json:"public"`
34+
} `json:"repository"`
35+
Changes []struct {
36+
Ref struct {
37+
ID string `json:"id"`
38+
DisplayID string `json:"displayId"`
39+
Type string `json:"type"`
40+
} `json:"ref"`
41+
RefID string `json:"refId"`
42+
FromHash string `json:"fromHash"`
43+
ToHash string `json:"toHash"`
44+
Type string `json:"type"`
45+
} `json:"changes"`
46+
}
47+
48+
type bitbucketUser struct {
49+
Name string `json:"name"`
50+
EmailAddress string `json:"emailAddress"`
51+
ID int `json:"id"`
52+
DisplayName string `json:"displayName"`
53+
Active bool `json:"active"`
54+
Slug string `json:"slug"`
55+
Type string `json:"type"`
56+
}
57+
58+
type bitbucketServerDate struct {
59+
time.Time
60+
}
61+
62+
func (bt *bitbucketServerDate) UnmarshalJSON(b []byte) (err error) {
63+
s := strings.Trim(string(b), "\"")
64+
if s == "null" {
65+
bt.Time = time.Now()
66+
return
67+
}
68+
bt.Time, err = time.Parse(bitbucketDateFormat, s)
69+
return
70+
}
71+
72+
// LoadFromJSON update object from json
73+
func (e *BitbucketServerPushEvent) LoadFromJSON(data []byte) (bool, error) {
74+
err := json.Unmarshal(data, &e)
75+
if err != nil {
76+
return false, err
77+
}
78+
return true, nil
79+
}
80+
81+
// ConvertToJSON convert object to json
82+
func (e *BitbucketServerPushEvent) ConvertToJSON() (string, error) {
83+
data, err := json.Marshal(&e)
84+
if err != nil {
85+
return "", err
86+
}
87+
return string(data), nil
88+
}

rabbit.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,19 @@ func main() {
121121

122122
r.GET("/api/project/:id", controller.GetProjectByID)
123123
r.GET("/api/project", controller.GetProjects)
124+
124125
r.POST(strings.TrimSuffix(viper.GetString("integrations.github.webhook_uri"), "/"), func(c *gin.Context) {
125126
controller.GithubListener(c, messages)
126127
})
128+
127129
r.POST(strings.TrimSuffix(viper.GetString("integrations.bitbucket.webhook_uri"), "/"), func(c *gin.Context) {
128130
controller.BitbucketListener(c, messages)
129131
})
130132

133+
r.POST(strings.TrimSuffix(viper.GetString("integrations.bitbucket_server.webhook_uri"), "/"), func(c *gin.Context) {
134+
controller.BitbucketServerListener(c, messages)
135+
})
136+
131137
for i := 0; i < viper.GetInt("broker.native.workers"); i++ {
132138
go controller.Worker(i+1, messages)
133139
}

0 commit comments

Comments
 (0)