diff --git a/conf/master b/conf/master index e39b0b5e3e5e..6be6a444879e 100644 --- a/conf/master +++ b/conf/master @@ -566,18 +566,35 @@ # The renderer to use on the minions to render the state data #renderer: yaml_jinja -# The Jinja renderer can strip extra carriage returns and whitespace -# See http://jinja.pocoo.org/docs/api/#high-level-api -# -# If this is set to True the first newline after a Jinja block is removed -# (block, not variable tag!). Defaults to False, corresponds to the Jinja -# environment init variable "trim_blocks". -#jinja_trim_blocks: False -# -# If this is set to True leading spaces and tabs are stripped from the start -# of a line to a block. Defaults to False, corresponds to the Jinja -# environment init variable "lstrip_blocks". -#jinja_lstrip_blocks: False +# Default Jinja environment options for all templates except sls templates +#jinja_env: +# block_start_string: '{%' +# block_end_string: '%}' +# variable_start_string: '{{' +# variable_end_string: '}}' +# comment_start_string: '{#' +# comment_end_string: '#}' +# line_statement_prefix: +# line_comment_prefix: +# trim_blocks: False +# lstrip_blocks: False +# newline_sequence: '\n' +# keep_trailing_newline: False + +# Jinja environment options for sls templates +#jinja_sls_env: +# block_start_string: '{%' +# block_end_string: '%}' +# variable_start_string: '{{' +# variable_end_string: '}}' +# comment_start_string: '{#' +# comment_end_string: '#}' +# line_statement_prefix: +# line_comment_prefix: +# trim_blocks: False +# lstrip_blocks: False +# newline_sequence: '\n' +# keep_trailing_newline: False # The failhard option tells the minions to stop immediately after the first # failure detected in the state execution, defaults to False diff --git a/conf/suse/master b/conf/suse/master index cdba8f7dacc2..e5de8caa0739 100644 --- a/conf/suse/master +++ b/conf/suse/master @@ -537,18 +537,35 @@ syndic_user: salt # The renderer to use on the minions to render the state data #renderer: yaml_jinja -# The Jinja renderer can strip extra carriage returns and whitespace -# See http://jinja.pocoo.org/docs/api/#high-level-api -# -# If this is set to True the first newline after a Jinja block is removed -# (block, not variable tag!). Defaults to False, corresponds to the Jinja -# environment init variable "trim_blocks". -#jinja_trim_blocks: False -# -# If this is set to True leading spaces and tabs are stripped from the start -# of a line to a block. Defaults to False, corresponds to the Jinja -# environment init variable "lstrip_blocks". -#jinja_lstrip_blocks: False +# Default Jinja environment options for all templates except sls templates +#jinja_env: +# block_start_string: '{%' +# block_end_string: '%}' +# variable_start_string: '{{' +# variable_end_string: '}}' +# comment_start_string: '{#' +# comment_end_string: '#}' +# line_statement_prefix: +# line_comment_prefix: +# trim_blocks: False +# lstrip_blocks: False +# newline_sequence: '\n' +# keep_trailing_newline: False + +# Jinja environment options for sls templates +#jinja_sls_env: +# block_start_string: '{%' +# block_end_string: '%}' +# variable_start_string: '{{' +# variable_end_string: '}}' +# comment_start_string: '{#' +# comment_end_string: '#}' +# line_statement_prefix: +# line_comment_prefix: +# trim_blocks: False +# lstrip_blocks: False +# newline_sequence: '\n' +# keep_trailing_newline: False # The failhard option tells the minions to stop immediately after the first # failure detected in the state execution, defaults to False diff --git a/doc/ref/configuration/master.rst b/doc/ref/configuration/master.rst index 8dc4f83ca155..eab68fb9748b 100644 --- a/doc/ref/configuration/master.rst +++ b/doc/ref/configuration/master.rst @@ -1941,11 +1941,120 @@ the cloud profile or master config file, no templating will be performed. userdata_template: jinja +.. conf_master:: jinja_env + +``jinja_env`` +------------- + +.. versionadded:: Oxygen + +Default: ``{}`` + +jinja_env overrides the default Jinja environment options for +**all templates except sls templates**. +To set the options for sls templates use :conf_master:`jinja_sls_env`. + +.. note:: + + The `Jinja2 Environment documentation `_ is the official source for the default values. + Not all the options listed in the jinja documentation can be overridden using :conf_master:`jinja_env` or :conf_master:`jinja_sls_env`. + +The default options are: + +.. code-block:: yaml + + jinja_env: + block_start_string: '{%' + block_end_string: '%}' + variable_start_string: '{{' + variable_end_string: '}}' + comment_start_string: '{#' + comment_end_string: '#}' + line_statement_prefix: + line_comment_prefix: + trim_blocks: False + lstrip_blocks: False + newline_sequence: '\n' + keep_trailing_newline: False + +.. conf_master:: jinja_sls_env + +``jinja_sls_env`` +----------------- + +.. versionadded:: Oxygen + +Default: ``{}`` + +jinja_sls_env sets the Jinja environment options for **sls templates**. +The defaults and accepted options are exactly the same as they are +for :conf_master:`jinja_env`. + +The default options are: + +.. code-block:: yaml + + jinja_sls_env: + block_start_string: '{%' + block_end_string: '%}' + variable_start_string: '{{' + variable_end_string: '}}' + comment_start_string: '{#' + comment_end_string: '#}' + line_statement_prefix: + line_comment_prefix: + trim_blocks: False + lstrip_blocks: False + newline_sequence: '\n' + keep_trailing_newline: False + +Example using line statements and line comments to increase ease of use: + +If your configuration options are + +.. code-block:: yaml + jinja_sls_env: + line_statement_prefix: '%' + line_comment_prefix: '##' + +With these options jinja will interpret anything after a ``%`` at the start of a line (ignoreing whitespace) +as a jinja statement and will interpret anything after a ``##`` as a comment. + +This allows the following more convenient syntax to be used: + +.. code-block:: yaml + + ## (this comment will not stay once rendered) + # (this comment remains in the rendered template) + ## ensure all the formula services are running + % for service in formula_services: + enable_service_{{ serivce }}: + service.running: + name: {{ service }} + % endfor + +The following less convenient but equivalent syntax would have to +be used if you had not set the line_statement and line_comment options: + +.. code-block:: yaml + + {# (this comment will not stay once rendered) #} + # (this comment remains in the rendered template) + {# ensure all the formula services are running #} + {% for service in formula_services %} + enable_service_{{ service }}: + service.running: + name: {{ service }} + {% endfor %} + .. conf_master:: jinja_trim_blocks ``jinja_trim_blocks`` --------------------- +.. deprecated:: Oxygen + Replaced by :conf_master:`jinja_env` and :conf_master:`jinja_sls_env` + .. versionadded:: 2014.1.0 Default: ``False`` @@ -1963,6 +2072,9 @@ to the Jinja environment init variable ``trim_blocks``. ``jinja_lstrip_blocks`` ----------------------- +.. deprecated:: Oxygen + Replaced by :conf_master:`jinja_env` and :conf_master:`jinja_sls_env` + .. versionadded:: 2014.1.0 Default: ``False`` diff --git a/salt/config/__init__.py b/salt/config/__init__.py index f47b0ef373fb..b998ec5f814a 100644 --- a/salt/config/__init__.py +++ b/salt/config/__init__.py @@ -885,6 +885,12 @@ def _gather_buffer_space(): # check in with their lists of expected minions before giving up 'syndic_wait': int, + # Override Jinja environment option defaults for all templates except sls templates + 'jinja_env': dict, + + # Set Jinja environment options for sls templates + 'jinja_sls_env': dict, + # If this is set to True leading spaces and tabs are stripped from the start # of a line to a block. 'jinja_lstrip_blocks': bool, @@ -1636,6 +1642,8 @@ def _gather_buffer_space(): 'winrepo_passphrase': '', 'winrepo_refspecs': _DFLT_REFSPECS, 'syndic_wait': 5, + 'jinja_env': {}, + 'jinja_sls_env': {}, 'jinja_lstrip_blocks': False, 'jinja_trim_blocks': False, 'tcp_keepalive': True, diff --git a/salt/daemons/masterapi.py b/salt/daemons/masterapi.py index 07b2738b799e..080d5bf8ee4e 100644 --- a/salt/daemons/masterapi.py +++ b/salt/daemons/masterapi.py @@ -491,6 +491,8 @@ def _master_opts(self, load): mopts['state_auto_order'] = self.opts['state_auto_order'] mopts['state_events'] = self.opts['state_events'] mopts['state_aggregate'] = self.opts['state_aggregate'] + mopts['jinja_env'] = self.opts['jinja_env'] + mopts['jinja_sls_env'] = self.opts['jinja_sls_env'] mopts['jinja_lstrip_blocks'] = self.opts['jinja_lstrip_blocks'] mopts['jinja_trim_blocks'] = self.opts['jinja_trim_blocks'] return mopts diff --git a/salt/master.py b/salt/master.py index 19e11002c203..e08f1a24f7ce 100644 --- a/salt/master.py +++ b/salt/master.py @@ -1146,6 +1146,8 @@ def _master_opts(self, load): mopts[u'state_auto_order'] = self.opts[u'state_auto_order'] mopts[u'state_events'] = self.opts[u'state_events'] mopts[u'state_aggregate'] = self.opts[u'state_aggregate'] + mopts[u'jinja_env'] = self.opts[u'jinja_env'] + mopts[u'jinja_sls_env'] = self.opts[u'jinja_sls_env'] mopts[u'jinja_lstrip_blocks'] = self.opts[u'jinja_lstrip_blocks'] mopts[u'jinja_trim_blocks'] = self.opts[u'jinja_trim_blocks'] return mopts diff --git a/salt/state.py b/salt/state.py index 5527a0f87c79..e978ca2f561f 100644 --- a/salt/state.py +++ b/salt/state.py @@ -2782,6 +2782,8 @@ def __gen_opts(self, opts): opts[u'default_top'] = mopts.get(u'default_top', opts.get(u'default_top')) opts[u'state_events'] = mopts.get(u'state_events') opts[u'state_aggregate'] = mopts.get(u'state_aggregate', opts.get(u'state_aggregate', False)) + opts[u'jinja_env'] = mopts.get(u'jinja_env', {}) + opts[u'jinja_sls_env'] = mopts.get(u'jinja_sls_env', {}) opts[u'jinja_lstrip_blocks'] = mopts.get(u'jinja_lstrip_blocks', False) opts[u'jinja_trim_blocks'] = mopts.get(u'jinja_trim_blocks', False) return opts diff --git a/salt/utils/templates.py b/salt/utils/templates.py index bbb834bb4122..d77f757235b9 100644 --- a/salt/utils/templates.py +++ b/salt/utils/templates.py @@ -314,16 +314,40 @@ def render_jinja_tmpl(tmplstr, context, tmplpath=None): env_args['extensions'].append('jinja2.ext.loopcontrols') env_args['extensions'].append(salt.utils.jinja.SerializerExtension) + opt_jinja_env = opts.get('jinja_env', {}) + opt_jinja_sls_env = opts.get('jinja_sls_env', {}) + + opt_jinja_env = opt_jinja_env if isinstance(opt_jinja_env, dict) else {} + opt_jinja_sls_env = opt_jinja_sls_env if isinstance(opt_jinja_sls_env, dict) else {} + # Pass through trim_blocks and lstrip_blocks Jinja parameters # trim_blocks removes newlines around Jinja blocks # lstrip_blocks strips tabs and spaces from the beginning of # line to the start of a block. if opts.get('jinja_trim_blocks', False): log.debug('Jinja2 trim_blocks is enabled') - env_args['trim_blocks'] = True + log.warning('jinja_trim_blocks is deprecated and will be removed in a future release, please use jinja_env and/or jinja_sls_env instead') + opt_jinja_env['trim_blocks'] = True + opt_jinja_sls_env['trim_blocks'] = True if opts.get('jinja_lstrip_blocks', False): log.debug('Jinja2 lstrip_blocks is enabled') - env_args['lstrip_blocks'] = True + log.warning('jinja_lstrip_blocks is deprecated and will be removed in a future release, please use jinja_env and/or jinja_sls_env instead') + opt_jinja_env['lstrip_blocks'] = True + opt_jinja_sls_env['lstrip_blocks'] = True + + def opt_jinja_env_helper(opts, optname): + for k, v in six.iteritems(opts): + k = k.lower() + if hasattr(jinja2.defaults, k.upper()): + log.debug('Jinja2 environment {0} was set to {1} by {2}'.format(k, v, optname)) + env_args[k] = v + else: + log.warning('Jinja2 environment {0} is not recognized'.format(k)) + + if 'sls' in context and context['sls'] != '': + opt_jinja_env_helper(opt_jinja_sls_env, 'jinja_sls_env') + else: + opt_jinja_env_helper(opt_jinja_env, 'jinja_env') if opts.get('allow_undefined', False): jinja_env = jinja2.Environment(**env_args) diff --git a/tests/unit/templates/test_jinja.py b/tests/unit/templates/test_jinja.py index 146e23074702..445d2e4479d8 100644 --- a/tests/unit/templates/test_jinja.py +++ b/tests/unit/templates/test_jinja.py @@ -481,6 +481,67 @@ def test_render_with_undefined_variable_unicode(self): ) +class TestJinjaDefaultOptions(TestCase): + + def __init__(self, *args, **kws): + TestCase.__init__(self, *args, **kws) + self.local_opts = { + 'cachedir': TEMPLATES_DIR, + 'file_client': 'local', + 'file_ignore_regex': None, + 'file_ignore_glob': None, + 'file_roots': { + 'test': [os.path.join(TEMPLATES_DIR, 'files', 'test')] + }, + 'pillar_roots': { + 'test': [os.path.join(TEMPLATES_DIR, 'files', 'test')] + }, + 'fileserver_backend': ['roots'], + 'hash_type': 'md5', + 'extension_modules': os.path.join( + os.path.dirname(os.path.abspath(__file__)), + 'extmods'), + 'jinja_env': { + 'line_comment_prefix': '##', + 'line_statement_prefix': '%', + }, + } + self.local_salt = { + 'myvar': 'zero', + 'mylist': [0, 1, 2, 3], + } + + def test_comment_prefix(self): + + template = """ + %- set myvar = 'one' + ## ignored comment 1 + {{- myvar -}} + {%- set myvar = 'two' %} ## ignored comment 2 + {{- myvar }} ## ignored comment 3 + %- if myvar == 'two': + %- set myvar = 'three' + %- endif + {{- myvar -}} + """ + rendered = render_jinja_tmpl(template, + dict(opts=self.local_opts, saltenv='test', salt=self.local_salt)) + self.assertEqual(rendered, u'onetwothree') + + def test_statement_prefix(self): + + template = """ + {%- set mylist = ['1', '2', '3'] %} + %- set mylist = ['one', 'two', 'three'] + %- for item in mylist: + {{- item }} + %- endfor + """ + rendered = render_jinja_tmpl(template, + dict(opts=self.local_opts, saltenv='test', salt=self.local_salt)) + self.assertEqual(rendered, u'onetwothree') + + class TestCustomExtensions(TestCase): def __init__(self, *args, **kws):