Basic labels support
Adds basic labels support to the registry code (V2), and the API. Note that this does not yet add any UI related support.
This commit is contained in:
parent
427070b453
commit
608ffd9663
24 changed files with 907 additions and 36 deletions
159
endpoints/api/manifest.py
Normal file
159
endpoints/api/manifest.py
Normal file
|
@ -0,0 +1,159 @@
|
|||
""" 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/<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'],
|
||||
'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/<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')
|
||||
@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
|
||||
|
Reference in a new issue