Adding in cancel notifications
This commit is contained in:
parent
b7aac159ae
commit
4103a0b75f
9 changed files with 153 additions and 29 deletions
|
@ -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])
|
|
@ -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')))
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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.
|
@ -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'))
|
||||
|
||||
|
||||
|
|
Reference in a new issue