302 lines
11 KiB
Python
302 lines
11 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
# Copyright 2010-2011 OpenStack LLC.
|
|
# All Rights Reserved.
|
|
#
|
|
# 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 logging
|
|
|
|
import webob.exc
|
|
|
|
from glance.common import exception
|
|
from glance.common import utils
|
|
from glance.common import wsgi
|
|
from glance.db.sqlalchemy import api as db_api
|
|
|
|
|
|
logger = logging.getLogger('glance.registry.api.v1.members')
|
|
|
|
|
|
class Controller(object):
|
|
|
|
def __init__(self, conf):
|
|
self.conf = conf
|
|
db_api.configure_db(conf)
|
|
|
|
def index(self, req, image_id):
|
|
"""
|
|
Get the members of an image.
|
|
"""
|
|
try:
|
|
image = db_api.image_get(req.context, image_id)
|
|
except exception.NotFound:
|
|
raise webob.exc.HTTPNotFound()
|
|
except exception.Forbidden:
|
|
# If it's private and doesn't belong to them, don't let on
|
|
# that it exists
|
|
msg = _("Access by %(user)s to image %(id)s "
|
|
"denied") % ({'user': req.context.user,
|
|
'id': image_id})
|
|
logger.info(msg)
|
|
raise webob.exc.HTTPNotFound()
|
|
|
|
return dict(members=make_member_list(image['members'],
|
|
member_id='member',
|
|
can_share='can_share'))
|
|
|
|
@utils.mutating
|
|
def update_all(self, req, image_id, body):
|
|
"""
|
|
Replaces the members of the image with those specified in the
|
|
body. The body is a dict with the following format::
|
|
|
|
{"memberships": [
|
|
{"member_id": <MEMBER_ID>,
|
|
["can_share": [True|False]]}, ...
|
|
]}
|
|
"""
|
|
if req.context.owner is None:
|
|
raise webob.exc.HTTPUnauthorized(_("No authenticated user"))
|
|
|
|
# Make sure the image exists
|
|
session = db_api.get_session()
|
|
try:
|
|
image = db_api.image_get(req.context, image_id, session=session)
|
|
except exception.NotFound:
|
|
raise webob.exc.HTTPNotFound()
|
|
except exception.Forbidden:
|
|
# If it's private and doesn't belong to them, don't let on
|
|
# that it exists
|
|
msg = _("Access by %(user)s to image %(id)s "
|
|
"denied") % ({'user': req.context.user,
|
|
'id': image_id})
|
|
logger.info(msg)
|
|
raise webob.exc.HTTPNotFound()
|
|
|
|
# Can they manipulate the membership?
|
|
if not req.context.is_image_sharable(image):
|
|
msg = _("No permission to share that image")
|
|
raise webob.exc.HTTPForbidden(msg)
|
|
|
|
# Get the membership list
|
|
try:
|
|
memb_list = body['memberships']
|
|
except Exception, e:
|
|
# Malformed entity...
|
|
msg = _("Invalid membership association: %s") % e
|
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
|
|
|
add = []
|
|
existing = {}
|
|
# Walk through the incoming memberships
|
|
for memb in memb_list:
|
|
try:
|
|
datum = dict(image_id=image['id'],
|
|
member=memb['member_id'],
|
|
can_share=None)
|
|
except Exception, e:
|
|
# Malformed entity...
|
|
msg = _("Invalid membership association: %s") % e
|
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
|
|
|
# Figure out what can_share should be
|
|
if 'can_share' in memb:
|
|
datum['can_share'] = bool(memb['can_share'])
|
|
|
|
# Try to find the corresponding membership
|
|
try:
|
|
membership = db_api.image_member_find(req.context,
|
|
datum['image_id'],
|
|
datum['member'],
|
|
session=session)
|
|
|
|
# Are we overriding can_share?
|
|
if datum['can_share'] is None:
|
|
datum['can_share'] = membership['can_share']
|
|
|
|
existing[membership['id']] = {
|
|
'values': datum,
|
|
'membership': membership,
|
|
}
|
|
except exception.NotFound:
|
|
# Default can_share
|
|
datum['can_share'] = bool(datum['can_share'])
|
|
add.append(datum)
|
|
|
|
# We now have a filtered list of memberships to add and
|
|
# memberships to modify. Let's start by walking through all
|
|
# the existing image memberships...
|
|
for memb in image['members']:
|
|
if memb['id'] in existing:
|
|
# Just update the membership in place
|
|
update = existing[memb['id']]['values']
|
|
db_api.image_member_update(req.context, memb, update,
|
|
session=session)
|
|
else:
|
|
# Outdated one; needs to be deleted
|
|
db_api.image_member_delete(req.context, memb, session=session)
|
|
|
|
# Now add the non-existant ones
|
|
for memb in add:
|
|
db_api.image_member_create(req.context, memb, session=session)
|
|
|
|
# Make an appropriate result
|
|
return webob.exc.HTTPNoContent()
|
|
|
|
@utils.mutating
|
|
def update(self, req, image_id, id, body=None):
|
|
"""
|
|
Adds a membership to the image, or updates an existing one.
|
|
If a body is present, it is a dict with the following format::
|
|
|
|
{"member": {
|
|
"can_share": [True|False]
|
|
}}
|
|
|
|
If "can_share" is provided, the member's ability to share is
|
|
set accordingly. If it is not provided, existing memberships
|
|
remain unchanged and new memberships default to False.
|
|
"""
|
|
if req.context.owner is None:
|
|
raise webob.exc.HTTPUnauthorized(_("No authenticated user"))
|
|
|
|
# Make sure the image exists
|
|
try:
|
|
image = db_api.image_get(req.context, image_id)
|
|
except exception.NotFound:
|
|
raise webob.exc.HTTPNotFound()
|
|
except exception.Forbidden:
|
|
# If it's private and doesn't belong to them, don't let on
|
|
# that it exists
|
|
msg = _("Access by %(user)s to image %(id)s "
|
|
"denied") % ({'user': req.context.user,
|
|
'id': image_id})
|
|
logger.info(msg)
|
|
raise webob.exc.HTTPNotFound()
|
|
|
|
# Can they manipulate the membership?
|
|
if not req.context.is_image_sharable(image):
|
|
msg = _("No permission to share that image")
|
|
raise webob.exc.HTTPForbidden(msg)
|
|
|
|
# Determine the applicable can_share value
|
|
can_share = None
|
|
if body:
|
|
try:
|
|
can_share = bool(body['member']['can_share'])
|
|
except Exception, e:
|
|
# Malformed entity...
|
|
msg = _("Invalid membership association: %s") % e
|
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
|
|
|
# Look up an existing membership...
|
|
try:
|
|
session = db_api.get_session()
|
|
membership = db_api.image_member_find(req.context,
|
|
image_id, id,
|
|
session=session)
|
|
if can_share is not None:
|
|
values = dict(can_share=can_share)
|
|
db_api.image_member_update(req.context, membership, values,
|
|
session=session)
|
|
except exception.NotFound:
|
|
values = dict(image_id=image['id'], member=id,
|
|
can_share=bool(can_share))
|
|
db_api.image_member_create(req.context, values, session=session)
|
|
|
|
return webob.exc.HTTPNoContent()
|
|
|
|
@utils.mutating
|
|
def delete(self, req, image_id, id):
|
|
"""
|
|
Removes a membership from the image.
|
|
"""
|
|
if req.context.owner is None:
|
|
raise webob.exc.HTTPUnauthorized(_("No authenticated user"))
|
|
|
|
# Make sure the image exists
|
|
try:
|
|
image = db_api.image_get(req.context, image_id)
|
|
except exception.NotFound:
|
|
raise webob.exc.HTTPNotFound()
|
|
except exception.Forbidden:
|
|
# If it's private and doesn't belong to them, don't let on
|
|
# that it exists
|
|
msg = _("Access by %(user)s to image %(id)s "
|
|
"denied") % ({'user': req.context.user,
|
|
'id': image_id})
|
|
logger.info(msg)
|
|
raise webob.exc.HTTPNotFound()
|
|
|
|
# Can they manipulate the membership?
|
|
if not req.context.is_image_sharable(image):
|
|
msg = _("No permission to share that image")
|
|
raise webob.exc.HTTPForbidden(msg)
|
|
|
|
# Look up an existing membership
|
|
try:
|
|
session = db_api.get_session()
|
|
member_ref = db_api.image_member_find(req.context,
|
|
image_id,
|
|
id,
|
|
session=session)
|
|
db_api.image_member_delete(req.context,
|
|
member_ref,
|
|
session=session)
|
|
except exception.NotFound:
|
|
pass
|
|
|
|
# Make an appropriate result
|
|
return webob.exc.HTTPNoContent()
|
|
|
|
def index_shared_images(self, req, id):
|
|
"""
|
|
Retrieves images shared with the given member.
|
|
"""
|
|
params = {}
|
|
try:
|
|
memberships = db_api.image_member_get_memberships(req.context,
|
|
id,
|
|
**params)
|
|
except exception.NotFound, e:
|
|
msg = _("Invalid marker. Membership could not be found.")
|
|
raise webob.exc.HTTPBadRequest(explanation=msg)
|
|
|
|
return dict(shared_images=make_member_list(memberships,
|
|
image_id='image_id',
|
|
can_share='can_share'))
|
|
|
|
|
|
def make_member_list(members, **attr_map):
|
|
"""
|
|
Create a dict representation of a list of members which we can use
|
|
to serialize the members list. Keyword arguments map the names of
|
|
optional attributes to include to the database attribute.
|
|
"""
|
|
|
|
def _fetch_memb(memb, attr_map):
|
|
return dict([(k, memb[v]) for k, v in attr_map.items()
|
|
if v in memb.keys()])
|
|
|
|
# Return the list of members with the given attribute mapping
|
|
return [_fetch_memb(memb, attr_map) for memb in members
|
|
if not memb.deleted]
|
|
|
|
|
|
def create_resource(conf):
|
|
"""Image members resource factory method."""
|
|
deserializer = wsgi.JSONRequestDeserializer()
|
|
serializer = wsgi.JSONResponseSerializer()
|
|
return wsgi.Resource(Controller(conf), deserializer, serializer)
|