Merge remote-tracking branch 'origin/redalert'

Conflicts:
	app.py
This commit is contained in:
Jake Moshenko 2014-08-04 16:56:34 -04:00
commit 0372013f70
46 changed files with 2432 additions and 432 deletions

View file

@ -307,6 +307,8 @@ import endpoints.api.organization
import endpoints.api.permission
import endpoints.api.prototype
import endpoints.api.repository
import endpoints.api.repositorynotification
import endpoints.api.repoemail
import endpoints.api.repotoken
import endpoints.api.robot
import endpoints.api.search
@ -315,4 +317,3 @@ import endpoints.api.tag
import endpoints.api.team
import endpoints.api.trigger
import endpoints.api.user
import endpoints.api.webhook

View file

@ -0,0 +1,56 @@
import logging
from flask import request, abort
from endpoints.api import (resource, nickname, require_repo_admin, RepositoryParamResource,
log_action, validate_json_request, NotFound, internal_only)
from app import tf
from data import model
from data.database import db
from util.useremails import send_repo_authorization_email
import features
logger = logging.getLogger(__name__)
def record_view(record):
return {
'email': record.email,
'repository': record.repository.name,
'namespace': record.repository.namespace,
'confirmed': record.confirmed
}
@internal_only
@resource('/v1/repository/<repopath:repository>/authorizedemail/<email>')
class RepositoryAuthorizedEmail(RepositoryParamResource):
""" Resource for checking and authorizing e-mail addresses to receive repo notifications. """
@require_repo_admin
@nickname('checkRepoEmailAuthorized')
def get(self, namespace, repository, email):
""" Checks to see if the given e-mail address is authorized on this repository. """
record = model.get_email_authorized_for_repo(namespace, repository, email)
if not record:
abort(404)
return record_view(record)
@require_repo_admin
@nickname('sendAuthorizeRepoEmail')
def post(self, namespace, repository, email):
""" Starts the authorization process for an e-mail address on a repository. """
with tf(db):
record = model.get_email_authorized_for_repo(namespace, repository, email)
if record and record.confirmed:
return record_view(record)
if not record:
record = model.create_email_authorization_for_repo(namespace, repository, email)
send_repo_authorization_email(namespace, repository, email, record.code)
return record_view(record)

View file

