Add notification filtering for builds based on ref regex

Fixes #1835
This commit is contained in:
Joseph Schorr 2016-09-14 16:48:17 -04:00
parent 0dce935c40
commit 03d4445a02
3 changed files with 206 additions and 9 deletions

View file

@ -1,6 +1,7 @@
import logging import logging
import time import time
import json import json
import re
from datetime import datetime from datetime import datetime
from notificationhelper import build_event_data from notificationhelper import build_event_data
@ -138,7 +139,28 @@ class VulnerabilityFoundEvent(NotificationEvent):
', '.join(event_data['tags'])) ', '.join(event_data['tags']))
class BuildQueueEvent(NotificationEvent): class BaseBuildEvent(NotificationEvent):
def should_perform(self, event_data, notification_data):
event_config = json.loads(notification_data.event_config_json)
ref_regex = event_config.get('ref-regex') or None
if ref_regex is None:
return True
# Lookup the ref. If none, this is a non-git build and we should not fire the event.
ref = event_data.get('trigger_metadata', {}).get('ref', None)
if ref is None:
return False
# Try parsing the regex string as a regular expression. If we fail, we fail to fire
# the event.
try:
return bool(re.compile(str(ref_regex)).match(ref))
except Exception:
logger.warning('Regular expression error for build event filter: %s', ref_regex)
return False
class BuildQueueEvent(BaseBuildEvent):
@classmethod @classmethod
def event_name(cls): def event_name(cls):
return 'build_queued' return 'build_queued'
@ -177,7 +199,7 @@ class BuildQueueEvent(NotificationEvent):
return 'Build queued ' + _build_summary(event_data) return 'Build queued ' + _build_summary(event_data)
class BuildStartEvent(NotificationEvent): class BuildStartEvent(BaseBuildEvent):
@classmethod @classmethod
def event_name(cls): def event_name(cls):
return 'build_start' return 'build_start'
@ -205,7 +227,7 @@ class BuildStartEvent(NotificationEvent):
return 'Build started ' + _build_summary(event_data) return 'Build started ' + _build_summary(event_data)
class BuildSuccessEvent(NotificationEvent): class BuildSuccessEvent(BaseBuildEvent):
@classmethod @classmethod
def event_name(cls): def event_name(cls):
return 'build_success' return 'build_success'
@ -234,7 +256,7 @@ class BuildSuccessEvent(NotificationEvent):
return 'Build succeeded ' + _build_summary(event_data) return 'Build succeeded ' + _build_summary(event_data)
class BuildFailureEvent(NotificationEvent): class BuildFailureEvent(BaseBuildEvent):
@classmethod @classmethod
def event_name(cls): def event_name(cls):
return 'build_failure' return 'build_failure'

View file

