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. """ """ Exception raised if a build job could not be instantiated for some reason. """
pass pass
class BuildJob(object): class BuildJob(object):
""" Represents a single in-progress build job. """ """ Represents a single in-progress build job. """
def __init__(self, job_item): def __init__(self, job_item):
@ -21,6 +22,7 @@ class BuildJob(object):
try: try:
self.job_details = json.loads(job_item.body) self.job_details = json.loads(job_item.body)
self.build_notifier = BuildJobNotifier(self.build_uuid)
except ValueError: except ValueError:
raise BuildJobLoadException( raise BuildJobLoadException(
'Could not parse build queue item config with ID %s' % self.job_details['build_uuid'] '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 return self.job_item.retries_remaining > 0
def send_notification(self, kind, error_message=None, image_id=None, manifest_digests=None): def send_notification(self, kind, error_message=None, image_id=None, manifest_digests=None):
tags = self.build_config.get('docker_tags', ['latest']) self.build_notifier.send_notification(kind, error_message, image_id, manifest_digests)
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])
@lru_cache(maxsize=1) @lru_cache(maxsize=1)
def _load_repo_build(self): def _load_repo_build(self):
@ -182,3 +156,61 @@ class BuildJob(object):
return list(cached_tags)[0] return list(cached_tags)[0]
return None 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 """ """ This tries to cancel the build returns true if request is successful false if it can't be cancelled """
with db_transaction(): with db_transaction():
from app import build_canceller from app import build_canceller
from buildman.jobutil.buildjob import BuildJobNotifier
# Reload the build for update. # Reload the build for update.
# We are loading the build for update so checks should be as quick as possible. # We are loading the build for update so checks should be as quick as possible.
try: try:
@ -210,6 +211,7 @@ def cancel_repository_build(build, build_queue):
for cancelled in cancel_builds: for cancelled in cancel_builds:
if cancelled(): if cancelled():
build.phase = BUILD_PHASE.CANCELLED build.phase = BUILD_PHASE.CANCELLED
BuildJobNotifier(build.uuid).send_notification("build_cancelled")
build.save() build.save()
return True return True
build.phase = original_phase build.phase = original_phase

View file

@ -358,3 +358,38 @@ class BuildFailureEvent(BaseBuildEvent):
def get_summary(self, event_data, notification_data): def get_summary(self, event_data, notification_data):
return 'Build failure ' + _build_summary(event_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_queued')
ExternalNotificationEvent.create(name='build_start') ExternalNotificationEvent.create(name='build_start')
ExternalNotificationEvent.create(name='build_success') ExternalNotificationEvent.create(name='build_success')
ExternalNotificationEvent.create(name='build_cancelled')
ExternalNotificationEvent.create(name='build_failure') ExternalNotificationEvent.create(name='build_failure')
ExternalNotificationEvent.create(name='vulnerability_found') ExternalNotificationEvent.create(name='vulnerability_found')
@ -374,6 +375,7 @@ def initialize_database():
NotificationKind.create(name='build_queued') NotificationKind.create(name='build_queued')
NotificationKind.create(name='build_start') NotificationKind.create(name='build_start')
NotificationKind.create(name='build_success') NotificationKind.create(name='build_success')
NotificationKind.create(name='build_cancelled')
NotificationKind.create(name='build_failure') NotificationKind.create(name='build_failure')
NotificationKind.create(name='vulnerability_found') NotificationKind.create(name='vulnerability_found')
NotificationKind.create(name='service_key_submitted') NotificationKind.create(name='service_key_submitted')

View file

@ -80,6 +80,22 @@ function(Config, Features, VulnerabilityService) {
'placeholder': '(refs/heads/somebranch)|(refs/tags/sometag)' '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) { for (var i = 0; i < buildEvents.length; ++i) {

View file

@ -121,6 +121,14 @@ function($rootScope, $interval, UserService, ApiService, StringBuilderService, P
}, },
'dismissable': true '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': { 'vulnerability_found': {
'level': function(metadata) { 'level': function(metadata) {
var priority = metadata['vulnerability']['priority']; 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_success'))
self.assertIsNotNone(NotificationEvent.get_event('build_failure')) self.assertIsNotNone(NotificationEvent.get_event('build_failure'))
self.assertIsNotNone(NotificationEvent.get_event('build_start')) self.assertIsNotNone(NotificationEvent.get_event('build_start'))
self.assertIsNotNone(NotificationEvent.get_event('build_cancelled'))
self.assertIsNotNone(NotificationEvent.get_event('vulnerability_found')) self.assertIsNotNone(NotificationEvent.get_event('vulnerability_found'))