Merge pull request #1575 from dhozac/parameterized-playbook-include
Add parameterized playbook includes
This commit is contained in:
commit
c890ae18e7
8 changed files with 81 additions and 22 deletions
|
@ -22,6 +22,7 @@ from ansible import utils
|
||||||
from ansible import errors
|
from ansible import errors
|
||||||
import ansible.callbacks
|
import ansible.callbacks
|
||||||
import os
|
import os
|
||||||
|
import shlex
|
||||||
import collections
|
import collections
|
||||||
from play import Play
|
from play import Play
|
||||||
|
|
||||||
|
@ -123,7 +124,7 @@ class PlayBook(object):
|
||||||
|
|
||||||
# *****************************************************
|
# *****************************************************
|
||||||
|
|
||||||
def _load_playbook_from_file(self, path):
|
def _load_playbook_from_file(self, path, vars={}):
|
||||||
'''
|
'''
|
||||||
run top level error checking on playbooks and allow them to include other playbooks.
|
run top level error checking on playbooks and allow them to include other playbooks.
|
||||||
'''
|
'''
|
||||||
|
@ -135,21 +136,45 @@ class PlayBook(object):
|
||||||
if type(playbook_data) != list:
|
if type(playbook_data) != list:
|
||||||
raise errors.AnsibleError("parse error: playbooks must be formatted as a YAML list")
|
raise errors.AnsibleError("parse error: playbooks must be formatted as a YAML list")
|
||||||
|
|
||||||
|
basedir = os.path.dirname(path)
|
||||||
for play in playbook_data:
|
for play in playbook_data:
|
||||||
if type(play) != dict:
|
if type(play) != dict:
|
||||||
raise errors.AnsibleError("parse error: each play in a playbook must a YAML dictionary (hash), recieved: %s" % play)
|
raise errors.AnsibleError("parse error: each play in a playbook must a YAML dictionary (hash), recieved: %s" % play)
|
||||||
if 'include' in play:
|
if 'include' in play:
|
||||||
if len(play.keys()) == 1:
|
if len(play.keys()) <= 2:
|
||||||
included_path = utils.path_dwim(self.basedir, play['include'])
|
tokens = shlex.split(play['include'])
|
||||||
(plays, basedirs) = self._load_playbook_from_file(included_path)
|
|
||||||
|
items = ['']
|
||||||
|
for k in play.keys():
|
||||||
|
if not k.startswith("with_"):
|
||||||
|
continue
|
||||||
|
plugin_name = k[5:]
|
||||||
|
if plugin_name not in utils.plugins.lookup_loader:
|
||||||
|
raise errors.AnsibleError("cannot find lookup plugin named %s for usage in with_%s" % (plugin_name, plugin_name))
|
||||||
|
terms = utils.template_ds(basedir, play[k], vars)
|
||||||
|
items = utils.plugins.lookup_loader.get(plugin_name, basedir=basedir, runner=None).run(terms, inject=vars)
|
||||||
|
break
|
||||||
|
|
||||||
|
for item in items:
|
||||||
|
incvars = vars.copy()
|
||||||
|
incvars['item'] = item
|
||||||
|
for t in tokens[1:]:
|
||||||
|
(k,v) = t.split("=", 1)
|
||||||
|
incvars[k] = utils.template_ds(basedir, v, incvars)
|
||||||
|
included_path = utils.path_dwim(basedir, tokens[0])
|
||||||
|
(plays, basedirs) = self._load_playbook_from_file(included_path, incvars)
|
||||||
|
for p in plays:
|
||||||
|
if 'vars' not in p:
|
||||||
|
p['vars'] = {}
|
||||||
|
p['vars'].update(incvars)
|
||||||
accumulated_plays.extend(plays)
|
accumulated_plays.extend(plays)
|
||||||
play_basedirs.extend(basedirs)
|
play_basedirs.extend(basedirs)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise errors.AnsibleError("parse error: top level includes cannot be used with other directives: %s" % play)
|
raise errors.AnsibleError("parse error: playbook includes cannot be used with other directives: %s" % play)
|
||||||
else:
|
else:
|
||||||
accumulated_plays.append(play)
|
accumulated_plays.append(play)
|
||||||
play_basedirs.append(os.path.dirname(path))
|
play_basedirs.append(basedir)
|
||||||
|
|
||||||
return (accumulated_plays, play_basedirs)
|
return (accumulated_plays, play_basedirs)
|
||||||
|
|
||||||
|
|
|
@ -111,7 +111,7 @@ class Play(object):
|
||||||
plugin_name = k[5:]
|
plugin_name = k[5:]
|
||||||
if plugin_name not in utils.plugins.lookup_loader:
|
if plugin_name not in utils.plugins.lookup_loader:
|
||||||
raise errors.AnsibleError("cannot find lookup plugin named %s for usage in with_%s" % (plugin_name, plugin_name))
|
raise errors.AnsibleError("cannot find lookup plugin named %s for usage in with_%s" % (plugin_name, plugin_name))
|
||||||
terms = utils.varReplaceWithItems(self.basedir, x[k], task_vars)
|
terms = utils.template_ds(self.basedir, x[k], task_vars)
|
||||||
items = utils.plugins.lookup_loader.get(plugin_name, basedir=self.basedir, runner=None).run(terms, inject=task_vars)
|
items = utils.plugins.lookup_loader.get(plugin_name, basedir=self.basedir, runner=None).run(terms, inject=task_vars)
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
|
@ -119,7 +119,7 @@ class Play(object):
|
||||||
mv['item'] = item
|
mv['item'] = item
|
||||||
for t in tokens[1:]:
|
for t in tokens[1:]:
|
||||||
(k,v) = t.split("=", 1)
|
(k,v) = t.split("=", 1)
|
||||||
mv[k] = utils.varReplaceWithItems(self.basedir, v, mv)
|
mv[k] = utils.template_ds(self.basedir, v, mv)
|
||||||
include_file = utils.template(self.basedir, tokens[0], mv)
|
include_file = utils.template(self.basedir, tokens[0], mv)
|
||||||
data = utils.parse_yaml_from_file(utils.path_dwim(self.basedir, include_file))
|
data = utils.parse_yaml_from_file(utils.path_dwim(self.basedir, include_file))
|
||||||
for y in data:
|
for y in data:
|
||||||
|
|
|
@ -273,7 +273,7 @@ class Runner(object):
|
||||||
items_plugin = self.module_vars.get('items_lookup_plugin', None)
|
items_plugin = self.module_vars.get('items_lookup_plugin', None)
|
||||||
if items_plugin is not None and items_plugin in utils.plugins.lookup_loader:
|
if items_plugin is not None and items_plugin in utils.plugins.lookup_loader:
|
||||||
items_terms = self.module_vars.get('items_lookup_terms', '')
|
items_terms = self.module_vars.get('items_lookup_terms', '')
|
||||||
items_terms = utils.varReplaceWithItems(self.basedir, items_terms, inject)
|
items_terms = utils.template_ds(self.basedir, items_terms, inject)
|
||||||
items = utils.plugins.lookup_loader.get(items_plugin, runner=self, basedir=self.basedir).run(items_terms, inject=inject)
|
items = utils.plugins.lookup_loader.get(items_plugin, runner=self, basedir=self.basedir).run(items_terms, inject=inject)
|
||||||
if type(items) != list:
|
if type(items) != list:
|
||||||
raise errors.AnsibleError("lookup plugins have to return a list: %r" % items)
|
raise errors.AnsibleError("lookup plugins have to return a list: %r" % items)
|
||||||
|
|
|
@ -183,8 +183,8 @@ def _varReplaceLookups(basedir, raw, vars):
|
||||||
|
|
||||||
return ''.join(done)
|
return ''.join(done)
|
||||||
|
|
||||||
def varReplaceWithItems(basedir, varname, vars):
|
def template_ds(basedir, varname, vars):
|
||||||
''' helper function used by with_items '''
|
''' templates a data structure by traversing it and substituting for other data structures '''
|
||||||
|
|
||||||
if isinstance(varname, basestring):
|
if isinstance(varname, basestring):
|
||||||
m = _varFind(varname, vars)
|
m = _varFind(varname, vars)
|
||||||
|
@ -192,17 +192,17 @@ def varReplaceWithItems(basedir, varname, vars):
|
||||||
return varname
|
return varname
|
||||||
if m['start'] == 0 and m['end'] == len(varname):
|
if m['start'] == 0 and m['end'] == len(varname):
|
||||||
if m['replacement'] is not None:
|
if m['replacement'] is not None:
|
||||||
return varReplaceWithItems(basedir, m['replacement'], vars)
|
return template_ds(basedir, m['replacement'], vars)
|
||||||
else:
|
else:
|
||||||
return varname
|
return varname
|
||||||
else:
|
else:
|
||||||
return template(basedir, varname, vars)
|
return template(basedir, varname, vars)
|
||||||
elif isinstance(varname, (list, tuple)):
|
elif isinstance(varname, (list, tuple)):
|
||||||
return [varReplaceWithItems(basedir, v, vars) for v in varname]
|
return [template_ds(basedir, v, vars) for v in varname]
|
||||||
elif isinstance(varname, dict):
|
elif isinstance(varname, dict):
|
||||||
d = {}
|
d = {}
|
||||||
for (k, v) in varname.iteritems():
|
for (k, v) in varname.iteritems():
|
||||||
d[k] = varReplaceWithItems(basedir, v, vars)
|
d[k] = template_ds(basedir, v, vars)
|
||||||
return d
|
return d
|
||||||
else:
|
else:
|
||||||
return varname
|
return varname
|
||||||
|
|
|
@ -204,6 +204,27 @@ class TestPlaybook(unittest.TestCase):
|
||||||
|
|
||||||
assert len(EVENTS) == 44
|
assert len(EVENTS) == 44
|
||||||
|
|
||||||
|
def test_includes(self):
|
||||||
|
pb = os.path.join(self.test_dir, 'playbook-includer.yml')
|
||||||
|
actual = self._run(pb)
|
||||||
|
|
||||||
|
# if different, this will output to screen
|
||||||
|
print "**ACTUAL**"
|
||||||
|
print utils.jsonify(actual, format=True)
|
||||||
|
expected = {
|
||||||
|
"localhost": {
|
||||||
|
"changed": 0,
|
||||||
|
"failures": 0,
|
||||||
|
"ok": 5,
|
||||||
|
"skipped": 0,
|
||||||
|
"unreachable": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
print "**EXPECTED**"
|
||||||
|
print utils.jsonify(expected, format=True)
|
||||||
|
|
||||||
|
assert utils.jsonify(expected, format=True) == utils.jsonify(actual,format=True)
|
||||||
|
|
||||||
def test_playbook_vars(self):
|
def test_playbook_vars(self):
|
||||||
test_callbacks = TestCallbacks()
|
test_callbacks = TestCallbacks()
|
||||||
playbook = ansible.playbook.PlayBook(
|
playbook = ansible.playbook.PlayBook(
|
||||||
|
|
|
@ -286,9 +286,9 @@ class TestUtils(unittest.TestCase):
|
||||||
assert res == u'hello world world'
|
assert res == u'hello world world'
|
||||||
|
|
||||||
#####################################
|
#####################################
|
||||||
### varReplaceWithItems function tests
|
### template_ds function tests
|
||||||
|
|
||||||
def test_varReplaceWithItems_basic(self):
|
def test_template_ds_basic(self):
|
||||||
vars = {
|
vars = {
|
||||||
'data': {
|
'data': {
|
||||||
'var': [
|
'var': [
|
||||||
|
@ -313,19 +313,19 @@ class TestUtils(unittest.TestCase):
|
||||||
}
|
}
|
||||||
|
|
||||||
template = '${data.var}'
|
template = '${data.var}'
|
||||||
res = ansible.utils.varReplaceWithItems(None, template, vars)
|
res = ansible.utils.template_ds(None, template, vars)
|
||||||
assert sorted(res) == sorted(vars['data']['var'])
|
assert sorted(res) == sorted(vars['data']['var'])
|
||||||
|
|
||||||
template = '${data.types}'
|
template = '${data.types}'
|
||||||
res = ansible.utils.varReplaceWithItems(None, template, vars)
|
res = ansible.utils.template_ds(None, template, vars)
|
||||||
assert sorted(res) == sorted(vars['data']['types'])
|
assert sorted(res) == sorted(vars['data']['types'])
|
||||||
|
|
||||||
template = '${data.alphas}'
|
template = '${data.alphas}'
|
||||||
res = ansible.utils.varReplaceWithItems(None, template, vars)
|
res = ansible.utils.template_ds(None, template, vars)
|
||||||
assert sorted(res) == sorted(vars['alphas'])
|
assert sorted(res) == sorted(vars['alphas'])
|
||||||
|
|
||||||
template = '${data.nonexisting}'
|
template = '${data.nonexisting}'
|
||||||
res = ansible.utils.varReplaceWithItems(None, template, vars)
|
res = ansible.utils.template_ds(None, template, vars)
|
||||||
assert res == template
|
assert res == template
|
||||||
|
|
||||||
#####################################
|
#####################################
|
||||||
|
|
5
test/playbook-included.yml
Normal file
5
test/playbook-included.yml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
---
|
||||||
|
- hosts: all
|
||||||
|
gather_facts: False
|
||||||
|
tasks:
|
||||||
|
- action: debug msg="$variable"
|
8
test/playbook-includer.yml
Normal file
8
test/playbook-includer.yml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
- include: playbook-included.yml variable=foobar
|
||||||
|
- include: playbook-included.yml variable=foofoo
|
||||||
|
- include: playbook-included.yml variable=$item
|
||||||
|
with_items:
|
||||||
|
- foo
|
||||||
|
- bar
|
||||||
|
- baz
|
Loading…
Reference in a new issue