This repository has been archived on 2020-03-24. You can view files and clone it, but cannot push or open issues or pull requests.
quay/endpoints/api/manifest.py
Charlton Austin 5e6aa6648b fix(endpoints): added in proper error handling
before we would return a 400 without a message because the errors were not being caught

Issue: https://www.pivotaltracker.com/story/show/145459707

- [ ] It works!
- [ ] Comments provide sufficient explanations for the next contributor
- [ ] Tests cover changes and corner cases
- [ ] Follows Quay syntax patterns and format
2017-06-21 16:59:48 -04:00

176 lines
6 KiB
Python

""" Manage the manifests of a repository. """
from app import label_validator
from flask import request
from endpoints.api import (resource, nickname, require_repo_read, require_repo_write,
RepositoryParamResource, log_action, validate_json_request,
path_param, parse_args, query_param, truthy_bool, abort, api,
disallow_for_app_repositories)
from endpoints.exception import NotFound
from data import model
from digest import digest_tools
from util.validation import VALID_LABEL_KEY_REGEX
BASE_MANIFEST_ROUTE = '/v1/repository/<apirepopath:repository>/manifest/<regex("{0}"):manifestref>'
MANIFEST_DIGEST_ROUTE = BASE_MANIFEST_ROUTE.format(digest_tools.DIGEST_PATTERN)
ALLOWED_LABEL_MEDIA_TYPES = ['text/plain', 'application/json']
def label_view(label):
view = {
'id': label.uuid,
'key': label.key,
'value': label.value,
'source_type': label.source_type.name,
'media_type': label.media_type.name,
}
return view
@resource(MANIFEST_DIGEST_ROUTE + '/labels')
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
@path_param('manifestref', 'The digest of the manifest')
class RepositoryManifestLabels(RepositoryParamResource):
""" Resource for listing the labels on a specific repository manifest. """
schemas = {
'AddLabel': {
'type': 'object',
'description': 'Adds a label to a manifest',
'required': [
'key',
'value',
'media_type',
],
'properties': {
'key': {
'type': 'string',
'description': 'The key for the label',
},
'value': {
'type': 'string',
'description': 'The value for the label',
},
'media_type': {
'type': ['string', 'null'],
'description': 'The media type for this label',
'enum': ALLOWED_LABEL_MEDIA_TYPES + [None],
},
},
},
}
@require_repo_read
@nickname('listManifestLabels')
@disallow_for_app_repositories
@parse_args()
@query_param('filter', 'If specified, only labels matching the given prefix will be returned',
type=str, default=None)
def get(self, namespace, repository, manifestref, parsed_args):
try:
tag_manifest = model.tag.load_manifest_by_digest(namespace, repository, manifestref)
except model.DataModelException:
raise NotFound()
labels = model.label.list_manifest_labels(tag_manifest, prefix_filter=parsed_args['filter'])
return {
'labels': [label_view(label) for label in labels]
}
@require_repo_write
@nickname('addManifestLabel')
@disallow_for_app_repositories
@validate_json_request('AddLabel')
def post(self, namespace, repository, manifestref):
""" Adds a new label into the tag manifest. """
try:
tag_manifest = model.tag.load_manifest_by_digest(namespace, repository, manifestref)
except model.DataModelException:
raise NotFound()
label_data = request.get_json()
# Check for any reserved prefixes.
if label_validator.has_reserved_prefix(label_data['key']):
abort(400, message='Label has a reserved prefix')
label = None
try:
label = model.label.create_manifest_label(tag_manifest, label_data['key'],
label_data['value'], 'api',
media_type_name=label_data['media_type'])
except model.InvalidLabelKeyException:
abort(400, message='Label is of an invalid format or missing please use %s format for labels'.format(
VALID_LABEL_KEY_REGEX))
except model.InvalidMediaTypeException:
abort(400, message='Media type is invalid please use a valid media type of text/plain or application/json')
metadata = {
'id': label.uuid,
'key': label_data['key'],
'value': label_data['value'],
'manifest_digest': manifestref,
'media_type': label_data['media_type'],
'namespace': namespace,
'repo': repository,
}
log_action('manifest_label_add', namespace, metadata, repo=tag_manifest.tag.repository)
resp = {'label': label_view(label)}
repo_string = '%s/%s' % (namespace, repository)
headers = {
'Location': api.url_for(ManageRepositoryManifestLabel, repository=repo_string,
manifestref=manifestref, labelid=label.uuid),
}
return resp, 201, headers
@resource(MANIFEST_DIGEST_ROUTE + '/labels/<labelid>')
@path_param('repository', 'The full path of the repository. e.g. namespace/name')
@path_param('manifestref', 'The digest of the manifest')
@path_param('labelid', 'The ID of the label')
class ManageRepositoryManifestLabel(RepositoryParamResource):
""" Resource for managing the labels on a specific repository manifest. """
@require_repo_read
@nickname('getManifestLabel')
@disallow_for_app_repositories
def get(self, namespace, repository, manifestref, labelid):
""" Retrieves the label with the specific ID under the manifest. """
try:
tag_manifest = model.tag.load_manifest_by_digest(namespace, repository, manifestref)
except model.DataModelException:
raise NotFound()
label = model.label.get_manifest_label(labelid, tag_manifest)
if label is None:
raise NotFound()
return label_view(label)
@require_repo_write
@nickname('deleteManifestLabel')
@disallow_for_app_repositories
def delete(self, namespace, repository, manifestref, labelid):
""" Deletes an existing label from a manifest. """
try:
tag_manifest = model.tag.load_manifest_by_digest(namespace, repository, manifestref)
except model.DataModelException:
raise NotFound()
deleted = model.label.delete_manifest_label(labelid, tag_manifest)
if deleted is None:
raise NotFound()
metadata = {
'id': labelid,
'key': deleted.key,
'value': deleted.value,
'manifest_digest': manifestref,
'namespace': namespace,
'repo': repository,
}
log_action('manifest_label_delete', namespace, metadata, repo=tag_manifest.tag.repository)
return '', 204