Merge pull request #255 from coreos-inc/betterbb
Change to use the new BitBucket webhooks
This commit is contained in:
commit
fee8bf8607
1 changed files with 120 additions and 36 deletions
|
@ -318,7 +318,7 @@ class BitbucketBuildTrigger(BuildTriggerHandler):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def is_active(self):
|
def is_active(self):
|
||||||
return 'hook_id' in self.config
|
return 'webhook_id' in self.config
|
||||||
|
|
||||||
def activate(self, standard_webhook_url):
|
def activate(self, standard_webhook_url):
|
||||||
config = self.config
|
config = self.config
|
||||||
|
@ -333,37 +333,48 @@ class BitbucketBuildTrigger(BuildTriggerHandler):
|
||||||
]
|
]
|
||||||
|
|
||||||
repository = self._get_repository_client()
|
repository = self._get_repository_client()
|
||||||
(result, data, err_msg) = repository.deploykeys().create(
|
(result, created_deploykey, err_msg) = repository.deploykeys().create(
|
||||||
app.config['REGISTRY_TITLE'] + ' webhook key', public_key)
|
app.config['REGISTRY_TITLE'] + ' webhook key', public_key)
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
msg = 'Unable to add deploy key to repository: %s' % err_msg
|
msg = 'Unable to add deploy key to repository: %s' % err_msg
|
||||||
raise TriggerActivationException(msg)
|
raise TriggerActivationException(msg)
|
||||||
|
|
||||||
config['deploy_key_id'] = data['pk']
|
config['deploy_key_id'] = created_deploykey['pk']
|
||||||
|
|
||||||
# Add a webhook callback.
|
# Add a webhook callback.
|
||||||
(result, data, err_msg) = repository.services().create('POST', URL=standard_webhook_url)
|
description = 'Webhook for invoking builds on %s' % app.config['REGISTRY_TITLE_SHORT']
|
||||||
|
webhook_events = ['repo:push']
|
||||||
|
(result, created_webhook, err_msg) = repository.webhooks().create(
|
||||||
|
description, standard_webhook_url, webhook_events)
|
||||||
|
|
||||||
if not result:
|
if not result:
|
||||||
msg = 'Unable to add webhook to repository: %s' % err_msg
|
msg = 'Unable to add webhook to repository: %s' % err_msg
|
||||||
raise TriggerActivationException(msg)
|
raise TriggerActivationException(msg)
|
||||||
|
|
||||||
config['hook_id'] = data['id']
|
config['webhook_id'] = created_webhook['uuid']
|
||||||
self.config = config
|
self.config = config
|
||||||
return config, {'private_key': private_key}
|
return config, {'private_key': private_key}
|
||||||
|
|
||||||
def deactivate(self):
|
def deactivate(self):
|
||||||
config = self.config
|
config = self.config
|
||||||
|
|
||||||
hook_id = config.pop('hook_id', None)
|
# TODO(jschorr): Remove the old hook removal code once everyone is migrated.
|
||||||
|
old_hook_id = config.pop('hook_id', None)
|
||||||
|
webhook_id = config.pop('webhook_id', None)
|
||||||
deploy_key_id = config.pop('deploy_key_id', None)
|
deploy_key_id = config.pop('deploy_key_id', None)
|
||||||
|
|
||||||
try:
|
|
||||||
repository = self._get_repository_client()
|
repository = self._get_repository_client()
|
||||||
|
|
||||||
# Remove the webhook link.
|
# Remove the old service hook.
|
||||||
if hook_id is not None:
|
if old_hook_id is not None:
|
||||||
(result, _, err_msg) = repository.services().delete(hook_id)
|
(result, _, err_msg) = repository.services().delete(old_hook_id)
|
||||||
|
if not result:
|
||||||
|
msg = 'Unable to remove service hook from repository: %s' % err_msg
|
||||||
|
raise TriggerDeactivationException(msg)
|
||||||
|
|
||||||
|
# Remove the webhook.
|
||||||
|
if webhook_id is not None:
|
||||||
|
(result, _, err_msg) = repository.webhooks().delete(webhook_id)
|
||||||
if not result:
|
if not result:
|
||||||
msg = 'Unable to remove webhook from repository: %s' % err_msg
|
msg = 'Unable to remove webhook from repository: %s' % err_msg
|
||||||
raise TriggerDeactivationException(msg)
|
raise TriggerDeactivationException(msg)
|
||||||
|
@ -374,10 +385,6 @@ class BitbucketBuildTrigger(BuildTriggerHandler):
|
||||||
if not result:
|
if not result:
|
||||||
msg = 'Unable to remove deploy key from repository: %s' % err_msg
|
msg = 'Unable to remove deploy key from repository: %s' % err_msg
|
||||||
raise TriggerDeactivationException(msg)
|
raise TriggerDeactivationException(msg)
|
||||||
except GitHubBadCredentialsException:
|
|
||||||
msg = ('Unable to remove trigger as credentials are invalid. ' +
|
|
||||||
'Please manually remove the webhook and deploy key.')
|
|
||||||
raise TriggerDeactivationException(msg)
|
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
@ -470,7 +477,17 @@ class BitbucketBuildTrigger(BuildTriggerHandler):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _prepare_build(self, commit_sha, ref, is_manual):
|
_BITBUCKET_COMMIT_URL = 'https://bitbucket.org/%s/%s/commits/%s'
|
||||||
|
|
||||||
|
def _prepare_build(self, commit_sha, ref, is_manual, target=None, actor=None):
|
||||||
|
def _build_user_block(info):
|
||||||
|
return {
|
||||||
|
'username': info['username'],
|
||||||
|
'url': info['links']['html']['href'],
|
||||||
|
'avatar_url': info['links']['avatar']['href'],
|
||||||
|
'display_name': info['display_name']
|
||||||
|
}
|
||||||
|
|
||||||
config = self.config
|
config = self.config
|
||||||
repository = self._get_repository_client()
|
repository = self._get_repository_client()
|
||||||
|
|
||||||
|
@ -478,7 +495,9 @@ class BitbucketBuildTrigger(BuildTriggerHandler):
|
||||||
# the tags.
|
# the tags.
|
||||||
default_branch = self._get_default_branch(repository)
|
default_branch = self._get_default_branch(repository)
|
||||||
|
|
||||||
# Lookup the commit sha.
|
# Lookup the commit sha (if necessary)
|
||||||
|
data = {}
|
||||||
|
if target is None:
|
||||||
(result, data, _) = repository.changesets().get(commit_sha)
|
(result, data, _) = repository.changesets().get(commit_sha)
|
||||||
if not result:
|
if not result:
|
||||||
raise TriggerStartException('Could not lookup commit SHA')
|
raise TriggerStartException('Could not lookup commit SHA')
|
||||||
|
@ -486,15 +505,23 @@ class BitbucketBuildTrigger(BuildTriggerHandler):
|
||||||
namespace = repository.namespace
|
namespace = repository.namespace
|
||||||
name = repository.repository_name
|
name = repository.repository_name
|
||||||
|
|
||||||
|
# Build the commit information.
|
||||||
|
commit_url = self._BITBUCKET_COMMIT_URL % (namespace, name, commit_sha)
|
||||||
|
if target is not None and 'links' in target:
|
||||||
|
commit_url = target['links']['html']['href']
|
||||||
|
|
||||||
commit_info = {
|
commit_info = {
|
||||||
'url': 'https://bitbucket.org/%s/%s/commits/%s' % (namespace, name, commit_sha),
|
'url': commit_url,
|
||||||
'message': data['message'],
|
'message': target['message'] if target else data['message'],
|
||||||
'date': data['timestamp']
|
'date': target['date'] if target else data['timestamp']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Add the commit's author.
|
||||||
|
if target.get('author') and 'user' in target['author']:
|
||||||
|
commit_info['author'] = _build_user_block(target['author']['user'])
|
||||||
|
elif data.get('raw_author'):
|
||||||
# Try to lookup the author by email address. The raw_author field (if it exists) is returned
|
# Try to lookup the author by email address. The raw_author field (if it exists) is returned
|
||||||
# in the form: "Joseph Schorr <joseph.schorr@coreos.com>"
|
# in the form: "Joseph Schorr <joseph.schorr@coreos.com>"
|
||||||
if data.get('raw_author'):
|
|
||||||
match = re.compile(r'.*<(.+)>').match(data['raw_author'])
|
match = re.compile(r'.*<(.+)>').match(data['raw_author'])
|
||||||
if match:
|
if match:
|
||||||
email_address = match.group(1)
|
email_address = match.group(1)
|
||||||
|
@ -507,6 +534,10 @@ class BitbucketBuildTrigger(BuildTriggerHandler):
|
||||||
'avatar_url': data['user']['avatar']
|
'avatar_url': data['user']['avatar']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Add the commit's actor (committer).
|
||||||
|
if actor is not None:
|
||||||
|
commit_info['committer'] = _build_user_block(actor)
|
||||||
|
|
||||||
metadata = {
|
metadata = {
|
||||||
'commit': commit_sha,
|
'commit': commit_sha,
|
||||||
'ref': ref,
|
'ref': ref,
|
||||||
|
@ -524,8 +555,61 @@ class BitbucketBuildTrigger(BuildTriggerHandler):
|
||||||
|
|
||||||
return prepared
|
return prepared
|
||||||
|
|
||||||
|
|
||||||
def handle_trigger_request(self, request):
|
def handle_trigger_request(self, request):
|
||||||
|
# Check for a payload field. If found, this is a V1 service request.
|
||||||
|
payload_json = request.form.get('payload')
|
||||||
|
if payload_json:
|
||||||
|
return self.handle_V1_trigger_request(request)
|
||||||
|
|
||||||
|
# Otherwise, this is a V2 webhook request.
|
||||||
|
return self.handle_V2_trigger_request(request)
|
||||||
|
|
||||||
|
def handle_V2_trigger_request(self, request):
|
||||||
|
payload = request.get_json()
|
||||||
|
if not 'push' in payload:
|
||||||
|
logger.debug('Skipping BitBucket request due to missing push data in payload')
|
||||||
|
raise SkipRequestException()
|
||||||
|
|
||||||
|
push_payload = payload['push']
|
||||||
|
if not 'changes' in push_payload or not push_payload['changes']:
|
||||||
|
logger.debug('Skipping BitBucket request due to empty changes list')
|
||||||
|
raise SkipRequestException()
|
||||||
|
|
||||||
|
# Make sure we have a new change.
|
||||||
|
changes = push_payload['changes']
|
||||||
|
last_change = changes[-1]
|
||||||
|
if not last_change.get('new'):
|
||||||
|
logger.debug('Skipping BitBucket request due to change being a deletion')
|
||||||
|
raise SkipRequestException()
|
||||||
|
|
||||||
|
change_info = last_change['new']
|
||||||
|
change_target = change_info.get('target')
|
||||||
|
if not change_target:
|
||||||
|
logger.debug('Skipping BitBucket request due to missing change target')
|
||||||
|
raise SkipRequestException()
|
||||||
|
|
||||||
|
# Check if this build should be skipped by commit message.
|
||||||
|
commit_message = change_target.get('message', '')
|
||||||
|
if should_skip_commit(commit_message):
|
||||||
|
logger.debug('Skipping BitBucket request due to commit message request')
|
||||||
|
raise SkipRequestException()
|
||||||
|
|
||||||
|
# Check to see if this build should be skipped by ref.
|
||||||
|
ref = ('refs/heads/' + change_info['name'] if change_info['type'] == 'branch'
|
||||||
|
else 'refs/tags/' + change_info['name'])
|
||||||
|
|
||||||
|
logger.debug('Checking BitBucket request: %s', ref)
|
||||||
|
raise_if_skipped(self.config, ref)
|
||||||
|
|
||||||
|
# Prepare the build.
|
||||||
|
commit_sha = change_target['hash']
|
||||||
|
return self._prepare_build(commit_sha, ref, False, target=change_target,
|
||||||
|
actor=payload.get('actor'))
|
||||||
|
|
||||||
|
|
||||||
|
def handle_V1_trigger_request(self, request):
|
||||||
|
# TODO(jschorr): Delete this code after the migration has successfully completed.
|
||||||
|
|
||||||
# Parse the JSON payload.
|
# Parse the JSON payload.
|
||||||
payload_json = request.form.get('payload')
|
payload_json = request.form.get('payload')
|
||||||
if not payload_json:
|
if not payload_json:
|
||||||
|
|
Reference in a new issue