diff --git a/auth/auth.py b/auth/auth.py index 9c6281aef..fb08b9184 100644 --- a/auth/auth.py +++ b/auth/auth.py @@ -1,7 +1,7 @@ import logging from functools import wraps -from flask import request, _request_ctx_stack, abort, session +from flask import request, _request_ctx_stack, session from flask.ext.principal import identity_changed, Identity from base64 import b64decode @@ -10,19 +10,11 @@ from app import app from permissions import QuayDeferredPermissionUser from util.names import parse_namespace_repository +from util.http import abort logger = logging.getLogger(__name__) - -def get_authenticated_user(): - return getattr(_request_ctx_stack.top, 'authenticated_user', None) - - -def get_validated_token(): - return getattr(_request_ctx_stack.top, 'validated_token', None) - - def process_basic_auth(auth): normalized = [part.strip() for part in auth.split(' ') if part] if normalized[0].lower() != 'basic' or len(normalized) != 2: @@ -89,13 +81,13 @@ def process_token(auth): if len(token_details) != 1: logger.warning('Invalid token format: %s' % auth) - abort(401) + abort(401, message="Invalid token format: %(auth)", issue='invalid-auth-token', auth=auth) token_vals = {val[0]: val[1] for val in (detail.split('=') for detail in token_details)} if 'signature' not in token_vals: logger.warning('Token does not contain signature: %s' % auth) - abort(401) + abort(401, message="Token does not contain a valid signature: %(auth)", issue='invalid-auth-token', auth=auth) try: token_data = model.load_token_data(token_vals['signature']) @@ -103,7 +95,7 @@ def process_token(auth): except model.InvalidTokenException: logger.warning('Token could not be validated: %s' % token_vals['signature']) - abort(401) + abort(401, message="Token could not be validated: %(auth)", issue='invalid-auth-token', auth=auth) logger.debug('Successfully validated token: %s' % token_data.code) ctx = _request_ctx_stack.top @@ -134,7 +126,7 @@ def extract_namespace_repo_from_session(f): if 'namespace' not in session or 'repository' not in session: logger.error('Unable to load namespace or repository from session: %s' % session) - abort(400) + abort(400, message="Missing namespace in request") return f(session['namespace'], session['repository'], *args, **kwargs) return wrapper diff --git a/auth/auth_context.py b/auth/auth_context.py new file mode 100644 index 000000000..09ff8d759 --- /dev/null +++ b/auth/auth_context.py @@ -0,0 +1,7 @@ +from flask import _request_ctx_stack + +def get_authenticated_user(): + return getattr(_request_ctx_stack.top, 'authenticated_user', None) + +def get_validated_token(): + return getattr(_request_ctx_stack.top, 'validated_token', None) diff --git a/data/model.py b/data/model.py index 938efb640..1ddb09f2c 100644 --- a/data/model.py +++ b/data/model.py @@ -888,16 +888,20 @@ def create_repository(namespace, name, creating_user, visibility='private'): return repo -def __translate_ancestry(old_ancestry, translations, existing_images): +def __translate_ancestry(old_ancestry, translations, repository, username): if old_ancestry == '/': return '/' def translate_id(old_id): + logger.debug('Translating id: %s', old_id) if old_id not in translations: # Figure out which docker_image_id the old id refers to, then find a # a local one old = Image.select(Image.docker_image_id).where(Image.id == old_id).get() - translations[old_id] = existing_images[old.docker_image_id] + image_in_repo = find_create_or_link_image(old.docker_image_id, + repository, username, + translations) + translations[old_id] = image_in_repo.id return translations[old_id] @@ -906,9 +910,14 @@ def __translate_ancestry(old_ancestry, translations, existing_images): return '/%s/' % '/'.join(new_ids) -def create_or_link_image(docker_image_id, repository, username, translations, - existing_images): +def find_create_or_link_image(docker_image_id, repository, username, + translations): with transaction_factory(db): + repo_image = get_repo_image(repository.namespace, repository.name, + docker_image_id) + if repo_image: + return repo_image + query = (Image .select(Image, ImageStorage) .distinct() @@ -930,7 +939,8 @@ def create_or_link_image(docker_image_id, repository, username, translations, logger.debug(msg, docker_image_id, to_copy.storage.uuid) new_image_ancestry = __translate_ancestry(to_copy.ancestors, - translations, existing_images) + translations, repository, + username) storage = to_copy.storage origin_image_id = to_copy.id @@ -943,6 +953,7 @@ def create_or_link_image(docker_image_id, repository, username, translations, ancestors=new_image_ancestry) if origin_image_id: + logger.debug('Storing translation %s -> %s', origin_image_id, new_image.id) translations[origin_image_id] = new_image.id return new_image diff --git a/endpoints/common.py b/endpoints/common.py index 29f9447a6..1dbb2ab77 100644 --- a/endpoints/common.py +++ b/endpoints/common.py @@ -90,12 +90,14 @@ def common_login(db_user): @app.errorhandler(model.DataModelException) def handle_dme(ex): - return make_response(ex.message, 400) + logger.exception(ex) + return make_response('Internal Server Error', 500) @app.errorhandler(KeyError) def handle_dme_key_error(ex): - return make_response(ex.message, 400) + logger.exception(ex) + return make_response('Internal Server Error', 500) def generate_csrf_token(): diff --git a/endpoints/index.py b/endpoints/index.py index 864805b9a..549bcfec8 100644 --- a/endpoints/index.py +++ b/endpoints/index.py @@ -9,8 +9,8 @@ from collections import OrderedDict from data import model, userevent from data.queue import webhook_queue from app import mixpanel, app -from auth.auth import (process_auth, get_authenticated_user, - get_validated_token) +from auth.auth import process_auth +from auth.auth_context import get_authenticated_user, get_validated_token from util.names import parse_repository_name from util.email import send_confirmation_email from auth.permissions import (ModifyRepositoryPermission, UserPermission, @@ -193,17 +193,15 @@ def create_repository(namespace, repository): for desc in image_descriptions]) new_repo_images = dict(added_images) - existing_image_translations = {} for existing in model.get_repository_images(namespace, repository): if existing.docker_image_id in new_repo_images: - existing_image_translations[existing.docker_image_id] = existing.id added_images.pop(existing.docker_image_id) username = get_authenticated_user() and get_authenticated_user().username translations = {} for image_description in added_images.values(): - model.create_or_link_image(image_description['id'], repo, username, - translations, existing_image_translations) + model.find_create_or_link_image(image_description['id'], repo, username, + translations) response = make_response('Created', 201) diff --git a/endpoints/registry.py b/endpoints/registry.py index 9727d5ccb..51bb7219e 100644 --- a/endpoints/registry.py +++ b/endpoints/registry.py @@ -2,7 +2,7 @@ import logging import json from flask import (make_response, request, session, Response, redirect, - Blueprint, abort as flask_abort) + Blueprint) from functools import wraps from datetime import datetime from time import time @@ -259,7 +259,7 @@ def get_image_json(namespace, repository, image_id, headers): data = store.get_content(store.image_json_path(namespace, repository, image_id, uuid)) except IOError: - flask_abort(404) + abort(404, message='Image data not found') try: size = store.get_size(store.image_layer_path(namespace, repository, diff --git a/initdb.py b/initdb.py index c6e9bbc29..97ede01d1 100644 --- a/initdb.py +++ b/initdb.py @@ -67,7 +67,8 @@ def __create_subtree(repo, structure, creator_username, parent): logger.debug('new docker id: %s' % docker_image_id) checksum = __gen_checksum(docker_image_id) - new_image = model.create_or_link_image(docker_image_id, repo, None, {}, {}) + new_image = model.find_create_or_link_image(docker_image_id, repo, None, + {}) new_image.storage.uuid = IMAGE_UUIDS[image_num % len(IMAGE_UUIDS)] new_image.storage.save() diff --git a/static/css/quay.css b/static/css/quay.css index d2861cc0e..43c2e1480 100644 --- a/static/css/quay.css +++ b/static/css/quay.css @@ -234,6 +234,10 @@ i.toggle-icon:hover { top: 4px; } +.entity-reference-element { + white-space: nowrap; +} + .entity-reference-element i.fa-exclamation-triangle { color: #c09853; margin-left: 10px; @@ -394,6 +398,10 @@ i.toggle-icon:hover { line-height: 25px; } +.logs-view-element .log-performer { + white-space: nowrap; +} + .billing-options-element .current-card { font-size: 16px; margin-bottom: 20px; diff --git a/static/directives/entity-reference.html b/static/directives/entity-reference.html index 6cb91a5c0..12ee81f86 100644 --- a/static/directives/entity-reference.html +++ b/static/directives/entity-reference.html @@ -1,19 +1,27 @@ - + - {{entity.name}} - {{entity.name}} + {{entity.name}} + {{entity.name}} - + - - {{getPrefix(entity.name)}}{{getShortenedName(entity.name)}} + + + {{ getPrefix(entity.name) }}+{{ getShortenedName(entity.name) }} + + + {{ getPrefix(entity.name) }}+{{ getShortenedName(entity.name) }} + + + + {{getShortenedName(entity.name)}} - + diff --git a/static/directives/entity-search.html b/static/directives/entity-search.html index e87f3e2c3..04dac6c0f 100644 --- a/static/directives/entity-search.html +++ b/static/directives/entity-search.html @@ -7,6 +7,10 @@