Merge pull request #1053 from dhozac/varreplace-include
Allow including files through variables
This commit is contained in:
commit
544a8e44d7
10 changed files with 81 additions and 30 deletions
|
@ -265,7 +265,7 @@ class PlayBook(object):
|
||||||
for host, results in results.get('contacted',{}).iteritems():
|
for host, results in results.get('contacted',{}).iteritems():
|
||||||
if results.get('changed', False):
|
if results.get('changed', False):
|
||||||
for handler_name in task.notify:
|
for handler_name in task.notify:
|
||||||
self._flag_handler(play.handlers(), utils.template(handler_name, task.module_vars), host)
|
self._flag_handler(play.handlers(), utils.template(play.basedir, handler_name, task.module_vars), host)
|
||||||
|
|
||||||
# *****************************************************
|
# *****************************************************
|
||||||
|
|
||||||
|
|
|
@ -57,7 +57,7 @@ class Play(object):
|
||||||
raise errors.AnsibleError('hosts declaration is required')
|
raise errors.AnsibleError('hosts declaration is required')
|
||||||
elif isinstance(hosts, list):
|
elif isinstance(hosts, list):
|
||||||
hosts = ';'.join(hosts)
|
hosts = ';'.join(hosts)
|
||||||
hosts = utils.template(hosts, playbook.extra_vars)
|
hosts = utils.template(basedir, hosts, playbook.extra_vars)
|
||||||
self._ds = ds
|
self._ds = ds
|
||||||
self.playbook = playbook
|
self.playbook = playbook
|
||||||
self.basedir = basedir
|
self.basedir = basedir
|
||||||
|
@ -69,7 +69,7 @@ class Play(object):
|
||||||
self.vars = self._get_vars()
|
self.vars = self._get_vars()
|
||||||
self._tasks = ds.get('tasks', [])
|
self._tasks = ds.get('tasks', [])
|
||||||
self._handlers = ds.get('handlers', [])
|
self._handlers = ds.get('handlers', [])
|
||||||
self.remote_user = utils.template(ds.get('user', self.playbook.remote_user), playbook.extra_vars)
|
self.remote_user = utils.template(basedir, ds.get('user', self.playbook.remote_user), playbook.extra_vars)
|
||||||
self.remote_port = ds.get('port', self.playbook.remote_port)
|
self.remote_port = ds.get('port', self.playbook.remote_port)
|
||||||
self.sudo = ds.get('sudo', self.playbook.sudo)
|
self.sudo = ds.get('sudo', self.playbook.sudo)
|
||||||
self.sudo_user = ds.get('sudo_user', self.playbook.sudo_user)
|
self.sudo_user = ds.get('sudo_user', self.playbook.sudo_user)
|
||||||
|
@ -106,8 +106,8 @@ class Play(object):
|
||||||
tokens = shlex.split(x['include'])
|
tokens = shlex.split(x['include'])
|
||||||
for t in tokens[1:]:
|
for t in tokens[1:]:
|
||||||
(k,v) = t.split("=", 1)
|
(k,v) = t.split("=", 1)
|
||||||
task_vars[k] = utils.template(v, task_vars)
|
task_vars[k] = utils.template(self.basedir, v, task_vars)
|
||||||
include_file = utils.template(tokens[0], task_vars)
|
include_file = utils.template(self.basedir, tokens[0], task_vars)
|
||||||
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))
|
||||||
elif type(x) == dict:
|
elif type(x) == dict:
|
||||||
data = [x]
|
data = [x]
|
||||||
|
@ -261,10 +261,10 @@ class Play(object):
|
||||||
found = False
|
found = False
|
||||||
sequence = []
|
sequence = []
|
||||||
for real_filename in filename:
|
for real_filename in filename:
|
||||||
filename2 = utils.template(real_filename, self.vars)
|
filename2 = utils.template(self.basedir, real_filename, self.vars)
|
||||||
filename3 = filename2
|
filename3 = filename2
|
||||||
if host is not None:
|
if host is not None:
|
||||||
filename3 = utils.template(filename2, self.playbook.SETUP_CACHE[host])
|
filename3 = utils.template(self.basedir, filename2, self.playbook.SETUP_CACHE[host])
|
||||||
filename4 = utils.path_dwim(self.basedir, filename3)
|
filename4 = utils.path_dwim(self.basedir, filename3)
|
||||||
sequence.append(filename4)
|
sequence.append(filename4)
|
||||||
if os.path.exists(filename4):
|
if os.path.exists(filename4):
|
||||||
|
@ -294,10 +294,10 @@ class Play(object):
|
||||||
else:
|
else:
|
||||||
# just one filename supplied, load it!
|
# just one filename supplied, load it!
|
||||||
|
|
||||||
filename2 = utils.template(filename, self.vars)
|
filename2 = utils.template(self.basedir, filename, self.vars)
|
||||||
filename3 = filename2
|
filename3 = filename2
|
||||||
if host is not None:
|
if host is not None:
|
||||||
filename3 = utils.template(filename2, self.playbook.SETUP_CACHE[host])
|
filename3 = utils.template(self.basedir, filename2, self.playbook.SETUP_CACHE[host])
|
||||||
filename4 = utils.path_dwim(self.basedir, filename3)
|
filename4 = utils.path_dwim(self.basedir, filename3)
|
||||||
if self._has_vars_in(filename4):
|
if self._has_vars_in(filename4):
|
||||||
return
|
return
|
||||||
|
|
|
@ -103,8 +103,8 @@ class Task(object):
|
||||||
# allow the user to list comma delimited tags
|
# allow the user to list comma delimited tags
|
||||||
import_tags = import_tags.split(",")
|
import_tags = import_tags.split(",")
|
||||||
|
|
||||||
self.name = utils.template(self.name, self.module_vars)
|
self.name = utils.template(None, self.name, self.module_vars)
|
||||||
self.action = utils.template(self.action, self.module_vars)
|
self.action = utils.template(None, self.action, self.module_vars)
|
||||||
|
|
||||||
# handle mutually incompatible options
|
# handle mutually incompatible options
|
||||||
if self.with_items is not None and self.first_available_file is not None:
|
if self.with_items is not None and self.first_available_file is not None:
|
||||||
|
|
|
@ -200,7 +200,7 @@ class Runner(object):
|
||||||
|
|
||||||
cmd = ""
|
cmd = ""
|
||||||
if not is_new_style:
|
if not is_new_style:
|
||||||
args = utils.template(args, inject)
|
args = utils.template(self.basedir, args, inject)
|
||||||
argsfile = self._transfer_str(conn, tmp, 'arguments', args)
|
argsfile = self._transfer_str(conn, tmp, 'arguments', args)
|
||||||
if async_jid is None:
|
if async_jid is None:
|
||||||
cmd = "%s %s" % (remote_module_path, argsfile)
|
cmd = "%s %s" % (remote_module_path, argsfile)
|
||||||
|
@ -258,7 +258,7 @@ class Runner(object):
|
||||||
|
|
||||||
items = self.module_vars.get('items', [])
|
items = self.module_vars.get('items', [])
|
||||||
if isinstance(items, basestring) and items.startswith("$"):
|
if isinstance(items, basestring) and items.startswith("$"):
|
||||||
items = utils.varLookup(items, inject)
|
items = utils.varLookup(self.basedir, items, inject)
|
||||||
if type(items) != list:
|
if type(items) != list:
|
||||||
raise errors.AnsibleError("with_items only takes a list: %s" % items)
|
raise errors.AnsibleError("with_items only takes a list: %s" % items)
|
||||||
|
|
||||||
|
@ -327,13 +327,13 @@ class Runner(object):
|
||||||
for (k,v) in self.module_args.iteritems():
|
for (k,v) in self.module_args.iteritems():
|
||||||
new_args = new_args + "%s='%s' " % (k,v)
|
new_args = new_args + "%s='%s' " % (k,v)
|
||||||
self.module_args = new_args
|
self.module_args = new_args
|
||||||
self.module_args = utils.template(self.module_args, inject)
|
self.module_args = utils.template(self.basedir, self.module_args, inject)
|
||||||
|
|
||||||
def _check_conditional(conditional):
|
def _check_conditional(conditional):
|
||||||
def is_set(var):
|
def is_set(var):
|
||||||
return not var.startswith("$")
|
return not var.startswith("$")
|
||||||
return eval(conditional)
|
return eval(conditional)
|
||||||
conditional = utils.template(self.conditional, inject)
|
conditional = utils.template(self.basedir, self.conditional, inject)
|
||||||
if not _check_conditional(conditional):
|
if not _check_conditional(conditional):
|
||||||
result = utils.jsonify(dict(skipped=True))
|
result = utils.jsonify(dict(skipped=True))
|
||||||
self.callbacks.on_skipped(host, inject.get('item',None))
|
self.callbacks.on_skipped(host, inject.get('item',None))
|
||||||
|
@ -352,7 +352,7 @@ class Runner(object):
|
||||||
result = dict(failed=True, msg="FAILED: %s" % str(e))
|
result = dict(failed=True, msg="FAILED: %s" % str(e))
|
||||||
return ReturnData(host=host, comm_ok=False, result=result)
|
return ReturnData(host=host, comm_ok=False, result=result)
|
||||||
|
|
||||||
module_name = utils.template(self.module_name, inject)
|
module_name = utils.template(self.basedir, self.module_name, inject)
|
||||||
|
|
||||||
tmp = ''
|
tmp = ''
|
||||||
if self.module_name != 'raw':
|
if self.module_name != 'raw':
|
||||||
|
@ -499,7 +499,7 @@ class Runner(object):
|
||||||
if module_common.REPLACER in module_data:
|
if module_common.REPLACER in module_data:
|
||||||
is_new_style=True
|
is_new_style=True
|
||||||
module_data = module_data.replace(module_common.REPLACER, module_common.MODULE_COMMON)
|
module_data = module_data.replace(module_common.REPLACER, module_common.MODULE_COMMON)
|
||||||
encoded_args = "\"\"\"%s\"\"\"" % utils.template(self.module_args, inject).replace("\"","\\\"")
|
encoded_args = "\"\"\"%s\"\"\"" % utils.template(self.basedir, self.module_args, inject).replace("\"","\\\"")
|
||||||
module_data = module_data.replace(module_common.REPLACER_ARGS, encoded_args)
|
module_data = module_data.replace(module_common.REPLACER_ARGS, encoded_args)
|
||||||
|
|
||||||
# use the correct python interpreter for the host
|
# use the correct python interpreter for the host
|
||||||
|
|
|
@ -48,7 +48,7 @@ class ActionModule(object):
|
||||||
if 'first_available_file' in inject:
|
if 'first_available_file' in inject:
|
||||||
found = False
|
found = False
|
||||||
for fn in inject.get('first_available_file'):
|
for fn in inject.get('first_available_file'):
|
||||||
fn = utils.template(fn, inject)
|
fn = utils.template(self.runner.basedir, fn, inject)
|
||||||
if os.path.exists(fn):
|
if os.path.exists(fn):
|
||||||
source = fn
|
source = fn
|
||||||
found = True
|
found = True
|
||||||
|
@ -57,7 +57,7 @@ class ActionModule(object):
|
||||||
results=dict(failed=True, msg="could not find src in first_available_file list")
|
results=dict(failed=True, msg="could not find src in first_available_file list")
|
||||||
return ReturnData(conn=conn, results=results)
|
return ReturnData(conn=conn, results=results)
|
||||||
|
|
||||||
source = utils.template(source, inject)
|
source = utils.template(self.runner.basedir, source, inject)
|
||||||
source = utils.path_dwim(self.runner.basedir, source)
|
source = utils.path_dwim(self.runner.basedir, source)
|
||||||
|
|
||||||
local_md5 = utils.md5(source)
|
local_md5 = utils.md5(source)
|
||||||
|
|
|
@ -44,9 +44,9 @@ class ActionModule(object):
|
||||||
return ReturnData(conn=conn, result=results)
|
return ReturnData(conn=conn, result=results)
|
||||||
|
|
||||||
# apply templating to source argument
|
# apply templating to source argument
|
||||||
source = utils.template(source, inject)
|
source = utils.template(self.runner.basedir, source, inject)
|
||||||
# apply templating to dest argument
|
# apply templating to dest argument
|
||||||
dest = utils.template(dest, inject)
|
dest = utils.template(self.runner.basedir, dest, inject)
|
||||||
|
|
||||||
# files are saved in dest dir, with a subdir for each host, then the filename
|
# files are saved in dest dir, with a subdir for each host, then the filename
|
||||||
dest = "%s/%s/%s" % (utils.path_dwim(self.runner.basedir, dest), conn.host, source)
|
dest = "%s/%s/%s" % (utils.path_dwim(self.runner.basedir, dest), conn.host, source)
|
||||||
|
|
|
@ -51,7 +51,7 @@ class ActionModule(object):
|
||||||
if 'first_available_file' in inject:
|
if 'first_available_file' in inject:
|
||||||
found = False
|
found = False
|
||||||
for fn in self.runner.module_vars.get('first_available_file'):
|
for fn in self.runner.module_vars.get('first_available_file'):
|
||||||
fn = utils.template(fn, inject)
|
fn = utils.template(self.runner.basedir, fn, inject)
|
||||||
if os.path.exists(fn):
|
if os.path.exists(fn):
|
||||||
source = fn
|
source = fn
|
||||||
found = True
|
found = True
|
||||||
|
@ -60,7 +60,7 @@ class ActionModule(object):
|
||||||
result = dict(failed=True, msg="could not find src in first_available_file list")
|
result = dict(failed=True, msg="could not find src in first_available_file list")
|
||||||
return ReturnData(conn=conn, comm_ok=False, result=result)
|
return ReturnData(conn=conn, comm_ok=False, result=result)
|
||||||
|
|
||||||
source = utils.template(source, inject)
|
source = utils.template(self.runner.basedir, source, inject)
|
||||||
|
|
||||||
# template the source data locally & transfer
|
# template the source data locally & transfer
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -31,6 +31,7 @@ import time
|
||||||
import StringIO
|
import StringIO
|
||||||
import imp
|
import imp
|
||||||
import glob
|
import glob
|
||||||
|
import subprocess
|
||||||
|
|
||||||
VERBOSITY=0
|
VERBOSITY=0
|
||||||
|
|
||||||
|
@ -182,7 +183,7 @@ def _varLookup(name, vars):
|
||||||
_KEYCRE = re.compile(r"\$(?P<complex>\{){0,1}((?(complex)[\w\.\[\]]+|\w+))(?(complex)\})")
|
_KEYCRE = re.compile(r"\$(?P<complex>\{){0,1}((?(complex)[\w\.\[\]]+|\w+))(?(complex)\})")
|
||||||
|
|
||||||
def varLookup(varname, vars):
|
def varLookup(varname, vars):
|
||||||
''' helper function used by varReplace '''
|
''' helper function used by with_items '''
|
||||||
|
|
||||||
m = _KEYCRE.search(varname)
|
m = _KEYCRE.search(varname)
|
||||||
if not m:
|
if not m:
|
||||||
|
@ -206,10 +207,9 @@ def varReplace(raw, vars):
|
||||||
|
|
||||||
# Determine replacement value (if unknown variable then preserve
|
# Determine replacement value (if unknown variable then preserve
|
||||||
# original)
|
# original)
|
||||||
varname = m.group(2)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
replacement = unicode(_varLookup(varname, vars))
|
replacement = unicode(_varLookup(m.group(2), vars))
|
||||||
except VarNotFoundException:
|
except VarNotFoundException:
|
||||||
replacement = m.group()
|
replacement = m.group()
|
||||||
|
|
||||||
|
@ -220,7 +220,42 @@ def varReplace(raw, vars):
|
||||||
|
|
||||||
return ''.join(done)
|
return ''.join(done)
|
||||||
|
|
||||||
def template(text, vars):
|
_FILEPIPECRE = re.compile(r"\$(?P<special>FILE|PIPE)\{([^\}]+)\}")
|
||||||
|
def varReplaceFilesAndPipes(basedir, raw):
|
||||||
|
done = [] # Completed chunks to return
|
||||||
|
|
||||||
|
while raw:
|
||||||
|
m = _FILEPIPECRE.search(raw)
|
||||||
|
if not m:
|
||||||
|
done.append(raw)
|
||||||
|
break
|
||||||
|
|
||||||
|
# Determine replacement value (if unknown variable then preserve
|
||||||
|
# original)
|
||||||
|
|
||||||
|
if m.group(1) == "FILE":
|
||||||
|
try:
|
||||||
|
f = open(path_dwim(basedir, m.group(2)), "r")
|
||||||
|
except IOError:
|
||||||
|
raise VarNotFoundException()
|
||||||
|
replacement = f.read()
|
||||||
|
f.close()
|
||||||
|
elif m.group(1) == "PIPE":
|
||||||
|
p = subprocess.Popen(m.group(2), shell=True, stdout=subprocess.PIPE)
|
||||||
|
(stdout, stderr) = p.communicate()
|
||||||
|
if p.returncode != 0:
|
||||||
|
raise VarNotFoundException()
|
||||||
|
replacement = stdout
|
||||||
|
|
||||||
|
start, end = m.span()
|
||||||
|
done.append(raw[:start]) # Keep stuff leading up to token
|
||||||
|
done.append(replacement) # Append replacement value
|
||||||
|
raw = raw[end:] # Continue with remainder of string
|
||||||
|
|
||||||
|
return ''.join(done)
|
||||||
|
|
||||||
|
|
||||||
|
def template(basedir, text, vars):
|
||||||
''' run a text buffer through the templating engine until it no longer changes '''
|
''' run a text buffer through the templating engine until it no longer changes '''
|
||||||
|
|
||||||
prev_text = ''
|
prev_text = ''
|
||||||
|
@ -235,6 +270,7 @@ def template(text, vars):
|
||||||
raise errors.AnsibleError("template recursion depth exceeded")
|
raise errors.AnsibleError("template recursion depth exceeded")
|
||||||
prev_text = text
|
prev_text = text
|
||||||
text = varReplace(unicode(text), vars)
|
text = varReplace(unicode(text), vars)
|
||||||
|
text = varReplaceFilesAndPipes(basedir, text)
|
||||||
return text
|
return text
|
||||||
|
|
||||||
def template_from_file(basedir, path, vars):
|
def template_from_file(basedir, path, vars):
|
||||||
|
@ -251,7 +287,7 @@ def template_from_file(basedir, path, vars):
|
||||||
res = t.render(vars)
|
res = t.render(vars)
|
||||||
if data.endswith('\n') and not res.endswith('\n'):
|
if data.endswith('\n') and not res.endswith('\n'):
|
||||||
res = res + '\n'
|
res = res + '\n'
|
||||||
return template(res, vars)
|
return template(basedir, res, vars)
|
||||||
|
|
||||||
def parse_yaml(data):
|
def parse_yaml(data):
|
||||||
''' convert a yaml string to a data structure '''
|
''' convert a yaml string to a data structure '''
|
||||||
|
|
|
@ -16,7 +16,7 @@ class TestUtils(unittest.TestCase):
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
res = ansible.utils._varLookup('data.who', vars)
|
res = ansible.utils.varLookup('${data.who}', vars)
|
||||||
|
|
||||||
assert sorted(res) == sorted(vars['data']['who'])
|
assert sorted(res) == sorted(vars['data']['who'])
|
||||||
|
|
||||||
|
@ -209,10 +209,24 @@ class TestUtils(unittest.TestCase):
|
||||||
'person': 'one',
|
'person': 'one',
|
||||||
}
|
}
|
||||||
|
|
||||||
res = ansible.utils.template(template, vars)
|
res = ansible.utils.template(None, template, vars)
|
||||||
|
|
||||||
assert res == u'hello oh great one'
|
assert res == u'hello oh great one'
|
||||||
|
|
||||||
|
def test_varReplace_include(self):
|
||||||
|
template = 'hello $FILE{world}'
|
||||||
|
|
||||||
|
res = ansible.utils.template("test", template, {})
|
||||||
|
|
||||||
|
assert res == u'hello world\n'
|
||||||
|
|
||||||
|
def test_varReplace_include_script(self):
|
||||||
|
template = 'hello $PIPE{echo world}'
|
||||||
|
|
||||||
|
res = ansible.utils.template("test", template, {})
|
||||||
|
|
||||||
|
assert res == u'hello world\n'
|
||||||
|
|
||||||
#####################################
|
#####################################
|
||||||
### Template function tests
|
### Template function tests
|
||||||
|
|
||||||
|
|
1
test/world
Normal file
1
test/world
Normal file
|
@ -0,0 +1 @@
|
||||||
|
world
|
Loading…
Reference in a new issue