Repository: ansible/ansible

Issue:
support improved varReplace syntax
Currently we have to break into Jinja to get nested variables 

{{ somevar["key"]["value"] }}

which is too much like Python

we can do $foo for simple variables

Proposing we also allow this:

${somevar.key.value}



Discussion:
once this is done utils.template should default to no_engine=True

except in the template module in Runner.py

I have closed #301 as this will be the way we fix that bug, so we only need this ticket open.

accidentally closed this one.   still need this, and can then move to default to no_engine=False as well.


Resolving patch:
diff --git a/lib/ansible/runner.py b/lib/ansible/runner.py
--- a/lib/ansible/runner.py
+++ b/lib/ansible/runner.py
@@ -545,7 +545,7 @@ def _execute_template(self, conn, host, tmp):
         source_data = file(utils.path_dwim(self.basedir, source)).read()
         resultant = ''            
         try:
-            resultant = utils.template(source_data, inject, self.setup_cache)
+            resultant = utils.template(source_data, inject, self.setup_cache, no_engine=False)
         except Exception, e:
             return (host, False, dict(failed=True, msg=str(e)), '')
         xfered = self._transfer_str(conn, tmp, 'source', resultant)
diff --git a/lib/ansible/utils.py b/lib/ansible/utils.py
--- a/lib/ansible/utils.py
+++ b/lib/ansible/utils.py
@@ -198,7 +198,19 @@ def parse_json(data):
             return { "failed" : True, "parsed" : False, "msg" : data }
         return results
 
-_KEYCRE = re.compile(r"\$(\w+)")
+def varLookup(name, vars):
+    ''' find the contents of a possibly complex variable in vars. '''
+    path = name.split('.')
+    space = vars
+    for part in path:
+        if part in space:
+            space = space[part]
+        else:
+            return
+    return space
+
+_KEYCRE = re.compile(r"\$(?P<complex>\{){0,1}((?(complex)[\w\.]+|\w+))(?(complex)\})")
+#                        if { -> complex     if complex, allow . and need trailing }
 
 def varReplace(raw, vars):
     '''Perform variable replacement of $vars
@@ -220,8 +232,9 @@ def varReplace(raw, vars):
 
         # Determine replacement value (if unknown variable then preserve
         # original)
-        varname = m.group(1).lower()
-        replacement = str(vars.get(varname, m.group()))
+        varname = m.group(2).lower()
+
+        replacement = str(varLookup(varname, vars) or m.group())
 
         start, end = m.span()
         done.append(raw[:start])    # Keep stuff leading up to token
@@ -230,7 +243,7 @@ def varReplace(raw, vars):
 
     return ''.join(done)
 
-def template(text, vars, setup_cache, no_engine=False):
+def template(text, vars, setup_cache, no_engine=True):
     ''' run a text buffer through the templating engine '''
     vars = vars.copy()
     text = varReplace(str(text), vars)


Test patch:
diff --git a/test/TestUtils.py b/test/TestUtils.py
new file mode 100644
--- /dev/null
+++ b/test/TestUtils.py
@@ -0,0 +1,155 @@
+import os
+import unittest
+
+import ansible.utils
+
+class TestUtils(unittest.TestCase):
+
+    #####################################
+    ### varLookup function tests
+
+    def test_varLookup_list(self):
+        vars = {
+            'data': {
+                'who': ['joe', 'jack', 'jeff']
+            }
+        }
+
+        res = ansible.utils.varLookup('data.who', vars)
+
+        assert sorted(res) == sorted(vars['data']['who'])
+
+    #####################################
+    ### varReplace function tests
+
+    def test_varReplace_simple(self):
+        template = 'hello $who'
+        vars = {
+            'who': 'world',
+        }
+
+        res = ansible.utils.varReplace(template, vars)
+
+        assert res == 'hello world'
+
+    def test_varReplace_multiple(self):
+        template = '$what $who'
+        vars = {
+            'what': 'hello',
+            'who': 'world',
+        }
+
+        res = ansible.utils.varReplace(template, vars)
+
+        assert res == 'hello world'
+
+    def test_varReplace_middle(self):
+        template = 'hello $who!'
+        vars = {
+            'who': 'world',
+        }
+
+        res = ansible.utils.varReplace(template, vars)
+
+        assert res == 'hello world!'
+
+    def test_varReplace_alternative(self):
+        template = 'hello ${who}'
+        vars = {
+            'who': 'world',
+        }
+
+        res = ansible.utils.varReplace(template, vars)
+
+        assert res == 'hello world'
+
+    def test_varReplace_almost_alternative(self):
+        template = 'hello $who}'
+        vars = {
+            'who': 'world',
+        }
+
+        res = ansible.utils.varReplace(template, vars)
+
+        assert res == 'hello world}'
+
+    def test_varReplace_almost_alternative2(self):
+        template = 'hello ${who'
+        vars = {
+            'who': 'world',
+        }
+
+        res = ansible.utils.varReplace(template, vars)
+
+        assert res == template
+
+    def test_varReplace_alternative_greed(self):
+        template = 'hello ${who} }'
+        vars = {
+            'who': 'world',
+        }
+
+        res = ansible.utils.varReplace(template, vars)
+
+        assert res == 'hello world }'
+
+    def test_varReplace_notcomplex(self):
+        template = 'hello $mydata.who'
+        vars = {
+            'data': {
+                'who': 'world',
+            },
+        }
+
+        res = ansible.utils.varReplace(template, vars)
+
+        print res
+        assert res == template
+
+    def test_varReplace_nested(self):
+        template = 'hello ${data.who}'
+        vars = {
+            'data': {
+                'who': 'world'
+            },
+        }
+
+        res = ansible.utils.varReplace(template, vars)
+
+        assert res == 'hello world'
+
+    def test_varReplace_nested_int(self):
+        template = '$what ${data.who}'
+        vars = {
+            'data': {
+                'who': 2
+            },
+            'what': 'hello',
+        }
+
+        res = ansible.utils.varReplace(template, vars)
+
+        assert res == 'hello 2'
+
+    #####################################
+    ### Template function tests
+
+    def test_template_basic(self):
+        template = 'hello {{ who }}'
+        vars = {
+            'who': 'world',
+        }
+
+        res = ansible.utils.template(template, vars, {}, no_engine=False)
+
+        assert res == 'hello world'
+
+    def test_template_whitespace(self):
+        template = 'hello {{ who }}\n'
+        vars = {
+            'who': 'world',
+        }
+
+        res = ansible.utils.template(template, vars, {}, no_engine=False)
+
+        assert res == 'hello world\n'