@ -20,22 +20,62 @@ function(Config, Features, VulnerabilityService) {
{ {
'id': 'build_queued', 'id': 'build_queued',
'title': 'Dockerfile Build Queued', 'title': 'Dockerfile Build Queued',
'icon': 'fa-tasks' 'icon': 'fa-tasks',
'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,
}
]
}, },
{ {
'id': 'build_start', 'id': 'build_start',
'title': 'Dockerfile Build Started', 'title': 'Dockerfile Build Started',
'icon': 'fa-circle-o-notch' 'icon': 'fa-circle-o-notch',
'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,
}
]
}, },
{ {
'id': 'build_success', 'id': 'build_success',
'title': 'Dockerfile Build Successfully Completed', 'title': 'Dockerfile Build Successfully Completed',
'icon': 'fa-check-circle-o' 'icon': 'fa-check-circle-o',
'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,
}
]
}, },
{ {
'id': 'build_failure', 'id': 'build_failure',
'title': 'Dockerfile Build Failed', 'title': 'Dockerfile Build Failed',
'icon': 'fa-times-circle-o' 'icon': 'fa-times-circle-o',
'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,
}
]
}]; }];
for (var i = 0; i < buildEvents.length; ++i) { for (var i = 0; i < buildEvents.length; ++i) {
@ -52,7 +92,7 @@ function(Config, Features, VulnerabilityService) {
{ {
'name': 'level', 'name': 'level',
'type': 'enum', 'type': 'enum',
'title': 'Minimum Severity Level', 'title': 'minimum severity level',
'values': VulnerabilityService.LEVELS, 'values': VulnerabilityService.LEVELS,
'help_text': 'A vulnerability must have a severity of the chosen level (or higher) ' + 'help_text': 'A vulnerability must have a severity of the chosen level (or higher) ' +
'for this notification to fire. Defcon 1 is a special severity level ' + 'for this notification to fire. Defcon 1 is a special severity level ' +

135
test/test_notifications.py Normal file
View file

@ -0,0 +1,135 @@
import unittest
from endpoints.notificationevent import BuildSuccessEvent
from util.morecollections import AttrDict
class TestShouldPerform(unittest.TestCase):
def test_build_nofilter(self):
notification_data = AttrDict({
'event_config_json': '{}',
})
# No build data at all.
self.assertTrue(BuildSuccessEvent().should_perform({}, notification_data))
# With trigger metadata but no ref.
self.assertTrue(BuildSuccessEvent().should_perform({
'trigger_metadata': {},
}, notification_data))
# With trigger metadata and a ref.
self.assertTrue(BuildSuccessEvent().should_perform({
'trigger_metadata': {
'ref': 'refs/heads/somebranch',
},
}, notification_data))
def test_build_emptyfilter(self):
notification_data = AttrDict({
'event_config_json': '{"ref-regex": ""}',
})
# No build data at all.
self.assertTrue(BuildSuccessEvent().should_perform({}, notification_data))
# With trigger metadata but no ref.
self.assertTrue(BuildSuccessEvent().should_perform({
'trigger_metadata': {},
}, notification_data))
# With trigger metadata and a ref.
self.assertTrue(BuildSuccessEvent().should_perform({
'trigger_metadata': {
'ref': 'refs/heads/somebranch',
},
}, notification_data))
def test_build_invalidfilter(self):
notification_data = AttrDict({
'event_config_json': '{"ref-regex": "]["}',
})
# No build data at all.
self.assertFalse(BuildSuccessEvent().should_perform({}, notification_data))
# With trigger metadata but no ref.
self.assertFalse(BuildSuccessEvent().should_perform({
'trigger_metadata': {},
}, notification_data))
# With trigger metadata and a ref.
self.assertFalse(BuildSuccessEvent().should_perform({
'trigger_metadata': {
'ref': 'refs/heads/somebranch',
},
}, notification_data))
def test_build_withfilter(self):
notification_data = AttrDict({
'event_config_json': '{"ref-regex": "refs/heads/master"}',
})
# No build data at all.
self.assertFalse(BuildSuccessEvent().should_perform({}, notification_data))
# With trigger metadata but no ref.
self.assertFalse(BuildSuccessEvent().should_perform({
'trigger_metadata': {},
}, notification_data))
# With trigger metadata and a not-matching ref.
self.assertFalse(BuildSuccessEvent().should_perform({
'trigger_metadata': {
'ref': 'refs/heads/somebranch',
},
}, notification_data))
# With trigger metadata and a matching ref.
self.assertTrue(BuildSuccessEvent().should_perform({
'trigger_metadata': {
'ref': 'refs/heads/master',
},
}, notification_data))
def test_build_withwildcardfilter(self):
notification_data = AttrDict({
'event_config_json': '{"ref-regex": "refs/heads/.+"}',
})
# No build data at all.
self.assertFalse(BuildSuccessEvent().should_perform({}, notification_data))
# With trigger metadata but no ref.
self.assertFalse(BuildSuccessEvent().should_perform({
'trigger_metadata': {},
}, notification_data))
# With trigger metadata and a not-matching ref.
self.assertFalse(BuildSuccessEvent().should_perform({
'trigger_metadata': {
'ref': 'refs/tags/sometag',
},
}, notification_data))
# With trigger metadata and a matching ref.
self.assertTrue(BuildSuccessEvent().should_perform({
'trigger_metadata': {
'ref': 'refs/heads/master',
},
}, notification_data))
# With trigger metadata and another matching ref.
self.assertTrue(BuildSuccessEvent().should_perform({
'trigger_metadata': {
'ref': 'refs/heads/somebranch',
},
}, notification_data))
if __name__ == '__main__':
unittest.main()