bug fixes and add a cmd.call to the cmd state.

The cmd.call state integrates with pydsl such that it's possible
to declare a state that would call a python function when the state
is applied.
This commit is contained in:
Jack Kuan 2013-01-13 12:10:00 -05:00
parent 3d436065b0
commit d5c2826e8e
3 changed files with 100 additions and 37 deletions

View File

@ -26,7 +26,7 @@ class Sls(object):
def extend(self, *states):
for s in states:
self.extends.append(self.all_decls.pop(state.id))
self.extends.append(self.all_decls.pop(s._id))
def state(self, id=None):
if not id:
@ -38,37 +38,44 @@ class Sls(object):
self.decls.append(s)
return s
def to_highstate(self):
def to_highstate(self, slsmod):
# generate a state that uses the stateconf.set state, which
# is a no-op state, to hold a reference to the sls module
# containing the DSL statements. This is to prevent the module
# from being GC'ed, so that objects defined in it will be
# available while salt is executing the states.
self.state().stateconf.set(slsmod=slsmod)
highstate = Dict()
highstate['include'] = self.includes[:]
highstate['extend'] = extend = Dict()
for ext in self.extends:
extend[ext.id] = ext
extend[ext._id] = ext
for decl in self.decls:
highstate[decl.id] = decl._repr()
highstate[decl._id] = decl._repr()
return highstate
class StateDeclaration(object):
def __init__(self, id=None):
self.id = id
self.mods = []
self._id = id
self._mods = []
def __getattr__(self, name):
for m in self.mods:
if m.name == name:
for m in self._mods:
if m._name == name:
return m
else:
m = StateModule(name, self.id)
self.mods.append(m)
m = StateModule(name, self._id)
self._mods.append(m)
return m
def __str__(self):
return self.id
return self._id
def __iter__(self):
return iter(self.mods)
return iter(self._mods)
def _repr(self):
return dict(m._repr() for m in self)
@ -77,33 +84,33 @@ class StateDeclaration(object):
class StateModule(object):
def __init__(self, name, parent_decl):
self.state_id = parent_decl
self.name = name
self.func = None
self._state_id = parent_decl
self._name = name
self._func = None
def __getattr__(self, name):
if self.func:
if name == self.func.name:
return self.func
if self._func:
if name == self._func.name:
return self._func
else:
return getattr(self.func, name)
self.func = f = StateFunction(name)
return getattr(self._func, name)
self._func = f = StateFunction(name)
return f
def __call__(self, name, *args, **kws):
return getattr(self, name).configure(args, kws)
def __str__(self):
return self.name
return self._name
def _repr(self):
return (self.name, [self.func.name]+self.func.args)
return (self._name, [self._func.name]+self._func.args)
def _generate_requsite_method(t):
def req(self, ref, mod=None):
self.reference(t, ref, mod)
def req(self, mod, ref=None):
self.reference(t, mod, ref)
return self
return req
@ -120,20 +127,26 @@ class StateFunction(object):
def configure(self, args, kws):
args = list(args)
if args:
first = args[0]
if self.name == 'call' and callable(first):
args[0] = first.__name__
kws = dict(func=first, args=args[1:], kws=kws)
del args[1:]
args[0] = dict(name=args[0])
for k, v in kws.iteritems():
args.append({k: v})
self.args.extend(args)
return self
def reference(self, req_type, ref, mod):
if isinstance(ref, StateModule):
mod = ref.name
ref = ref.state_id
elif ref and mod:
ref = str(ref)
mod = str(mod)
self.args.append({req_type: [{mod: ref}]})
def reference(self, req_type, mod, ref):
if isinstance(mod, StateModule):
ref = mod._state_id
elif not (mod and ref):
raise ValueError("Invalid argument(s) to a requisite expression!")
self.args.append({req_type: [{str(mod): str(ref)}]})
return self
ns = locals()

View File

@ -3,6 +3,15 @@ import imp
def render(template, env='', sls='', tmplpath=None, **kws):
mod = imp.new_module(sls)
# Note: mod object is transient. It's existence only lasts as long as
# the lowstate data structure that the highstate in the sls file
# is compiled to.
mod.__name__ = sls
# to workaround state.py's use of copy.deepcopy(chunck)
mod.__deepcopy__ = lambda x: mod
dsl_sls = __salt__['pydsl.sls'](sls)
mod.__dict__.update(
include=dsl_sls.include,
@ -12,10 +21,10 @@ def render(template, env='', sls='', tmplpath=None, **kws):
__grains__=__grains__,
__opts__=__opts__,
__pillar__=__pillar__,
env=env,
sls=sls,
tmplpath=tmplpath,
__env__=env,
__sls__=sls,
__file__=tmplpath,
**kws)
exec template.read() in mod.__dict__
return dsl_sls.to_highstate()
return dsl_sls.to_highstate(mod)

View File

@ -120,10 +120,10 @@ import copy
import json
import shlex
import logging
import sys
# Import salt libs
from salt.exceptions import CommandExecutionError
import salt.state
log = logging.getLogger(__name__)
@ -566,6 +566,47 @@ def script(name,
os.setegid(pgid)
def call(name, func, args=(), kws=None,
onlyif=None,
unless=None,
stateful=False,
**kwargs):
'''
'''
ret = {'name': name,
'changes': {},
'result': False,
'comment': ''}
cmd_kwargs = {'cwd': kwargs.get('cwd'),
'runas': kwargs.get('user'),
'shell': kwargs.get('shell') or __grains__['shell'],
'env': kwargs.get('env')}
pgid = os.getegid()
try:
cret = _run_check(cmd_kwargs, onlyif, unless, None, None, None, None)
if isinstance(cret, dict):
ret.update(cret)
return ret
finally:
os.setegid(pgid)
if not kws:
kws = {}
result = func(*args, **kws)
if isinstance(result, dict):
return result
elif isinstance(result, basestring) and stateful:
return _reinterpreted_state(result)
else:
# result must be json serializable else we get an error
ret['changes'] = {'retval': result}
ret['result'] = True if result is None else bool(result)
if isinstance(result, basestring):
ret['comment'] = result
return ret
def mod_watch(name, **kwargs):
'''
Execute a cmd function based on a watch call