Add some docs on render pipe.

Also, added a stateconf state so that its stateconf.set function
can be used by the stateconf renderer.
This commit is contained in:
Jack Kuan 2012-11-13 02:01:34 -05:00
parent a76331e0e1
commit 7f3ac3fe89
5 changed files with 84 additions and 52 deletions

View file

@ -45,6 +45,39 @@ use the Python or ``py`` renderer:
The first line is a shebang that references the ``py`` renderer.
Composing Renderers
-------------------
A render can be composed from other renderers by connecting them in a series
of pipes(``|``). In fact, the default ``Jinja + YAML`` renderer is implemented
by combining a yaml renderer and a jinja renderer. Such renderer configuration
is specified as: ``jinja | yaml``.
Other renderer combinations are possible, here's a few examples:
``yaml``
i.e, just YAML, no templating.
``mako | yaml``
pass the input to the ``mako`` renderer, whose output is then fed into the
``yaml`` renderer.
``jinja | mako | yaml``
This one allows you to use both jinja and mako templating syntax in the
input and then parse the final rendererd output as YAML.
For backward compatibility, ``jinja | yaml`` can also be written as
``yaml_jinja``, and similarly, the ``yaml_mako``, ``yaml_wempy``,
``json_jinja``, ``json_mako``, and ``json_wempy`` renderers are all supported
as well.
Keep in mind that not all renderers can be used alone or with any other renderers.
For example, the template renderers shouldn't be used alone as their outputs are
just strings, which still need to be parsed by another renderer to turn them into
highstate data structures. Also, for example, it doesn't make sense to specify
``yaml | jinja`` either, because the output of the yaml renderer is a highstate
data structure(a dict in Python), which cannot be used as the input to a template
renderer. Therefore, when combining renderers, you should know what each renderer
accepts as input and what it returns as output.
Writing Renderers
-----------------
@ -64,26 +97,13 @@ renderers included with Salt can be found here:
:blob:`salt/renderers`
Here is a simple Jinja + YAML example:
Here is a simple YAML renderer example:
.. code-block:: python
# Import Python libs
import os
# Import Third Party libs
import yaml
from jinja2 import Template
def render(template):
'''
Render the data passing the functions and grains into the rendering system
'''
if not os.path.isfile(template):
return {}
passthrough = {}
passthrough.update(__salt__)
passthrough.update(__grains__)
template = Template(open(template, 'r').read())
yaml_data = template.render(**passthrough)
return yaml.load(yaml_data)
def render(yaml_data, env='', sls='', argline='', **kws):
if not isinstance(yaml_data, basestring):
yaml_data = yaml_data.read()
data = yaml.load(yaml_data)
return data if data else {}

View file

