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:
Ankur Rishi
2014-06-11 12:43:27 -07:00
parent 36f4689bab
commit 2b4be05007
22 changed files with 325 additions and 3 deletions

View File

@@ -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):

View File

@@ -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,

View File

@@ -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',

View File

@@ -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,

View File

@@ -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 ###

View File

@@ -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():

View File

@@ -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):

View File

@@ -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')

View File

@@ -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')

View 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')

View File

View 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

View File

@@ -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

View File

@@ -0,0 +1,5 @@
Namespaces:
=: test.mpl.v1.app
Name: Thing

View File

@@ -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

View 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')

View 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')