python-muranoclient/muranoclient/v1/artifact_packages.py

369 lines
12 KiB
Python

# Copyright (c) 2015 Mirantis, Inc.
#
# 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.
from glanceclient import exc as glance_exc
import six
import yaml
from muranoclient.common import exceptions as exc
from muranoclient.common import utils
from muranoclient.i18n import _
def rewrap_http_exceptions(func):
def inner(*args, **kwargs):
try:
return func(*args, **kwargs)
except glance_exc.HTTPException as e:
raise exc.from_code(e.code)
return inner
class ArtifactRepo(object):
def __init__(self, client, tenant=None):
self.tenant = tenant
self.client = client
def create(self, fqn, data, **kwargs):
package = utils.Package.from_file(data)
manifest = package.manifest
package_draft = {
'name': manifest.get('FullName', fqn),
'version': manifest.get('Version', '0.0.0'),
'description': manifest.get('Description'),
'display_name': manifest.get('Name', fqn),
'type': manifest.get('Type', 'Application'),
'author': manifest.get('Author'),
'tags': manifest.get('Tags', []),
'class_definitions': package.classes.keys()
}
for k, v in six.iteritems(kwargs):
package_draft[k] = v
inherits = self._get_local_inheritance(package.classes)
package_draft['inherits'] = inherits
keywords = self._keywords_from_display_name(
package_draft['display_name'])
keywords.extend(package_draft['tags'])
package_draft['keywords'] = keywords
# NOTE(ativelkov): this is very racy, but until we have a chance to
# enforce uniqueness right in glance this is the only way to do it
is_public = package_draft.get('visibility', 'private')
if is_public:
filters = {}
else:
filters = {'owner': self.tenant}
existing = self.list(name=package_draft['name'],
version=package_draft['version'], **filters)
try:
next(existing)
raise exc.HTTPConflict("Package already exists")
except StopIteration:
pass
res = self.client.artifacts.create(**package_draft)
app_id = res.id
self.client.artifacts.upload_blob(app_id, 'archive', package.file())
if package.logo is not None:
self.client.artifacts.upload_blob(app_id, 'logo', package.logo)
if package.ui is not None:
self.client.artifacts.upload_blob(app_id, 'ui_definition',
package.ui)
package.file().close()
self.client.artifacts.active(app_id)
return self.client.artifacts.get(app_id)
@staticmethod
def _get_local_inheritance(classes):
result = {}
for class_name, klass in six.iteritems(classes):
if 'Extends' not in klass:
continue
ns = klass.get('Namespaces')
if ns:
resolver = NamespaceResolver(ns)
else:
resolver = None
if isinstance(klass['Extends'], list):
bases = klass['Extends']
else:
bases = [klass['Extends']]
for base_class in bases:
if resolver:
base_fqn = resolver.resolve_name(base_class)
else:
base_fqn = base_class
result.setdefault(base_fqn, []).append(class_name)
return result
@staticmethod
def _keywords_from_display_name(display_name):
return display_name.split()[:10]
def list(self,
sort_field='name',
sort_dir='asc',
type=None,
tags=None,
limit=None,
page_size=None,
**filters):
sort = "%s:%s" % (sort_field, sort_dir)
if type is not None:
filters['type'] = type
if tags is not None:
filters['tag'] = tags
return self.client.artifacts.list(sort=sort,
limit=limit,
page_size=page_size,
filters=filters)
def get(self, app_id):
return self.client.artifacts.get(app_id)
def delete(self, app_id):
return self.client.artifacts.delete(app_id)
def update(self, app_id, props_to_remove=None, **new_props):
new_keywords = []
new_name = new_props.get('display_name')
new_tags = new_props.get('tags')
if new_name:
new_keywords.extend(self._keywords_from_display_name(new_name))
if new_tags:
new_keywords.extend(new_tags)
if new_keywords:
new_props['keywords'] = new_keywords
visibility = new_props.get('visibility')
if visibility == 'public':
package = self.client.artifacts.get(app_id)
# NOTE(ativelkov): this is very racy, but until we have a chance to
# enforce uniqueness right in glance this is the only way to do it
existing = self.list(name=package.name,
version=package.version,
visibility='public')
try:
while True:
package = next(existing)
if package.id == app_id:
continue
else:
raise exc.HTTPConflict("Package already exists")
except StopIteration:
pass
return self.client.artifacts.update(app_id,
remove_props=props_to_remove,
**new_props)
def toggle_active(self, app_id):
old_val = self.get(app_id).type_specific_properties['enabled']
return self.update(app_id, enabled=(not old_val))
def toggle_public(self, app_id):
visibility = self.get(app_id).visibility
if visibility == 'public':
return self.update(app_id, visibility='private')
else:
return self.update(app_id, visibility='public')
def download(self, app_id):
return self.client.artifacts.download_blob(app_id, 'archive')
def get_ui(self, app_id, loader_cls=None):
ui_stream = "".join(
self.client.artifacts.download_blob(app_id, 'ui_definition'))
if loader_cls is None:
loader_cls = yaml.Loader
return yaml.load(ui_stream, loader_cls)
def get_logo(self, app_id):
return self.client.artifacts.download_blob(app_id, 'logo')
class PackageManagerAdapter(object):
def __init__(self, legacy, glare):
self.legacy = legacy
self.glare = glare
def categories(self):
return self.legacy.categories()
@rewrap_http_exceptions
def create(self, data, files):
is_public = data.pop('is_public', None)
if is_public is not None:
data['visibility'] = 'public' if is_public else 'private'
fqn = list(files.keys())[0]
pkg = self.glare.create(fqn, files[fqn], **data)
return PackageWrapper(pkg)
@rewrap_http_exceptions
def filter(self, **kwargs):
kwargs.pop('catalog', None) # NOTE(ativelkov): Glare ignores 'catalog'
kwargs.pop('owned', None) # NOTE(ativelkov): Glare ignores 'owned'
include_disabled = kwargs.pop('include_disabled', False)
order_by = kwargs.pop('order_by', None)
search = kwargs.pop('search', None)
category = kwargs.pop('category', None)
fqn = kwargs.pop('fqn', None)
class_name = kwargs.pop('class_name', None)
if category:
kwargs['categories'] = category
if search:
kwargs['keywords'] = search
if order_by:
kwargs['sort_field'] = order_by
if not include_disabled:
kwargs['enabled'] = True
if fqn:
kwargs['name'] = fqn
if class_name:
kwargs['class_definitions'] = class_name
for pkg in self.glare.list(**kwargs):
yield PackageWrapper(pkg)
@rewrap_http_exceptions
def list(self, include_disabled=False):
return self.filter(include_disabled=include_disabled)
@rewrap_http_exceptions
def delete(self, app_id):
return self.glare.delete(app_id)
@rewrap_http_exceptions
def get(self, app_id):
return PackageWrapper(self.glare.get(app_id))
@rewrap_http_exceptions
def update(self, app_id, body, operation='replace'):
is_public = body.pop('is_public', None)
name = body.pop('name', None)
if is_public is not None:
body['visibility'] = 'public' if is_public else 'private'
if name is not None:
body['display_name'] = name
if operation == 'replace':
return PackageWrapper(self.glare.update(app_id, None, **body))
@rewrap_http_exceptions
def toggle_active(self, app_id):
return self.glare.toggle_active(app_id)
@rewrap_http_exceptions
def toggle_public(self, app_id):
return self.glare.toggle_public(app_id)
@rewrap_http_exceptions
def download(self, app_id):
return "".join(self.glare.download(app_id))
@rewrap_http_exceptions
def get_logo(self, app_id):
return "".join(self.glare.get_logo(app_id))
@rewrap_http_exceptions
def get_ui(self, app_id, loader_cls=None):
return self.glare.get_ui(app_id, loader_cls)
class PackageWrapper(object):
def __init__(self, item):
self._item = item
@property
def updated(self):
return self._item.updated_at
@property
def created(self):
return self._item.created_at
@property
def is_public(self):
return self._item.visibility == 'public'
@property
def name(self):
return self._item.type_specific_properties['display_name']
@property
def fully_qualified_name(self):
return self._item.name
@property
def owner_id(self):
return self._item.owner
def __getstate__(self):
return {"item": self._item}
def __setstate__(self, state):
self._item = state['item']
def __getattr__(self, name):
if name in self._item.type_specific_properties:
return self._item.type_specific_properties.get(name)
else:
return getattr(self._item, name)
def to_dict(self):
keys = ('author', 'categories', 'class_definitions', 'created',
'description', 'enabled', 'fully_qualified_name', 'id',
'is_public', 'name', 'owner_id', 'tags', 'type',
'updated')
missing_keys = [key for key in keys if not hasattr(self, key)]
if missing_keys:
raise KeyError(_("Some attributes are missing in "
"%(pkg_name)s: %(attrs)s.") %
{'pkg_name': self.name,
'attrs': ", ".join(missing_keys)})
return {key: getattr(self, key) for key in keys}
class NamespaceResolver(object):
"""Copied from main murano repo
original at murano/dsl/namespace_resolver.py
"""
def __init__(self, namespaces):
self._namespaces = namespaces
self._namespaces[''] = ''
def resolve_name(self, name, relative=None):
if name is None:
raise ValueError()
if name and name.startswith(':'):
return name[1:]
if ':' in name:
parts = name.split(':')
if len(parts) != 2 or not parts[1]:
raise NameError('Incorrectly formatted name ' + name)
if parts[0] not in self._namespaces:
raise KeyError('Unknown namespace prefix ' + parts[0])
return '.'.join((self._namespaces[parts[0]], parts[1]))
if not relative and '=' in self._namespaces and '.' not in name:
return '.'.join((self._namespaces['='], name))
if relative and '.' not in name:
return '.'.join((relative, name))
return name