@ -0,0 +1,142 @@
import json
from flask import request, abort
from app import notification_queue
from endpoints.api import (RepositoryParamResource, nickname, resource, require_repo_admin,
log_action, validate_json_request, api, NotFound, request_error)
from endpoints.notificationevent import NotificationEvent
from endpoints.notificationmethod import (NotificationMethod,
CannotValidateNotificationMethodException)
from endpoints.notificationhelper import build_notification_data
from data import model
def notification_view(notification):
config = {}
try:
config = json.loads(notification.config_json)
except:
config = {}
return {
'uuid': notification.uuid,
'event': notification.event.name,
'method': notification.method.name,
'config': config
}
@resource('/v1/repository/<repopath:repository>/notification/')
class RepositoryNotificationList(RepositoryParamResource):
""" Resource for dealing with listing and creating notifications on a repository. """
schemas = {
'NotificationCreateRequest': {
'id': 'NotificationCreateRequest',
'type': 'object',
'description': 'Information for creating a notification on a repository',
'required': [
'event',
'method',
'config'
],
'properties': {
'event': {
'type': 'string',
'description': 'The event on which the notification will respond',
},
'method': {
'type': 'string',
'description': 'The method of notification (such as email or web callback)',
},
'config': {
'type': 'object',
'description': 'JSON config information for the specific method of notification'
}
}
},
}
@require_repo_admin
@nickname('createRepoNotification')
@validate_json_request('NotificationCreateRequest')
def post(self, namespace, repository):
""" Create a new notification for the specified repository. """
repo = model.get_repository(namespace, repository)
json = request.get_json()
method_handler = NotificationMethod.get_method(json['method'])
if not method_handler:
raise request_error(message='Unknown method')
try:
method_handler.validate(repo, json['config'])
except CannotValidateNotificationMethodException as ex:
raise request_error(message=ex.message)
notification = model.create_repo_notification(repo, json['event'], json['method'],
json['config'])
resp = notification_view(notification)
log_action('add_repo_notification', namespace,
{'repo': repository, 'notification_id': notification.uuid,
'event': json['event'], 'method': json['method']},
repo=repo)
return resp, 201
@require_repo_admin
@nickname('listRepoNotifications')
def get(self, namespace, repository):
""" List the notifications for the specified repository. """
notifications = model.list_repo_notifications(namespace, repository)
return {
'notifications': [notification_view(n) for n in notifications]
}
@resource('/v1/repository/<repopath:repository>/notification/<uuid>')
class RepositoryNotification(RepositoryParamResource):
""" Resource for dealing with specific notifications. """
@require_repo_admin
@nickname('getRepoNotification')
def get(self, namespace, repository, uuid):
""" Get information for the specified notification. """
try:
notification = model.get_repo_notification(namespace, repository, uuid)
except model.InvalidNotificationException:
raise NotFound()
return notification_view(notification)
@require_repo_admin
@nickname('deleteRepoNotification')
def delete(self, namespace, repository, uuid):
""" Deletes the specified notification. """
notification = model.delete_repo_notification(namespace, repository, uuid)
log_action('delete_repo_notification', namespace,
{'repo': repository, 'notification_id': uuid,
'event': notification.event.name, 'method': notification.method.name},
repo=model.get_repository(namespace, repository))
return 'No Content', 204
@resource('/v1/repository/<repopath:repository>/notification/<uuid>/test')
class TestRepositoryNotification(RepositoryParamResource):
""" Resource for queuing a test of a notification. """
@require_repo_admin
@nickname('testRepoNotification')
def post(self, namespace, repository, uuid):
""" Queues a test notification for this repository. """
try:
notification = model.get_repo_notification(namespace, repository, uuid)
except model.InvalidNotificationException:
raise NotFound()
event_info = NotificationEvent.get_event(notification.event.name)
sample_data = event_info.get_sample_data(repository=notification.repository)
notification_data = build_notification_data(notification, sample_data)
notification_queue.put([namespace, repository, notification.event.name],
json.dumps(notification_data))
return {}

View file

@ -2,9 +2,11 @@ from endpoints.api import (ApiResource, parse_args, query_param, truthy_bool, ni
require_scope)
from data import model
from auth.permissions import (OrganizationMemberPermission, ViewTeamPermission,
ReadRepositoryPermission, UserAdminPermission)
ReadRepositoryPermission, UserAdminPermission,
AdministerOrganizationPermission)
from auth.auth_context import get_authenticated_user
from auth import scopes
from util.gravatar import compute_hash
@resource('/v1/entities/<prefix>')
@ -14,10 +16,12 @@ class EntitySearch(ApiResource):
@query_param('namespace', 'Namespace to use when querying for org entities.', type=str,
default='')
@query_param('includeTeams', 'Whether to include team names.', type=truthy_bool, default=False)
@query_param('includeOrgs', 'Whether to include orgs names.', type=truthy_bool, default=False)
@nickname('getMatchingEntities')
def get(self, args, prefix):
""" Get a list of entities that match the specified prefix. """
teams = []
org_data = []
namespace_name = args['namespace']
robot_namespace = None
@ -34,6 +38,15 @@ class EntitySearch(ApiResource):
if args['includeTeams']:
teams = model.get_matching_teams(prefix, organization)
if args['includeOrgs'] and AdministerOrganizationPermission(namespace_name) \
and namespace_name.startswith(prefix):
org_data = [{
'name': namespace_name,
'kind': 'org',
'is_org_member': True,
'gravatar': compute_hash(organization.email),
}]
except model.InvalidOrganizationException:
# namespace name was a user
user = get_authenticated_user()
@ -69,7 +82,7 @@ class EntitySearch(ApiResource):
user_data = [user_view(user) for user in users]
return {
'results': team_data + user_data
'results': team_data + user_data + org_data
}
@ -113,4 +126,4 @@ class FindRepositories(ApiResource):
'repositories': [repo_view(repo) for repo in matching
if (repo.visibility.name == 'public' or
ReadRepositoryPermission(repo.namespace, repo.name).can())]
}
}

