Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions reclass/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def main():
class_mappings = defaults.get('class_mappings')
reclass = Core(storage, class_mappings)

sys.path.append(options.inventory_base_uri)

if options.mode == MODE_NODEINFO:
data = reclass.nodeinfo(options.nodename)

Expand Down
23 changes: 21 additions & 2 deletions reclass/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
#import sys
import fnmatch
import shlex
from reclass.datatypes import Entity, Classes, Parameters
import inspect
from reclass.datatypes import Entity, Classes, Parameters, Mutators
from reclass.errors import MappingFormatError, ClassNotFound

class Core(object):
Expand All @@ -22,6 +23,7 @@ def __init__(self, storage, class_mappings, input_data=None):
self._storage = storage
self._class_mappings = class_mappings
self._input_data = input_data
self._mutators = Mutators()

@staticmethod
def _get_timestamp():
Expand Down Expand Up @@ -120,6 +122,7 @@ def _nodeinfo(self, nodename):
ret = self._recurse_entity(node_entity, merge_base, seen=seen,
nodename=node_entity.name)
ret.interpolate()
self._mutators.push(ret.mutators)
return ret

def _nodeinfo_as_dict(self, nodename, entity):
Expand All @@ -132,8 +135,23 @@ def _nodeinfo_as_dict(self, nodename, entity):
ret.update(entity.as_dict())
return ret

def _mutate(self, **kwargs):
params = set(kwargs.keys())
mutators = self._mutators.as_deque()
while len(mutators):
mutator = mutators.popleft()
args, varargs, keywords, defaults = inspect.getargspec(mutator)
required = set(args)
# If the caller passed us all parameters that this mutator requires,
# call the mutator with the arguments it wants
if (required & params) == required:
args = { arg: value for arg, value in kwargs.items() if arg in required }
mutator(**args)

def nodeinfo(self, nodename):
return self._nodeinfo_as_dict(nodename, self._nodeinfo(nodename))
inventory = self.inventory()
self._mutate(inventory=inventory, nodename=nodename)
return inventory['nodes'][nodename]

def inventory(self):
entities = {}
Expand All @@ -155,6 +173,7 @@ def inventory(self):
classes[c].append(f)
else:
classes[c] = [f]
self._mutate(nodes=nodes, classes=classes, applications=applications)

return {'__reclass__' : {'timestamp': Core._get_timestamp()},
'nodes': nodes,
Expand Down
1 change: 1 addition & 0 deletions reclass/datatypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@
from classes import Classes
from entity import Entity
from parameters import Parameters
from mutators import Mutators
13 changes: 12 additions & 1 deletion reclass/datatypes/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from classes import Classes
from applications import Applications
from parameters import Parameters
from mutators import Mutators

class Entity(object):
'''
Expand All @@ -17,13 +18,15 @@ class Entity(object):
uri of the Entity that is being merged.
'''
def __init__(self, classes=None, applications=None, parameters=None,
uri=None, name=None, environment=None):
uri=None, name=None, environment=None, mutators=None):
if classes is None: classes = Classes()
self._set_classes(classes)
if applications is None: applications = Applications()
self._set_applications(applications)
if parameters is None: parameters = Parameters()
self._set_parameters(parameters)
if mutators is None: mutators = Mutators()
self._set_mutators(mutators)
self._uri = uri or ''
self._name = name or ''
self._environment = environment or ''
Expand All @@ -34,6 +37,7 @@ def __init__(self, classes=None, applications=None, parameters=None,
classes = property(lambda s: s._classes)
applications = property(lambda s: s._applications)
parameters = property(lambda s: s._parameters)
mutators = property(lambda s: s._mutators)

def _set_classes(self, classes):
if not isinstance(classes, Classes):
Expand All @@ -53,10 +57,17 @@ def _set_parameters(self, parameters):
'instance of type %s' % type(parameters))
self._parameters = parameters

def _set_mutators(self, mutators):
if not isinstance(mutators, Mutators):
raise TypeError('Entity.mutators cannot be set to '\
'instance of type %s' % type(mutators))
self._mutators = mutators

def merge(self, other):
self._classes.merge_unique(other._classes)
self._applications.merge_unique(other._applications)
self._parameters.merge(other._parameters)
self._mutators.push(other._mutators)
self._name = other.name
self._uri = other.uri
self._environment = other.environment
Expand Down
62 changes: 62 additions & 0 deletions reclass/datatypes/mutators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#
# -*- coding: utf-8 -*-
#
# This file is part of reclass (http://github.com/madduck/reclass)
#
# Copyright © 2007–14 martin f. krafft <madduck@madduck.net>
# Released under the terms of the Artistic Licence 2.0
#

from collections import deque

class Mutators(object):
'''
A class to maintain a push-only stack of python callables, with the following specialty:

+ If a callable is being pushed onto the stack which is already there,
it is removed from its previous position and placed back on top

Class also provides a static method for building lists of callables from iterables
of module paths
'''
@staticmethod
def load_callables(iterable):
callables = []
for path in iterable:
modname, callname = path.rsplit('.', 1)
module = __import__(modname, globals(), locals(), [callname], -1)
call = getattr(module, callname)
callables.append(call)
return callables
def __init__(self, iterable=None):
self._stack = deque()
if iterable is not None:
self.push(iterable)
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__,
self.as_list())
def __len__(self):
return len(self._stack)
def __eq__(self, rhs):
if isinstance(rhs, deque):
return self._stack == rhs
if isinstance(rhs, list):
return list(self._stack) == rhs
else:
try:
return self._stack == rhs._stack
except AttributeError:
return False
def __ne__(self, rhs):
return not self.__eq__(rhs)
def push(self, iterable):
if isinstance(iterable, self.__class__):
iterable = iterable.as_list()
for item in iterable:
if item in self._stack:
self._stack.remove(item)
self._stack.appendleft(item)
def as_list(self):
return list(self._stack)
def as_deque(self):
return deque(self._stack)
8 changes: 7 additions & 1 deletion reclass/storage/yaml_fs/yamlfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,20 @@ def get_entity(self, name=None, default_environment=None):
parameters = {}
parameters = datatypes.Parameters(parameters)

mutators = self._data.get('mutators')
if mutators is None:
mutators = []
mutators = datatypes.Mutators(datatypes.Mutators.load_callables(mutators))

env = self._data.get('environment', default_environment)

if name is None:
name = self._path

return datatypes.Entity(classes, applications, parameters,
name=name, environment=env,
uri='yaml_fs://{0}'.format(self._path))
uri='yaml_fs://{0}'.format(self._path),
mutators=mutators)

def __repr__(self):
return '<{0} {1}, {2}>'.format(self.__class__.__name__, self._path,
Expand Down