Support for multi-class yamls in client

MuranoClient needs to analyse package classes (names and inheritance
chains) to properly add packages to Glare. This requires it to
properly resolve class names in the appropriate namespaces.

Since Mitaka Murano supports multi-class yamls, i.e. yaml files
containing multiple MuranoPL classes in a single yaml file using it
multi-document format. Optionally this allows to reuse a single
namespace definition for multiple classes defined in the same file.

Client was not aware of this feature, always loading classes as
single-document yamls. It was also always using the current-class
namespace definitions, thus likely to fail if the shared namespace
definition is in use.

This has been addressed.

Closes-bug: #1608440
Change-Id: Ibd08454f71e0266c76a2ac517b441df97311d8b9
This commit is contained in:
Alexander Tivelkov 2016-08-01 14:04:35 +03:00
parent aa02b816d3
commit 98adda6a46
3 changed files with 61 additions and 36 deletions

View File

@ -357,18 +357,38 @@ class Package(FileWrapperMixin):
except Exception:
return []
@property
def resolvers(self):
if not hasattr(self, '_resolvers'):
self.classes
return self._resolvers
@property
def classes(self):
if not hasattr(self, '_classes'):
self._classes = {}
self._resolvers = {}
for class_name, class_file in six.iteritems(
self.manifest.get('Classes', {})):
filename = "Classes/%s" % class_file
if filename not in self.contents.namelist():
continue
klass = yaml.load(self.contents.open(filename),
DummyYaqlYamlLoader)
self._classes[class_name] = klass
klass_list = yaml.load_all(self.contents.open(filename),
DummyYaqlYamlLoader)
if not klass_list:
raise ValueError('No classes defined in file')
resolver = None
for klass in klass_list:
ns = klass.get('Namespaces')
if ns:
resolver = NamespaceResolver(ns)
name = klass.get('Name')
if name and resolver:
name = resolver.resolve_name(name)
if name == class_name:
self._classes[class_name] = klass
self._resolvers[class_name] = resolver
break
return self._classes
@property
@ -749,3 +769,32 @@ def traverse_and_replace(obj,
_maybe_replace(obj, key, value)
else:
_maybe_replace(obj, key, value)
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

View File

@ -54,7 +54,8 @@ class ArtifactRepo(object):
for k, v in six.iteritems(kwargs):
package_draft[k] = v
inherits = self._get_local_inheritance(package.classes)
inherits = self._get_local_inheritance(package.classes,
package.resolvers)
# check for global inheritance
ancestor_queue = collections.deque(inherits.keys())
@ -109,16 +110,16 @@ class ArtifactRepo(object):
return self.client.artifacts.get(app_id)
@staticmethod
def _get_local_inheritance(classes):
def _get_local_inheritance(classes, resolvers):
result = {}
for class_name, klass in six.iteritems(classes):
if 'Extends' not in klass:
continue
ns = klass.get('Namespaces')
if ns:
resolver = NamespaceResolver(ns)
resolver = utils.NamespaceResolver(ns)
else:
resolver = None
resolver = resolvers.get(class_name)
if isinstance(klass['Extends'], list):
bases = klass['Extends']
@ -356,32 +357,3 @@ class PackageWrapper(object):
{'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

View File

@ -0,0 +1,4 @@
---
fixes:
- Fixed a bug when a package containing multi-class yamls could not be added
to glare-based catalog.