Merge pull request #2652 from charltonaustin/failing_repository_notifications_to_be_disabled_after_n_failures_in_a_row_144646649
Failing repository notifications to be disabled after n failures in a row 144646649
This commit is contained in:
commit
a71f60a9c1
14 changed files with 156 additions and 33 deletions
|
@ -2,6 +2,7 @@
|
|||
|
||||
import json
|
||||
|
||||
import logging
|
||||
from flask import request
|
||||
|
||||
from app import notification_queue
|
||||
|
@ -14,7 +15,7 @@ from endpoints.notificationmethod import (NotificationMethod,
|
|||
CannotValidateNotificationMethodException)
|
||||
from endpoints.notificationhelper import build_notification_data
|
||||
from data import model
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
def notification_view(note):
|
||||
config = {}
|
||||
|
@ -36,6 +37,7 @@ def notification_view(note):
|
|||
'config': config,
|
||||
'title': note.title,
|
||||
'event_config': event_config,
|
||||
'number_of_failures': note.number_of_failures,
|
||||
}
|
||||
|
||||
|
||||
|
@ -154,6 +156,18 @@ class RepositoryNotification(RepositoryParamResource):
|
|||
|
||||
return 'No Content', 204
|
||||
|
||||
@require_repo_admin
|
||||
@nickname('resetRepositoryNotificationFailures')
|
||||
@disallow_for_app_repositories
|
||||
def post(self, namespace, repository, uuid):
|
||||
""" Resets repository notification to 0 failures. """
|
||||
model.notification.reset_notification_number_of_failures(namespace, repository, uuid)
|
||||
log_action('reset_repo_notification', namespace,
|
||||
{'repo': repository, 'namespace': namespace, 'notification_id': uuid},
|
||||
repo=model.repository.get_repository(namespace, repository))
|
||||
|
||||
return 'No Content', 204
|
||||
|
||||
|
||||
@resource('/v1/repository/<apirepopath:repository>/notification/<uuid>/test')
|
||||
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
|
||||
|
|
|
@ -45,6 +45,7 @@ FIELD_ARGS = {'trigger_uuid': '1234', 'field_name': 'foobar'}
|
|||
(RepositoryNotificationList, 'post', None),
|
||||
(RepositoryNotification, 'get', NOTIFICATION_ARGS),
|
||||
(RepositoryNotification, 'delete', NOTIFICATION_ARGS),
|
||||
(RepositoryNotification, 'post', NOTIFICATION_ARGS),
|
||||
(TestRepositoryNotification, 'post', NOTIFICATION_ARGS),
|
||||
(RepositoryImageSecurity, 'get', IMAGE_ARGS),
|
||||
(RepositoryManifestSecurity, 'get', MANIFEST_ARGS),
|
||||
|
|
|
@ -2,6 +2,7 @@ import pytest
|
|||
from flask_principal import AnonymousIdentity
|
||||
|
||||
from endpoints.api import api
|
||||
from endpoints.api.repositorynotification import RepositoryNotification
|
||||
from endpoints.api.team import OrganizationTeamSyncing
|
||||
from endpoints.api.test.shared import client_with_identity, conduct_api_call
|
||||
from endpoints.api.repository import RepositoryTrust
|
||||
|
@ -16,6 +17,8 @@ TEAM_PARAMS = {'orgname': 'buynlarge', 'teamname': 'owners'}
|
|||
BUILD_PARAMS = {'build_uuid': 'test-1234'}
|
||||
REPO_PARAMS = {'repository': 'devtable/someapp'}
|
||||
SEARCH_PARAMS = {'query': ''}
|
||||
NOTIFICATION_PARAMS = {'namespace': 'devtable', 'repository': 'devtable/simple', 'uuid': 'some uuid'}
|
||||
|
||||
|
||||
@pytest.mark.parametrize('resource,method,params,body,identity,expected', [
|
||||
(OrganizationTeamSyncing, 'POST', TEAM_PARAMS, {}, None, 403),
|
||||
|
@ -52,6 +55,11 @@ SEARCH_PARAMS = {'query': ''}
|
|||
(RepositorySignatures, 'GET', REPO_PARAMS, {}, 'reader', 403),
|
||||
(RepositorySignatures, 'GET', REPO_PARAMS, {}, 'devtable', 404),
|
||||
|
||||
(RepositoryNotification, 'POST', NOTIFICATION_PARAMS, {}, None, 403),
|
||||
(RepositoryNotification, 'POST', NOTIFICATION_PARAMS, {}, 'freshuser', 403),
|
||||
(RepositoryNotification, 'POST', NOTIFICATION_PARAMS, {}, 'reader', 403),
|
||||
(RepositoryNotification, 'POST', NOTIFICATION_PARAMS, {}, 'devtable', 204),
|
||||
|
||||
(RepositoryTrust, 'POST', REPO_PARAMS, {'trust_enabled': True}, None, 403),
|
||||
(RepositoryTrust, 'POST', REPO_PARAMS, {'trust_enabled': True}, 'freshuser', 403),
|
||||
(RepositoryTrust, 'POST', REPO_PARAMS, {'trust_enabled': True}, 'reader', 403),
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import logging
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
|
||||
import requests
|
||||
|
||||
from flask_mail import Message
|
||||
|
||||
from app import mail, app, OVERRIDE_CONFIG_DIRECTORY
|
||||
|
@ -11,10 +10,9 @@ from data import model
|
|||
from util.config.validator import SSL_FILENAMES
|
||||
from workers.queueworker import JobException
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
METHOD_TIMEOUT = app.config.get('NOTIFICATION_SEND_TIMEOUT', 10) # Seconds
|
||||
METHOD_TIMEOUT = app.config.get('NOTIFICATION_SEND_TIMEOUT', 10) # Seconds
|
||||
|
||||
|
||||
class InvalidNotificationMethodException(Exception):
|
||||
|
@ -53,6 +51,7 @@ class NotificationMethod(object):
|
|||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def perform(self, notification_obj, event_handler, notification_data):
|
||||
"""
|
||||
Performs the notification method.
|
||||
|
@ -80,7 +79,7 @@ class QuayNotificationMethod(NotificationMethod):
|
|||
def validate(self, repository, config_data):
|
||||
status, err_message, target_users = self.find_targets(repository, config_data)
|
||||
if err_message:
|
||||
raise CannotValidateNotificationMethodException(err_message)
|
||||
raise CannotValidateNotificationMethodException(err_message)
|
||||
|
||||
def find_targets(self, repository, config_data):
|
||||
target_info = config_data['target']
|
||||
|
@ -116,7 +115,6 @@ class QuayNotificationMethod(NotificationMethod):
|
|||
# Lookup the team's members
|
||||
return (True, None, model.organization.get_organization_team_members(org_team.id))
|
||||
|
||||
|
||||
def perform(self, notification_obj, event_handler, notification_data):
|
||||
repository = notification_obj.repository
|
||||
if not repository:
|
||||
|
@ -152,7 +150,6 @@ class EmailMethod(NotificationMethod):
|
|||
'is not authorized to receive '
|
||||
'notifications for this repository')
|
||||
|
||||
|
||||
def perform(self, notification_obj, event_handler, notification_data):
|
||||
config_data = json.loads(notification_obj.config_json)
|
||||
email = config_data.get('email', '')
|
||||
|
@ -165,7 +162,7 @@ class EmailMethod(NotificationMethod):
|
|||
msg.html = event_handler.get_message(notification_data['event_data'], notification_data)
|
||||
|
||||
try:
|
||||
mail.send(msg)
|
||||
mail.send(msg)
|
||||
except Exception as ex:
|
||||
logger.exception('Email was unable to be sent: %s' % ex.message)
|
||||
raise NotificationMethodPerformException(ex.message)
|
||||
|
@ -193,7 +190,7 @@ class WebhookMethod(NotificationMethod):
|
|||
try:
|
||||
resp = requests.post(url, data=json.dumps(payload), headers=headers, cert=SSLClientCert,
|
||||
timeout=METHOD_TIMEOUT)
|
||||
if resp.status_code/100 != 2:
|
||||
if resp.status_code / 100 != 2:
|
||||
error_message = '%s response for webhook to url: %s' % (resp.status_code, url)
|
||||
logger.error(error_message)
|
||||
logger.error(resp.content)
|
||||
|
@ -208,6 +205,7 @@ class FlowdockMethod(NotificationMethod):
|
|||
""" Method for sending notifications to Flowdock via the Team Inbox API:
|
||||
https://www.flowdock.com/api/team-inbox
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def method_name(cls):
|
||||
return 'flowdock'
|
||||
|
@ -232,7 +230,7 @@ class FlowdockMethod(NotificationMethod):
|
|||
headers = {'Content-type': 'application/json'}
|
||||
payload = {
|
||||
'source': 'Quay',
|
||||
'from_address': 'support@quay.io',
|
||||
'from_address': 'support@quay.io',
|
||||
'subject': event_handler.get_summary(notification_data['event_data'], notification_data),
|
||||
'content': event_handler.get_message(notification_data['event_data'], notification_data),
|
||||
'from_name': owner.username,
|
||||
|
@ -244,7 +242,7 @@ class FlowdockMethod(NotificationMethod):
|
|||
|
||||
try:
|
||||
resp = requests.post(url, data=json.dumps(payload), headers=headers, timeout=METHOD_TIMEOUT)
|
||||
if resp.status_code/100 != 2:
|
||||
if resp.status_code / 100 != 2:
|
||||
error_message = '%s response for flowdock to url: %s' % (resp.status_code, url)
|
||||
logger.error(error_message)
|
||||
logger.error(resp.content)
|
||||
|
@ -259,6 +257,7 @@ class HipchatMethod(NotificationMethod):
|
|||
""" Method for sending notifications to Hipchat via the API:
|
||||
https://www.hipchat.com/docs/apiv2/method/send_room_notification
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def method_name(cls):
|
||||
return 'hipchat'
|
||||
|
@ -305,7 +304,7 @@ class HipchatMethod(NotificationMethod):
|
|||
|
||||
try:
|
||||
resp = requests.post(url, data=json.dumps(payload), headers=headers, timeout=METHOD_TIMEOUT)
|
||||
if resp.status_code/100 != 2:
|
||||
if resp.status_code / 100 != 2:
|
||||
error_message = '%s response for hipchat to url: %s' % (resp.status_code, url)
|
||||
logger.error(error_message)
|
||||
logger.error(resp.content)
|
||||
|
@ -318,6 +317,7 @@ class HipchatMethod(NotificationMethod):
|
|||
|
||||
from HTMLParser import HTMLParser
|
||||
|
||||
|
||||
class SlackAdjuster(HTMLParser):
|
||||
def __init__(self):
|
||||
self.reset()
|
||||
|
@ -335,7 +335,7 @@ class SlackAdjuster(HTMLParser):
|
|||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag == 'a':
|
||||
self.result.append('<%s|' % (self.get_attr(attrs, 'href'), ))
|
||||
self.result.append('<%s|' % (self.get_attr(attrs, 'href'),))
|
||||
|
||||
if tag == 'i':
|
||||
self.result.append('_')
|
||||
|
@ -359,6 +359,7 @@ class SlackAdjuster(HTMLParser):
|
|||
def get_data(self):
|
||||
return ''.join(self.result)
|
||||
|
||||
|
||||
def adjust_tags(html):
|
||||
s = SlackAdjuster()
|
||||
s.feed(html)
|
||||
|
@ -423,7 +424,7 @@ class SlackMethod(NotificationMethod):
|
|||
|
||||
try:
|
||||
resp = requests.post(url, data=json.dumps(payload), headers=headers, timeout=METHOD_TIMEOUT)
|
||||
if resp.status_code/100 != 2:
|
||||
if resp.status_code / 100 != 2:
|
||||
error_message = '%s response for Slack to url: %s' % (resp.status_code, url)
|
||||
logger.error(error_message)
|
||||
logger.error(resp.content)
|
||||
|
|
Reference in a new issue