View file

@ -74,10 +74,12 @@ def user_view(user):
def notification_view(notification):
return {
'id': notification.uuid,
'organization': notification.target.username if notification.target.organization else None,
'kind': notification.kind.name,
'created': format_date(notification.created),
'metadata': json.loads(notification.metadata_json),
'dismissed': notification.dismissed
}
@ -409,6 +411,46 @@ class UserNotificationList(ApiResource):
}
@resource('/v1/user/notifications/<uuid>')
@internal_only
class UserNotification(ApiResource):
schemas = {
'UpdateNotification': {
'id': 'UpdateNotification',
'type': 'object',
'description': 'Information for updating a notification',
'properties': {
'dismissed': {
'type': 'boolean',
'description': 'Whether the notification is dismissed by the user',
},
},
},
}
@require_user_admin
@nickname('getUserNotification')
def get(self, uuid):
notification = model.lookup_notification(get_authenticated_user(), uuid)
if not notification:
raise NotFound()
return notification_view(notification)
@require_user_admin
@nickname('updateUserNotification')
@validate_json_request('UpdateNotification')
def put(self, uuid):
notification = model.lookup_notification(get_authenticated_user(), uuid)
if not notification:
raise NotFound()
notification.dismissed = request.get_json().get('dismissed', False)
notification.save()
return notification_view(notification)
def authorization_view(access_token):
oauth_app = access_token.application
return {

View file

@ -1,77 +0,0 @@
import json
from flask import request
from endpoints.api import (RepositoryParamResource, nickname, resource, require_repo_admin,
log_action, validate_json_request, api, NotFound)
from data import model
def webhook_view(webhook):
return {
'public_id': webhook.public_id,
'parameters': json.loads(webhook.parameters),
}
@resource('/v1/repository/<repopath:repository>/webhook/')
class WebhookList(RepositoryParamResource):
""" Resource for dealing with listing and creating webhooks. """
schemas = {
'WebhookCreateRequest': {
'id': 'WebhookCreateRequest',
'type': 'object',
'description': 'Arbitrary json.',
},
}
@require_repo_admin
@nickname('createWebhook')
@validate_json_request('WebhookCreateRequest')
def post(self, namespace, repository):
""" Create a new webhook for the specified repository. """
repo = model.get_repository(namespace, repository)
webhook = model.create_webhook(repo, request.get_json())
resp = webhook_view(webhook)
repo_string = '%s/%s' % (namespace, repository)
headers = {
'Location': api.url_for(Webhook, repository=repo_string, public_id=webhook.public_id),
}
log_action('add_repo_webhook', namespace,
{'repo': repository, 'webhook_id': webhook.public_id},
repo=repo)
return resp, 201, headers
@require_repo_admin
@nickname('listWebhooks')
def get(self, namespace, repository):
""" List the webhooks for the specified repository. """
webhooks = model.list_webhooks(namespace, repository)
return {
'webhooks': [webhook_view(webhook) for webhook in webhooks]
}
@resource('/v1/repository/<repopath:repository>/webhook/<public_id>')
class Webhook(RepositoryParamResource):
""" Resource for dealing with specific webhooks. """
@require_repo_admin
@nickname('getWebhook')
def get(self, namespace, repository, public_id):
""" Get information for the specified webhook. """
try:
webhook = model.get_webhook(namespace, repository, public_id)
except model.InvalidWebhookException:
raise NotFound()
return webhook_view(webhook)
@require_repo_admin
@nickname('deleteWebhook')
def delete(self, namespace, repository, public_id):
""" Delete the specified webhook. """
model.delete_webhook(namespace, repository, public_id)
log_action('delete_repo_webhook', namespace,
{'repo': repository, 'webhook_id': public_id},
repo=model.get_repository(namespace, repository))
return 'No Content', 204

View file

@ -9,7 +9,7 @@ from flask.ext.principal import identity_changed
from random import SystemRandom
from data import model
from app import app, login_manager, dockerfile_build_queue
from app import app, login_manager, dockerfile_build_queue, notification_queue
from auth.permissions import QuayDeferredPermissionUser
from auth import scopes
from endpoints.api.discovery import swagger_route_data
@ -17,10 +17,12 @@ from werkzeug.routing import BaseConverter
from functools import wraps
from config import getFrontendVisibleConfig
from external_libraries import get_external_javascript, get_external_css
from endpoints.notificationhelper import spawn_notification
import features
logger = logging.getLogger(__name__)
profile = logging.getLogger('application.profiler')
route_data = None
@ -220,6 +222,7 @@ def start_build(repository, dockerfile_id, tags, build_name, subdir, manual,
'pull_credentials': model.get_pull_credentials(pull_robot_name) if pull_robot_name else None
}), retries_remaining=1)
# Add the build to the repo's log.
metadata = {
'repo': repository.name,
'namespace': repository.namespace,
@ -236,4 +239,21 @@ def start_build(repository, dockerfile_id, tags, build_name, subdir, manual,
ip=request.remote_addr, metadata=metadata,
repository=repository)
# Add notifications for the build queue.
profile.debug('Adding notifications for repository')
event_data = {
'build_id': build_request.uuid,
'build_name': build_name,
'docker_tags': tags,
'is_manual': manual
}
if trigger:
event_data['trigger_id'] = trigger.uuid
event_data['trigger_kind'] = trigger.service.name
spawn_notification(repository, 'build_queued', event_data,
subpage='build?current=%s' % build_request.uuid,
pathargs=['build', build_request.uuid])
return build_request

View file

@ -8,7 +8,7 @@ from collections import OrderedDict
from data import model
from data.model import oauth
from app import analytics, app, webhook_queue, authentication, userevents, storage
from app import analytics, app, authentication, userevents, storage
from auth.auth import process_auth
from auth.auth_context import get_authenticated_user, get_validated_token, get_validated_oauth_token
from util.names import parse_repository_name
@ -17,6 +17,7 @@ from auth.permissions import (ModifyRepositoryPermission, UserAdminPermission,
ReadRepositoryPermission, CreateRepositoryPermission)
from util.http import abort
from endpoints.notificationhelper import spawn_notification
logger = logging.getLogger(__name__)
@ -307,7 +308,7 @@ def update_images(namespace, repository):
'action': 'pushed_repo',
'repository': repository,
'namespace': namespace
}
}
event = userevents.get_event(username)
event.publish_event_data('docker-cli', user_data)
@ -315,28 +316,16 @@ def update_images(namespace, repository):
profile.debug('GCing repository')
num_removed = model.garbage_collect_repository(namespace, repository)
# Generate a job for each webhook that has been added to this repo
profile.debug('Adding webhooks for repository')
# Generate a job for each notification that has been added to this repo
profile.debug('Adding notifications for repository')
webhooks = model.list_webhooks(namespace, repository)
for webhook in webhooks:
webhook_data = json.loads(webhook.parameters)
repo_string = '%s/%s' % (namespace, repository)
profile.debug('Creating webhook for repository \'%s\' for url \'%s\'',
repo_string, webhook_data['url'])
webhook_data['payload'] = {
'repository': repo_string,
'namespace': namespace,
'name': repository,
'docker_url': 'quay.io/%s' % repo_string,
'homepage': 'https://quay.io/repository/%s' % repo_string,
'visibility': repo.visibility.name,
'updated_tags': updated_tags,
'pushed_image_count': len(image_with_checksums),
'pruned_image_count': num_removed,
}
webhook_queue.put([namespace, repository], json.dumps(webhook_data))
event_data = {
'updated_tags': updated_tags,
'pushed_image_count': len(image_with_checksums),
'pruned_image_count': num_removed
}
spawn_notification(repo, 'repo_push', event_data)
return make_response('Updated', 204)
abort(403)

