From 98adda6a46fd5d2032077758de1d72cefe4ef55c Mon Sep 17 00:00:00 2001 From: Alexander Tivelkov Date: Mon, 1 Aug 2016 14:04:35 +0300 Subject: [PATCH] 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 --- muranoclient/common/utils.py | 55 ++++++++++++++++++- muranoclient/v1/artifact_packages.py | 38 ++----------- ...-class-yamls-support-914b3d155324214f.yaml | 4 ++ 3 files changed, 61 insertions(+), 36 deletions(-) create mode 100644 releasenotes/notes/multi-class-yamls-support-914b3d155324214f.yaml diff --git a/muranoclient/common/utils.py b/muranoclient/common/utils.py index 58590199..7972d8db 100644 --- a/muranoclient/common/utils.py +++ b/muranoclient/common/utils.py @@ -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 diff --git a/muranoclient/v1/artifact_packages.py b/muranoclient/v1/artifact_packages.py index 26e69172..acd231b5 100644 --- a/muranoclient/v1/artifact_packages.py +++ b/muranoclient/v1/artifact_packages.py @@ -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 diff --git a/releasenotes/notes/multi-class-yamls-support-914b3d155324214f.yaml b/releasenotes/notes/multi-class-yamls-support-914b3d155324214f.yaml new file mode 100644 index 00000000..1d26c2fb --- /dev/null +++ b/releasenotes/notes/multi-class-yamls-support-914b3d155324214f.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - Fixed a bug when a package containing multi-class yamls could not be added + to glare-based catalog.