Skip to content

Commit

Permalink
Merge pull request madduck#42 from salt-formulas/pr/37
Browse files Browse the repository at this point in the history
Pr/37
  • Loading branch information
epcim authored May 9, 2018
2 parents 7904774 + 67e737c commit 4493d8a
Show file tree
Hide file tree
Showing 3 changed files with 122 additions and 5 deletions.
76 changes: 76 additions & 0 deletions README-extentions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,82 @@ Instead of failing on the first undefinded reference error all missing reference
group_errors: True
Use references in class names
-----------------------------

Allows to use references in the class names.

References pointed to in class names cannot themselves reference another key, they should be simple strings.

To avoid pitfalls do not over-engineer your class references. They should be used only for core conditions and only for them.
A short example: `- system.wrodpress.db.${_class:database_backend}`.

Best practices:
- use references in class names always load your global class specification prior the reference is used.
- structure your class references under parameters under one key (for example `_class`).
- use class references as a kind of "context" or "global" available options you always know what they are set.

Class referencing for existing reclass users. Frequently when constructing your models you had to load or not load some
classes based on your setup. In most cases this lead to fork of a model or introducing kind of template generator (like cookiecutter) to
create a model based on the base "context" or "global" variables. Class referencing is a simple way how to avoid
"pre-processors" like this and if/else conditions around class section.


Assuming following class setup:

* node is loading `third.yml` class only


Classes:

.. code-block:: yaml
#/etc/reclass/classes/global.yml
parameters:
_class:
env:
override: 'env.dev'
lab:
name: default
#/etc/reclass/classes/lab/env/dev.yml
parameters:
lab:
name: dev
#/etc/reclass/classes/second.yml
classes:
- global
- lab.${_class:env:override}
#/etc/reclass/classes/third.yml
classes:
- global
- second
Reclass --nodeinfo then returns:

.. code-block:: yaml
...
...
applications: []
environment: base
exports: {}
classes:
- global
- lab.${_class:env:override}
- second
parameters:
_class:
env:
override: env.dev
lab:
name: dev
...
...
Inventory Queries
-----------------

Expand Down
27 changes: 22 additions & 5 deletions reclass/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
from reclass.settings import Settings
from reclass.output.yaml_outputter import ExplicitDumper
from reclass.datatypes import Entity, Classes, Parameters, Exports
from reclass.errors import MappingFormatError, ClassNotFound, InvQueryClassNotFound, InvQueryError, InterpolationError
from reclass.errors import MappingFormatError, ClassNameResolveError, ClassNotFound, InvQueryClassNameResolveError, InvQueryClassNotFound, InvQueryError, InterpolationError, ResolveError
from reclass.values.parser import Parser

try:
basestring
Expand All @@ -30,6 +31,8 @@

class Core(object):

_parser = Parser()

def __init__(self, storage, class_mappings, settings, input_data=None):
self._storage = storage
self._class_mappings = class_mappings
Expand Down Expand Up @@ -96,7 +99,7 @@ def _get_input_data_entity(self):
p = Parameters(self._input_data, self._settings)
return Entity(self._settings, parameters=p, name='input data')

def _recurse_entity(self, entity, merge_base=None, seen=None, nodename=None, environment=None):
def _recurse_entity(self, entity, merge_base=None, context=None, seen=None, nodename=None, environment=None):
if seen is None:
seen = {}

Expand All @@ -106,7 +109,19 @@ def _recurse_entity(self, entity, merge_base=None, seen=None, nodename=None, env
if merge_base is None:
merge_base = Entity(self._settings, name='empty (@{0})'.format(nodename))

if context is None:
context = Entity(self._settings, name='empty (@{0})'.format(nodename))

for klass in entity.classes.as_list():
if klass.count('$') > 0:
try:
klass = str(self._parser.parse(klass, self._settings).render(merge_base.parameters.as_dict(), {}))
except ResolveError as e:
try:
klass = str(self._parser.parse(klass, self._settings).render(context.parameters.as_dict(), {}))
except ResolveError as e:
raise ClassNameResolveError(klass, nodename, entity.uri)

if klass not in seen:
try:
class_entity = self._storage.get_class(klass, environment, self._settings)
Expand All @@ -121,7 +136,7 @@ def _recurse_entity(self, entity, merge_base=None, seen=None, nodename=None, env
e.uri = entity.uri
raise

descent = self._recurse_entity(class_entity, seen=seen,
descent = self._recurse_entity(class_entity, context=merge_base, seen=seen,
nodename=nodename, environment=environment)
# on every iteration, we merge the result of the recursive
# descent into what we have so far…
Expand Down Expand Up @@ -159,6 +174,8 @@ def _get_inventory(self, all_envs, environment, queries):
node = self._node_entity(nodename)
except ClassNotFound as e:
raise InvQueryClassNotFound(e)
except ClassNameResolveError as e:
raise InvQueryClassNameResolveError(e)
if queries is None:
try:
node.interpolate_exports()
Expand Down Expand Up @@ -186,8 +203,8 @@ def _node_entity(self, nodename):
seen = {}
merge_base = self._recurse_entity(base_entity, seen=seen, nodename=nodename,
environment=node_entity.environment)
return self._recurse_entity(node_entity, merge_base, seen=seen, nodename=nodename,
environment=node_entity.environment)
return self._recurse_entity(node_entity, merge_base=merge_base, context=merge_base, seen=seen,
nodename=nodename, environment=node_entity.environment)

def _nodeinfo(self, nodename, inventory):
try:
Expand Down
24 changes: 24 additions & 0 deletions reclass/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,17 @@ def _get_error_message(self):
return msg


class ClassNameResolveError(InterpolationError):
def __init__(self, classname, nodename, uri):
super(ClassNameResolveError, self).__init__(msg=None, uri=uri, nodename=nodename)
self.name = classname

def _get_error_message(self):
msg = [ 'In {0}'.format(self.uri),
'Class name {0} not resolvable'.format(self.name) ]
return msg


class InvQueryClassNotFound(InterpolationError):

def __init__(self, classNotFoundError, nodename=''):
Expand All @@ -172,6 +183,19 @@ def _get_error_message(self):
return msg


class InvQueryClassNameResolveError(InterpolationError):
def __init__(self, classNameResolveError, nodename=''):
super(InvQueryClassNameResolveError, self).__init__(msg=None, nodename=nodename)
self.classNameResolveError = classNameResolveError
self._traceback = self.classNameResolveError._traceback

def _get_error_message(self):
msg = [ 'Inventory Queries:',
'-> {0}'.format(self.classNameResolveError.nodename) ]
msg.append(self.classNameResolveError._get_error_message())
return msg


class ResolveError(InterpolationError):

def __init__(self, reference, uri=None, context=None):
Expand Down

0 comments on commit 4493d8a

Please sign in to comment.