View file

@ -0,0 +1,207 @@
import logging
import io
import os.path
import tarfile
import base64
from notificationhelper import build_event_data
logger = logging.getLogger(__name__)
class InvalidNotificationEventException(Exception):
pass
class NotificationEvent(object):
def __init__(self):
pass
def get_summary(self, event_data, notification_data):
"""
Returns a human readable one-line summary for the given notification data.
"""
raise NotImplementedError
def get_message(self, event_data, notification_data):
"""
Returns a human readable HTML message for the given notification data.
"""
raise NotImplementedError
def get_sample_data(self, repository=None):
"""
Returns sample data for testing the raising of this notification, with an optional
repository.
"""
raise NotImplementedError
@classmethod
def event_name(cls):
"""
Particular event implemented by subclasses.
"""
raise NotImplementedError
@classmethod
def get_event(cls, eventname):
for subc in cls.__subclasses__():
if subc.event_name() == eventname:
return subc()
raise InvalidNotificationEventException('Unable to find event: %s' % eventname)
class RepoPushEvent(NotificationEvent):
@classmethod
def event_name(cls):
return 'repo_push'
def get_summary(self, event_data, notification_data):
return 'Repository %s updated' % (event_data['repository'])
def get_message(self, event_data, notification_data):
if not event_data.get('updated_tags', []):
html = """
Repository <a href="%s">%s</a> has been updated via a push.
""" % (event_data['homepage'],
event_data['repository'])
else:
html = """
Repository <a href="%s">%s</a> has been updated via a push.
<br><br>
Tags Updated: %s
""" % (event_data['homepage'],
event_data['repository'],
', '.join(event_data['updated_tags']))
return html
def get_sample_data(self, repository):
return build_event_data(repository, {
'updated_tags': ['latest', 'foo', 'bar'],
'pushed_image_count': 10,
'pruned_image_count': 3
})
class BuildQueueEvent(NotificationEvent):
@classmethod
def event_name(cls):
return 'build_queued'
def get_sample_data(self, repository):
build_uuid = 'fake-build-id'
return build_event_data(repository, {
'is_manual': False,
'build_id': build_uuid,
'build_name': 'some-fake-build',
'docker_tags': ['latest', 'foo', 'bar'],
'trigger_kind': 'GitHub'
}, subpage='/build?current=%s' % build_uuid)
def get_summary(self, event_data, notification_data):
return 'Build queued for repository %s' % (event_data['repository'])
def get_message(self, event_data, notification_data):
is_manual = event_data['is_manual']
if is_manual:
html = """
A <a href="%s">new build</a> has been manually queued to start on repository %s.
<br><br>
Build ID: %s
""" % (event_data['homepage'], event_data['repository'], event_data['build_id'])
else:
html = """
A <a href="%s">new build</a> has been queued via a %s trigger to start on repository %s.
<br><br>
Build ID: %s
""" % (event_data['homepage'], event_data['trigger_kind'],
event_data['repository'], event_data['build_id'])
return html
class BuildStartEvent(NotificationEvent):
@classmethod
def event_name(cls):
return 'build_start'
def get_sample_data(self, repository):
build_uuid = 'fake-build-id'
return build_event_data(repository, {
'build_id': build_uuid,
'build_name': 'some-fake-build',
'docker_tags': ['latest', 'foo', 'bar'],
'trigger_kind': 'GitHub'
}, subpage='/build?current=%s' % build_uuid)
def get_summary(self, event_data, notification_data):
return 'Build started for repository %s' % (event_data['repository'])
def get_message(self, event_data, notification_data):
html = """
A <a href="%s">new build</a> has started on repository %s.
<br><br>
Build ID: %s
""" % (event_data['homepage'], event_data['repository'], event_data['build_id'])
return html
class BuildSuccessEvent(NotificationEvent):
@classmethod
def event_name(cls):
return 'build_success'
def get_sample_data(self, repository):
build_uuid = 'fake-build-id'
return build_event_data(repository, {
'build_id': build_uuid,
'build_name': 'some-fake-build',
'docker_tags': ['latest', 'foo', 'bar'],
'trigger_kind': 'GitHub'
}, subpage='/build?current=%s' % build_uuid)
def get_summary(self, event_data, notification_data):
return 'Build succeeded for repository %s' % (event_data['repository'])
def get_message(self, event_data, notification_data):
html = """
A <a href="%s">build</a> has finished on repository %s.
<br><br>
Build ID: %s
""" % (event_data['homepage'], event_data['repository'], event_data['build_id'])
return html
class BuildFailureEvent(NotificationEvent):
@classmethod
def event_name(cls):
return 'build_failure'
def get_sample_data(self, repository):
return build_event_data(repository, {
'build_id': build_uuid,
'build_name': 'some-fake-build',
'docker_tags': ['latest', 'foo', 'bar'],
'trigger_kind': 'GitHub',
'error_message': 'This is a fake error message'
}, subpage='/build?current=%s' % build_uuid)
def get_summary(self, event_data, notification_data):
return 'Build failure for repository %s' % (event_data['repository'])
def get_message(self, event_data, notification_data):
html = """
A <a href="%s">build</a> has failed on repository %s.
<br><br>
Reason: %s<br>
Build ID: %s<br>
""" % (event_data['homepage'], event_data['repository'],
event_data['error_message'], event_data['build_id'])
return html

