Skip to content

Commit

Permalink
Merge pull request #25578 from davidjb/relative-includes
Browse files Browse the repository at this point in the history
Allow parent relative includes in state files
  • Loading branch information
Mike Place committed Jul 22, 2015
2 parents f4ad36b + 9ad0ddc commit 3fe4a75
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 16 deletions.
8 changes: 5 additions & 3 deletions doc/ref/renderers/all/salt.renderers.stateconf.rst
Original file line number Diff line number Diff line change
Expand Up @@ -172,20 +172,22 @@ Here's a list of features enabled by this renderer.
include:
- .apache
- .db.mysql
- ..app.django
exclude:
- sls: .users
If the above is written in a salt file at `salt://some/where.sls` then
it will include `salt://some/apache.sls` and `salt://some/db/mysql.sls`,
and exclude `salt://some/users.ssl`. Actually, it does that by rewriting
the above ``include`` and ``exclude`` into:
it will include `salt://some/apache.sls`, `salt://some/db/mysql.sls` and
`salt://app/django.sls`, and exclude `salt://some/users.ssl`. Actually,
it does that by rewriting the above ``include`` and ``exclude`` into:

.. code-block:: yaml
include:
- some.apache
- some.db.mysql
- app.django
exclude:
- sls: some.users
Expand Down
19 changes: 18 additions & 1 deletion doc/ref/states/include.rst
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,23 @@ the running sls formula was added, simply precede the formula name with a
- .virt
- .virt.hyper
In Salt 2015.8, the ability to include sls formulas which are relative to the
the parents of the running sls forumla was added. In order to achieve this,
prece the formula name with more than one `.`. Much like Python's relative
import abilities, two or more leading dots give a relative import to the
parent or parents of the current package, with each `.` representing one level
after the first.

Within a sls fomula at ``example.dev.virtual``, the following:

.. code-block:: yaml
include:
- ..http
- ...base
would result in ``example.http`` and ``base`` being included.

Exclude
=======

Expand All @@ -58,7 +75,7 @@ high data has been compiled, so nothing should be able to override an
exclude.

Since the exclude can remove an id or an sls the type of component to
exclude needs to be defined. an exclude statement that verifies that the
exclude needs to be defined. An exclude statement that verifies that the
running highstate does not contain the `http` sls and the `/etc/vimrc` id
would look like this:

Expand Down
29 changes: 20 additions & 9 deletions salt/renderers/stateconf.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,15 +264,9 @@ def rewrite_single_shorthand_state_decl(data): # pylint: disable=C0103
data[sid] = {states: []}


def _parent_sls(sls):
i = sls.rfind('.')
return sls[:i] + '.' if i != -1 else ''


def rewrite_sls_includes_excludes(data, sls, saltenv):
# 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)
for sid in data:
if sid == 'include':
includes = data[sid]
Expand All @@ -283,15 +277,32 @@ def rewrite_sls_includes_excludes(data, sls, saltenv):
slsenv = saltenv
incl = each
if incl.startswith('.'):
includes[i] = {slsenv: (sls + incl[1:])}
includes[i] = {slsenv: _relative_to_abs_sls(incl, sls)}
elif sid == 'exclude':
for sdata in data[sid]:
if 'sls' in sdata and sdata['sls'].startswith('.'):
sdata['sls'] = sls + sdata['sls'][1:]
sdata['sls'] = _relative_to_abs_sls(sdata['sls'], sls)


def _local_to_abs_sid(sid, sls): # id must starts with '.'
return _parent_sls(sls) + sid[1:] if '::' in sid else sls + '::' + sid[1:]
if '::' in sid:
return _relative_to_abs_sls(sid, sls)
else:
abs_sls = _relative_to_abs_sls(sid, sls + '.')
return '::'.join(abs_sls.rsplit('.', 1))


def _relative_to_abs_sls(relative, sls):
""" Convert ``relative`` sls reference into absolute, relative to ``sls``.
"""
levels, suffix = re.match('^(\.+)(.*)$', relative).groups()
level_count = len(levels)
p_comps = sls.split('.')
if level_count > len(p_comps):
raise SaltRenderError(
'Attempted relative include goes beyond top level package'
)
return '.'.join(p_comps[:-level_count] + [suffix])


def nvlist(thelist, names=None):
Expand Down
17 changes: 14 additions & 3 deletions salt/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import logging
import datetime
import traceback
import re

# Import salt libs
import salt.utils
Expand Down Expand Up @@ -2633,11 +2634,21 @@ def render_state(self, sls, saltenv, mods, matches, local=False):
continue

if inc_sls.startswith('.'):
levels, include = \
re.match('^(\.+)(.*)$', inc_sls).groups()
level_count = len(levels)
p_comps = sls.split('.')
if state_data.get('source', '').endswith('/init.sls'):
inc_sls = sls + inc_sls
else:
inc_sls = '.'.join(p_comps[:-1]) + inc_sls
p_comps.append('init')
if level_count > len(p_comps):
msg = ('Attempted relative include of {0!r} '
'within SLS \'{1}:{2}\' '
'goes beyond top level package '
.format(inc_sls, saltenv, sls))
log.error(msg)
errors.append(msg)
continue
inc_sls = '.'.join(p_comps[:-level_count] + [include])

if env_key != xenv_key:
# Resolve inc_sls in the specified environment
Expand Down
31 changes: 31 additions & 0 deletions tests/unit/stateconf_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import salt.loader
import salt.config
import integration
from salt.exceptions import SaltRenderError
from salt.ext.six.moves import StringIO

# Import 3rd-party libs
Expand Down Expand Up @@ -207,6 +208,36 @@ def test_relative_include_and_extend(self):
''', sls='test.work')
self.assertTrue('test.utils::some_state' in result['extend'])

def test_multilevel_relative_include_with_requisites(self):
for req in REQUISITES:
result = self._render_sls('''
include:
- .shared
- ..utils
- ...helper
state_id:
cmd.run:
- name: echo test
- cwd: /
- {0}:
- cmd: ..utils::some_state
'''.format(req), sls='test.nested.work')
self.assertEqual(result['include'][0],
{'base': 'test.nested.shared'})
self.assertEqual(result['include'][1], {'base': 'test.utils'})
self.assertEqual(result['include'][2], {'base': 'helper'})
self.assertEqual(
result['state_id']['cmd.run'][2][req][0]['cmd'],
'test.utils::some_state'
)

def test_multilevel_relative_include_beyond_top_level(self):
self.assertRaises(SaltRenderError, self._render_sls, '''
include:
- ...shared
''', sls='test.work')

def test_start_state_generation(self):
result = self._render_sls('''
A:
Expand Down

0 comments on commit 3fe4a75

Please sign in to comment.