Source code for glance.api.glare.v0_1.glare

# Copyright (c) 2015 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

import os
import sys

import glance_store
import jsonschema
from oslo_config import cfg
from oslo_serialization import jsonutils as json
from oslo_utils import encodeutils
from oslo_utils import excutils
import semantic_version
import six
import six.moves.urllib.parse as urlparse
import webob.exc

from glance.common import exception
from glance.common.glare import loader
from glance.common.glare import serialization
from glance.common import jsonpatchvalidator
from glance.common import utils
from glance.common import wsgi
import glance.db
from glance.glare import gateway
from glance.glare import Showlevel
from glance.i18n import _, _LE
from oslo_log import log as logging

LOG = logging.getLogger(__name__)

possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
                                                os.pardir,
                                                os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'glance', '__init__.py')):
    sys.path.insert(0, possible_topdir)

CONF = cfg.CONF
CONF.import_group("profiler", "glance.common.wsgi")


[docs]class ArtifactsController(object): def __init__(self, db_api=None, store_api=None, plugins=None): self.db_api = db_api or glance.db.get_api() self.store_api = store_api or glance_store self.plugins = plugins or loader.ArtifactsPluginLoader( 'glance.artifacts.types') self.gateway = gateway.Gateway(self.db_api, self.store_api, self.plugins) @staticmethod def _do_update_op(artifact, change): """Call corresponding method of the updater proxy. Here 'change' is a typical jsonpatch request dict: * 'path' - a json-pointer string; * 'op' - one of the allowed operation types; * 'value' - value to set (omitted when op = remove) """ update_op = getattr(artifact, change['op']) update_op(change['path'], change.get('value')) return artifact @staticmethod def _get_artifact_with_dependencies(repo, art_id, type_name=None, type_version=None): """Retrieves an artifact with dependencies from db by its id. Show level is direct (only direct dependencies are shown). """ return repo.get(art_id, show_level=Showlevel.DIRECT, type_name=type_name, type_version=type_version)
[docs] def show(self, req, type_name, type_version, show_level=Showlevel.TRANSITIVE, **kwargs): """Retrieves one artifact by id with its dependencies""" artifact_repo = self.gateway.get_artifact_repo(req.context) try: art_id = kwargs.get('id') artifact = artifact_repo.get(art_id, type_name=type_name, type_version=type_version, show_level=show_level) return artifact except exception.ArtifactNotFound as e: raise webob.exc.HTTPNotFound(explanation=e.msg)
[docs] def list(self, req, type_name, type_version, state, **kwargs): """Retrieves a list of artifacts that match some params""" artifact_repo = self.gateway.get_artifact_repo(req.context) filters = kwargs.pop('filters', {}) filters.update(type_name={'value': type_name}, state={'value': state}) if type_version is not None: filters['type_version'] = {'value': type_version} if 'version' in filters: for filter in filters['version']: if filter['value'] == 'latest': if 'name' not in filters: raise webob.exc.HTTPBadRequest( 'Filtering by latest version without specifying' ' a name is not supported.') filter['value'] = self._get_latest_version( req, filters['name'][0]['value'], type_name, type_version) else: try: semantic_version.Version(filter['value'], partial=True) except ValueError: msg = (_('The format of the version %s is not valid. ' 'Use semver notation') % filter['value']) raise webob.exc.HTTPBadRequest(explanation=msg) res = artifact_repo.list(filters=filters, show_level=Showlevel.BASIC, **kwargs) result = {'artifacts': res} limit = kwargs.get("limit") if limit is not None and len(res) != 0 and len(res) == limit: result['next_marker'] = res[-1].id return result
def _get_latest_version(self, req, name, type_name, type_version=None, state='creating'): artifact_repo = self.gateway.get_artifact_repo(req.context) filters = dict(name=[{"value": name}], type_name={"value": type_name}, state={"value": state}) if type_version is not None: filters["type_version"] = {"value": type_version} result = artifact_repo.list(filters=filters, show_level=Showlevel.NONE, sort_keys=[('version', None)]) if len(result): return result[0].version msg = "No artifacts have been found" raise exception.ArtifactNotFound(message=msg) @utils.mutating
[docs] def create(self, req, artifact_type, artifact_data, **kwargs): try: artifact_factory = self.gateway.get_artifact_type_factory( req.context, artifact_type) new_artifact = artifact_factory.new_artifact(**artifact_data) artifact_repo = self.gateway.get_artifact_repo(req.context) artifact_repo.add(new_artifact) # retrieve artifact from db return self._get_artifact_with_dependencies(artifact_repo, new_artifact.id) except (TypeError, exception.ArtifactNotFound, exception.Invalid, exception.DuplicateLocation) as e: raise webob.exc.HTTPBadRequest(explanation=e) except exception.Forbidden as e: raise webob.exc.HTTPForbidden(explanation=e.msg) except exception.LimitExceeded as e: raise webob.exc.HTTPRequestEntityTooLarge( explanation=e.msg, request=req, content_type='text/plain') except exception.Duplicate as e: raise webob.exc.HTTPConflict(explanation=e.msg) except exception.NotAuthenticated as e: raise webob.exc.HTTPUnauthorized(explanation=e.msg)
@utils.mutating
[docs] def update_property(self, req, id, type_name, type_version, path, data, **kwargs): """Updates a single property specified by request url.""" artifact_repo = self.gateway.get_artifact_repo(req.context) try: artifact = self._get_artifact_with_dependencies(artifact_repo, id, type_name, type_version) self._ensure_write_access(artifact, req.context) if artifact.metadata.attributes.blobs.get(path) is not None: msg = _('Invalid Content-Type for work with %s') % path raise webob.exc.HTTPBadRequest(explanation=msg) # use updater mixin to perform updates: generate update path if req.method == "PUT": # replaces existing value or creates a new one if getattr(artifact, kwargs["attr"]): artifact.replace(path=path, value=data) else: artifact.add(path=path, value=data) else: # append to an existing value or create a new one artifact.add(path=path, value=data) artifact_repo.save(artifact) return self._get_artifact_with_dependencies(artifact_repo, id) except (exception.InvalidArtifactPropertyValue, exception.ArtifactInvalidProperty, exception.InvalidJsonPatchPath, exception.ArtifactCircularDependency) as e: raise webob.exc.HTTPBadRequest(explanation=e.msg) except exception.NotFound as e: raise webob.exc.HTTPNotFound(explanation=e.msg) except exception.NotAuthenticated as e: raise webob.exc.HTTPUnauthorized(explanation=e.msg)
@utils.mutating
[docs] def update(self, req, id, type_name, type_version, changes, **kwargs): """Performs an update via json patch request""" artifact_repo = self.gateway.get_artifact_repo(req.context) try: artifact = self._get_artifact_with_dependencies(artifact_repo, id, type_name, type_version) self._ensure_write_access(artifact, req.context) updated = artifact for change in changes: if artifact.metadata.attributes.blobs.get(change['path']): msg = _('Invalid request PATCH for work with blob') raise webob.exc.HTTPBadRequest(explanation=msg) else: updated = self._do_update_op(updated, change) artifact_repo.save(updated) return self._get_artifact_with_dependencies(artifact_repo, id) except (exception.InvalidJsonPatchPath, exception.Invalid) as e: raise webob.exc.HTTPBadRequest(explanation=e.msg) except exception.NotFound as e: raise webob.exc.HTTPNotFound(explanation=e.msg) except exception.Forbidden as e: raise webob.exc.HTTPForbidden(explanation=e.msg) except exception.StorageQuotaFull as e: msg = (_("Denying attempt to upload artifact because it exceeds " "the quota: %s") % encodeutils.exception_to_unicode(e)) raise webob.exc.HTTPRequestEntityTooLarge( explanation=msg, request=req, content_type='text/plain') except exception.LimitExceeded as e: raise webob.exc.HTTPRequestEntityTooLarge( explanation=e.msg, request=req, content_type='text/plain') except exception.NotAuthenticated as e: raise webob.exc.HTTPUnauthorized(explanation=e.msg)
@utils.mutating
[docs] def delete(self, req, id, type_name, type_version, **kwargs): artifact_repo = self.gateway.get_artifact_repo(req.context) try: artifact = self._get_artifact_with_dependencies( artifact_repo, id, type_name=type_name, type_version=type_version) self._ensure_write_access(artifact, req.context) artifact_repo.remove(artifact) except exception.Invalid as e: raise webob.exc.HTTPBadRequest(explanation=e.msg) except (glance_store.Forbidden, exception.Forbidden) as e: raise webob.exc.HTTPForbidden(explanation=e.msg) except (glance_store.NotFound, exception.NotFound) as e: msg = (_("Failed to find artifact %(artifact_id)s to delete") % {'artifact_id': id}) raise webob.exc.HTTPNotFound(explanation=msg) except glance_store.exceptions.InUseByStore as e: msg = (_("Artifact %s could not be deleted " "because it is in use: %s") % (id, e.msg)) # noqa raise webob.exc.HTTPConflict(explanation=msg) except exception.NotAuthenticated as e: raise webob.exc.HTTPUnauthorized(explanation=e.msg)
@utils.mutating
[docs] def publish(self, req, id, type_name, type_version, **kwargs): artifact_repo = self.gateway.get_artifact_repo(req.context) try: artifact = self._get_artifact_with_dependencies( artifact_repo, id, type_name=type_name, type_version=type_version) self._ensure_write_access(artifact, req.context) return artifact_repo.publish(artifact, context=req.context) except exception.Forbidden as e: raise webob.exc.HTTPForbidden(explanation=e.msg) except exception.NotFound as e: raise webob.exc.HTTPNotFound(explanation=e.msg) except exception.Invalid as e: raise webob.exc.HTTPBadRequest(explanation=e.msg) except exception.NotAuthenticated as e: raise webob.exc.HTTPUnauthorized(explanation=e.msg)
def _upload_list_property(self, method, blob_list, index, data, size): if method == 'PUT' and not index and len(blob_list) > 0: # PUT replaces everything, so PUT to non-empty collection is # forbidden raise webob.exc.HTTPMethodNotAllowed( explanation=_("Unable to PUT to non-empty collection")) if index is not None and index > len(blob_list): raise webob.exc.HTTPBadRequest( explanation=_("Index is out of range")) if index is None: # both POST and PUT create a new blob list blob_list.append((data, size)) elif method == 'POST': blob_list.insert(index, (data, size)) else: blob_list[index] = (data, size) @utils.mutating
[docs] def upload(self, req, id, type_name, type_version, attr, size, data, index, **kwargs): artifact_repo = self.gateway.get_artifact_repo(req.context) artifact = None try: artifact = self._get_artifact_with_dependencies(artifact_repo, id, type_name, type_version) self._ensure_write_access(artifact, req.context) blob_prop = artifact.metadata.attributes.blobs.get(attr) if blob_prop is None: raise webob.exc.HTTPBadRequest( explanation=_("Not a blob property '%s'") % attr) if isinstance(blob_prop, list): blob_list = getattr(artifact, attr) self._upload_list_property(req.method, blob_list, index, data, size) else: if index is not None: raise webob.exc.HTTPBadRequest( explanation=_("Not a list property '%s'") % attr) setattr(artifact, attr, (data, size)) artifact_repo.save(artifact) return artifact except ValueError as e: exc_message = encodeutils.exception_to_unicode(e) LOG.debug("Cannot save data for artifact %(id)s: %(e)s", {'id': id, 'e': exc_message}) self._restore(artifact_repo, artifact) raise webob.exc.HTTPBadRequest( explanation=exc_message) except glance_store.StoreAddDisabled: msg = _("Error in store configuration. Adding artifacts to store " "is disabled.") LOG.exception(msg) self._restore(artifact_repo, artifact) raise webob.exc.HTTPGone(explanation=msg, request=req, content_type='text/plain') except (glance_store.Duplicate, exception.InvalidImageStatusTransition) as e: LOG.exception(encodeutils.exception_to_unicode(e)) raise webob.exc.HTTPConflict(explanation=e.msg, request=req) except exception.Forbidden as e: msg = ("Not allowed to upload data for artifact %s" % id) LOG.debug(msg) raise webob.exc.HTTPForbidden(explanation=msg, request=req) except exception.NotFound as e: raise webob.exc.HTTPNotFound(explanation=e.msg) except glance_store.StorageFull as e: msg = _("Artifact storage media " "is full: %s") % encodeutils.exception_to_unicode(e) LOG.error(msg) self._restore(artifact_repo, artifact) raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg, request=req) except exception.StorageQuotaFull as e: msg = _("Artifact exceeds the storage " "quota: %s") % encodeutils.exception_to_unicode(e) LOG.error(msg) self._restore(artifact_repo, artifact) raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg, request=req) except exception.ImageSizeLimitExceeded as e: msg = _("The incoming artifact blob is " "too large: %s") % encodeutils.exception_to_unicode(e) LOG.error(msg) self._restore(artifact_repo, artifact) raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg, request=req) except glance_store.StorageWriteDenied as e: msg = _("Insufficient permissions on artifact " "storage media: %s") % encodeutils.exception_to_unicode(e) LOG.error(msg) self._restore(artifact_repo, artifact) raise webob.exc.HTTPServiceUnavailable(explanation=msg, request=req) except webob.exc.HTTPGone as e: with excutils.save_and_reraise_exception(): LOG.error(_LE("Failed to upload artifact blob data due to" " HTTP error")) except webob.exc.HTTPError as e: with excutils.save_and_reraise_exception(): LOG.error(_LE("Failed to upload artifact blob data due to HTTP" " error")) self._restore(artifact_repo, artifact) except Exception as e: with excutils.save_and_reraise_exception(): LOG.exception(_LE("Failed to upload artifact blob data due to " "internal error")) self._restore(artifact_repo, artifact)
[docs] def download(self, req, id, type_name, type_version, attr, index, **kwargs): artifact_repo = self.gateway.get_artifact_repo(req.context) try: artifact = artifact_repo.get(id, type_name, type_version) if attr in artifact.metadata.attributes.blobs: if isinstance(artifact.metadata.attributes.blobs[attr], list): if index is None: raise webob.exc.HTTPBadRequest( explanation=_("Index is required")) blob_list = getattr(artifact, attr) try: return blob_list[index] except IndexError as e: raise webob.exc.HTTPBadRequest(explanation=e.message) else: if index is not None: raise webob.exc.HTTPBadRequest(_("Not a list " "property")) return getattr(artifact, attr) else: message = _("Not a downloadable entity") raise webob.exc.HTTPBadRequest(explanation=message) except exception.Forbidden as e: raise webob.exc.HTTPForbidden(explanation=e.msg) except (glance_store.NotFound, exception.NotFound) as e: raise webob.exc.HTTPNotFound(explanation=e.msg) except exception.Invalid as e: raise webob.exc.HTTPBadRequest(explanation=e.msg)
def _restore(self, artifact_repo, artifact): """Restore the artifact to queued status. :param artifact_repo: The instance of ArtifactRepo :param artifact: The artifact will be restored """ try: if artifact_repo and artifact: artifact.state = 'creating' artifact_repo.save(artifact) except Exception as e: msg = (_LE("Unable to restore artifact %(artifact_id)s: %(e)s") % {'artifact_id': artifact.id, 'e': encodeutils.exception_to_unicode(e)}) LOG.exception(msg)
[docs] def list_artifact_types(self, req): plugins = self.plugins.plugin_map response = [] base_link = "%s/v0.1/artifacts" % (CONF.public_endpoint or req.host_url) for type_name, plugin in six.iteritems(plugins.get("by_typename")): metadata = dict( type_name=type_name, displayed_name=plugin[0].metadata.type_display_name, versions=[] ) for version in plugin: endpoint = version.metadata.endpoint type_version = "v" + version.metadata.type_version version_metadata = dict( id=type_version, link="%s/%s/%s" % (base_link, endpoint, type_version) ) type_description = version.metadata.type_description if type_description is not None: version_metadata['description'] = type_description metadata['versions'].append(version_metadata) response.append(metadata) return {"artifact_types": response}
@staticmethod def _ensure_write_access(artifact, context): if context.is_admin: return if context.owner is None or context.owner != artifact.owner: raise exception.ArtifactForbidden(id=artifact.id)
[docs]class RequestDeserializer(wsgi.JSONRequestDeserializer, jsonpatchvalidator.JsonPatchValidatorMixin): _available_sort_keys = ('name', 'status', 'container_format', 'disk_format', 'size', 'id', 'created_at', 'updated_at', 'version') _default_sort_dir = 'desc' _max_limit_number = 1000 def __init__(self, schema=None, plugins=None): super(RequestDeserializer, self).__init__( methods_allowed=["replace", "remove", "add"]) self.plugins = plugins or loader.ArtifactsPluginLoader( 'glance.artifacts.types') def _validate_show_level(self, show_level): try: return Showlevel.from_str(show_level.strip().lower()) except exception.ArtifactUnsupportedShowLevel as e: raise webob.exc.HTTPBadRequest(explanation=e.message)
[docs] def show(self, req): res = self._process_type_from_request(req, True) params = req.params.copy() show_level = params.pop('show_level', None) if show_level is not None: res['show_level'] = self._validate_show_level(show_level) return res
def _get_request_body(self, req): output = super(RequestDeserializer, self).default(req) if 'body' not in output: msg = _('Body expected in request.') raise webob.exc.HTTPBadRequest(explanation=msg) return output['body']
[docs] def validate_body(self, request): try: body = self._get_request_body(request) return super(RequestDeserializer, self).validate_body(body) except exception.JsonPatchException as e: raise webob.exc.HTTPBadRequest(explanation=e)
[docs] def default(self, request): return self._process_type_from_request(request)
def _check_type_version(self, type_version): try: semantic_version.Version(type_version, partial=True) except ValueError as e: raise webob.exc.HTTPBadRequest(explanation=e) def _process_type_from_request(self, req, allow_implicit_version=False): try: type_name = req.urlvars.get('type_name') type_version = req.urlvars.get('type_version') if type_version is not None: self._check_type_version(type_version) # Even if the type_version is not specified and # 'allow_implicit_version' is False, this call is still needed to # ensure that at least one version of this type exists. artifact_type = self.plugins.get_class_by_endpoint(type_name, type_version) res = { 'type_name': artifact_type.metadata.type_name, 'type_version': artifact_type.metadata.type_version if type_version is not None else None } if allow_implicit_version: res['artifact_type'] = artifact_type return res except exception.ArtifactPluginNotFound as e: raise webob.exc.HTTPBadRequest(explanation=e.msg) def _validate_headers(self, req, content_type='application/json'): header = req.headers.get('Content-Type') if header != content_type: msg = _('Invalid headers "Content-Type": %s') % header raise webob.exc.HTTPBadRequest(explanation=msg)
[docs] def create(self, req): self._validate_headers(req) res = self._process_type_from_request(req, True) res["artifact_data"] = self._get_request_body(req) return res
[docs] def update(self, req): self._validate_headers(req) res = self._process_type_from_request(req) res["changes"] = self.validate_body(req) return res
[docs] def update_property(self, req): self._validate_headers(req) """Data is expected in form {'data': ...}""" res = self._process_type_from_request(req) data_schema = { "type": "object", "properties": {"data": {}}, "required": ["data"], "$schema": "http://json-schema.org/draft-04/schema#"} try: json_body = json.loads(req.body) jsonschema.validate(json_body, data_schema) # TODO(ivasilevskaya): # by now the deepest nesting level == 1 (ex. some_list/3), # has to be fixed for dict properties attr = req.urlvars["attr"] path_left = req.urlvars["path_left"] path = (attr if not path_left else "%(attr)s/%(path_left)s" % {'attr': attr, 'path_left': path_left}) res.update(data=json_body["data"], path=path) return res except (ValueError, jsonschema.ValidationError) as e: msg = _("Invalid json body: %s") % e.message raise webob.exc.HTTPBadRequest(explanation=msg)
[docs] def upload(self, req): self._validate_headers(req, content_type='application/octet-stream') res = self._process_type_from_request(req) index = req.urlvars.get('path_left') try: # for blobs only one level of indexing is supported # (ex. bloblist/0) if index is not None: index = int(index) except ValueError: msg = _("Only list indexes are allowed for blob lists") raise webob.exc.HTTPBadRequest(explanation=msg) artifact_size = req.content_length or None res.update(size=artifact_size, data=req.body_file, index=index) return res
[docs] def download(self, req): res = self._process_type_from_request(req) index = req.urlvars.get('index') if index is not None: index = int(index) res.update(index=index) return res
def _validate_limit(self, limit): if limit is None: return self._max_limit_number try: limit = int(limit) except ValueError: msg = _("Limit param must be an integer") raise webob.exc.HTTPBadRequest(explanation=msg) if limit < 0: msg = _("Limit param must be positive") raise webob.exc.HTTPBadRequest(explanation=msg) if limit > self._max_limit_number: msg = _("Limit param" " must not be higher than %d") % self._max_limit_number raise webob.exc.HTTPBadRequest(explanation=msg) return limit def _validate_sort_key(self, sort_key, artifact_type, type_version=None): if sort_key in self._available_sort_keys: return sort_key, None elif type_version is None: msg = (_('Invalid sort key: %(sort_key)s. ' 'If type version is not set it must be one of' ' the following: %(available)s.') % {'sort_key': sort_key, 'available': ', '.join(self._available_sort_keys)}) raise webob.exc.HTTPBadRequest(explanation=msg) prop_type = artifact_type.metadata.attributes.all.get(sort_key) if prop_type is None or prop_type.DB_TYPE not in ['string', 'numeric', 'int', 'bool']: msg = (_('Invalid sort key: %(sort_key)s. ' 'You cannot sort by this property') % {'sort_key': sort_key}) raise webob.exc.HTTPBadRequest(explanation=msg) return sort_key, prop_type.DB_TYPE def _validate_sort_dir(self, sort_dir): if sort_dir not in ['asc', 'desc']: msg = _('Invalid sort direction: %s') % sort_dir raise webob.exc.HTTPBadRequest(explanation=msg) return sort_dir def _get_sorting_params(self, params, artifact_type, type_version=None): sort_keys = [] sort_dirs = [] if 'sort' in params: for sort_param in params.pop('sort').strip().split(','): key, _sep, dir = sort_param.partition(':') if not dir: dir = self._default_sort_dir sort_keys.append(self._validate_sort_key(key.strip(), artifact_type, type_version)) sort_dirs.append(self._validate_sort_dir(dir.strip())) if not sort_keys: sort_keys = [('created_at', None)] if not sort_dirs: sort_dirs = [self._default_sort_dir] return sort_keys, sort_dirs def _bring_to_type(self, type_name, value): mapper = {'int': int, 'string': str, 'text': str, 'bool': bool, 'numeric': float} return mapper[type_name](value) def _get_filters(self, artifact_type, params): error_msg = 'Unexpected filter property' filters = dict() for filter, raw_value in params.items(): # first, get the comparison operator left, sep, right = raw_value.strip().partition(':') if not sep: op = "default" value = left.strip() else: op = left.strip().upper() value = right.strip() # then, understand what's the property to filter and its value if '.' in filter: # Indicates a dict-valued property with a key prop_name, key = filter.split('.', 1) else: prop_name = filter key = None prop_type = artifact_type.metadata.attributes.all.get(prop_name) if prop_type is None: raise webob.exc.HTTPBadRequest(error_msg) key_only_check = False position = None if isinstance(prop_type, dict): if key is None: key = value val = None key_only_check = True else: val = value if isinstance(prop_type.properties, dict): # This one is to handle the case of composite dict, having # different types of values at different keys, i.e. object prop_type = prop_type.properties.get(key) if prop_type is None: raise webob.exc.HTTPBadRequest(error_msg) else: prop_type = prop_type.properties property_name = prop_name + '.' + key property_value = val else: if key is not None: raise webob.exc.HTTPBadRequest(error_msg) property_name = prop_name property_value = value # now detect the value DB type if prop_type.DB_TYPE is not None: str_type = prop_type.DB_TYPE elif isinstance(prop_type, list): if not isinstance(prop_type.item_type, list): position = "any" str_type = prop_type.item_type.DB_TYPE else: raise webob.exc.HTTPBadRequest('Filtering by tuple-like' ' fields is not supported') else: raise webob.exc.HTTPBadRequest(error_msg) if property_value is not None: property_value = self._bring_to_type(str_type, property_value) # convert the default operation to NE, EQ or IN if key_only_check: if op == 'default': op = 'NE' else: raise webob.exc.HTTPBadRequest('Comparison not supported ' 'for key-only filtering') else: if op == 'default': op = 'IN' if isinstance(prop_type, list) else 'EQ' filters.setdefault(property_name, []) filters[property_name].append(dict(operator=op, position=position, value=property_value, type=str_type)) return filters
[docs] def list(self, req): res = self._process_type_from_request(req, True) params = req.params.copy() show_level = params.pop('show_level', None) if show_level is not None: res['show_level'] = self._validate_show_level(show_level.strip()) limit = params.pop('limit', None) marker = params.pop('marker', None) query_params = dict() query_params['sort_keys'], query_params['sort_dirs'] = ( self._get_sorting_params(params, res['artifact_type'], res['type_version'])) if marker is not None: query_params['marker'] = marker query_params['limit'] = self._validate_limit(limit) query_params['filters'] = self._get_filters(res['artifact_type'], params) query_params['type_name'] = res['artifact_type'].metadata.type_name return query_params
[docs] def list_artifact_types(self, req): return {}
[docs]class ResponseSerializer(wsgi.JSONResponseSerializer): # TODO(ivasilevskaya): ideally this should be autogenerated/loaded ARTIFACTS_ENDPOINT = '/v0.1/artifacts' fields = ['id', 'name', 'version', 'type_name', 'type_version', 'visibility', 'state', 'owner', 'scope', 'created_at', 'updated_at', 'tags', 'dependencies', 'blobs', 'properties'] def __init__(self, schema=None): super(ResponseSerializer, self).__init__()
[docs] def default(self, response, res): artifact = serialization.serialize_for_client( res, show_level=Showlevel.DIRECT) body = json.dumps(artifact, ensure_ascii=False) response.unicode_body = six.text_type(body) response.content_type = 'application/json'
[docs] def create(self, response, artifact): response.status_int = 201 self.default(response, artifact) response.location = ( '%(root_url)s/%(type_name)s/v%(type_version)s/%(id)s' % dict( root_url=ResponseSerializer.ARTIFACTS_ENDPOINT, type_name=artifact.metadata.endpoint, type_version=artifact.metadata.type_version, id=artifact.id))
[docs] def list(self, response, res): params = dict(response.request.params) params.pop('marker', None) query = urlparse.urlencode(params) type_name = response.request.urlvars.get('type_name') type_version = response.request.urlvars.get('type_version') if response.request.urlvars.get('state') == 'creating': drafts = "/drafts" else: drafts = "" artifacts_list = [ serialization.serialize_for_client(a, show_level=Showlevel.NONE) for a in res['artifacts']] url = "/v0.1/artifacts" if type_name: url += "/" + type_name if type_version: url += "/v" + type_version url += drafts if query: first_url = url + "?" + query else: first_url = url body = { "artifacts": artifacts_list, "first": first_url } if 'next_marker' in res: params['marker'] = res['next_marker'] next_query = urlparse.urlencode(params) body['next'] = url + '?' + next_query content = json.dumps(body, ensure_ascii=False) response.unicode_body = six.text_type(content) response.content_type = 'application/json'
[docs] def delete(self, response, result): response.status_int = 204
[docs] def download(self, response, blob): response.headers['Content-Type'] = 'application/octet-stream' response.app_iter = iter(blob.data_stream) if blob.checksum: response.headers['Content-MD5'] = blob.checksum response.headers['Content-Length'] = str(blob.size)
[docs] def list_artifact_types(self, response, res): body = json.dumps(res, ensure_ascii=False) response.unicode_body = six.text_type(body) response.content_type = 'application/json'
[docs]def create_resource(): """Images resource factory method""" plugins = loader.ArtifactsPluginLoader('glance.artifacts.types') deserializer = RequestDeserializer(plugins=plugins) serializer = ResponseSerializer() controller = ArtifactsController(plugins=plugins) return wsgi.Resource(controller, deserializer, serializer)

Project Source