@ -1,7 +1,7 @@
"""
This module provides a custom renderer that process a salt file with a
specified templating engine(eg, jinja) and a chosen data renderer(eg, yaml),
extract arguments for any ``state.config`` state and provide the extracted
extract arguments for any ``stateconf.set`` state and provide the extracted
arguments (including salt specific args, such as 'require', etc) as template
context. The goal is to make writing reusable/configurable/ parameterized
salt files easier and cleaner, therefore, additionally, it also:
@ -14,12 +14,12 @@ salt files easier and cleaner, therefore, additionally, it also:
<%include file="templates/sls-parts.mako"/>
<%namespace file="salt://lib/templates/utils.mako" import="helper"/>
- Recognizes the special state function, ``state.config``, that configures a
- Recognizes the special state function, ``stateconf.set``, that configures a
default list of named arguments useable within the template context of
the salt file. Example::
sls_params:
state.config:
stateconf.set:
- name1: value1
- name2: value2
- name3:
@ -41,10 +41,10 @@ salt files easier and cleaner, therefore, additionally, it also:
This even works with ``include`` + ``extend`` so that you can override
the default configured arguments by including the salt file and then extend
the ``state.config`` states that come from the included salt file.
the ``stateconf.set`` states that come from the included salt file.
Notice that the end of configuration marker(``# --- end of state config --``)
is needed to separate the use of 'state.config' form the rest of your salt
is needed to separate the use of 'stateconf.set' form the rest of your salt
file.
- Adds support for relative include and exclude of .sls files. Example::
@ -89,7 +89,7 @@ salt files easier and cleaner, therefore, additionally, it also:
extend:
.file::sls_params:
state.config:
stateconf.set:
- name1: something
Above will be pre-processed into::
@ -99,11 +99,11 @@ salt files easier and cleaner, therefore, additionally, it also:
extend:
some.file::sls_params:
state.config:
stateconf.set:
- name1: something
- Optionally(disable via the `-G` renderer option), generates a
``state.config`` goal state(state id named as ``.goal`` by default) that
``stateconf.set`` goal state(state id named as ``.goal`` by default) that
requires all other states in the salt file.
Such goal state is intended to be required by some state in an including
@ -168,7 +168,7 @@ re-write or renames state id's and their references.
# - support synthetic argument? Eg,
#
# apache:
# state.config:
# stateconf.set:
# - host: localhost
# - port: 1234
# - url: 'http://${host}:${port}/'
@ -176,7 +176,7 @@ re-write or renames state id's and their references.
# Currently, this won't work, but can be worked around like so:
#
# apache:
# state.config:
# stateconf.set:
# - host: localhost
# - port: 1234
# ## - url: 'http://${host}:${port}/'
@ -202,11 +202,24 @@ __opts__ = {
'stateconf_end_marker': r'#\s*-+\s*end of state config\s*-+',
# eg, something like "# --- end of state config --" works by default.
'stateconf_goal_state': '.goal'
'stateconf_goal_state': '.goal',
# name of the state id for the generated goal state.
'stateconf_state_func': 'stateconf.set'
# names the state and the state function to be recognized as a special state
# from which to gather sls file context variables. It should be specified
# in the 'state.func' notation, and both the state module and the function must
# actually exist and the function should be a dummy, no-op state function that
# simply returns a dict(name=name, result=True, changes={}, comment='')
}
STATE_FUNC = STATE_NAME = ''
STATE_FUNC = __opts__['stateconf_state_func']
STATE_NAME = STATE_FUNC.split('.')[0]
MOD_BASENAME = ospath.basename(__file__)
INVALID_USAGE_ERROR = SaltRenderError(
"Invalid use of %s renderer!\n"
@ -318,7 +331,7 @@ def render(template_file, env='', sls='', argline='', **kws):
process_sls_data(sls_templ[:match.start()], extract=True)
# if some config has been extracted then remove the sls-name prefix
# of the keys in the extracted state.config context to make them easier
# of the keys in the extracted stateconf.set context to make them easier
# to use in the salt file.
if STATE_CONF:
tmplctx = STATE_CONF.copy()
@ -526,7 +539,7 @@ def add_goal_state(data):
for sid, _, state, _ in \
statelist(data, set(['include', 'exclude', 'extend'])):
reqlist.append({state_name(state): sid})
data[goal_sid] = {'state.config': [dict(require=reqlist)]}
data[goal_sid] = {STATE_FUNC: [dict(require=reqlist)]}
def state_name(sname):
"""Return the name of the state regardless if sname is
@ -545,14 +558,14 @@ class Bunch(dict):
# With sls:
#
# state_id:
# state.config:
# stateconf.set:
# - name1: value1
#
# STATE_CONF is:
# { state_id => {name1: value1} }
#
STATE_CONF = {} # state.config
STATE_CONF_EXT = {} # state.config under extend: ...
STATE_CONF = {} # stateconf.set
STATE_CONF_EXT = {} # stateconf.set under extend: ...
def extract_state_confs(data, is_extend=False):
for state_id, state_dict in data.iteritems():
@ -560,10 +573,10 @@ def extract_state_confs(data, is_extend=False):
extract_state_confs(state_dict, True)
continue
if 'state' in state_dict:
key = 'state'
elif 'state.config' in state_dict:
key = 'state.config'
if STATE_NAME in state_dict:
key = STATE_NAME
elif STATE_FUNC in state_dict:
key = STATE_FUNC
else:
continue

View file

@ -138,14 +138,6 @@ def _is_true(v):
raise ValueError('Failed parsing boolean value: {0}'.format(v))
def _no_op(name, **kws):
'''
No-op state to support state config via the stateconf renderer.
'''
return dict(name=name, result=True, changes={}, comment='')
config = _no_op
def _run_check(cmd_kwargs, onlyif, unless, cwd, user, group, shell):
'''
Execute the onlyif logic and return data if the onlyif fails

7
salt/states/stateconf.py Normal file
View file

@ -0,0 +1,7 @@
def _no_op(name, **kws):
'''
No-op state to support state config via the stateconf renderer.
'''
return dict(name=name, result=True, changes={}, comment='')
set = context = _no_op

View file

@ -31,13 +31,13 @@ class StateConfigRendererTestCase(TestCase):
def test_state_config(self):
result = render_sls('''
.sls_params:
state.config:
stateconf.set:
- name1: value1
- name2: value2
.extra:
state:
- config
stateconf:
- set
- name: value
# --- end of state config ---
@ -151,7 +151,7 @@ extend:
''', sls='test.goalstate', argline='yaml . jinja')
self.assertTrue(len(result), len('ABCDE')+1)
reqs = result['test.goalstate::goal']['state.config'][0]['require']
reqs = result['test.goalstate::goal']['stateconf.set'][0]['require']
self.assertEqual(set([i.itervalues().next() for i in reqs]),
set('ABCDE'))
@ -201,7 +201,7 @@ G:
self.assertEqual(G_req[1]['cmd'], 'D')
self.assertEqual(G_req[2]['cmd'], 'F')
goal_args = result['test::goal']['state.config']
goal_args = result['test::goal']['stateconf.set']
self.assertEqual(len(goal_args), 1)
self.assertEqual(
[i.itervalues().next() for i in goal_args[0]['require']],