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: <name of vendor> CompanyUrl: Link: <url> Text: <anchor text> Summary: <plain text summary of vendor> Description: <html summary for dashboard> Logo: <png filename of vendor logo in same dir> Targets: blueprint additional-author-information Change-Id: I9bb67089ad1bc554524ee828b9bfda38dc8251f6
This commit is contained in:
@@ -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):
|
||||
|
@@ -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,
|
||||
|
@@ -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',
|
||||
|
@@ -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,
|
||||
|
@@ -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 ###
|
@@ -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():
|
||||
|
@@ -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):
|
||||
|
@@ -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')
|
||||
|
||||
|
@@ -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')
|
||||
|
71
murano/tests/api/v1/test_catalog.py
Normal file
71
murano/tests/api/v1/test_catalog.py
Normal file
@@ -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')
|
0
murano/tests/packages/__init__.py
Normal file
0
murano/tests/packages/__init__.py
Normal file
@@ -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
|
||||
|
@@ -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
|
Binary file not shown.
After Width: | Height: | Size: 157 B |
Binary file not shown.
After Width: | Height: | Size: 157 B |
@@ -0,0 +1,5 @@
|
||||
Namespaces:
|
||||
=: test.mpl.v1.app
|
||||
|
||||
Name: Thing
|
||||
|
@@ -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
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 157 B |
Binary file not shown.
After Width: | Height: | Size: 157 B |
0
murano/tests/packages/versions/__init__.py
Normal file
0
murano/tests/packages/versions/__init__.py
Normal file
52
murano/tests/packages/versions/test_hot_v1.py
Normal file
52
murano/tests/packages/versions/test_hot_v1.py
Normal file
@@ -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')
|
52
murano/tests/packages/versions/test_mpl_v1.py
Normal file
52
murano/tests/packages/versions/test_mpl_v1.py
Normal file
@@ -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')
|
Reference in New Issue
Block a user