View file

@ -0,0 +1,46 @@
from app import app, notification_queue
from data import model
import json
def build_event_data(repo, extra_data={}, subpage=None):
repo_string = '%s/%s' % (repo.namespace, repo.name)
homepage = '%s://%s/repository/%s' % (app.config['PREFERRED_URL_SCHEME'],
app.config['SERVER_HOSTNAME'],
repo_string)
if subpage:
if not subpage.startswith('/'):
subpage = '/' + subpage
homepage = homepage + subpage
event_data = {
'repository': repo_string,
'namespace': repo.namespace,
'name': repo.name,
'docker_url': '%s/%s' % (app.config['SERVER_HOSTNAME'], repo_string),
'homepage': homepage,
'visibility': repo.visibility.name
}
event_data.update(extra_data)
return event_data
def build_notification_data(notification, event_data):
return {
'notification_uuid': notification.uuid,
'repository_namespace': notification.repository.namespace,
'repository_name': notification.repository.name,
'event_data': event_data
}
def spawn_notification(repo, event_name, extra_data={}, subpage=None, pathargs=[]):
event_data = build_event_data(repo, extra_data=extra_data, subpage=subpage)
notifications = model.list_repo_notifications(repo.namespace, repo.name, event_name=event_name)
for notification in notifications:
notification_data = build_notification_data(notification, event_data)
path = [repo.namespace, repo.name, event_name] + pathargs
notification_queue.put(path, json.dumps(notification_data))

