""" 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) from endpoints.exception import NotFound from data import model from digest import digest_tools BASE_MANIFEST_ROUTE = '/v1/repository//manifest/' 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'], 'description': 'The media type for this label', 'enum': ALLOWED_LABEL_MEDIA_TYPES, }, }, }, } @require_repo_read @nickname('listManifestLabels') @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') @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 = model.label.create_manifest_label(tag_manifest, label_data['key'], label_data['value'], 'api', media_type_name=label_data['media_type']) metadata = { 'id': label.uuid, 'key': label_data['key'], 'value': label_data['value'], 'manifest_digest': manifestref, 'media_type': label_data['media_type'], } 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/') @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') @parse_args() 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') 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 } log_action('manifest_label_delete', namespace, metadata, repo=tag_manifest.tag.repository) return 'Deleted', 204