mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge pull request #3546 from kjkuan/pydsl_include
Add support for #3483
This commit is contained in:
commit
f4005c0e0f
8 changed files with 368 additions and 144 deletions
|
@ -71,7 +71,6 @@ Example of a ``cmd`` state calling a python function::
|
|||
#TODOs:
|
||||
#
|
||||
# - support exclude declarations
|
||||
# - support include declarations with env
|
||||
#
|
||||
# - allow this:
|
||||
# state('X').cmd.run.cwd = '/'
|
||||
|
@ -92,8 +91,9 @@ try:
|
|||
except ImportError:
|
||||
Dict = dict
|
||||
|
||||
from salt.state import HighState
|
||||
|
||||
REQUISITES = set("require watch use require_in watch_in use_in".split())
|
||||
REQUISITES = set('require watch use require_in watch_in use_in'.split())
|
||||
class PyDslError(Exception):
|
||||
pass
|
||||
|
||||
|
@ -102,31 +102,77 @@ class Options(dict):
|
|||
return self.get(name)
|
||||
|
||||
|
||||
def sls(sls):
|
||||
return Sls(sls)
|
||||
SLS_MATCHES = None
|
||||
|
||||
|
||||
def sls(*args):
|
||||
return Sls(*args)
|
||||
|
||||
class Sls(object):
|
||||
|
||||
# tracks all state declarations globally across sls files
|
||||
all_decls = weakref.WeakValueDictionary()
|
||||
|
||||
def __init__(self, sls):
|
||||
# a stack of current rendering Sls objects, maintained and used by the pydsl renderer.
|
||||
render_stack = []
|
||||
|
||||
def __init__(self, sls, mod, rendered_sls):
|
||||
self.name = sls
|
||||
self.includes = []
|
||||
self.included_highstate = {}
|
||||
self.extends = []
|
||||
self.decls = []
|
||||
self.options = Options()
|
||||
self.funcs = [] # track the ordering of state func declarations
|
||||
self.slsmod = mod # the current rendering sls python module
|
||||
self.rendered_sls = rendered_sls # a set of names of rendered sls modules
|
||||
|
||||
def set(self, **options):
|
||||
self.options.update(options)
|
||||
|
||||
def include(self, *sls_names):
|
||||
self.includes.extend(sls_names)
|
||||
def include(self, *sls_names, **kws):
|
||||
env = kws.get('env', self.slsmod.__env__)
|
||||
|
||||
if kws.get('delayed', False):
|
||||
for incl in sls_names:
|
||||
self.includes.append((env, incl))
|
||||
return
|
||||
|
||||
HIGHSTATE = HighState.current
|
||||
if not HIGHSTATE:
|
||||
raise PyDslError('include() only works when running state.highstate!')
|
||||
|
||||
global SLS_MATCHES
|
||||
if SLS_MATCHES is None:
|
||||
SLS_MATCHES = HIGHSTATE.top_matches(HIGHSTATE.get_top())
|
||||
|
||||
highstate = self.included_highstate
|
||||
slsmods = [] # a list of pydsl sls modules rendered.
|
||||
for sls in sls_names:
|
||||
if sls not in self.rendered_sls:
|
||||
self.rendered_sls.add(sls) # needed in case the starting sls uses the pydsl renderer.
|
||||
histates, errors = HIGHSTATE.render_state(sls, env, self.rendered_sls, SLS_MATCHES)
|
||||
HIGHSTATE.merge_included_states(highstate, histates, errors)
|
||||
if errors:
|
||||
raise PyDslError('\n'.join(errors))
|
||||
HIGHSTATE.clean_duplicate_extends(highstate)
|
||||
|
||||
id = '_slsmod_'+sls
|
||||
if id not in highstate:
|
||||
slsmods.append(None)
|
||||
else:
|
||||
for arg in highstate[id]['stateconf']:
|
||||
if isinstance(arg, dict) and iter(arg).next() == 'slsmod':
|
||||
slsmods.append(arg['slsmod'])
|
||||
break
|
||||
|
||||
if not slsmods:
|
||||
return None
|
||||
return slsmods[0] if len(slsmods) == 1 else slsmods
|
||||
|
||||
def extend(self, *state_funcs):
|
||||
if self.options.ordered and self.last_func():
|
||||
raise PyDslError("Can't extend while the ordered option is turned on!")
|
||||
raise PyDslError('Cannot extend() while the ordered option is turned on!')
|
||||
for f in state_funcs:
|
||||
id = f.mod._state_id
|
||||
self.extends.append(self.all_decls[id])
|
||||
|
@ -158,26 +204,33 @@ class Sls(object):
|
|||
def track_func(self, statefunc):
|
||||
self.funcs.append(statefunc)
|
||||
|
||||
def to_highstate(self, slsmod=None):
|
||||
def to_highstate(self):
|
||||
# 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.
|
||||
if slsmod:
|
||||
self.state().stateconf.set(slsmod=slsmod)
|
||||
self.state('_slsmod_'+self.name).stateconf.set(slsmod=self.slsmod)
|
||||
|
||||
highstate = Dict()
|
||||
if self.includes:
|
||||
highstate['include'] = self.includes[:]
|
||||
highstate['include'] = [{t[0]: t[1]} for t in self.includes]
|
||||
if self.extends:
|
||||
highstate['extend'] = extend = Dict()
|
||||
for ext in self.extends:
|
||||
extend[ext._id] = ext._repr(context='extend')
|
||||
for ext in self.extends:
|
||||
extend[ext._id] = ext._repr(context='extend')
|
||||
for decl in self.decls:
|
||||
highstate[decl._id] = decl._repr()
|
||||
|
||||
|
||||
if self.included_highstate:
|
||||
errors = []
|
||||
HighState.current.merge_included_states(highstate, self.included_highstate, errors)
|
||||
if errors:
|
||||
raise PyDslError('\n'.join(errors))
|
||||
return highstate
|
||||
|
||||
|
||||
def load_highstate(self, highstate):
|
||||
for sid, decl in highstate.iteritems():
|
||||
s = self.state(sid)
|
||||
|
@ -292,8 +345,8 @@ class StateFunction(object):
|
|||
|
||||
def _repr(self, context=None):
|
||||
if not self.name and context != 'extend':
|
||||
raise PyDslError("No state function specified for module: "
|
||||
"{0}".format(self.mod._name))
|
||||
raise PyDslError('No state function specified for module: '
|
||||
'{0}'.format(self.mod._name))
|
||||
if not self.name and context == 'extend':
|
||||
return self.args
|
||||
return [self.name]+self.args
|
||||
|
@ -323,7 +376,7 @@ class StateFunction(object):
|
|||
ref = mod._state_id
|
||||
elif not (mod and ref):
|
||||
raise PyDslError(
|
||||
"Invalid a requisite reference declaration! {0}: {1}".format(
|
||||
'Invalid a requisite reference declaration! {0}: {1}'.format(
|
||||
mod, ref))
|
||||
self.args.append({req_type: [{str(mod): str(ref)}]})
|
||||
|
||||
|
@ -333,3 +386,4 @@ class StateFunction(object):
|
|||
del ns
|
||||
del req_type
|
||||
|
||||
|
||||
|
|
|
@ -133,6 +133,7 @@ def highstate(test=None, **kwargs):
|
|||
opts['test'] = test
|
||||
|
||||
st_ = salt.state.HighState(opts)
|
||||
salt.state.HighState.current = st_
|
||||
ret = st_.call_highstate(exclude=kwargs.get('exclude', []))
|
||||
serial = salt.payload.Serial(__opts__)
|
||||
cache_file = os.path.join(__opts__['cachedir'], 'highstate.p')
|
||||
|
|
|
@ -18,18 +18,15 @@ from salt.utils.dictupdate import update
|
|||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
PILLAR = None
|
||||
def get_pillar(opts, grains, id_, env=None):
|
||||
'''
|
||||
Return the correct pillar driver based on the file_client option
|
||||
'''
|
||||
try:
|
||||
return {
|
||||
'remote': RemotePillar,
|
||||
'local': Pillar
|
||||
}.get(opts['file_client'], 'local')(opts, grains, id_, env)
|
||||
except KeyError:
|
||||
return Pillar(opts, grains, id_, env)
|
||||
return {
|
||||
'remote': RemotePillar,
|
||||
'local': Pillar
|
||||
}.get(opts['file_client'], Pillar)(opts, grains, id_, env)
|
||||
|
||||
|
||||
class RemotePillar(object):
|
||||
|
|
|
@ -146,6 +146,41 @@ whose arguments are just :term:`function declaration` objects.
|
|||
include('edit.vim', 'http.server')
|
||||
extend(state('apache2').service.watch(file='/etc/httpd/httpd.conf')
|
||||
|
||||
The ``include`` function, by default, causes the included sls file to be rendered
|
||||
as soon as the ``include`` function is called. It returns a list of rendered module
|
||||
objects; sls files not rendered with the pydsl renderer return ``None``'s.
|
||||
This behavior creates no :term:`include declaration`'s in the resulting high state
|
||||
data structure.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
import types
|
||||
|
||||
# including multiple sls returns a list.
|
||||
_, mod = include('a-non-pydsl-sls', 'a-pydsl-sls')
|
||||
|
||||
assert _ is None
|
||||
assert isinstance(slsmods[1], types.ModuleType)
|
||||
|
||||
# including a single sls returns a single object
|
||||
mod = include('a-pydsl-sls')
|
||||
|
||||
# myfunc is a function that calls state(...) to create more states.
|
||||
mod.myfunc(1, 2, "three")
|
||||
|
||||
Notice how you can define a reusable function in your pydsl sls module and then
|
||||
call it via the module returned by ``include``.
|
||||
|
||||
It's still possible to do late includes by passing the ``delayed=True`` keyword
|
||||
argument to ``include``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
include('edit.vim', 'http.server', delayed=True)
|
||||
|
||||
Above will just create a :term:`include declaration` in the rendered result, and
|
||||
such call always returns ``None``.
|
||||
|
||||
|
||||
Special integration with the `cmd` state
|
||||
-----------------------------------------
|
||||
|
@ -246,8 +281,9 @@ or after a state in the including sls file.
|
|||
|
||||
import imp
|
||||
|
||||
__all__ = ['render']
|
||||
|
||||
def render(template, env='', sls='', tmplpath=None, **kws):
|
||||
def render(template, env='', sls='', tmplpath=None, rendered_sls=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
|
||||
|
@ -255,15 +291,15 @@ def render(template, env='', sls='', tmplpath=None, **kws):
|
|||
|
||||
mod.__name__ = sls
|
||||
|
||||
# to workaround state.py's use of copy.deepcopy(chunck)
|
||||
# to workaround state.py's use of copy.deepcopy(chunk)
|
||||
mod.__deepcopy__ = lambda x: mod
|
||||
|
||||
dsl_sls = __salt__['pydsl.sls'](sls)
|
||||
dsl_sls = __salt__['pydsl.sls'](sls, mod, rendered_sls)
|
||||
mod.__dict__.update(
|
||||
__pydsl__=dsl_sls,
|
||||
include=dsl_sls.include,
|
||||
extend=dsl_sls.extend,
|
||||
state=dsl_sls.state,
|
||||
include=_wrap_sls(dsl_sls.include),
|
||||
extend=_wrap_sls(dsl_sls.extend),
|
||||
state=_wrap_sls(dsl_sls.state),
|
||||
__salt__=__salt__,
|
||||
__grains__=__grains__,
|
||||
__opts__=__opts__,
|
||||
|
@ -272,6 +308,17 @@ def render(template, env='', sls='', tmplpath=None, **kws):
|
|||
__sls__=sls,
|
||||
__file__=tmplpath,
|
||||
**kws)
|
||||
dsl_sls.render_stack.append(dsl_sls)
|
||||
exec template.read() in mod.__dict__
|
||||
return dsl_sls.to_highstate(mod)
|
||||
highstate = dsl_sls.to_highstate()
|
||||
dsl_sls.render_stack.pop()
|
||||
return highstate
|
||||
|
||||
|
||||
def _wrap_sls(method):
|
||||
def _sls_method(*args, **kws):
|
||||
sls = method.im_class.render_stack[-1]
|
||||
return getattr(sls, method.__name__)(*args, **kws)
|
||||
return _sls_method
|
||||
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ def render(input, env='', sls='', argline='', **kws):
|
|||
data = copy.deepcopy(high)
|
||||
try:
|
||||
rewrite_single_shorthand_state_decl(data)
|
||||
rewrite_sls_includes_excludes(data, sls)
|
||||
rewrite_sls_includes_excludes(data, sls, env)
|
||||
|
||||
if not extract and implicit_require:
|
||||
sid = has_names_decls(data)
|
||||
|
@ -137,7 +137,7 @@ def render(input, env='', sls='', argline='', **kws):
|
|||
add_implicit_requires(data)
|
||||
|
||||
if gen_start_state:
|
||||
add_start_state(data)
|
||||
add_start_state(data, sls)
|
||||
|
||||
if not extract and not no_goal_state:
|
||||
add_goal_state(data)
|
||||
|
@ -265,7 +265,7 @@ def _parent_sls(sls):
|
|||
return sls[:i] + '.' if i != -1 else ''
|
||||
|
||||
|
||||
def rewrite_sls_includes_excludes(data, sls):
|
||||
def rewrite_sls_includes_excludes(data, sls, env):
|
||||
# if the path of the included/excluded sls starts with a leading dot(.)
|
||||
# then it's taken to be relative to the including/excluding sls.
|
||||
sls = _parent_sls(sls)
|
||||
|
@ -273,8 +273,13 @@ def rewrite_sls_includes_excludes(data, sls):
|
|||
if sid == 'include':
|
||||
includes = data[sid]
|
||||
for i, each in enumerate(includes):
|
||||
if each.startswith('.'):
|
||||
includes[i] = sls + each[1:]
|
||||
if isinstance(each, dict):
|
||||
slsenv, incl = each.popitem()
|
||||
else:
|
||||
slsenv = env
|
||||
incl = each
|
||||
if incl.startswith('.'):
|
||||
includes[i] = {slsenv: sls+incl[1:]}
|
||||
elif sid == 'exclude':
|
||||
for sdata in data[sid]:
|
||||
if 'sls' in sdata and sdata['sls'].startswith('.'):
|
||||
|
@ -331,9 +336,13 @@ def nvlist2(thelist, names=None):
|
|||
|
||||
def statelist(states_dict, sid_excludes=set(['include', 'exclude'])):
|
||||
for sid, states in states_dict.iteritems():
|
||||
if sid.startswith('__'):
|
||||
continue
|
||||
if sid in sid_excludes:
|
||||
continue
|
||||
for sname, args in states.iteritems():
|
||||
if sname.startswith('__'):
|
||||
continue
|
||||
yield sid, states, sname, args
|
||||
|
||||
|
||||
|
@ -448,7 +457,7 @@ def add_implicit_requires(data):
|
|||
prev_state = (state_name(sname), sid)
|
||||
|
||||
|
||||
def add_start_state(data):
|
||||
def add_start_state(data, sls):
|
||||
start_sid = __opts__['stateconf_start_state']
|
||||
if start_sid in data:
|
||||
raise SaltRenderError(
|
||||
|
@ -457,8 +466,19 @@ def add_start_state(data):
|
|||
)
|
||||
if not data:
|
||||
return
|
||||
first = statelist(data, set(['include', 'exclude', 'extend'])).next()[0]
|
||||
reqin = {state_name(data[first].iterkeys().next()): first}
|
||||
|
||||
# the start state is either the first state whose id declaration has
|
||||
# no __sls__, or it's the first state whose id declaration has a
|
||||
# __sls__ == sls.
|
||||
non_sids = set(['include', 'exclude', 'extend'])
|
||||
for sid, states in data.iteritems():
|
||||
if sid in non_sids or sid.startswith('__'):
|
||||
continue
|
||||
if '__sls__' not in states or states['__sls__'] == sls:
|
||||
break
|
||||
else:
|
||||
raise SaltRenderError('Can\'t determine the first state in the sls file!')
|
||||
reqin = {state_name(data[sid].iterkeys().next()): sid}
|
||||
data[start_sid] = { STATE_FUNC: [ {'require_in': [reqin]} ] }
|
||||
|
||||
|
||||
|
@ -471,8 +491,13 @@ def add_goal_state(data):
|
|||
)
|
||||
else:
|
||||
reqlist = []
|
||||
for sid, _, state, _ in \
|
||||
for sid, states, state, _ in \
|
||||
statelist(data, set(['include', 'exclude', 'extend'])):
|
||||
if '__sls__' in states:
|
||||
# Then id declaration must have been included from a
|
||||
# rendered sls. Currently, this is only possible with
|
||||
# pydsl's high state output.
|
||||
continue
|
||||
reqlist.append({state_name(state): sid})
|
||||
data[goal_sid] = {STATE_FUNC: [dict(require=reqlist)]}
|
||||
|
||||
|
|
177
salt/state.py
177
salt/state.py
|
@ -1763,12 +1763,12 @@ class BaseHighState(object):
|
|||
state = None
|
||||
try:
|
||||
state = compile_template(
|
||||
fn_, self.state.rend, self.state.opts['renderer'], env, sls)
|
||||
fn_, self.state.rend, self.state.opts['renderer'], env, sls, rendered_sls=mods)
|
||||
except Exception as exc:
|
||||
errors.append(('Rendering SLS {0} failed, render error:\n{1}'
|
||||
.format(sls, exc)))
|
||||
import traceback
|
||||
errors.append(('Rendering SLS {0} failed, render error:\n{1}\n{2}'
|
||||
.format(sls, traceback.format_exc(), exc)))
|
||||
mods.add(sls)
|
||||
nstate = None
|
||||
if state:
|
||||
if not isinstance(state, dict):
|
||||
errors.append(('SLS {0} does not render to a dictionary'
|
||||
|
@ -1809,16 +1809,17 @@ class BaseHighState(object):
|
|||
# the include must exist in the current environment
|
||||
if len(resolved_envs) == 1 or env in resolved_envs:
|
||||
if inc_sls not in mods:
|
||||
nstate, mods, err = self.render_state(
|
||||
nstate, err = self.render_state(
|
||||
inc_sls,
|
||||
resolved_envs[0] if len(resolved_envs) == 1 else env,
|
||||
mods,
|
||||
matches
|
||||
)
|
||||
if nstate:
|
||||
state.update(nstate)
|
||||
if err:
|
||||
errors += err
|
||||
if nstate:
|
||||
self.merge_included_states(state, nstate, errors)
|
||||
state.update(nstate)
|
||||
if err:
|
||||
errors.extend(err)
|
||||
else:
|
||||
msg = ''
|
||||
if not resolved_envs:
|
||||
|
@ -1836,41 +1837,8 @@ class BaseHighState(object):
|
|||
log.error(msg)
|
||||
if self.opts['failhard']:
|
||||
errors.append(msg)
|
||||
if 'extend' in state:
|
||||
ext = state.pop('extend')
|
||||
for name in ext:
|
||||
if not isinstance(ext[name], dict):
|
||||
errors.append(('Extension name {0} in sls {1} is '
|
||||
'not a dictionary'
|
||||
.format(name, sls)))
|
||||
continue
|
||||
if '__sls__' not in ext[name]:
|
||||
ext[name]['__sls__'] = sls
|
||||
if '__env__' not in ext[name]:
|
||||
ext[name]['__env__'] = env
|
||||
for key in ext[name]:
|
||||
if key.startswith('_'):
|
||||
continue
|
||||
if not isinstance(ext[name][key], list):
|
||||
continue
|
||||
if '.' in key:
|
||||
comps = key.split('.')
|
||||
ext[name][comps[0]] = ext[name].pop(key)
|
||||
ext[name][comps[0]].append(comps[1])
|
||||
if '__extend__' not in state:
|
||||
state['__extend__'] = [ext]
|
||||
else:
|
||||
state['__extend__'].append(ext)
|
||||
if 'exclude' in state:
|
||||
exc = state.pop('exclude')
|
||||
if not isinstance(exc, list):
|
||||
err = ('Exclude Declaration in SLS {0} is not formed '
|
||||
'as a list'.format(sls))
|
||||
errors.append(err)
|
||||
if '__exclude__' not in state:
|
||||
state['__exclude__'] = exc
|
||||
else:
|
||||
state['__exclude__'].extend(exc)
|
||||
self._handle_extend(state, sls, env, errors)
|
||||
self._handle_exclude(state, sls, env, errors)
|
||||
for name in state:
|
||||
if not isinstance(state[name], dict):
|
||||
if name == '__extend__':
|
||||
|
@ -1925,7 +1893,40 @@ class BaseHighState(object):
|
|||
state[name]['__env__'] = env
|
||||
else:
|
||||
state = {}
|
||||
return state, mods, errors
|
||||
return state, errors
|
||||
|
||||
def _handle_extend(self, state, sls, env, errors):
|
||||
if 'extend' in state:
|
||||
ext = state.pop('extend')
|
||||
for name in ext:
|
||||
if not isinstance(ext[name], dict):
|
||||
errors.append(('Extension name {0} in sls {1} is '
|
||||
'not a dictionary'
|
||||
.format(name, sls)))
|
||||
continue
|
||||
if '__sls__' not in ext[name]:
|
||||
ext[name]['__sls__'] = sls
|
||||
if '__env__' not in ext[name]:
|
||||
ext[name]['__env__'] = env
|
||||
for key in ext[name]:
|
||||
if key.startswith('_'):
|
||||
continue
|
||||
if not isinstance(ext[name][key], list):
|
||||
continue
|
||||
if '.' in key:
|
||||
comps = key.split('.')
|
||||
ext[name][comps[0]] = ext[name].pop(key)
|
||||
ext[name][comps[0]].append(comps[1])
|
||||
state.setdefault('__extend__', []).append(ext)
|
||||
|
||||
def _handle_exclude(self, state, sls, env, errors):
|
||||
if 'exclude' in state:
|
||||
exc = state.pop('exclude')
|
||||
if not isinstance(exc, list):
|
||||
err = ('Exclude Declaration in SLS {0} is not formed '
|
||||
'as a list'.format(sls))
|
||||
errors.append(err)
|
||||
state.setdefault('__exclude__', []).extend(exc)
|
||||
|
||||
def render_highstate(self, matches):
|
||||
'''
|
||||
|
@ -1933,55 +1934,56 @@ class BaseHighState(object):
|
|||
high data structure.
|
||||
'''
|
||||
highstate = {}
|
||||
errors = []
|
||||
for env, states in matches.items():
|
||||
mods = set()
|
||||
for sls_match in states:
|
||||
for sls in fnmatch.filter(self.avail[env], sls_match):
|
||||
state, mods, err = self.render_state(sls, env, mods, matches)
|
||||
# The extend members can not be treated as globally unique:
|
||||
if '__extend__' in state and '__extend__' in highstate:
|
||||
highstate['__extend__'].extend(state.pop('__extend__'))
|
||||
if '__exclude__' in state and '__exclude__' in highstate:
|
||||
highstate['__exclude__'].extend(state.pop('__exclude__'))
|
||||
for id_ in state:
|
||||
if id_ in highstate:
|
||||
if highstate[id_] != state[id_]:
|
||||
errors.append(('Detected conflicting IDs, SLS'
|
||||
' IDs need to be globally unique.\n The'
|
||||
' conflicting ID is "{0}" and is found in SLS'
|
||||
' "{1}:{2}" and SLS "{3}:{4}"').format(
|
||||
id_,
|
||||
highstate[id_]['__env__'],
|
||||
highstate[id_]['__sls__'],
|
||||
state[id_]['__env__'],
|
||||
state[id_]['__sls__'])
|
||||
)
|
||||
state, errors = self.render_state(sls, env, mods, matches)
|
||||
if state:
|
||||
try:
|
||||
highstate.update(state)
|
||||
except ValueError:
|
||||
errors.append(
|
||||
'Error when rendering state with '
|
||||
'contents: {0}'.format(
|
||||
state
|
||||
)
|
||||
)
|
||||
if err:
|
||||
errors += err
|
||||
# Clean out duplicate extend data
|
||||
self.merge_included_states(highstate, state, errors)
|
||||
|
||||
self.clean_duplicate_extends(highstate)
|
||||
return highstate, errors
|
||||
|
||||
|
||||
def clean_duplicate_extends(self, highstate):
|
||||
if '__extend__' in highstate:
|
||||
highext = []
|
||||
for ext in highstate['__extend__']:
|
||||
for key, val in ext.items():
|
||||
exists = False
|
||||
for hext in highext:
|
||||
if hext == {key: val}:
|
||||
exists = True
|
||||
if not exists:
|
||||
highext.append({key: val})
|
||||
highstate['__extend__'] = highext
|
||||
return highstate, errors
|
||||
for items in (ext.items() for ext in highstate['__extend__']):
|
||||
for item in items:
|
||||
if item not in highext:
|
||||
highext.append(item)
|
||||
highstate['__extend__'] = [{t[0]: t[1]} for t in highext]
|
||||
|
||||
|
||||
def merge_included_states(self, highstate, state, errors):
|
||||
# The extend members can not be treated as globally unique:
|
||||
if '__extend__' in state:
|
||||
highstate.setdefault('__extend__', []).extend(state.pop('__extend__'))
|
||||
if '__exclude__' in state:
|
||||
highstate.setdefault('__exclude__', []).extend(state.pop('__exclude__'))
|
||||
for id_ in state:
|
||||
if id_ in highstate:
|
||||
if highstate[id_] != state[id_]:
|
||||
errors.append(('Detected conflicting IDs, SLS'
|
||||
' IDs need to be globally unique.\n The'
|
||||
' conflicting ID is "{0}" and is found in SLS'
|
||||
' "{1}:{2}" and SLS "{3}:{4}"').format(
|
||||
id_,
|
||||
highstate[id_]['__env__'],
|
||||
highstate[id_]['__sls__'],
|
||||
state[id_]['__env__'],
|
||||
state[id_]['__sls__'])
|
||||
)
|
||||
try:
|
||||
highstate.update(state)
|
||||
except ValueError:
|
||||
errors.append(
|
||||
'Error when rendering state with contents: {0}'.format(state)
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
def call_highstate(self, exclude=None):
|
||||
'''
|
||||
|
@ -2073,6 +2075,7 @@ class HighState(BaseHighState):
|
|||
compound state derived from a group of template files stored on the
|
||||
salt master or in the local cache.
|
||||
'''
|
||||
current = None # Current active HighState object during a state.highstate run.
|
||||
def __init__(self, opts):
|
||||
self.client = salt.fileclient.get_file_client(opts)
|
||||
BaseHighState.__init__(self, opts)
|
||||
|
|
|
@ -107,7 +107,8 @@ state('H').cmd.run('echo world')
|
|||
include(
|
||||
'some.sls.file',
|
||||
'another.sls.file',
|
||||
'more.sls.file'
|
||||
'more.sls.file',
|
||||
delayed=True
|
||||
)
|
||||
A = state('A').cmd.run('echo hoho', cwd='/')
|
||||
state('B').cmd.run('echo hehe', cwd='/')
|
||||
|
@ -120,7 +121,7 @@ extend(
|
|||
''')
|
||||
self.assertEqual(len(result), 4)
|
||||
self.assertEqual(result['include'],
|
||||
'some.sls.file another.sls.file more.sls.file'.split())
|
||||
[{'base': sls} for sls in 'some.sls.file another.sls.file more.sls.file'.split()])
|
||||
extend = result['extend']
|
||||
self.assertEqual(extend['X']['cmd'][0], 'run')
|
||||
self.assertEqual(extend['X']['cmd'][1]['cwd'], '/a/b/c')
|
||||
|
@ -249,6 +250,7 @@ state('B').file.managed(source='/a/b/c')
|
|||
yyy = os.path.join(dirpath, 'yyy.sls')
|
||||
with open(yyy, 'w') as yyy:
|
||||
yyy.write('''#!pydsl|stateconf -ps
|
||||
__pydsl__.set(ordered=True)
|
||||
state('.D').cmd.run('echo D >> {0}', cwd='/')
|
||||
state('.E').cmd.run('echo E >> {1}', cwd='/')
|
||||
state('.F').cmd.run('echo F >> {2}', cwd='/')
|
||||
|
@ -259,10 +261,10 @@ state('.F').cmd.run('echo F >> {2}', cwd='/')
|
|||
aaa.write('''#!pydsl|stateconf -ps
|
||||
include('xxx', 'yyy')
|
||||
|
||||
# make all states in yyy run BEFORE states in this sls.
|
||||
# make all states in xxx run BEFORE states in this sls.
|
||||
extend(state('.start').stateconf.require(stateconf='xxx::goal'))
|
||||
|
||||
# make all states in xxx run AFTER this sls.
|
||||
# make all states in yyy run AFTER this sls.
|
||||
extend(state('.goal').stateconf.require_in(stateconf='yyy::start'))
|
||||
|
||||
__pydsl__.set(ordered=True)
|
||||
|
@ -272,17 +274,7 @@ state('.B').cmd.run('echo B >> {1}', cwd='/')
|
|||
state('.C').cmd.run('echo C >> {2}', cwd='/')
|
||||
'''.format(output, output, output))
|
||||
|
||||
OPTS['file_roots'] = dict(base=[dirpath])
|
||||
HIGHSTATE = HighState(OPTS)
|
||||
HIGHSTATE.state.load_modules()
|
||||
sys.modules['salt.loaded.int.render.pydsl'].__salt__ = HIGHSTATE.state.functions
|
||||
|
||||
high, errors = HIGHSTATE.render_highstate({'base': ['aaa']})
|
||||
# import pprint
|
||||
# pprint.pprint(errors)
|
||||
# pprint.pprint(high)
|
||||
out = HIGHSTATE.state.call_high(high)
|
||||
# pprint.pprint(out)
|
||||
state_highstate({'base': ['aaa']}, dirpath)
|
||||
with open(output, 'r') as f:
|
||||
self.assertEqual(''.join(f.read().split()), "XYZABCDEF")
|
||||
|
||||
|
@ -290,4 +282,109 @@ state('.C').cmd.run('echo C >> {2}', cwd='/')
|
|||
shutil.rmtree(dirpath, ignore_errors=True)
|
||||
|
||||
|
||||
def test_rendering_includes(self):
|
||||
if sys.version_info < (2, 7):
|
||||
self.skipTest('OrderedDict is not available')
|
||||
dirpath = tempfile.mkdtemp()
|
||||
output = os.path.join(dirpath, 'output')
|
||||
try:
|
||||
aaa = os.path.join(dirpath, 'aaa.sls')
|
||||
with open(aaa, 'w') as aaa:
|
||||
aaa.write('''#!pydsl|stateconf -ps
|
||||
include('xxx')
|
||||
yyy = include('yyy')
|
||||
|
||||
# ensure states in xxx are run first, then those in yyy and then those in aaa last.
|
||||
extend(state('yyy::start').stateconf.require(stateconf='xxx::goal'))
|
||||
extend(state('.start').stateconf.require(stateconf='yyy::goal'))
|
||||
|
||||
extend(state('yyy::Y2').cmd.run('echo Y2 extended >> {0}'))
|
||||
|
||||
__pydsl__.set(ordered=True)
|
||||
|
||||
yyy.hello('red', 1)
|
||||
yyy.hello('green', 2)
|
||||
yyy.hello('blue', 3)
|
||||
'''.format(output))
|
||||
xxx = os.path.join(dirpath, 'xxx.sls')
|
||||
with open(xxx, 'w') as xxx:
|
||||
xxx.write('''#!stateconf -os yaml . jinja
|
||||
include:
|
||||
- yyy
|
||||
|
||||
extend:
|
||||
yyy::start:
|
||||
stateconf.set:
|
||||
- require:
|
||||
- stateconf: .goal
|
||||
|
||||
yyy::Y1:
|
||||
cmd.run:
|
||||
- name: 'echo Y1 extended >> {0}'
|
||||
|
||||
.X1:
|
||||
cmd.run:
|
||||
- name: echo X1 >> {1}
|
||||
- cwd: /
|
||||
.X2:
|
||||
cmd.run:
|
||||
- name: echo X2 >> {2}
|
||||
- cwd: /
|
||||
.X3:
|
||||
cmd.run:
|
||||
- name: echo X3 >> {3}
|
||||
- cwd: /
|
||||
|
||||
'''.format(output, output, output, output))
|
||||
|
||||
yyy = os.path.join(dirpath, 'yyy.sls')
|
||||
with open(yyy, 'w') as yyy:
|
||||
yyy.write('''#!pydsl|stateconf -ps
|
||||
include('xxx')
|
||||
__pydsl__.set(ordered=True)
|
||||
|
||||
state('.Y1').cmd.run('echo Y1 >> {0}', cwd='/')
|
||||
state('.Y2').cmd.run('echo Y2 >> {1}', cwd='/')
|
||||
state('.Y3').cmd.run('echo Y3 >> {2}', cwd='/')
|
||||
|
||||
def hello(color, number):
|
||||
state(color).cmd.run('echo hello '+color+' '+str(number)+' >> {3}', cwd='/')
|
||||
'''.format(output, output, output, output))
|
||||
|
||||
state_highstate({'base': ['aaa']}, dirpath)
|
||||
expected = '''
|
||||
X1
|
||||
X2
|
||||
X3
|
||||
Y1 extended
|
||||
Y2 extended
|
||||
Y3
|
||||
hello red 1
|
||||
hello green 2
|
||||
hello blue 3
|
||||
'''.lstrip()
|
||||
|
||||
with open(output, 'r') as f:
|
||||
self.assertEqual(f.read(), expected)
|
||||
|
||||
finally:
|
||||
shutil.rmtree(dirpath, ignore_errors=True)
|
||||
|
||||
|
||||
|
||||
def state_highstate(matches, dirpath):
|
||||
OPTS['file_roots'] = dict(base=[dirpath])
|
||||
HIGHSTATE = HighState(OPTS)
|
||||
HighState.current = HIGHSTATE
|
||||
HIGHSTATE.state.load_modules()
|
||||
sys.modules['salt.loaded.int.render.pydsl'].__salt__ = \
|
||||
HIGHSTATE.state.functions
|
||||
|
||||
high, errors = HIGHSTATE.render_highstate({'base': ['aaa']})
|
||||
if errors:
|
||||
import pprint
|
||||
pprint.pprint('\n'.join(errors))
|
||||
pprint.pprint(high)
|
||||
|
||||
out = HIGHSTATE.state.call_high(high)
|
||||
# pprint.pprint(out)
|
||||
|
|
|
@ -152,7 +152,7 @@ state_id:
|
|||
- {0}:
|
||||
- cmd: .utils::some_state
|
||||
'''.format(req), sls='test.work')
|
||||
self.assertEqual(result['include'][1], 'test.utils')
|
||||
self.assertEqual(result['include'][1], {'base': 'test.utils'})
|
||||
self.assertEqual(result['state_id']['cmd.run'][2][req][0]['cmd'],
|
||||
'test.utils::some_state')
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue