From 2b4be050076e5c346b694f43265f82f3bd6edb60 Mon Sep 17 00:00:00 2001 From: Ankur Rishi Date: Wed, 11 Jun 2014 12:43:27 -0700 Subject: [PATCH] Add optional fields to packages for supplier info This commit adds the ability to include additional information about a package's supplier to the manifest.yaml file. The format of the optional fields are as follows: Supplier: Name: CompanyUrl: Link: Text: Summary: Description: Logo: Targets: blueprint additional-author-information Change-Id: I9bb67089ad1bc554524ee828b9bfda38dc8251f6 --- murano/api/v1/__init__.py | 4 +- murano/api/v1/catalog.py | 6 +- murano/api/v1/router.py | 4 + murano/cmd/manage.py | 2 + .../versions/002_add_package_supplier_info.py | 49 ++++++++++++ murano/db/models.py | 5 +- murano/packages/application_package.py | 32 ++++++++ murano/packages/versions/hot_v1.py | 1 + murano/packages/versions/mpl_v1.py | 1 + murano/tests/api/v1/test_catalog.py | 71 ++++++++++++++++++ murano/tests/packages/__init__.py | 0 .../test.hot.v1.app/manifest.yaml | 17 +++++ .../test.hot.v1.app/template.yaml | 8 ++ .../test.hot.v1.app/test_logo.png | Bin 0 -> 157 bytes .../test.hot.v1.app/test_supplier_logo.png | Bin 0 -> 157 bytes .../test.mpl.v1.app/Classes/Thing.yaml | 5 ++ .../test.mpl.v1.app/manifest.yaml | 19 +++++ .../test.mpl.v1.app/test_logo.png | Bin 0 -> 157 bytes .../test.mpl.v1.app/test_supplier_logo.png | Bin 0 -> 157 bytes murano/tests/packages/versions/__init__.py | 0 murano/tests/packages/versions/test_hot_v1.py | 52 +++++++++++++ murano/tests/packages/versions/test_mpl_v1.py | 52 +++++++++++++ 22 files changed, 325 insertions(+), 3 deletions(-) create mode 100644 murano/db/migration/alembic_migrations/versions/002_add_package_supplier_info.py create mode 100644 murano/tests/api/v1/test_catalog.py create mode 100644 murano/tests/packages/__init__.py create mode 100644 murano/tests/packages/test_packages/test.hot.v1.app/manifest.yaml create mode 100644 murano/tests/packages/test_packages/test.hot.v1.app/template.yaml create mode 100644 murano/tests/packages/test_packages/test.hot.v1.app/test_logo.png create mode 100644 murano/tests/packages/test_packages/test.hot.v1.app/test_supplier_logo.png create mode 100644 murano/tests/packages/test_packages/test.mpl.v1.app/Classes/Thing.yaml create mode 100644 murano/tests/packages/test_packages/test.mpl.v1.app/manifest.yaml create mode 100644 murano/tests/packages/test_packages/test.mpl.v1.app/test_logo.png create mode 100644 murano/tests/packages/test_packages/test.mpl.v1.app/test_supplier_logo.png create mode 100644 murano/tests/packages/versions/__init__.py create mode 100644 murano/tests/packages/versions/test_hot_v1.py create mode 100644 murano/tests/packages/versions/test_mpl_v1.py diff --git a/murano/api/v1/__init__.py b/murano/api/v1/__init__.py index 8f1fd24e..b5fa011e 100644 --- a/murano/api/v1/__init__.py +++ b/murano/api/v1/__init__.py @@ -30,7 +30,9 @@ PKG_PARAMS_MAP = {'display_name': 'name', 'description': 'description', 'author': 'author', 'classes': 'class_definitions', - 'tags': 'tags'} + 'tags': 'tags', + 'supplier': 'supplier', + 'supplier_logo': 'supplier_logo'} def get_draft(environment_id=None, session_id=None): diff --git a/murano/api/v1/catalog.py b/murano/api/v1/catalog.py index f35fcb42..b7538554 100644 --- a/murano/api/v1/catalog.py +++ b/murano/api/v1/catalog.py @@ -246,6 +246,10 @@ class Controller(object): package = db_api.package_get(package_id, req.context) return package.logo + def get_supplier_logo(self, req, package_id): + package = db_api.package_get(package_id, req.context) + return package.supplier_logo + def download(self, req, package_id): target = {'package_id': package_id} policy.check("download_package", req.context, target) @@ -270,7 +274,7 @@ class PackageSerializer(wsgi.ResponseSerializer): def serialize(self, action_result, accept, action): if action == 'get_ui': accept = 'text/plain' - elif action in ('download', 'get_logo'): + elif action in ('download', 'get_logo', 'get_supplier_logo'): accept = 'application/octet-stream' return super(PackageSerializer, self).serialize(action_result, accept, diff --git a/murano/api/v1/router.py b/murano/api/v1/router.py index f36a2f57..6d287169 100644 --- a/murano/api/v1/router.py +++ b/murano/api/v1/router.py @@ -173,6 +173,10 @@ class API(wsgi.Router): controller=catalog_resource, action='get_logo', conditions={'method': ['GET']}) + mapper.connect('/catalog/packages/{package_id}/supplier_logo', + controller=catalog_resource, + action='get_supplier_logo', + conditions={'method': ['GET']}) mapper.connect('/catalog/packages/{package_id}/download', controller=catalog_resource, action='download', diff --git a/murano/cmd/manage.py b/murano/cmd/manage.py index 9bc50d43..f7b5289d 100644 --- a/murano/cmd/manage.py +++ b/murano/cmd/manage.py @@ -63,6 +63,7 @@ def _do_import_package(_dir, categories, update=False): 'fully_qualified_name': pkg.full_name, 'type': pkg.package_type, 'author': pkg.author, + 'supplier': pkg.supplier, 'name': pkg.display_name, 'description': pkg.description, # note: we explicitly mark all the imported packages as public, @@ -70,6 +71,7 @@ def _do_import_package(_dir, categories, update=False): 'is_public': True, 'tags': pkg.tags, 'logo': pkg.logo, + 'supplier_logo': pkg.supplier_logo, 'ui_definition': pkg.raw_ui, 'class_definitions': pkg.classes, 'archive': pkg.blob, diff --git a/murano/db/migration/alembic_migrations/versions/002_add_package_supplier_info.py b/murano/db/migration/alembic_migrations/versions/002_add_package_supplier_info.py new file mode 100644 index 00000000..42b19b0b --- /dev/null +++ b/murano/db/migration/alembic_migrations/versions/002_add_package_supplier_info.py @@ -0,0 +1,49 @@ +# 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. + +"""empty message + +Revision ID: 002 +Revises: None +Create Date: 2014-06-23 16:34:33.698760 + +""" + +# revision identifiers, used by Alembic. +revision = '002' +down_revision = '001' + +from alembic import op +import sqlalchemy as sa + + +MYSQL_ENGINE = 'InnoDB' +MYSQL_CHARSET = 'utf8' + + +def upgrade(): + op.add_column( + 'package', + sa.Column('supplier_logo', sa.types.LargeBinary) + ) + op.add_column( + 'package', + sa.Column('supplier', sa.types.Text()) + ) + ### end Alembic commands ### + + +def downgrade(): + op.drop_column('package', 'supplier') + op.drop_column('package', 'supplier_logo') + ### end Alembic commands ### diff --git a/murano/db/models.py b/murano/db/models.py index 53eac06e..f4a5c098 100644 --- a/murano/db/models.py +++ b/murano/db/models.py @@ -270,6 +270,7 @@ class Package(BASE, ModificationsTrackedObject): unique=True) type = sa.Column(sa.String(20), nullable=False, default='class') author = sa.Column(sa.String(80), default='Openstack') + supplier = sa.Column(JsonBlob(), nullable=True, default={}) name = sa.Column(sa.String(80), nullable=False) enabled = sa.Column(sa.Boolean, default=True) description = sa.Column(sa.String(512), @@ -283,6 +284,7 @@ class Package(BASE, ModificationsTrackedObject): logo = sa.Column(sa.LargeBinary, nullable=True) owner_id = sa.Column(sa.String(36), nullable=False) ui_definition = sa.Column(sa.Text) + supplier_logo = sa.Column(sa.LargeBinary, nullable=True) categories = sa_orm.relationship("Category", secondary=package_to_category, cascade='save-update, merge', @@ -295,7 +297,8 @@ class Package(BASE, ModificationsTrackedObject): not_serializable = ['_sa_instance_state', 'archive', 'logo', - 'ui_definition'] + 'ui_definition', + 'supplier_logo'] nested_objects = ['categories', 'tags', 'class_definitions'] for key in not_serializable: if key in d.keys(): diff --git a/murano/packages/application_package.py b/murano/packages/application_package.py index ec3d7163..57ae8e6a 100644 --- a/murano/packages/application_package.py +++ b/murano/packages/application_package.py @@ -37,10 +37,12 @@ class ApplicationPackage(object): self._display_name = None self._description = None self._author = None + self._supplier = {} self._tags = None self._logo = None self._format = manifest.get('Format') self._logo_cache = None + self._supplier_logo_cache = None self._blob_cache = None @property @@ -63,6 +65,10 @@ class ApplicationPackage(object): def author(self): return self._author + @property + def supplier(self): + return self._supplier + @property def tags(self): return tuple(self._tags) @@ -73,6 +79,12 @@ class ApplicationPackage(object): self._load_logo(False) return self._logo_cache + @property + def supplier_logo(self): + if not self._supplier_logo_cache: + self._load_supplier_logo(False) + return self._supplier_logo_cache + @property def blob(self): if not self._blob_cache: @@ -105,6 +117,26 @@ class ApplicationPackage(object): raise e.PackageLoadError( "Unable to load logo: " + str(ex)), None, trace + def _load_supplier_logo(self, validate=False): + if 'Logo' not in self._supplier: + self._supplier['Logo'] = None + logo_file = self._supplier['Logo'] or 'supplier_logo.png' + full_path = os.path.join(self._source_directory, logo_file) + if not os.path.isfile(full_path) and logo_file == 'supplier_logo.png': + del self._supplier['Logo'] + return + try: + if validate: + if imghdr.what(full_path) != 'png': + raise e.PackageLoadError( + "Supplier Logo is not in PNG format") + with open(full_path) as stream: + self._supplier_logo_cache = stream.read() + except Exception as ex: + trace = sys.exc_info()[2] + raise e.PackageLoadError( + "Unable to load supplier logo: " + str(ex)), None, trace + def _zipdir(path, zipf): for root, dirs, files in os.walk(path): diff --git a/murano/packages/versions/hot_v1.py b/murano/packages/versions/hot_v1.py index 86befc92..6cd0b1d8 100644 --- a/murano/packages/versions/hot_v1.py +++ b/murano/packages/versions/hot_v1.py @@ -36,6 +36,7 @@ def load(package, yaml_content): package._display_name = yaml_content.get('Name', package._full_name) package._description = yaml_content.get('Description') package._author = yaml_content.get('Author') + package._supplier = yaml_content.get('Supplier') or {} package._logo = yaml_content.get('Logo') package._tags = yaml_content.get('Tags') diff --git a/murano/packages/versions/mpl_v1.py b/murano/packages/versions/mpl_v1.py index 1d204d2b..acfe0597 100644 --- a/murano/packages/versions/mpl_v1.py +++ b/murano/packages/versions/mpl_v1.py @@ -36,6 +36,7 @@ def load(package, yaml_content): package._display_name = yaml_content.get('Name', package._full_name) package._description = yaml_content.get('Description') package._author = yaml_content.get('Author') + package._supplier = yaml_content.get('Supplier') or {} package._classes = yaml_content.get('Classes') package._ui = yaml_content.get('UI', 'ui.yaml') package._logo = yaml_content.get('Logo') diff --git a/murano/tests/api/v1/test_catalog.py b/murano/tests/api/v1/test_catalog.py new file mode 100644 index 00000000..1f257b76 --- /dev/null +++ b/murano/tests/api/v1/test_catalog.py @@ -0,0 +1,71 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 imghdr +import mock +from murano.api.v1 import catalog +from murano.common import policy +from murano.db.catalog import api as db_catalog_api +from murano.packages import load_utils +import murano.tests.api.base as test_base +import os + + +@mock.patch.object(policy, 'check') +class TestCatalogApi(test_base.ControllerTest, test_base.MuranoTestCase): + def setUp(self): + super(TestCatalogApi, self).setUp() + self.controller = catalog.Controller() + + def test_load_package_with_supplier_info(self, mock_policy_check): + package_dir = os.path.abspath( + os.path.join( + __file__, + '../../../packages/test_packages/test.mpl.v1.app' + ) + ) + pkg = load_utils.load_from_dir( + package_dir + ) + package = { + 'fully_qualified_name': pkg.full_name, + 'type': pkg.package_type, + 'author': pkg.author, + 'supplier': pkg.supplier, + 'name': pkg.display_name, + 'description': pkg.description, + 'is_public': True, + 'tags': pkg.tags, + 'logo': pkg.logo, + 'supplier_logo': pkg.supplier_logo, + 'ui_definition': pkg.raw_ui, + 'class_definitions': pkg.classes, + 'archive': pkg.blob, + 'categories': [] + } + + saved_package = db_catalog_api.package_upload(package, '') + + req = self._get('/v1/catalog/packages/%s' % saved_package.id) + result = self.controller.get(req, saved_package.id) + + self.assertEqual(package['supplier'], result['supplier']) + + req = self._get( + '/v1/catalog/packages/%s/supplier_logo' % saved_package.id + ) + result = self.controller.get_supplier_logo(req, saved_package.id) + + self.assertEqual(imghdr.what('', result), 'png') diff --git a/murano/tests/packages/__init__.py b/murano/tests/packages/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/murano/tests/packages/test_packages/test.hot.v1.app/manifest.yaml b/murano/tests/packages/test_packages/test.hot.v1.app/manifest.yaml new file mode 100644 index 00000000..96a423a2 --- /dev/null +++ b/murano/tests/packages/test_packages/test.hot.v1.app/manifest.yaml @@ -0,0 +1,17 @@ +Format: Heat.HOT/1.0 +Type: Application +FullName: test.hot.v1.app +Name: Test HOT v1 App +Description: Test HOT v1 Application +Author: Test Runner +Tags: [Linux] +Logo: test_logo.png +Supplier: + Name: Supplier Name + CompanyUrl: + Text: Example Company + Link: http://example.com + Logo: test_supplier_logo.png + Summary: Company summary goes here + Description: Marked up company description goes here + diff --git a/murano/tests/packages/test_packages/test.hot.v1.app/template.yaml b/murano/tests/packages/test_packages/test.hot.v1.app/template.yaml new file mode 100644 index 00000000..cb19d9b4 --- /dev/null +++ b/murano/tests/packages/test_packages/test.hot.v1.app/template.yaml @@ -0,0 +1,8 @@ +heat_template_version: '2013-05-23' +resources: + test-server: + type: OS::Nova::Server + properties: + flavor: test.flavor + image: Some image name + key_name: default diff --git a/murano/tests/packages/test_packages/test.hot.v1.app/test_logo.png b/murano/tests/packages/test_packages/test.hot.v1.app/test_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e43885f140c5b1c62f47f0b5cffcfa94672b9518 GIT binary patch literal 157 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2s6ii6yp7}lMWc?smOq&xaLGB9lH z=l+w(3gmMZctjR6Fz_7)VaDV6D^h@hQYEetCBgY=CFO}lsSE*$nRz98ey$-3WyX4@ udWMGXpPkHss(3tI9780+lYjjGZ_mK`kLllX)5aE{ID@CFpUXO@geCwGLn*)j literal 0 HcmV?d00001 diff --git a/murano/tests/packages/test_packages/test.hot.v1.app/test_supplier_logo.png b/murano/tests/packages/test_packages/test.hot.v1.app/test_supplier_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e43885f140c5b1c62f47f0b5cffcfa94672b9518 GIT binary patch literal 157 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2s6ii6yp7}lMWc?smOq&xaLGB9lH z=l+w(3gmMZctjR6Fz_7)VaDV6D^h@hQYEetCBgY=CFO}lsSE*$nRz98ey$-3WyX4@ udWMGXpPkHss(3tI9780+lYjjGZ_mK`kLllX)5aE{ID@CFpUXO@geCwGLn*)j literal 0 HcmV?d00001 diff --git a/murano/tests/packages/test_packages/test.mpl.v1.app/Classes/Thing.yaml b/murano/tests/packages/test_packages/test.mpl.v1.app/Classes/Thing.yaml new file mode 100644 index 00000000..216c1009 --- /dev/null +++ b/murano/tests/packages/test_packages/test.mpl.v1.app/Classes/Thing.yaml @@ -0,0 +1,5 @@ +Namespaces: + =: test.mpl.v1.app + +Name: Thing + diff --git a/murano/tests/packages/test_packages/test.mpl.v1.app/manifest.yaml b/murano/tests/packages/test_packages/test.mpl.v1.app/manifest.yaml new file mode 100644 index 00000000..9ebddd3d --- /dev/null +++ b/murano/tests/packages/test_packages/test.mpl.v1.app/manifest.yaml @@ -0,0 +1,19 @@ +Format: 1.0 +Type: Application +FullName: test.mpl.v1.app +Description: Test V1 Application +Author: Test runner +Tags: [Linux] +Classes: + test.mpl.v1.app.Thing: Thing.yaml +Logo: test_logo.png +UI: ui.yaml +Supplier: + Name: Supplier Name + CompanyUrl: + Text: Example Company + Link: http://example.com + Logo: test_supplier_logo.png + Summary: Company summary goes here + Description: Marked up company description goes here + diff --git a/murano/tests/packages/test_packages/test.mpl.v1.app/test_logo.png b/murano/tests/packages/test_packages/test.mpl.v1.app/test_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e43885f140c5b1c62f47f0b5cffcfa94672b9518 GIT binary patch literal 157 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2s6ii6yp7}lMWc?smOq&xaLGB9lH z=l+w(3gmMZctjR6Fz_7)VaDV6D^h@hQYEetCBgY=CFO}lsSE*$nRz98ey$-3WyX4@ udWMGXpPkHss(3tI9780+lYjjGZ_mK`kLllX)5aE{ID@CFpUXO@geCwGLn*)j literal 0 HcmV?d00001 diff --git a/murano/tests/packages/test_packages/test.mpl.v1.app/test_supplier_logo.png b/murano/tests/packages/test_packages/test.mpl.v1.app/test_supplier_logo.png new file mode 100644 index 0000000000000000000000000000000000000000..e43885f140c5b1c62f47f0b5cffcfa94672b9518 GIT binary patch literal 157 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1SBVv2j2s6ii6yp7}lMWc?smOq&xaLGB9lH z=l+w(3gmMZctjR6Fz_7)VaDV6D^h@hQYEetCBgY=CFO}lsSE*$nRz98ey$-3WyX4@ udWMGXpPkHss(3tI9780+lYjjGZ_mK`kLllX)5aE{ID@CFpUXO@geCwGLn*)j literal 0 HcmV?d00001 diff --git a/murano/tests/packages/versions/__init__.py b/murano/tests/packages/versions/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/murano/tests/packages/versions/test_hot_v1.py b/murano/tests/packages/versions/test_hot_v1.py new file mode 100644 index 00000000..2aa50dc3 --- /dev/null +++ b/murano/tests/packages/versions/test_hot_v1.py @@ -0,0 +1,52 @@ +# +# 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 imghdr +import murano.packages.load_utils as load_utils +import murano.tests.base as test_base +import murano.tests.utils as test_utils +import os +from oslo.config import cfg + + +class TestHotV1(test_base.MuranoTestCase): + def setUp(self): + super(TestHotV1, self).setUp() + test_utils.setup_dummy_db() + self.addCleanup(test_utils.reset_dummy_db) + self.addCleanup(cfg.CONF.reset) + self.addCleanup(test_utils.reset_dummy_db) + + def test_supplier_info_load(self): + package_dir = os.path.abspath( + os.path.join(__file__, '../../test_packages/test.hot.v1.app') + ) + package = load_utils.load_from_dir(package_dir) + + self.assertNotEqual(package.supplier, None) + self.assertEqual(package.supplier['Name'], 'Supplier Name') + self.assertEqual(package.supplier['CompanyUrl'], { + 'Link': 'http://example.com', + 'Text': 'Example Company' + }) + self.assertEqual( + package.supplier['Summary'], + 'Company summary goes here' + ) + self.assertEqual( + package.supplier['Description'], + 'Marked up company description goes here' + ) + self.assertEqual(package.supplier['Logo'], 'test_supplier_logo.png') + + self.assertEqual(imghdr.what('', package.supplier_logo), 'png') diff --git a/murano/tests/packages/versions/test_mpl_v1.py b/murano/tests/packages/versions/test_mpl_v1.py new file mode 100644 index 00000000..2298c6a4 --- /dev/null +++ b/murano/tests/packages/versions/test_mpl_v1.py @@ -0,0 +1,52 @@ +# +# 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 imghdr +import murano.packages.load_utils as load_utils +import murano.tests.base as test_base +import murano.tests.utils as test_utils +import os +from oslo.config import cfg + + +class TestMplV1(test_base.MuranoTestCase): + def setUp(self): + super(TestMplV1, self).setUp() + test_utils.setup_dummy_db() + self.addCleanup(test_utils.reset_dummy_db) + self.addCleanup(cfg.CONF.reset) + self.addCleanup(test_utils.reset_dummy_db) + + def test_supplier_info_load(self): + package_dir = os.path.abspath( + os.path.join(__file__, '../../test_packages/test.mpl.v1.app') + ) + package = load_utils.load_from_dir(package_dir) + + self.assertNotEqual(package.supplier, None) + self.assertEqual(package.supplier['Name'], 'Supplier Name') + self.assertEqual(package.supplier['CompanyUrl'], { + 'Link': 'http://example.com', + 'Text': 'Example Company' + }) + self.assertEqual( + package.supplier['Summary'], + 'Company summary goes here' + ) + self.assertEqual( + package.supplier['Description'], + 'Marked up company description goes here' + ) + self.assertEqual(package.supplier['Logo'], 'test_supplier_logo.png') + + self.assertEqual(imghdr.what('', package.supplier_logo), 'png')