parent
23d85409a6
commit
dd3d939b31
6 changed files with 40 additions and 5 deletions
|
@ -8,6 +8,7 @@ from endpoints.api import (resource, nickname, require_repo_read, require_repo_w
|
||||||
from endpoints.api.image import image_view
|
from endpoints.api.image import image_view
|
||||||
from data import model
|
from data import model
|
||||||
from auth.auth_context import get_authenticated_user
|
from auth.auth_context import get_authenticated_user
|
||||||
|
from util.names import TAG_ERROR, TAG_REGEX
|
||||||
|
|
||||||
|
|
||||||
@resource('/v1/repository/<repopath:repository>/tag/')
|
@resource('/v1/repository/<repopath:repository>/tag/')
|
||||||
|
@ -85,6 +86,10 @@ class RepositoryTag(RepositoryParamResource):
|
||||||
@validate_json_request('MoveTag')
|
@validate_json_request('MoveTag')
|
||||||
def put(self, namespace, repository, tag):
|
def put(self, namespace, repository, tag):
|
||||||
""" Change which image a tag points to or create a new tag."""
|
""" Change which image a tag points to or create a new tag."""
|
||||||
|
|
||||||
|
if not TAG_REGEX.match(tag):
|
||||||
|
abort(400, TAG_ERROR)
|
||||||
|
|
||||||
image_id = request.get_json()['image']
|
image_id = request.get_json()['image']
|
||||||
image = model.image.get_repo_image(namespace, repository, image_id)
|
image = model.image.get_repo_image(namespace, repository, image_id)
|
||||||
if not image:
|
if not image:
|
||||||
|
|
|
@ -5,7 +5,7 @@ import json
|
||||||
from flask import abort, request, jsonify, make_response, session
|
from flask import abort, request, jsonify, make_response, session
|
||||||
|
|
||||||
from app import app
|
from app import app
|
||||||
from util.names import parse_repository_name
|
from util.names import TAG_ERROR, TAG_REGEX, parse_repository_name
|
||||||
from auth.auth import process_auth
|
from auth.auth import process_auth
|
||||||
from auth.permissions import (ReadRepositoryPermission,
|
from auth.permissions import (ReadRepositoryPermission,
|
||||||
ModifyRepositoryPermission)
|
ModifyRepositoryPermission)
|
||||||
|
@ -56,6 +56,9 @@ def put_tag(namespace, repository, tag):
|
||||||
permission = ModifyRepositoryPermission(namespace, repository)
|
permission = ModifyRepositoryPermission(namespace, repository)
|
||||||
|
|
||||||
if permission.can():
|
if permission.can():
|
||||||
|
if not TAG_REGEX.match(tag):
|
||||||
|
abort(400, TAG_ERROR)
|
||||||
|
|
||||||
docker_image_id = json.loads(request.data)
|
docker_image_id = json.loads(request.data)
|
||||||
model.tag.create_or_update_tag(namespace, repository, tag, docker_image_id)
|
model.tag.create_or_update_tag(namespace, repository, tag, docker_image_id)
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
<div ng-show="!addingTag">
|
<div ng-show="!addingTag">
|
||||||
<input type="text" class="form-control" id="tagName"
|
<input type="text" class="form-control" id="tagName"
|
||||||
placeholder="Enter tag name"
|
placeholder="Enter tag name"
|
||||||
ng-model="tagToCreate" ng-pattern="/^([a-z0-9_\.-]){3,30}$/"
|
ng-model="tagToCreate" ng-pattern="/^[\w][\w\.-]{0,127}$/"
|
||||||
ng-disabled="creatingTag" autofocus required>
|
ng-disabled="creatingTag" autofocus required>
|
||||||
|
|
||||||
<div style="margin: 10px; margin-top: 20px;"
|
<div style="margin: 10px; margin-top: 20px;"
|
||||||
|
|
|
@ -212,8 +212,7 @@ class RegistryTestCase(LiveServerTestCase):
|
||||||
|
|
||||||
|
|
||||||
# PUT /v1/repositories/{namespace}/{repository}/tags/latest
|
# PUT /v1/repositories/{namespace}/{repository}/tags/latest
|
||||||
self.conduct('PUT', '/v1/repositories/%s/%s/tags/latest' % (namespace, repository),
|
self.do_tag(namespace, repository, 'latest', images[0]['id'])
|
||||||
data='"' + images[0]['id'] + '"')
|
|
||||||
|
|
||||||
# PUT /v1/repositories/{namespace}/{repository}/images
|
# PUT /v1/repositories/{namespace}/{repository}/images
|
||||||
self.conduct('PUT', '/v1/repositories/%s/%s/images' % (namespace, repository),
|
self.conduct('PUT', '/v1/repositories/%s/%s/images' % (namespace, repository),
|
||||||
|
@ -246,6 +245,10 @@ class RegistryTestCase(LiveServerTestCase):
|
||||||
self.conduct('GET', image_prefix + 'json')
|
self.conduct('GET', image_prefix + 'json')
|
||||||
self.conduct('GET', image_prefix + 'layer')
|
self.conduct('GET', image_prefix + 'layer')
|
||||||
|
|
||||||
|
def do_tag(self, namespace, repository, tag, image_id, expected_code=200):
|
||||||
|
self.conduct('PUT', '/v1/repositories/%s/%s/tags/%s' % (namespace, repository, tag),
|
||||||
|
data='"%s"' % image_id, expected_code=expected_code)
|
||||||
|
|
||||||
def conduct_api_login(self, username, password):
|
def conduct_api_login(self, username, password):
|
||||||
self.conduct('POST', '/api/v1/signin',
|
self.conduct('POST', '/api/v1/signin',
|
||||||
data=json.dumps(dict(username=username, password=password)),
|
data=json.dumps(dict(username=username, password=password)),
|
||||||
|
@ -437,5 +440,19 @@ class RegistryTests(RegistryTestCase):
|
||||||
# org.
|
# org.
|
||||||
self.do_pull('buynlarge', 'newrepo', 'devtable', 'password')
|
self.do_pull('buynlarge', 'newrepo', 'devtable', 'password')
|
||||||
|
|
||||||
|
def test_tag_validation(self):
|
||||||
|
image_id = 'onlyimagehere'
|
||||||
|
images = [{
|
||||||
|
'id': image_id
|
||||||
|
}]
|
||||||
|
self.do_push('public', 'newrepo', 'public', 'password', images)
|
||||||
|
self.do_tag('public', 'newrepo', '1', image_id)
|
||||||
|
self.do_tag('public', 'newrepo', 'x' * 128, image_id)
|
||||||
|
self.do_tag('public', 'newrepo', '', image_id, expected_code=400)
|
||||||
|
self.do_tag('public', 'newrepo', 'x' * 129, image_id, expected_code=400)
|
||||||
|
self.do_tag('public', 'newrepo', '.fail', image_id, expected_code=400)
|
||||||
|
self.do_tag('public', 'newrepo', '-fail', image_id, expected_code=400)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
@ -2188,6 +2188,12 @@ class TestListAndDeleteTag(ApiTestCase):
|
||||||
|
|
||||||
self.assertEquals(staging_images, json['images'])
|
self.assertEquals(staging_images, json['images'])
|
||||||
|
|
||||||
|
# Require a valid tag name.
|
||||||
|
self.putResponse(RepositoryTag,
|
||||||
|
params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='-fail'),
|
||||||
|
data=dict(image=staging_images[0]['id']),
|
||||||
|
expected_code=400)
|
||||||
|
|
||||||
# Add a new tag to the staging image.
|
# Add a new tag to the staging image.
|
||||||
self.putResponse(RepositoryTag,
|
self.putResponse(RepositoryTag,
|
||||||
params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='sometag'),
|
params=dict(repository=ADMIN_ACCESS_USER + '/complex', tag='sometag'),
|
||||||
|
|
|
@ -6,6 +6,10 @@ from uuid import uuid4
|
||||||
|
|
||||||
REPOSITORY_NAME_REGEX = re.compile(r'^[\.a-zA-Z0-9_-]+$')
|
REPOSITORY_NAME_REGEX = re.compile(r'^[\.a-zA-Z0-9_-]+$')
|
||||||
|
|
||||||
|
TAG_REGEX = re.compile(r'^[\w][\w\.-]{0,127}$')
|
||||||
|
TAG_ERROR = ('Invalid tag: must match [A-Za-z0-9_.-], NOT start with "." or "-", '
|
||||||
|
'and can contain 1-128 characters')
|
||||||
|
|
||||||
def parse_namespace_repository(repository, include_tag=False):
|
def parse_namespace_repository(repository, include_tag=False):
|
||||||
parts = repository.rstrip('/').split('/', 1)
|
parts = repository.rstrip('/').split('/', 1)
|
||||||
if len(parts) < 2:
|
if len(parts) < 2:
|
||||||
|
|
Reference in a new issue