Add logic to post the job status for webhooks back to the service

under some circumstances.
This commit is contained in:
Jeff Bradberry
2019-09-17 16:22:02 -04:00
parent aa34984d7c
commit 4ad5054222
3 changed files with 64 additions and 9 deletions

View File

@@ -2,6 +2,7 @@ from hashlib import sha1
import hmac import hmac
import json import json
import logging import logging
import urllib.parse
from django.utils.encoding import force_bytes from django.utils.encoding import force_bytes
from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_exempt
@@ -53,7 +54,7 @@ class WebhookReceiverBase(APIView):
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
authentication_classes = () authentication_classes = ()
event_keys = {} ref_keys = {}
def get_queryset(self): def get_queryset(self):
qs_models = { qs_models = {
@@ -83,8 +84,11 @@ class WebhookReceiverBase(APIView):
def get_event_guid(self): def get_event_guid(self):
raise NotImplementedError raise NotImplementedError
def get_event_status_api(self):
raise NotImplementedError
def get_event_ref(self): def get_event_ref(self):
key = self.event_keys.get(self.get_event_type(), '') key = self.ref_keys.get(self.get_event_type(), '')
value = self.request.data value = self.request.data
for element in key.split('.'): for element in key.split('.'):
try: try:
@@ -126,6 +130,7 @@ class WebhookReceiverBase(APIView):
event_type = self.get_event_type() event_type = self.get_event_type()
event_guid = self.get_event_guid() event_guid = self.get_event_guid()
event_ref = self.get_event_ref() event_ref = self.get_event_ref()
status_api = self.get_event_status_api()
kwargs = { kwargs = {
'webhook_service': obj.webhook_service, 'webhook_service': obj.webhook_service,
@@ -147,11 +152,10 @@ class WebhookReceiverBase(APIView):
'tower_webhook_event_type': event_type, 'tower_webhook_event_type': event_type,
'tower_webhook_event_guid': event_guid, 'tower_webhook_event_guid': event_guid,
'tower_webhook_event_ref': event_ref, 'tower_webhook_event_ref': event_ref,
'tower_webhook_status_api': status_api,
'tower_webhook_payload': request.data, 'tower_webhook_payload': request.data,
}) })
} }
# if event_ref:
# kwargs['scm_branch'] = event_ref
new_job = obj.create_unified_job(**kwargs) new_job = obj.create_unified_job(**kwargs)
new_job.signal_start() new_job.signal_start()
@@ -162,7 +166,7 @@ class WebhookReceiverBase(APIView):
class GithubWebhookReceiver(WebhookReceiverBase): class GithubWebhookReceiver(WebhookReceiverBase):
service = 'github' service = 'github'
event_keys = { ref_keys = {
'pull_request': 'pull_request.head.sha', 'pull_request': 'pull_request.head.sha',
'pull_request_review': 'pull_request.head.sha', 'pull_request_review': 'pull_request.head.sha',
'pull_request_review_comment': 'pull_request.head.sha', 'pull_request_review_comment': 'pull_request.head.sha',
@@ -179,6 +183,11 @@ class GithubWebhookReceiver(WebhookReceiverBase):
def get_event_guid(self): def get_event_guid(self):
return self.request.META.get('HTTP_X_GITHUB_DELIVERY') return self.request.META.get('HTTP_X_GITHUB_DELIVERY')
def get_event_status_api(self):
if self.get_event_type() != 'pull_request':
return
return self.request.data.get('pull_request', {}).get('statuses_url')
def get_signature(self): def get_signature(self):
header_sig = self.request.META.get('HTTP_X_HUB_SIGNATURE') header_sig = self.request.META.get('HTTP_X_HUB_SIGNATURE')
if not header_sig: if not header_sig:
@@ -192,7 +201,7 @@ class GithubWebhookReceiver(WebhookReceiverBase):
class GitlabWebhookReceiver(WebhookReceiverBase): class GitlabWebhookReceiver(WebhookReceiverBase):
service = 'gitlab' service = 'gitlab'
event_keys = { ref_keys = {
'Push Hook': 'checkout_sha', 'Push Hook': 'checkout_sha',
'Tag Push Hook': 'checkout_sha', 'Tag Push Hook': 'checkout_sha',
'Merge Request Hook': 'object_attributes.last_commit.id', 'Merge Request Hook': 'object_attributes.last_commit.id',
@@ -207,6 +216,18 @@ class GitlabWebhookReceiver(WebhookReceiverBase):
h.update(force_bytes(self.request.body)) h.update(force_bytes(self.request.body))
return h.hexdigest() return h.hexdigest()
def get_event_status_api(self):
if self.get_event_type() != 'Merge Request Hook':
return
project = self.request.data.get('project', {})
repo_url = project.get('web_url')
if not repo_url:
return
parsed = urllib.parse.urlparse(repo_url)
return "{}://{}/projects/{}/repository/commits/{}/statuses".format(
parsed.scheme, parsed.netloc, project['id'], self.get_event_ref())
def get_signature(self): def get_signature(self):
return force_bytes(self.request.META.get('HTTP_X_GITLAB_TOKEN')) return force_bytes(self.request.META.get('HTTP_X_GITLAB_TOKEN'))
@@ -224,7 +245,7 @@ class GitlabWebhookReceiver(WebhookReceiverBase):
class BitbucketWebhookReceiver(WebhookReceiverBase): class BitbucketWebhookReceiver(WebhookReceiverBase):
service = 'bitbucket' service = 'bitbucket'
event_keys = { ref_keys = {
# Bitbucket Server # Bitbucket Server
'repo:refs_changed': 'changes.0.toHash', 'repo:refs_changed': 'changes.0.toHash',
'repo:comment:added': 'commit', 'repo:comment:added': 'commit',

View File

@@ -1,8 +1,10 @@
# Python # Python
import os
import json
from copy import copy, deepcopy from copy import copy, deepcopy
import json
import logging
import os
import requests
# Django # Django
from django.apps import apps from django.apps import apps
@@ -27,6 +29,9 @@ from awx.main.fields import JSONField, AskForField
from awx.main.constants import ACTIVE_STATES from awx.main.constants import ACTIVE_STATES
logger = logging.getLogger('awx.main.models.mixins')
__all__ = ['ResourceMixin', 'SurveyJobTemplateMixin', 'SurveyJobMixin', __all__ = ['ResourceMixin', 'SurveyJobTemplateMixin', 'SurveyJobMixin',
'TaskManagerUnifiedJobMixin', 'TaskManagerJobMixin', 'TaskManagerProjectUpdateMixin', 'TaskManagerUnifiedJobMixin', 'TaskManagerJobMixin', 'TaskManagerProjectUpdateMixin',
'TaskManagerInventoryUpdateMixin', 'CustomVirtualEnvMixin'] 'TaskManagerInventoryUpdateMixin', 'CustomVirtualEnvMixin']
@@ -553,3 +558,29 @@ class WebhookMixin(models.Model):
blank=True, blank=True,
max_length=128 max_length=128
) )
def update_scm_status(self, status):
if not self.webhook_credential:
logger.debug("No credential configured to post back webhook status, skipping.")
return
status_api = self.extra_vars_dict.get('tower_webhook_status_api')
if not status_api:
logger.debug("Webhook event did not have a status API endpoint associated, skipping.")
return
service_header = {
'github': 'Authorization',
'gitlab': 'PRIVATE-TOKEN',
}
try:
headers = {service_header[self.webhook_service]: self.webhook_credential.get_input('token')}
response = requests.post(status_api, headers=headers)
except Exception:
logger.exception("Posting webhook status caused an error.")
return
if response.status_code < 400:
logger.debug("Webhook status update sent.")
else:
logger.debug("Posting webhook status failed, code: {}".format(response.status_code))

View File

@@ -1422,3 +1422,6 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
def is_isolated(self): def is_isolated(self):
return bool(self.controller_node) return bool(self.controller_node)
def update_scm_status(self, status):
return