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

Add support for bitbucket cloud webhook #15699

Open
wants to merge 5 commits into
base: devel
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
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
3 changes: 2 additions & 1 deletion awx/api/urls/webhooks.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
from django.urls import re_path

from awx.api.views.webhooks import WebhookKeyView, GithubWebhookReceiver, GitlabWebhookReceiver, BitbucketDcWebhookReceiver
from awx.api.views.webhooks import WebhookKeyView, GithubWebhookReceiver, GitlabWebhookReceiver, BitbucketDcWebhookReceiver, BitbucketCloudWebhookReceiver


urlpatterns = [
re_path(r'^webhook_key/$', WebhookKeyView.as_view(), name='webhook_key'),
re_path(r'^github/$', GithubWebhookReceiver.as_view(), name='webhook_receiver_github'),
re_path(r'^gitlab/$', GitlabWebhookReceiver.as_view(), name='webhook_receiver_gitlab'),
re_path(r'^bitbucket_dc/$', BitbucketDcWebhookReceiver.as_view(), name='webhook_receiver_bitbucket_dc'),
re_path(r'^bitbucket_cloud/$', BitbucketCloudWebhookReceiver.as_view(), name='webhook_receiver_bitbucket_cloud'),
]
53 changes: 53 additions & 0 deletions awx/api/views/webhooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,3 +305,56 @@ def get_signature(self):
raise PermissionDenied
hash_alg, signature = header_sig.split('=')
return hash_alg, force_bytes(signature)


class BitbucketCloudWebhookReceiver(WebhookReceiverBase):
service = 'bitbucket_cloud'

ref_keys = {
'repo:push': 'push.changes.0.target.hash',
'pullrequest:created': 'pullrequest.source.commit.hash',
'pullrequest:updated': 'pullrequest.source.commit.hash',
'pullrequest:fulfilled': 'pullrequest.source.commit.hash',
}

def get_event_type(self):
return self.request.META.get('HTTP_X_EVENT_KEY')

def get_event_guid(self):
return self.request.META.get('HTTP_X_REQUEST_UUID')

def get_event_status_api(self):
# <bitbucket-base-url>/commit/<commit-hash>/statuses/build
if self.get_event_type() not in self.ref_keys.keys():
return
if self.get_event_ref() is None:
return
any_url = None
if 'repository' in self.request.data:
any_url = self.request.data['repository'].get('links', {}).get('self')
if any_url is None:
return
any_url = any_url.get('href')
if any_url is None:
return
return "{}/commit/{}/statuses/build".format(any_url, self.get_event_ref())

def is_ignored_request(self):
return self.get_event_type() not in [
'repo:push',
'pullrequest:created',
'pullrequest:updated',
'pullrequest:fulfilled',
]

def must_check_signature(self):
# Bitbucket does not sign ping requests...
return self.get_event_type() != 'diagnostics:ping'

def get_signature(self):
header_sig = self.request.META.get('HTTP_X_HUB_SIGNATURE')
if not header_sig:
logger.debug("Expected signature missing from header key X_HUB_SIGNATURE")
raise PermissionDenied
hash_alg, signature = header_sig.split('=')
return hash_alg, force_bytes(signature)
53 changes: 53 additions & 0 deletions awx/main/migrations/0200_alter_job_webhook_service_and_more.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Generated by Django 4.2.16 on 2024-12-11 09:36

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('main', '0199_alter_oauth2application_unique_together_and_more'),
]

operations = [
migrations.AlterField(
model_name='job',
name='webhook_service',
field=models.CharField(
blank=True,
choices=[('github', 'GitHub'), ('gitlab', 'GitLab'), ('bitbucket_dc', 'BitBucket DataCenter'), ('bitbucket_cloud', 'BitBucket Cloud')],
help_text='Service that webhook requests will be accepted from',
max_length=16,
),
),
migrations.AlterField(
model_name='jobtemplate',
name='webhook_service',
field=models.CharField(
blank=True,
choices=[('github', 'GitHub'), ('gitlab', 'GitLab'), ('bitbucket_dc', 'BitBucket DataCenter'), ('bitbucket_cloud', 'BitBucket Cloud')],
help_text='Service that webhook requests will be accepted from',
max_length=16,
),
),
migrations.AlterField(
model_name='workflowjob',
name='webhook_service',
field=models.CharField(
blank=True,
choices=[('github', 'GitHub'), ('gitlab', 'GitLab'), ('bitbucket_dc', 'BitBucket DataCenter'), ('bitbucket_cloud', 'BitBucket Cloud')],
help_text='Service that webhook requests will be accepted from',
max_length=16,
),
),
migrations.AlterField(
model_name='workflowjobtemplate',
name='webhook_service',
field=models.CharField(
blank=True,
choices=[('github', 'GitHub'), ('gitlab', 'GitLab'), ('bitbucket_dc', 'BitBucket DataCenter'), ('bitbucket_cloud', 'BitBucket Cloud')],
help_text='Service that webhook requests will be accepted from',
max_length=16,
),
),
]
17 changes: 17 additions & 0 deletions awx/main/models/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -575,6 +575,7 @@ class Meta:
('github', "GitHub"),
('gitlab', "GitLab"),
('bitbucket_dc', "BitBucket DataCenter"),
('bitbucket_cloud', "BitBucket Cloud"),
]

webhook_service = models.CharField(max_length=16, choices=SERVICES, blank=True, help_text=_('Service that webhook requests will be accepted from'))
Expand Down Expand Up @@ -636,6 +637,7 @@ def update_webhook_status(self, status):
'github': ('Authorization', 'token {}'),
'gitlab': ('PRIVATE-TOKEN', '{}'),
'bitbucket_dc': ('Authorization', 'Bearer {}'),
'bitbucket_cloud': ('Authorization', 'Bearer {}'),
}
service_statuses = {
'github': {
Expand All @@ -661,6 +663,14 @@ def update_webhook_status(self, status):
'error': 'FAILED',
'canceled': 'FAILED',
},
'bitbucket_cloud': {
'pending': 'INPROGRESS', # Bitbucket Cloud doesn't have any other statuses distinct from INPROGRESS, SUCCESSFUL, FAILED and STOPPED
'running': 'INPROGRESS',
'successful': 'SUCCESSFUL',
'failed': 'FAILED',
'error': 'FAILED',
'canceled': 'STOPPED',
},
}

statuses = service_statuses[self.webhook_service]
Expand All @@ -675,6 +685,13 @@ def update_webhook_status(self, status):
'key': 'ansible/awx' if license_type == 'open' else 'ansible/tower',
'url': self.get_ui_url(),
}
elif self.webhook_service == 'bitbucket_cloud':
data = {
'type': 'build',
'state': statuses[status],
'key': 'ansible/awx' if license_type == 'open' else 'ansible/tower',
'url': self.get_ui_url(),
}
else:
data = {
'state': statuses[status],
Expand Down
1 change: 1 addition & 0 deletions awx/main/tests/functional/test_credential.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def test_default_cred_types():
'aws_secretsmanager_credential',
'azure_kv',
'azure_rm',
'bitbucket_cloud_token',
'bitbucket_dc_token',
'centrify_vault_kv',
'conjur',
Expand Down
Loading