1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
# (c) 2012, Michael DeHaan <michael.dehaan@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
# Make coding more python3-ish
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
from jinja2.utils import missing
from ansible.errors import AnsibleError, AnsibleUndefinedVariable
from ansible.module_utils.six import iteritems
from ansible.module_utils._text import to_native
from ansible.module_utils.common._collections_compat import Mapping
__all__ = ['AnsibleJ2Vars']
class AnsibleJ2Vars(Mapping):
'''
Helper class to template all variable content before jinja2 sees it. This is
done by hijacking the variable storage that jinja2 uses, and overriding __contains__
and __getitem__ to look like a dict. Added bonus is avoiding duplicating the large
hashes that inject tends to be.
To facilitate using builtin jinja2 things like range, globals are also handled here.
'''
def __init__(self, templar, globals, locals=None, *extras):
'''
Initializes this object with a valid Templar() object, as
well as several dictionaries of variables representing
different scopes (in jinja2 terminology).
'''
self._templar = templar
self._globals = globals
self._extras = extras
self._locals = dict()
if isinstance(locals, dict):
for key, val in iteritems(locals):
if val is not missing:
if key[:2] == 'l_':
self._locals[key[2:]] = val
elif key not in ('context', 'environment', 'template'):
self._locals[key] = val
def __contains__(self, k):
if k in self._templar.available_variables:
return True
if k in self._locals:
return True
for i in self._extras:
if k in i:
return True
if k in self._globals:
return True
return False
def __iter__(self):
keys = set()
keys.update(self._templar.available_variables, self._locals, self._globals, *self._extras)
return iter(keys)
def __len__(self):
keys = set()
keys.update(self._templar.available_variables, self._locals, self._globals, *self._extras)
return len(keys)
def __getitem__(self, varname):
if varname not in self._templar.available_variables:
if varname in self._locals:
return self._locals[varname]
for i in self._extras:
if varname in i:
return i[varname]
if varname in self._globals:
return self._globals[varname]
else:
raise KeyError("undefined variable: %s" % varname)
variable = self._templar.available_variables[varname]
# HostVars is special, return it as-is, as is the special variable
# 'vars', which contains the vars structure
from ansible.vars.hostvars import HostVars
if isinstance(variable, dict) and varname == "vars" or isinstance(variable, HostVars) or hasattr(variable, '__UNSAFE__'):
return variable
else:
value = None
try:
value = self._templar.template(variable)
except AnsibleUndefinedVariable as e:
raise AnsibleUndefinedVariable("%s: %s" % (to_native(variable), e.message))
except Exception as e:
msg = getattr(e, 'message', None) or to_native(e)
raise AnsibleError("An unhandled exception occurred while templating '%s'. "
"Error was a %s, original message: %s" % (to_native(variable), type(e), msg))
return value
def add_locals(self, locals):
'''
If locals are provided, create a copy of self containing those
locals in addition to what is already in this variable proxy.
'''
if locals is None:
return self
# FIXME run this only on jinja2>=2.9?
# prior to version 2.9, locals contained all of the vars and not just the current
# local vars so this was not necessary for locals to propagate down to nested includes
new_locals = self._locals.copy()
new_locals.update(locals)
return AnsibleJ2Vars(self._templar, self._globals, locals=new_locals, *self._extras)
|