View file

@ -0,0 +1,189 @@
import logging
import io
import os.path
import tarfile
import base64
import json
from flask.ext.mail import Message
from app import mail, app
from data import model
logger = logging.getLogger(__name__)
class InvalidNotificationMethodException(Exception):
pass
class CannotValidateNotificationMethodException(Exception):
pass
class NotificationMethod(object):
def __init__(self):
pass
@classmethod
def method_name(cls):
"""
Particular method implemented by subclasses.
"""
raise NotImplementedError
def validate(self, repository, config_data):
"""
Validates that the notification can be created with the given data. Throws
a CannotValidateNotificationMethodException on failure.
"""
raise NotImplementedError
def perform(self, notification, event_handler, notification_data):
"""
Performs the notification method.
notification: The noticication record itself.
event_handler: The NotificationEvent handler.
notification_data: The dict of notification data placed in the queue.
"""
raise NotImplementedError
@classmethod
def get_method(cls, methodname):
for subc in cls.__subclasses__():
if subc.method_name() == methodname:
return subc()
raise InvalidNotificationMethodException('Unable to find method: %s' % methodname)
class QuayNotificationMethod(NotificationMethod):
@classmethod
def method_name(cls):
return 'quay_notification'
def validate(self, repository, config_data):
status, err_message, target_users = self.find_targets(repository, config_data)
if err_message:
raise CannotValidateNotificationMethodException(err_message)
def find_targets(self, repository, config_data):
target_info = config_data['target']
if target_info['kind'] == 'user':
target = model.get_user(target_info['name'])
if not target:
# Just to be safe.
return (True, 'Unknown user %s' % target_info['name'], [])
return (True, None, [target])
elif target_info['kind'] == 'org':
target = model.get_organization(target_info['name'])
if not target:
# Just to be safe.
return (True, 'Unknown organization %s' % target_info['name'], None)
# Only repositories under the organization can cause notifications to that org.
if target_info['name'] != repository.namespace:
return (False, 'Organization name must match repository namespace')
return (True, None, [target])
elif target_info['kind'] == 'team':
# Lookup the team.
team = None
try:
team = model.get_organization_team(repository.namespace, target_info['name'])
except model.InvalidTeamException:
# Probably deleted.
return (True, 'Unknown team %s' % target_info['name'], None)
# Lookup the team's members
return (True, None, model.get_organization_team_members(team.id))
def perform(self, notification, event_handler, notification_data):
repository = notification.repository
if not repository:
# Probably deleted.
return True
# Lookup the target user or team to which we'll send the notification.
config_data = json.loads(notification.config_json)
status, err_message, target_users = self.find_targets(repository, config_data)
if not status:
return False
# For each of the target users, create a notification.
for target_user in set(target_users or []):
model.create_notification(event_handler.event_name(), target_user,
metadata=notification_data['event_data'])
return True
class EmailMethod(NotificationMethod):
@classmethod
def method_name(cls):
return 'email'
def validate(self, repository, config_data):
email = config_data.get('email', '')
if not email:
raise CannotValidateNotificationMethodException('Missing e-mail address')
record = model.get_email_authorized_for_repo(repository.namespace, repository.name, email)
if not record or not record.confirmed:
raise CannotValidateNotificationMethodException('The specified e-mail address '
'is not authorized to receive '
'notifications for this repository')
def perform(self, notification, event_handler, notification_data):
config_data = json.loads(notification.config_json)
email = config_data.get('email', '')
if not email:
return False
msg = Message(event_handler.get_summary(notification_data['event_data'], notification_data),
sender='support@quay.io',
recipients=[email])
msg.html = event_handler.get_message(notification_data['event_data'], notification_data)
try:
with app.app_context():
mail.send(msg)
except Exception as ex:
logger.exception('Email was unable to be sent: %s' % ex.message)
return False
return True
class WebhookMethod(NotificationMethod):
@classmethod
def method_name(cls):
return 'webhook'
def validate(self, repository, config_data):
url = config_data.get('url', '')
if not url:
raise CannotValidateNotificationMethodException('Missing webhook URL')
def perform(self, notification, event_handler, notification_data):
config_data = json.loads(notification.config_json)
url = config_data.get('url', '')
if not url:
return False
payload = notification_data['event_data']
headers = {'Content-type': 'application/json'}
try:
resp = requests.post(url, data=json.dumps(payload), headers=headers)
if resp.status_code/100 != 2:
logger.error('%s response for webhook to url: %s' % (resp.status_code,
url))
return False
except requests.exceptions.RequestException as ex:
logger.exception('Webhook was unable to be sent: %s' % ex.message)
return False
return True

View file

@ -214,6 +214,26 @@ def receipt():
abort(404)
@web.route('/authrepoemail', methods=['GET'])
def confirm_repo_email():
code = request.values['code']
record = None
try:
record = model.confirm_email_authorization_for_repo(code)
except model.DataModelException as ex:
return render_page_template('confirmerror.html', error_message=ex.message)
message = """
Your E-mail address has been authorized to receive notifications for repository
<a href="%s://%s/repository/%s/%s">%s/%s</a>.
""" % (app.config['PREFERRED_URL_SCHEME'], app.config['SERVER_HOSTNAME'],
record.repository.namespace, record.repository.name,
record.repository.namespace, record.repository.name)
return render_page_template('message.html', message=message)
@web.route('/confirm', methods=['GET'])
def confirm_email():
code = request.values['code']