Merge pull request #1053 from dhozac/varreplace-include

Allow including files through variables
This commit is contained in:
Michael DeHaan 2012-09-18 17:53:02 -07:00
commit 544a8e44d7
10 changed files with 81 additions and 30 deletions

View file

@ -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)
# ***************************************************** # *****************************************************

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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:

View file

@ -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 '''

View file

@ -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
View file

@ -0,0 +1 @@
world