-
Notifications
You must be signed in to change notification settings - Fork 564
/
Copy pathbitbucket.go
351 lines (308 loc) · 13.4 KB
/
bitbucket.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
package controllers
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/diggerhq/digger/backend/ci_backends"
"github.com/diggerhq/digger/backend/controllers"
"github.com/diggerhq/digger/backend/locking"
"github.com/diggerhq/digger/backend/models"
"github.com/diggerhq/digger/backend/segment"
"github.com/diggerhq/digger/backend/utils"
ci_backends2 "github.com/diggerhq/digger/ee/backend/ci_backends"
"github.com/diggerhq/digger/libs/ci/generic"
dg_github "github.com/diggerhq/digger/libs/ci/github"
comment_updater "github.com/diggerhq/digger/libs/comment_utils/reporting"
dg_configuration "github.com/diggerhq/digger/libs/digger_config"
dg_locking "github.com/diggerhq/digger/libs/locking"
"github.com/diggerhq/digger/libs/scheduler"
"github.com/gin-gonic/gin"
"io"
"log"
"net/http"
"os"
"runtime/debug"
"strconv"
"strings"
)
type BBWebhookPayload map[string]interface{}
// TriggerPipeline triggers a CI pipeline with the specified type
func TriggerPipeline(pipelineType string) error {
// Implement pipeline triggering logic
return nil
}
// verifySignature verifies the X-Hub-Signature header
func verifySignature(c *gin.Context, body []byte, webhookSecret string) bool {
// Get the signature from the header
signature := c.GetHeader("X-Hub-Signature")
if signature == "" {
return false
}
// Remove the "sha256=" prefix if present
if len(signature) > 7 && signature[0:7] == "sha256=" {
signature = signature[7:]
}
// Create a new HMAC
mac := hmac.New(sha256.New, []byte(webhookSecret))
mac.Write(body)
expectedMAC := mac.Sum(nil)
expectedSignature := hex.EncodeToString(expectedMAC)
return hmac.Equal([]byte(signature), []byte(expectedSignature))
}
// WebhookHandler processes incoming Bitbucket webhook events
func (ee DiggerEEController) BitbucketWebhookHandler(c *gin.Context) {
eventKey := c.GetHeader("X-Event-Key")
if eventKey == "" {
log.Printf("unknown event")
return
}
connectionId := c.GetHeader("DIGGER_CONNECTION_ID")
connectionEncrypted, err := models.DB.GetVCSConnectionById(connectionId)
if err != nil {
log.Printf("failed to fetch connection: %v", err)
c.String(http.StatusInternalServerError, "error while processing connection")
return
}
secret := os.Getenv("DIGGER_ENCRYPTION_SECRET")
if secret == "" {
log.Printf("ERROR: no encryption secret specified, please specify DIGGER_ENCRYPTION_SECRET as 32 bytes base64 string")
c.String(http.StatusInternalServerError, "secret not specified")
return
}
connectionDecrypted, err := utils.DecryptConnection(connectionEncrypted, []byte(secret))
if err != nil {
log.Printf("ERROR: could not perform decryption: %v", err)
c.String(http.StatusInternalServerError, "unexpected error while fetching connection")
return
}
bitbucketAccessToken := connectionDecrypted.BitbucketAccessToken
orgId := connectionDecrypted.OrganisationID
bitbucketWebhookSecret := connectionDecrypted.BitbucketWebhookSecret
if bitbucketWebhookSecret == "" {
log.Printf("ERROR: no encryption secret specified, please specify DIGGER_ENCRYPTION_SECRET as 32 bytes base64 string")
c.String(http.StatusInternalServerError, "unexpected error while fetching connection")
return
}
var pullRequestCommentCreated = BitbucketCommentCreatedEvent{}
var repoPush = BitbucketPushEvent{}
var pullRequestCreated = BitbucketPullRequestCreatedEvent{}
bodyBytes, err := io.ReadAll(c.Request.Body)
if err != nil {
c.String(http.StatusBadRequest, "Error reading request body", err)
return
}
verifySignature(c, bodyBytes, bitbucketWebhookSecret)
switch eventKey {
case "pullrequest:comment_created":
err := json.Unmarshal(bodyBytes, &pullRequestCommentCreated)
if err != nil {
log.Printf("error parsing pullrequest:comment_created event: %v", err)
log.Printf("error parsing pullrequest:comment_created event: %v", err)
}
go handleIssueCommentEventBB(ee.BitbucketProvider, &pullRequestCommentCreated, ee.CiBackendProvider, orgId, &connectionEncrypted.ID, bitbucketAccessToken)
case "pullrequest:created":
err := json.Unmarshal(bodyBytes, &pullRequestCreated)
if err != nil {
log.Printf("error parsing pullrequest:created event: %v", err)
}
log.Printf("pullrequest:created")
case "repo:push":
err := json.Unmarshal(bodyBytes, &repoPush)
if err != nil {
log.Printf("error parsing repo:push event: %v", err)
}
log.Printf("repo:push")
default:
log.Printf("unknown event key: %s", eventKey)
return
}
c.String(http.StatusAccepted, "ok")
}
func handleIssueCommentEventBB(bitbucketProvider utils.BitbucketProvider, payload *BitbucketCommentCreatedEvent, ciBackendProvider ci_backends.CiBackendProvider, organisationId uint, vcsConnectionId *uint, bbAccessToken string) error {
defer func() {
if r := recover(); r != nil {
log.Printf("Recovered from panic in handleIssueCommentEventBB handler: %v", r)
log.Printf("\n=== PANIC RECOVERED ===\n")
log.Printf("Error: %v\n", r)
log.Printf("Stack Trace:\n%s", string(debug.Stack()))
log.Printf("=== END PANIC ===\n")
}
}()
repoFullName := payload.Repository.FullName
repoOwner := payload.Repository.Owner.Username
repoName := payload.Repository.Name
cloneURL := payload.Repository.Links.HTML.Href
issueNumber := payload.PullRequest.ID
// TODO: fetch right draft status
isDraft := false
commentId := payload.Comment.ID
commentBody := payload.Comment.Content.Raw
branch := payload.PullRequest.Source.Branch.Name
// TODO: figure why git fetch fails in bb pipeline
commitSha := "" //payload.PullRequest.Source.Commit.Hash
defaultBranch := payload.PullRequest.Source.Branch.Name
actor := payload.Actor.Nickname
//discussionId := payload.Comment.ID
if !strings.HasPrefix(commentBody, "digger") {
log.Printf("comment is not a Digger command, ignoring")
return nil
}
bbService, bberr := utils.GetBitbucketService(bitbucketProvider, bbAccessToken, repoOwner, repoName, issueNumber)
if bberr != nil {
log.Printf("GetGithubService error: %v", bberr)
return fmt.Errorf("error getting ghService to post error comment")
}
diggerYmlStr, config, projectsGraph, err := utils.GetDiggerConfigForBitbucketBranch(bitbucketProvider, bbAccessToken, repoFullName, repoOwner, repoName, cloneURL, branch, issueNumber)
if err != nil {
log.Printf("getDiggerConfigForPR error: %v", err)
utils.InitCommentReporter(bbService, issueNumber, fmt.Sprintf(":x: Could not load digger config, error: %v", err))
return fmt.Errorf("error getting digger config")
}
err = bbService.CreateCommentReaction(strconv.Itoa(commentId), string(dg_github.GithubCommentEyesReaction))
if err != nil {
log.Printf("CreateCommentReaction error: %v", err)
}
if !config.AllowDraftPRs && isDraft {
log.Printf("AllowDraftPRs is disabled, skipping PR: %v", issueNumber)
return nil
}
commentReporter, err := utils.InitCommentReporter(bbService, issueNumber, ":construction_worker: Digger starting....")
if err != nil {
log.Printf("Error initializing comment reporter: %v", err)
return fmt.Errorf("error initializing comment reporter")
}
diggerCommand, err := scheduler.GetCommandFromComment(commentBody)
if err != nil {
log.Printf("unknown digger command in comment: %v", commentBody)
utils.InitCommentReporter(bbService, issueNumber, fmt.Sprintf(":x: Could not recognise comment, error: %v", err))
return fmt.Errorf("unknown digger command in comment %v", err)
}
prBranchName, _, err := bbService.GetBranchName(issueNumber)
if err != nil {
log.Printf("GetBranchName error: %v", err)
utils.InitCommentReporter(bbService, issueNumber, fmt.Sprintf(":x: GetBranchName error: %v", err))
return fmt.Errorf("error while fetching branch name")
}
impactedProjects, impactedProjectsSourceMapping, requestedProject, _, err := generic.ProcessIssueCommentEvent(issueNumber, commentBody, config, projectsGraph, bbService)
if err != nil {
log.Printf("Error processing event: %v", err)
utils.InitCommentReporter(bbService, issueNumber, fmt.Sprintf(":x: Error processing event: %v", err))
return fmt.Errorf("error processing event")
}
log.Printf("Bitbucket IssueComment event processed successfully\n")
// perform unlocking in backend
if config.PrLocks {
for _, project := range impactedProjects {
prLock := dg_locking.PullRequestLock{
InternalLock: locking.BackendDBLock{
OrgId: organisationId,
},
CIService: bbService,
Reporter: comment_updater.NoopReporter{},
ProjectName: project.Name,
ProjectNamespace: repoFullName,
PrNumber: issueNumber,
}
err = dg_locking.PerformLockingActionFromCommand(prLock, *diggerCommand)
if err != nil {
utils.InitCommentReporter(bbService, issueNumber, fmt.Sprintf(":x: Failed perform lock action on project: %v %v", project.Name, err))
return fmt.Errorf("failed perform lock action on project: %v %v", project.Name, err)
}
}
}
// if commands are locking or unlocking we don't need to trigger any jobs
if *diggerCommand == scheduler.DiggerCommandUnlock ||
*diggerCommand == scheduler.DiggerCommandLock {
utils.InitCommentReporter(bbService, issueNumber, fmt.Sprintf(":white_check_mark: Command %v completed successfully", *diggerCommand))
return nil
}
jobs, _, err := generic.ConvertIssueCommentEventToJobs(repoFullName, actor, issueNumber, commentBody, impactedProjects, requestedProject, config.Workflows, prBranchName, defaultBranch)
if err != nil {
log.Printf("Error converting event to jobs: %v", err)
utils.InitCommentReporter(bbService, issueNumber, fmt.Sprintf(":x: Error converting event to jobs: %v", err))
return fmt.Errorf("error converting event to jobs")
}
log.Printf("GitHub IssueComment event converted to Jobs successfully\n")
err = utils.ReportInitialJobsStatus(commentReporter, jobs)
if err != nil {
log.Printf("Failed to comment initial status for jobs: %v", err)
utils.InitCommentReporter(bbService, issueNumber, fmt.Sprintf(":x: Failed to comment initial status for jobs: %v", err))
return fmt.Errorf("failed to comment initial status for jobs")
}
if len(jobs) == 0 {
log.Printf("no projects impacated, succeeding")
// This one is for aggregate reporting
err = utils.SetPRStatusForJobs(bbService, issueNumber, jobs)
return nil
}
err = utils.SetPRStatusForJobs(bbService, issueNumber, jobs)
if err != nil {
log.Printf("error setting status for PR: %v", err)
utils.InitCommentReporter(bbService, issueNumber, fmt.Sprintf(":x: error setting status for PR: %v", err))
fmt.Errorf("error setting status for PR: %v", err)
}
impactedProjectsMap := make(map[string]dg_configuration.Project)
for _, p := range impactedProjects {
impactedProjectsMap[p.Name] = p
}
impactedProjectsJobMap := make(map[string]scheduler.Job)
for _, j := range jobs {
impactedProjectsJobMap[j.ProjectName] = j
}
commentId64, err := strconv.ParseInt(commentReporter.CommentId, 10, 64)
if err != nil {
log.Printf("ParseInt err: %v", err)
return fmt.Errorf("parseint error: %v", err)
}
batchId, _, err := utils.ConvertJobsToDiggerJobs(*diggerCommand, models.DiggerVCSBitbucket, organisationId, impactedProjectsJobMap, impactedProjectsMap, projectsGraph, 0, branch, issueNumber, repoOwner, repoName, repoFullName, commitSha, commentId64, diggerYmlStr, 0, "", false, vcsConnectionId)
if err != nil {
log.Printf("ConvertJobsToDiggerJobs error: %v", err)
utils.InitCommentReporter(bbService, issueNumber, fmt.Sprintf(":x: ConvertJobsToDiggerJobs error: %v", err))
return fmt.Errorf("error convertingjobs")
}
if config.CommentRenderMode == dg_configuration.CommentRenderModeGroupByModule &&
(*diggerCommand == scheduler.DiggerCommandPlan || *diggerCommand == scheduler.DiggerCommandApply) {
sourceDetails, err := comment_updater.PostInitialSourceComments(bbService, issueNumber, impactedProjectsSourceMapping)
if err != nil {
log.Printf("PostInitialSourceComments error: %v", err)
utils.InitCommentReporter(bbService, issueNumber, fmt.Sprintf(":x: PostInitialSourceComments error: %v", err))
return fmt.Errorf("error posting initial comments")
}
batch, err := models.DB.GetDiggerBatch(batchId)
if err != nil {
log.Printf("GetDiggerBatch error: %v", err)
utils.InitCommentReporter(bbService, issueNumber, fmt.Sprintf(":x: PostInitialSourceComments error: %v", err))
return fmt.Errorf("error getting digger batch")
}
batch.SourceDetails, err = json.Marshal(sourceDetails)
if err != nil {
log.Printf("sourceDetails, json Marshal error: %v", err)
utils.InitCommentReporter(bbService, issueNumber, fmt.Sprintf(":x: json Marshal error: %v", err))
return fmt.Errorf("error marshalling sourceDetails")
}
err = models.DB.UpdateDiggerBatch(batch)
if err != nil {
log.Printf("UpdateDiggerBatch error: %v", err)
utils.InitCommentReporter(bbService, issueNumber, fmt.Sprintf(":x: UpdateDiggerBatch error: %v", err))
return fmt.Errorf("error updating digger batch")
}
}
segment.Track(strconv.Itoa(int(organisationId)), "backend_trigger_job")
// hardcoded bitbucket ci backend for this controller
// TODO: making this configurable based on env variable and connection
ciBackend := ci_backends2.BitbucketPipelineCI{
RepoName: repoName,
RepoOwner: repoOwner,
Branch: branch,
Client: bbService,
}
err = controllers.TriggerDiggerJobs(ciBackend, repoFullName, repoOwner, repoName, batchId, issueNumber, bbService, nil)
if err != nil {
log.Printf("TriggerDiggerJobs error: %v", err)
utils.InitCommentReporter(bbService, issueNumber, fmt.Sprintf(":x: TriggerDiggerJobs error: %v", err))
return fmt.Errorf("error triggering Digger Jobs")
}
return nil
}