refstack/refstack/api/controllers/products.py

293 lines
11 KiB
Python

# Copyright (c) 2015 Mirantis, Inc.
# 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.
"""Product controller."""
import json
import uuid
from oslo_db.exception import DBReferenceError
from oslo_log import log
import pecan
from pecan.secure import secure
from refstack.api import constants as const
from refstack.api.controllers import validation
from refstack.api import utils as api_utils
from refstack.api import validators
from refstack import db
LOG = log.getLogger(__name__)
class VersionsController(validation.BaseRestControllerWithValidation):
"""/v1/products/<product_id>/versions handler."""
__validator__ = validators.ProductVersionValidator
@pecan.expose('json')
def get(self, id):
"""Get all versions for a product."""
product = db.get_product(id)
vendor_id = product['organization_id']
is_admin = (api_utils.check_user_is_foundation_admin() or
api_utils.check_user_is_vendor_admin(vendor_id))
if not product['public'] and not is_admin:
pecan.abort(403, 'Forbidden.')
allowed_keys = ['id', 'product_id', 'version', 'cpid']
return db.get_product_versions(id, allowed_keys=allowed_keys)
@pecan.expose('json')
def get_one(self, id, version_id):
"""Get specific version information."""
product = db.get_product(id)
vendor_id = product['organization_id']
is_admin = (api_utils.check_user_is_foundation_admin() or
api_utils.check_user_is_vendor_admin(vendor_id))
if not product['public'] and not is_admin:
pecan.abort(403, 'Forbidden.')
allowed_keys = ['id', 'product_id', 'version', 'cpid']
return db.get_product_version(version_id, allowed_keys=allowed_keys)
@secure(api_utils.is_authenticated)
@pecan.expose('json')
def post(self, id):
"""'secure' decorator doesn't work at store_item. it must be here."""
self.product_id = id
return super(VersionsController, self).post()
@pecan.expose('json')
def store_item(self, version_info):
"""Add a new version for the product."""
if (not api_utils.check_user_is_product_admin(self.product_id) and
not api_utils.check_user_is_foundation_admin()):
pecan.abort(403, 'Forbidden.')
creator = api_utils.get_user_id()
pecan.response.status = 201
allowed_keys = ['id', 'product_id', 'version', 'cpid']
return db.add_product_version(self.product_id, version_info['version'],
creator, version_info.get('cpid'),
allowed_keys)
@secure(api_utils.is_authenticated)
@pecan.expose('json', method='PUT')
def put(self, id, version_id, **kw):
"""Update details for a specific version.
Endpoint: /v1/products/<product_id>/versions/<version_id>
"""
if (not api_utils.check_user_is_product_admin(id) and
not api_utils.check_user_is_foundation_admin()):
pecan.abort(403, 'Forbidden.')
version_info = {'id': version_id}
if 'cpid' in kw:
version_info['cpid'] = kw['cpid']
version = db.update_product_version(version_info)
pecan.response.status = 200
return version
@secure(api_utils.is_authenticated)
@pecan.expose('json')
def delete(self, id, version_id):
"""Delete a product version.
Endpoint: /v1/products/<product_id>/versions/<version_id>
"""
if (not api_utils.check_user_is_product_admin(id) and
not api_utils.check_user_is_foundation_admin()):
pecan.abort(403, 'Forbidden.')
try:
version = db.get_product_version(version_id,
allowed_keys=['version'])
if not version['version']:
pecan.abort(400, 'Can not delete the empty version as it is '
'used for basic product/test association. '
'This version was implicitly created with '
'the product, and so it cannot be deleted '
'explicitly.')
db.delete_product_version(version_id)
except DBReferenceError:
pecan.abort(400, 'Unable to delete. There are still tests '
'associated to this product version.')
pecan.response.status = 204
class ProductsController(validation.BaseRestControllerWithValidation):
"""/v1/products handler."""
__validator__ = validators.ProductValidator
_custom_actions = {
"action": ["POST"],
}
versions = VersionsController()
@pecan.expose('json')
def get(self):
"""Get information of all products."""
filters = api_utils.parse_input_params(['organization_id'])
allowed_keys = ['id', 'name', 'description', 'product_ref_id', 'type',
'product_type', 'public', 'organization_id']
user = api_utils.get_user_id()
is_admin = user in db.get_foundation_users()
try:
if is_admin:
products = db.get_products(allowed_keys=allowed_keys,
filters=filters)
for s in products:
s['can_manage'] = True
else:
result = dict()
filters['public'] = True
products = db.get_products(allowed_keys=allowed_keys,
filters=filters)
for s in products:
_id = s['id']
result[_id] = s
result[_id]['can_manage'] = False
filters.pop('public')
products = db.get_products_by_user(user,
allowed_keys=allowed_keys,
filters=filters)
for s in products:
_id = s['id']
if _id not in result:
result[_id] = s
result[_id]['can_manage'] = True
products = list(result.values())
except Exception as ex:
LOG.exception('An error occurred during '
'operation with database: %s' % ex)
pecan.abort(400)
products.sort(key=lambda x: x['name'])
return {'products': products}
@pecan.expose('json')
def get_one(self, id):
"""Get information about product."""
allowed_keys = ['id', 'name', 'description',
'product_ref_id', 'product_type',
'public', 'properties', 'created_at', 'updated_at',
'organization_id', 'created_by_user', 'type']
product = db.get_product(id, allowed_keys=allowed_keys)
vendor_id = product['organization_id']
is_admin = (api_utils.check_user_is_foundation_admin() or
api_utils.check_user_is_vendor_admin(vendor_id))
if not is_admin and not product['public']:
pecan.abort(403, 'Forbidden.')
if not is_admin:
admin_only_keys = ['created_by_user', 'created_at', 'updated_at',
'properties']
for key in list(product):
if key in admin_only_keys:
product.pop(key)
product['can_manage'] = is_admin
return product
@secure(api_utils.is_authenticated)
@pecan.expose('json')
def post(self):
"""'secure' decorator doesn't work at store_item. it must be here."""
return super(ProductsController, self).post()
@pecan.expose('json')
def store_item(self, product):
"""Handler for storing item. Should return new item id."""
creator = api_utils.get_user_id()
product['type'] = (const.SOFTWARE
if product['product_type'] == const.DISTRO
else const.CLOUD)
if product['type'] == const.SOFTWARE:
product['product_ref_id'] = str(uuid.uuid4())
vendor_id = product.pop('organization_id', None)
if not vendor_id:
# find or create default vendor for new product
# TODO(andrey-mp): maybe just fill with info here and create
# at DB layer in one transaction
default_vendor_name = 'vendor_' + creator
vendors = db.get_organizations_by_user(creator)
for v in vendors:
if v['name'] == default_vendor_name:
vendor_id = v['id']
break
else:
vendor = {'name': default_vendor_name}
vendor = db.add_organization(vendor, creator)
vendor_id = vendor['id']
product['organization_id'] = vendor_id
product = db.add_product(product, creator)
return {'id': product['id']}
@secure(api_utils.is_authenticated)
@pecan.expose('json', method='PUT')
def put(self, id, **kw):
"""Handler for update item. Should return full info with updates."""
product = db.get_product(id)
vendor_id = product['organization_id']
vendor = db.get_organization(vendor_id)
is_admin = (api_utils.check_user_is_foundation_admin()
or api_utils.check_user_is_vendor_admin(vendor_id))
if not is_admin:
pecan.abort(403, 'Forbidden.')
product_info = {'id': id}
if 'name' in kw:
product_info['name'] = kw['name']
if 'description' in kw:
product_info['description'] = kw['description']
if 'product_ref_id' in kw:
product_info['product_ref_id'] = kw['product_ref_id']
if 'public' in kw:
# user can mark product as public only if
# his/her vendor is public(official)
public = api_utils.str_to_bool(kw['public'])
if (vendor['type'] not in (const.OFFICIAL_VENDOR, const.FOUNDATION)
and public):
pecan.abort(403, 'Forbidden.')
product_info['public'] = public
if 'properties' in kw:
product_info['properties'] = json.dumps(kw['properties'])
db.update_product(product_info)
pecan.response.status = 200
product = db.get_product(id)
product['can_manage'] = True
return product
@secure(api_utils.is_authenticated)
@pecan.expose('json')
def delete(self, id):
"""Delete product."""
if (not api_utils.check_user_is_foundation_admin() and
not api_utils.check_user_is_product_admin(id)):
pecan.abort(403, 'Forbidden.')
try:
db.delete_product(id)
except DBReferenceError:
pecan.abort(400, 'Unable to delete. There are still tests '
'associated to versions of this product.')
pecan.response.status = 204