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. """
|
""" 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])
|
|
@ -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 """
|
""" 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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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')
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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.
|
@ -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'))
|
||||||
|
|
||||||
|
|
||||||
|
|
Reference in a new issue