Adding in cancel notifications

This commit is contained in:
Charlton Austin 2016-11-29 11:48:08 -05:00
parent b7aac159ae
commit 4103a0b75f
9 changed files with 153 additions and 29 deletions

View file

@ -14,6 +14,7 @@ class BuildJobLoadException(Exception):
""" Exception raised if a build job could not be instantiated for some reason. """
pass
class BuildJob(object):
""" Represents a single in-progress build job. """
def __init__(self, job_item):
@ -21,6 +22,7 @@ class BuildJob(object):
try:
self.job_details = json.loads(job_item.body)
self.build_notifier = BuildJobNotifier(self.build_uuid)
except ValueError:
raise BuildJobLoadException(
'Could not parse build queue item config with ID %s' % self.job_details['build_uuid']
@ -34,35 +36,7 @@ class BuildJob(object):
return self.job_item.retries_remaining > 0
def send_notification(self, kind, error_message=None, image_id=None, manifest_digests=None):
tags = self.build_config.get('docker_tags', ['latest'])
event_data = {
'build_id': self.repo_build.uuid,
'build_name': self.repo_build.display_name,
'docker_tags': tags,
'trigger_id': self.repo_build.trigger.uuid,
'trigger_kind': self.repo_build.trigger.service.name,
'trigger_metadata': self.build_config.get('trigger_metadata', {})
}
if image_id is not None:
event_data['image_id'] = image_id
if manifest_digests:
event_data['manifest_digests'] = manifest_digests
if error_message is not None:
event_data['error_message'] = error_message
# TODO(jzelinskie): remove when more endpoints have been converted to using
# interfaces
repo = AttrDict({
'namespace_name': self.repo_build.repository.namespace_user.username,
'name': self.repo_build.repository.name,
})
spawn_notification(repo, kind, event_data,
subpage='build/%s' % self.repo_build.uuid,
pathargs=['build', self.repo_build.uuid])
self.build_notifier.send_notification(kind, error_message, image_id, manifest_digests)
@lru_cache(maxsize=1)
def _load_repo_build(self):
@ -182,3 +156,61 @@ class BuildJob(object):
return list(cached_tags)[0]
return None
class BuildJobNotifier(object):
""" A class for sending notifications to a job that only relies on the build_uuid """
def __init__(self, build_uuid):
self.build_uuid = build_uuid
@property
def repo_build(self):
return self._load_repo_build()
@lru_cache(maxsize=1)
def _load_repo_build(self):
try:
return model.build.get_repository_build(self.build_uuid)
except model.InvalidRepositoryBuildException:
raise BuildJobLoadException(
'Could not load repository build with ID %s' % self.build_uuid)
@property
def build_config(self):
try:
return json.loads(self.repo_build.job_config)
except ValueError:
raise BuildJobLoadException(
'Could not parse repository build job config with ID %s' % self.repo_build.uuid
)
def send_notification(self, kind, error_message=None, image_id=None, manifest_digests=None):
tags = self.build_config.get('docker_tags', ['latest'])
event_data = {
'build_id': self.repo_build.uuid,
'build_name': self.repo_build.display_name,
'docker_tags': tags,
'trigger_id': self.repo_build.trigger.uuid,
'trigger_kind': self.repo_build.trigger.service.name,
'trigger_metadata': self.build_config.get('trigger_metadata', {})
}
if image_id is not None:
event_data['image_id'] = image_id
if manifest_digests:
event_data['manifest_digests'] = manifest_digests
if error_message is not None:
event_data['error_message'] = error_message
# TODO(jzelinskie): remove when more endpoints have been converted to using
# interfaces
repo = AttrDict({
'namespace_name': self.repo_build.repository.namespace_user.username,
'name': self.repo_build.repository.name,
})
spawn_notification(repo, kind, event_data,
subpage='build/%s' % self.repo_build.uuid,
pathargs=['build', self.repo_build.uuid])

View file

@ -0,0 +1,28 @@
"""Create new notification type
Revision ID: 94836b099894
Revises: faf752bd2e0a
Create Date: 2016-11-30 10:29:51.519278
"""
# revision identifiers, used by Alembic.
revision = '94836b099894'
down_revision = 'faf752bd2e0a'
from alembic import op
def upgrade(tables):
op.bulk_insert(tables.externalnotificationevent,
[
{'name': 'build_cancelled'},
])
def downgrade(tables):
op.execute(tables
.externalnotificationevent
.delete()
.where(tables.
externalnotificationevent.c.name == op.inline_literal('build_cancelled')))

View file

@ -197,6 +197,7 @@ def cancel_repository_build(build, build_queue):
""" This tries to cancel the build returns true if request is successful false if it can't be cancelled """
with db_transaction():
from app import build_canceller
from buildman.jobutil.buildjob import BuildJobNotifier
# Reload the build for update.
# We are loading the build for update so checks should be as quick as possible.
try:
@ -210,6 +211,7 @@ def cancel_repository_build(build, build_queue):
for cancelled in cancel_builds:
if cancelled():
build.phase = BUILD_PHASE.CANCELLED
BuildJobNotifier(build.uuid).send_notification("build_cancelled")
build.save()
return True
build.phase = original_phase

View file

@ -358,3 +358,38 @@ class BuildFailureEvent(BaseBuildEvent):
def get_summary(self, event_data, notification_data):
return 'Build failure ' + _build_summary(event_data)
class BuildCancelledEvent(BaseBuildEvent):
@classmethod
def event_name(cls):
return 'build_cancelled'
def get_level(self, event_data, notification_data):
return 'info'
def get_sample_data(self, notification):
build_uuid = 'fake-build-id'
# TODO(jzelinskie): remove when more endpoints have been converted to using
# interfaces
repo = AttrDict({
'namespace_name': notification.repository.namespace_user.username,
'name': notification.repository.name,
})
return build_event_data(repo, {
'build_id': build_uuid,
'build_name': 'some-fake-build',
'docker_tags': ['latest', 'foo', 'bar'],
'trigger_id': '1245634',
'trigger_kind': 'GitHub',
'trigger_metadata': {
"default_branch": "master",
"ref": "refs/heads/somebranch",
"commit": "42d4a62c53350993ea41069e9f2cfdefb0df097d"
},
'image_id': '1245657346'
}, subpage='/build/%s' % build_uuid)
def get_summary(self, event_data, notification_data):
return 'Build cancelled ' + _build_summary(event_data)

View file

@ -359,6 +359,7 @@ def initialize_database():
ExternalNotificationEvent.create(name='build_queued')
ExternalNotificationEvent.create(name='build_start')
ExternalNotificationEvent.create(name='build_success')
ExternalNotificationEvent.create(name='build_cancelled')
ExternalNotificationEvent.create(name='build_failure')
ExternalNotificationEvent.create(name='vulnerability_found')
@ -374,6 +375,7 @@ def initialize_database():
NotificationKind.create(name='build_queued')
NotificationKind.create(name='build_start')
NotificationKind.create(name='build_success')
NotificationKind.create(name='build_cancelled')
NotificationKind.create(name='build_failure')
NotificationKind.create(name='vulnerability_found')
NotificationKind.create(name='service_key_submitted')

View file

@ -80,6 +80,22 @@ function(Config, Features, VulnerabilityService) {
'placeholder': '(refs/heads/somebranch)|(refs/tags/sometag)'
}
]
},
{
'id': 'build_cancelled',
'title': 'Docker Build Cancelled',
'icon': 'fa-minus-circle',
'fields': [
{
'name': 'ref-regex',
'type': 'regex',
'title': 'matching ref(s)',
'help_text': 'An optional regular expression for matching the git branch or tag ' +
'git ref. If left blank, the notification will fire for all builds.',
'optional': true,
'placeholder': '(refs/heads/somebranch)|(refs/tags/sometag)'
}
]
}];
for (var i = 0; i < buildEvents.length; ++i) {

View file

@ -121,6 +121,14 @@ function($rootScope, $interval, UserService, ApiService, StringBuilderService, P
},
'dismissable': true
},
'build_cancelled': {
'level': 'info',
'message': 'A build was cancelled for repository {repository}',
'page': function(metadata) {
return '/repository/' + metadata.repository + '/build?current=' + metadata.build_id;
},
'dismissable': true
},
'vulnerability_found': {
'level': function(metadata) {
var priority = metadata['vulnerability']['priority'];

Binary file not shown.

View file

@ -11,6 +11,7 @@ class TestCreate(unittest.TestCase):
self.assertIsNotNone(NotificationEvent.get_event('build_success'))
self.assertIsNotNone(NotificationEvent.get_event('build_failure'))
self.assertIsNotNone(NotificationEvent.get_event('build_start'))
self.assertIsNotNone(NotificationEvent.get_event('build_cancelled'))
self.assertIsNotNone(NotificationEvent.get_event('vulnerability_found'))