Skip to content

Commit 578dfe6

Browse files
committed
feat(scm): add bitbucket webhook support
Signed-off-by: Ushira Dineth <[email protected]>
1 parent ddb91e7 commit 578dfe6

File tree

3 files changed

+87
-5
lines changed

3 files changed

+87
-5
lines changed

docs/getting-started.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,52 @@ To configure the GitOps Promoter with Bitbucket, you will need to create a repos
234234
* **Repositories**: Read and Write
235235
* **Pull requests**: Read and Write
236236

237+
### Webhooks (Optional - but highly recommended)
238+
239+
> [!NOTE]
240+
> We do support configuration of a Bitbucket webhook that triggers PR creation upon Push. However, we do not configure
241+
> the ingress to allow Bitbucket to reach the GitOps Promoter. You will need to configure the ingress to allow Bitbucket to reach
242+
> the GitOps Promoter via the service promoter-webhook-receiver which listens on port `3333`. If you do not use webhooks
243+
> you might want to adjust the auto reconciliation interval to a lower value using these `promotionStrategyRequeueDuration` and
244+
> `changeTransferPolicyRequeueDuration` fields of the `ControllerConfiguration` resource.
245+
246+
To enable webhook support for automatic PR creation on push:
247+
248+
1. Navigate to your repository URL
249+
2. Click on "Repository settings" in the sidebar
250+
3. Navigate to "Webhooks"
251+
4. Click "Add webhook"
252+
5. Configure the webhook:
253+
* **Title**: GitOps Promoter
254+
* **URL**: `https://argo-github-app-webhook.com/` # Replace with your domain
255+
* **Triggers**: Select "Repository: Push"
256+
257+
Here is an example Ingress configuration for the webhook receiver:
258+
259+
```yaml
260+
apiVersion: networking.k8s.io/v1
261+
kind: Ingress
262+
metadata:
263+
name: promoter-webhook-receiver
264+
namespace: promoter-system
265+
annotations:
266+
# Add any necessary annotations for your ingress controller
267+
# For example, if using nginx-ingress:
268+
# nginx.ingress.kubernetes.io/ssl-redirect: "true"
269+
spec:
270+
rules:
271+
- host: argo-github-app-webhook.com # Replace with your domain
272+
http:
273+
paths:
274+
- path: /
275+
pathType: Prefix
276+
backend:
277+
service:
278+
name: promoter-webhook-receiver
279+
port:
280+
number: 3333
281+
```
282+
237283
### Configuration
238284

239285
This access token should be used in a secret as follows:

internal/webhookreceiver/server.go

Lines changed: 35 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,11 @@ var logger = ctrl.Log.WithName("webhookReceiver")
2323

2424
// Provider type constants
2525
const (
26-
ProviderGitHub = "github"
27-
ProviderGitLab = "gitlab"
28-
ProviderForgejo = "forgejo"
29-
ProviderUnknown = ""
26+
ProviderGitHub = "github"
27+
ProviderGitLab = "gitlab"
28+
ProviderForgejo = "forgejo"
29+
ProviderBitbucket = "bitbucket"
30+
ProviderUnknown = ""
3031
)
3132

3233
// WebhookReceiver is a server that listens for webhooks and triggers reconciles of ChangeTransferPolicies.
@@ -75,7 +76,7 @@ func (wr *WebhookReceiver) Start(ctx context.Context, addr string) error {
7576
}
7677

7778
// DetectProvider determines the SCM provider based on webhook headers.
78-
// Returns ProviderGitHub, ProviderGitLab, ProviderForgejo, or ProviderUnknown.
79+
// Returns ProviderGitHub, ProviderGitLab, ProviderForgejo, ProviderBitbucket, or ProviderUnknown.
7980
func (wr *WebhookReceiver) DetectProvider(r *http.Request) string {
8081
// Check for GitHub webhook headers
8182
if r.Header.Get("X-Github-Event") != "" || r.Header.Get("X-Github-Delivery") != "" {
@@ -92,6 +93,11 @@ func (wr *WebhookReceiver) DetectProvider(r *http.Request) string {
9293
return ProviderForgejo
9394
}
9495

96+
// Check for Bitbucket Cloud webhook headers
97+
if r.Header.Get("X-Hook-UUID") != "" {
98+
return ProviderBitbucket
99+
}
100+
95101
return ProviderUnknown
96102
}
97103

@@ -193,6 +199,20 @@ func (wr *WebhookReceiver) findChangeTransferPolicy(ctx context.Context, provide
193199
beforeSha = gjson.GetBytes(jsonBytes, "before").String()
194200
ref = gjson.GetBytes(jsonBytes, "ref").String()
195201
}
202+
case ProviderBitbucket:
203+
// Bitbucket Cloud webhook format
204+
if gjson.GetBytes(jsonBytes, "push.changes").Exists() && gjson.GetBytes(jsonBytes, "actor").Exists() {
205+
changes := gjson.GetBytes(jsonBytes, "push.changes")
206+
if changes.IsArray() && len(changes.Array()) > 0 {
207+
firstChange := changes.Array()[0]
208+
beforeSha = firstChange.Get("old.target.hash").String()
209+
if newName := firstChange.Get("new.name"); newName.Exists() {
210+
ref = "refs/heads/" + newName.String()
211+
} else if oldName := firstChange.Get("old.name"); oldName.Exists() {
212+
ref = "refs/heads/" + oldName.String()
213+
}
214+
}
215+
}
196216
default:
197217
logger.V(4).Info("unsupported provider", "provider", provider)
198218
return nil, nil
@@ -255,5 +275,15 @@ func (wr *WebhookReceiver) extractDeliveryID(r *http.Request) string {
255275
if id := r.Header.Get("X-Gitea-Delivery"); id != "" {
256276
return id
257277
}
278+
// Bitbucket Cloud
279+
// X-Request-UUID: Unique identifier for the webhook request
280+
// X-Hook-UUID: Unique identifier for the webhook itself (also used for provider detection)
281+
// Note: Go's http.Header.Get is case-insensitive, so this will match X-Request-UUID correctly
282+
if id := r.Header.Get("X-Request-Uuid"); id != "" {
283+
return id
284+
}
285+
if id := r.Header.Get("X-Hook-Uuid"); id != "" {
286+
return id
287+
}
258288
return ""
259289
}

internal/webhookreceiver/server_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ var _ = Describe("DetectProvider", func() {
5555
},
5656
expectedResult: webhookreceiver.ProviderForgejo,
5757
},
58+
"Bitbucket Cloud webhook with X-Hook-UUID": {
59+
headers: map[string]string{
60+
"X-Hook-UUID": "12345-abcde",
61+
},
62+
expectedResult: webhookreceiver.ProviderBitbucket,
63+
},
5864
"Unknown provider - no headers": {
5965
headers: map[string]string{},
6066
expectedResult: webhookreceiver.ProviderUnknown,

0 commit comments

Comments
 (0)