Merge remote-tracking branch 'origin/master' into rustedbuilds
This commit is contained in:
commit
de49ce151b
14 changed files with 124 additions and 50 deletions
20
auth/auth.py
20
auth/auth.py
|
@ -1,7 +1,7 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from functools import wraps
|
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 flask.ext.principal import identity_changed, Identity
|
||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
|
|
||||||
|
@ -10,19 +10,11 @@ from app import app
|
||||||
from permissions import QuayDeferredPermissionUser
|
from permissions import QuayDeferredPermissionUser
|
||||||
|
|
||||||
from util.names import parse_namespace_repository
|
from util.names import parse_namespace_repository
|
||||||
|
from util.http import abort
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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):
|
def process_basic_auth(auth):
|
||||||
normalized = [part.strip() for part in auth.split(' ') if part]
|
normalized = [part.strip() for part in auth.split(' ') if part]
|
||||||
if normalized[0].lower() != 'basic' or len(normalized) != 2:
|
if normalized[0].lower() != 'basic' or len(normalized) != 2:
|
||||||
|
@ -89,13 +81,13 @@ def process_token(auth):
|
||||||
|
|
||||||
if len(token_details) != 1:
|
if len(token_details) != 1:
|
||||||
logger.warning('Invalid token format: %s' % auth)
|
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
|
token_vals = {val[0]: val[1] for val in
|
||||||
(detail.split('=') for detail in token_details)}
|
(detail.split('=') for detail in token_details)}
|
||||||
if 'signature' not in token_vals:
|
if 'signature' not in token_vals:
|
||||||
logger.warning('Token does not contain signature: %s' % auth)
|
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:
|
try:
|
||||||
token_data = model.load_token_data(token_vals['signature'])
|
token_data = model.load_token_data(token_vals['signature'])
|
||||||
|
@ -103,7 +95,7 @@ def process_token(auth):
|
||||||
except model.InvalidTokenException:
|
except model.InvalidTokenException:
|
||||||
logger.warning('Token could not be validated: %s' %
|
logger.warning('Token could not be validated: %s' %
|
||||||
token_vals['signature'])
|
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)
|
logger.debug('Successfully validated token: %s' % token_data.code)
|
||||||
ctx = _request_ctx_stack.top
|
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:
|
if 'namespace' not in session or 'repository' not in session:
|
||||||
logger.error('Unable to load namespace or repository from session: %s' %
|
logger.error('Unable to load namespace or repository from session: %s' %
|
||||||
session)
|
session)
|
||||||
abort(400)
|
abort(400, message="Missing namespace in request")
|
||||||
|
|
||||||
return f(session['namespace'], session['repository'], *args, **kwargs)
|
return f(session['namespace'], session['repository'], *args, **kwargs)
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
7
auth/auth_context.py
Normal file
7
auth/auth_context.py
Normal file
|
@ -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)
|
|
@ -888,16 +888,20 @@ def create_repository(namespace, name, creating_user, visibility='private'):
|
||||||
return repo
|
return repo
|
||||||
|
|
||||||
|
|
||||||
def __translate_ancestry(old_ancestry, translations, existing_images):
|
def __translate_ancestry(old_ancestry, translations, repository, username):
|
||||||
if old_ancestry == '/':
|
if old_ancestry == '/':
|
||||||
return '/'
|
return '/'
|
||||||
|
|
||||||
def translate_id(old_id):
|
def translate_id(old_id):
|
||||||
|
logger.debug('Translating id: %s', old_id)
|
||||||
if old_id not in translations:
|
if old_id not in translations:
|
||||||
# Figure out which docker_image_id the old id refers to, then find a
|
# Figure out which docker_image_id the old id refers to, then find a
|
||||||
# a local one
|
# a local one
|
||||||
old = Image.select(Image.docker_image_id).where(Image.id == old_id).get()
|
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]
|
return translations[old_id]
|
||||||
|
|
||||||
|
@ -906,9 +910,14 @@ def __translate_ancestry(old_ancestry, translations, existing_images):
|
||||||
return '/%s/' % '/'.join(new_ids)
|
return '/%s/' % '/'.join(new_ids)
|
||||||
|
|
||||||
|
|
||||||
def create_or_link_image(docker_image_id, repository, username, translations,
|
def find_create_or_link_image(docker_image_id, repository, username,
|
||||||
existing_images):
|
translations):
|
||||||
with transaction_factory(db):
|
with transaction_factory(db):
|
||||||
|
repo_image = get_repo_image(repository.namespace, repository.name,
|
||||||
|
docker_image_id)
|
||||||
|
if repo_image:
|
||||||
|
return repo_image
|
||||||
|
|
||||||
query = (Image
|
query = (Image
|
||||||
.select(Image, ImageStorage)
|
.select(Image, ImageStorage)
|
||||||
.distinct()
|
.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)
|
logger.debug(msg, docker_image_id, to_copy.storage.uuid)
|
||||||
|
|
||||||
new_image_ancestry = __translate_ancestry(to_copy.ancestors,
|
new_image_ancestry = __translate_ancestry(to_copy.ancestors,
|
||||||
translations, existing_images)
|
translations, repository,
|
||||||
|
username)
|
||||||
|
|
||||||
storage = to_copy.storage
|
storage = to_copy.storage
|
||||||
origin_image_id = to_copy.id
|
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)
|
ancestors=new_image_ancestry)
|
||||||
|
|
||||||
if origin_image_id:
|
if origin_image_id:
|
||||||
|
logger.debug('Storing translation %s -> %s', origin_image_id, new_image.id)
|
||||||
translations[origin_image_id] = new_image.id
|
translations[origin_image_id] = new_image.id
|
||||||
|
|
||||||
return new_image
|
return new_image
|
||||||
|
|
|
@ -90,12 +90,14 @@ def common_login(db_user):
|
||||||
|
|
||||||
@app.errorhandler(model.DataModelException)
|
@app.errorhandler(model.DataModelException)
|
||||||
def handle_dme(ex):
|
def handle_dme(ex):
|
||||||
return make_response(ex.message, 400)
|
logger.exception(ex)
|
||||||
|
return make_response('Internal Server Error', 500)
|
||||||
|
|
||||||
|
|
||||||
@app.errorhandler(KeyError)
|
@app.errorhandler(KeyError)
|
||||||
def handle_dme_key_error(ex):
|
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():
|
def generate_csrf_token():
|
||||||
|
|
|
@ -9,8 +9,8 @@ from collections import OrderedDict
|
||||||
from data import model, userevent
|
from data import model, userevent
|
||||||
from data.queue import webhook_queue
|
from data.queue import webhook_queue
|
||||||
from app import mixpanel, app
|
from app import mixpanel, app
|
||||||
from auth.auth import (process_auth, get_authenticated_user,
|
from auth.auth import process_auth
|
||||||
get_validated_token)
|
from auth.auth_context import get_authenticated_user, get_validated_token
|
||||||
from util.names import parse_repository_name
|
from util.names import parse_repository_name
|
||||||
from util.email import send_confirmation_email
|
from util.email import send_confirmation_email
|
||||||
from auth.permissions import (ModifyRepositoryPermission, UserPermission,
|
from auth.permissions import (ModifyRepositoryPermission, UserPermission,
|
||||||
|
@ -193,17 +193,15 @@ def create_repository(namespace, repository):
|
||||||
for desc in image_descriptions])
|
for desc in image_descriptions])
|
||||||
new_repo_images = dict(added_images)
|
new_repo_images = dict(added_images)
|
||||||
|
|
||||||
existing_image_translations = {}
|
|
||||||
for existing in model.get_repository_images(namespace, repository):
|
for existing in model.get_repository_images(namespace, repository):
|
||||||
if existing.docker_image_id in new_repo_images:
|
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)
|
added_images.pop(existing.docker_image_id)
|
||||||
|
|
||||||
username = get_authenticated_user() and get_authenticated_user().username
|
username = get_authenticated_user() and get_authenticated_user().username
|
||||||
translations = {}
|
translations = {}
|
||||||
for image_description in added_images.values():
|
for image_description in added_images.values():
|
||||||
model.create_or_link_image(image_description['id'], repo, username,
|
model.find_create_or_link_image(image_description['id'], repo, username,
|
||||||
translations, existing_image_translations)
|
translations)
|
||||||
|
|
||||||
response = make_response('Created', 201)
|
response = make_response('Created', 201)
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from flask import (make_response, request, session, Response, redirect,
|
from flask import (make_response, request, session, Response, redirect,
|
||||||
Blueprint, abort as flask_abort)
|
Blueprint)
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from time import time
|
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,
|
data = store.get_content(store.image_json_path(namespace, repository,
|
||||||
image_id, uuid))
|
image_id, uuid))
|
||||||
except IOError:
|
except IOError:
|
||||||
flask_abort(404)
|
abort(404, message='Image data not found')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
size = store.get_size(store.image_layer_path(namespace, repository,
|
size = store.get_size(store.image_layer_path(namespace, repository,
|
||||||
|
|
|
@ -67,7 +67,8 @@ def __create_subtree(repo, structure, creator_username, parent):
|
||||||
logger.debug('new docker id: %s' % docker_image_id)
|
logger.debug('new docker id: %s' % docker_image_id)
|
||||||
checksum = __gen_checksum(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.uuid = IMAGE_UUIDS[image_num % len(IMAGE_UUIDS)]
|
||||||
new_image.storage.save()
|
new_image.storage.save()
|
||||||
|
|
||||||
|
|
|
@ -234,6 +234,10 @@ i.toggle-icon:hover {
|
||||||
top: 4px;
|
top: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.entity-reference-element {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.entity-reference-element i.fa-exclamation-triangle {
|
.entity-reference-element i.fa-exclamation-triangle {
|
||||||
color: #c09853;
|
color: #c09853;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
|
@ -394,6 +398,10 @@ i.toggle-icon:hover {
|
||||||
line-height: 25px;
|
line-height: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.logs-view-element .log-performer {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
.billing-options-element .current-card {
|
.billing-options-element .current-card {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
|
|
|
@ -1,19 +1,27 @@
|
||||||
<span class="entity-reference-element">
|
<span class="entity-reference-element">
|
||||||
<span ng-show="entity.kind == 'team'">
|
<span ng-if="entity.kind == 'team'">
|
||||||
<i class="fa fa-group" title="Team" bs-tooltip="tooltip.title" data-container="body"></i>
|
<i class="fa fa-group" title="Team" bs-tooltip="tooltip.title" data-container="body"></i>
|
||||||
<span class="entity-name">
|
<span class="entity-name">
|
||||||
<span ng-show="!getIsAdmin(namespace)">{{entity.name}}</span>
|
<span ng-if="!getIsAdmin(namespace)">{{entity.name}}</span>
|
||||||
<span ng-show="getIsAdmin(namespace)"><a href="/organization/{{ namespace }}/teams/{{ entity.name }}">{{entity.name}}</a></span>
|
<span ng-if="getIsAdmin(namespace)"><a href="/organization/{{ namespace }}/teams/{{ entity.name }}">{{entity.name}}</a></span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<span ng-show="entity.kind != 'team'">
|
<span ng-if="entity.kind != 'team'">
|
||||||
<i class="fa fa-user" ng-show="!entity.is_robot" title="User" bs-tooltip="tooltip.title" data-container="body"></i>
|
<i class="fa fa-user" ng-show="!entity.is_robot" title="User" bs-tooltip="tooltip.title" data-container="body"></i>
|
||||||
<i class="fa fa-wrench" ng-show="entity.is_robot" title="Robot Account" bs-tooltip="tooltip.title" data-container="body"></i>
|
<i class="fa fa-wrench" ng-show="entity.is_robot" title="Robot Account" bs-tooltip="tooltip.title" data-container="body"></i>
|
||||||
<span class="entity-name">
|
<span class="entity-name" ng-if="entity.is_robot">
|
||||||
<span ng-show="entity.is_robot" class="prefix">{{getPrefix(entity.name)}}</span><span>{{getShortenedName(entity.name)}}</span>
|
<a href="{{ getRobotUrl(entity.name) }}" ng-if="getIsAdmin(getPrefix(entity.name))">
|
||||||
|
<span class="prefix">{{ getPrefix(entity.name) }}+</span><span>{{ getShortenedName(entity.name) }}</span>
|
||||||
|
</a>
|
||||||
|
<span ng-if="!getIsAdmin(getPrefix(entity.name))">
|
||||||
|
<span class="prefix">{{ getPrefix(entity.name) }}+</span><span>{{ getShortenedName(entity.name) }}</span>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
<i class="fa fa-exclamation-triangle" ng-show="entity.is_org_member === false"
|
<span class="entity-name" ng-if="!entity.is_robot">
|
||||||
title="This user is not a member of the organization" bs-tooltip="tooltip.title">
|
<span>{{getShortenedName(entity.name)}}</span>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<i class="fa fa-exclamation-triangle" ng-if="entity.is_org_member === false"
|
||||||
|
title="This user is not a member of the organization" bs-tooltip="tooltip.title" data-container="body">
|
||||||
</i>
|
</i>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -8,6 +8,10 @@
|
||||||
<ul class="dropdown-menu" role="menu" aria-labelledby="entityDropdownMenu">
|
<ul class="dropdown-menu" role="menu" aria-labelledby="entityDropdownMenu">
|
||||||
<li ng-show="lazyLoading" style="padding: 10px"><div class="quay-spinner"></div></li>
|
<li ng-show="lazyLoading" style="padding: 10px"><div class="quay-spinner"></div></li>
|
||||||
|
|
||||||
|
<li role="presentation" class="dropdown-header" ng-show="!lazyLoading && !robots && !isAdmin && !teams">
|
||||||
|
You do not have permission to manage teams and robots for this organization
|
||||||
|
</li>
|
||||||
|
|
||||||
<li role="presentation" ng-repeat="team in teams" ng-show="!lazyLoading"
|
<li role="presentation" ng-repeat="team in teams" ng-show="!lazyLoading"
|
||||||
ng-click="setEntity(team.name, 'team', false)">
|
ng-click="setEntity(team.name, 'team', false)">
|
||||||
<a role="menuitem" tabindex="-1" href="javascript:void(0)">
|
<a role="menuitem" tabindex="-1" href="javascript:void(0)">
|
||||||
|
|
|
@ -53,10 +53,10 @@
|
||||||
</td>
|
</td>
|
||||||
<td>{{ log.datetime }}</td>
|
<td>{{ log.datetime }}</td>
|
||||||
<td>
|
<td>
|
||||||
<span ng-show="log.performer">
|
<span class="log-performer" ng-show="log.performer">
|
||||||
<span class="entity-reference" entity="log.performer" namespace="organization.name"></span>
|
<span class="entity-reference" entity="log.performer" namespace="organization.name"></span>
|
||||||
</span>
|
</span>
|
||||||
<span ng-show="!log.performer && log.metadata.token">
|
<span class="log-performer" ng-show="!log.performer && log.metadata.token">
|
||||||
<i class="fa fa-key"></i>
|
<i class="fa fa-key"></i>
|
||||||
<span>{{ log.metadata.token }}</span>
|
<span>{{ log.metadata.token }}</span>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -838,15 +838,34 @@ quayApp.directive('entityReference', function () {
|
||||||
'entity': '=entity',
|
'entity': '=entity',
|
||||||
'namespace': '=namespace'
|
'namespace': '=namespace'
|
||||||
},
|
},
|
||||||
controller: function($scope, $element, UserService) {
|
controller: function($scope, $element, UserService, $sanitize) {
|
||||||
$scope.getIsAdmin = function(namespace) {
|
$scope.getIsAdmin = function(namespace) {
|
||||||
return UserService.isNamespaceAdmin(namespace);
|
return UserService.isNamespaceAdmin(namespace);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.getRobotUrl = function(name) {
|
||||||
|
var namespace = $scope.getPrefix(name);
|
||||||
|
if (!namespace) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$scope.getIsAdmin(namespace)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
var org = UserService.getOrganization(namespace);
|
||||||
|
if (!org) {
|
||||||
|
// This robot is owned by the user.
|
||||||
|
return '/user/?tab=robots&showRobot=' + $sanitize(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return '/organization/' + org['name'] + '/admin?tab=robots&showRobot=' + $sanitize(name);
|
||||||
|
};
|
||||||
|
|
||||||
$scope.getPrefix = function(name) {
|
$scope.getPrefix = function(name) {
|
||||||
if (!name) { return ''; }
|
if (!name) { return ''; }
|
||||||
var plus = name.indexOf('+');
|
var plus = name.indexOf('+');
|
||||||
return name.substr(0, plus + 1);
|
return name.substr(0, plus);
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.getShortenedName = function(name) {
|
$scope.getShortenedName = function(name) {
|
||||||
|
@ -1500,7 +1519,7 @@ quayApp.directive('robotsManager', function () {
|
||||||
'organization': '=organization',
|
'organization': '=organization',
|
||||||
'user': '=user'
|
'user': '=user'
|
||||||
},
|
},
|
||||||
controller: function($scope, $element, ApiService) {
|
controller: function($scope, $element, ApiService, $routeParams) {
|
||||||
$scope.ROBOT_PATTERN = ROBOT_PATTERN;
|
$scope.ROBOT_PATTERN = ROBOT_PATTERN;
|
||||||
$scope.robots = null;
|
$scope.robots = null;
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
|
@ -1512,6 +1531,15 @@ quayApp.directive('robotsManager', function () {
|
||||||
$scope.showRobotCounter++;
|
$scope.showRobotCounter++;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
$scope.findRobotIndexByName = function(name) {
|
||||||
|
for (var i = 0; i < $scope.robots.length; ++i) {
|
||||||
|
if ($scope.robots[i].name == name) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
};
|
||||||
|
|
||||||
$scope.getShortenedName = function(name) {
|
$scope.getShortenedName = function(name) {
|
||||||
var plus = name.indexOf('+');
|
var plus = name.indexOf('+');
|
||||||
return name.substr(plus + 1);
|
return name.substr(plus + 1);
|
||||||
|
@ -1534,11 +1562,9 @@ quayApp.directive('robotsManager', function () {
|
||||||
$scope.deleteRobot = function(info) {
|
$scope.deleteRobot = function(info) {
|
||||||
var shortName = $scope.getShortenedName(info.name);
|
var shortName = $scope.getShortenedName(info.name);
|
||||||
ApiService.deleteRobot($scope.organization, null, {'robot_shortname': shortName}).then(function(resp) {
|
ApiService.deleteRobot($scope.organization, null, {'robot_shortname': shortName}).then(function(resp) {
|
||||||
for (var i = 0; i < $scope.robots.length; ++i) {
|
var index = $scope.findRobotIndexByName(info.name);
|
||||||
if ($scope.robots[i].name == info.name) {
|
if (index >= 0) {
|
||||||
$scope.robots.splice(i, 1);
|
$scope.robots.splice(index, 1);
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, function() {
|
}, function() {
|
||||||
bootbox.dialog({
|
bootbox.dialog({
|
||||||
|
@ -1562,6 +1588,13 @@ quayApp.directive('robotsManager', function () {
|
||||||
ApiService.getRobots($scope.organization).then(function(resp) {
|
ApiService.getRobots($scope.organization).then(function(resp) {
|
||||||
$scope.robots = resp.robots;
|
$scope.robots = resp.robots;
|
||||||
$scope.loading = false;
|
$scope.loading = false;
|
||||||
|
|
||||||
|
if ($routeParams.showRobot) {
|
||||||
|
var index = $scope.findRobotIndexByName($routeParams.showRobot);
|
||||||
|
if (index >= 0) {
|
||||||
|
$scope.showRobot($scope.robots[index]);
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -2004,8 +2037,17 @@ quayApp.directive('entitySearch', function () {
|
||||||
$scope.lazyLoad = function() {
|
$scope.lazyLoad = function() {
|
||||||
if (!$scope.namespace || !$scope.lazyLoading) { return; }
|
if (!$scope.namespace || !$scope.lazyLoading) { return; }
|
||||||
|
|
||||||
|
// Determine whether we can admin this namespace.
|
||||||
$scope.isAdmin = UserService.isNamespaceAdmin($scope.namespace);
|
$scope.isAdmin = UserService.isNamespaceAdmin($scope.namespace);
|
||||||
|
|
||||||
|
// If the scope is an organization and we are not part of it, then nothing more we can do.
|
||||||
|
if (!$scope.isAdmin && $scope.isOrganization && !UserService.getOrganization($scope.namespace)) {
|
||||||
|
$scope.teams = null;
|
||||||
|
$scope.robots = null;
|
||||||
|
$scope.lazyLoading = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if ($scope.isOrganization && $scope.includeTeams) {
|
if ($scope.isOrganization && $scope.includeTeams) {
|
||||||
ApiService.getOrganization(null, {'orgname': $scope.namespace}).then(function(resp) {
|
ApiService.getOrganization(null, {'orgname': $scope.namespace}).then(function(resp) {
|
||||||
$scope.teams = resp.teams;
|
$scope.teams = resp.teams;
|
||||||
|
|
|
@ -43,7 +43,7 @@ class TestImageSharing(unittest.TestCase):
|
||||||
|
|
||||||
def createStorage(self, docker_image_id, repository=REPO, username=ADMIN_ACCESS_USER):
|
def createStorage(self, docker_image_id, repository=REPO, username=ADMIN_ACCESS_USER):
|
||||||
repository_obj = model.get_repository(repository.split('/')[0], repository.split('/')[1])
|
repository_obj = model.get_repository(repository.split('/')[0], repository.split('/')[1])
|
||||||
image = model.create_or_link_image(docker_image_id, repository_obj, username, {}, {})
|
image = model.find_create_or_link_image(docker_image_id, repository_obj, username, {})
|
||||||
return image.storage.id
|
return image.storage.id
|
||||||
|
|
||||||
def assertSameStorage(self, docker_image_id, storage_id, repository=REPO, username=ADMIN_ACCESS_USER):
|
def assertSameStorage(self, docker_image_id, storage_id, repository=REPO, username=ADMIN_ACCESS_USER):
|
||||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
|
|
||||||
from app import mixpanel
|
from app import mixpanel
|
||||||
from flask import request, abort as flask_abort, jsonify
|
from flask import request, abort as flask_abort, jsonify
|
||||||
from auth.auth import get_authenticated_user, get_validated_token
|
from auth.auth_context import get_authenticated_user, get_validated_token
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ DEFAULT_MESSAGE[409] = 'Conflict'
|
||||||
DEFAULT_MESSAGE[501] = 'Not Implemented'
|
DEFAULT_MESSAGE[501] = 'Not Implemented'
|
||||||
|
|
||||||
def abort(status_code, message=None, issue=None, **kwargs):
|
def abort(status_code, message=None, issue=None, **kwargs):
|
||||||
|
|
||||||
message = (str(message) % kwargs if message else
|
message = (str(message) % kwargs if message else
|
||||||
DEFAULT_MESSAGE.get(status_code, ''))
|
DEFAULT_MESSAGE.get(status_code, ''))
|
||||||
|
|
||||||
|
|
Reference in a new issue