diff --git a/lib/ansible/parsing/yaml/dumper.py b/lib/ansible/parsing/yaml/dumper.py index f04c3200f2e1e8..f1d3d8c8a4e5b6 100644 --- a/lib/ansible/parsing/yaml/dumper.py +++ b/lib/ansible/parsing/yaml/dumper.py @@ -24,7 +24,7 @@ from ansible.parsing.yaml.objects import AnsibleUnicode, AnsibleSequence, AnsibleMapping from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode -from ansible.vars.hostvars import HostVars +from ansible.vars.hostvars import HostVars, HostVarsVars from ansible.vars.unsafe_proxy import AnsibleUnsafeText @@ -62,6 +62,11 @@ def represent_vault_encrypted_unicode(self, data): represent_hostvars, ) +AnsibleDumper.add_representer( + HostVarsVars, + represent_hostvars, +) + AnsibleDumper.add_representer( AnsibleSequence, yaml.representer.SafeRepresenter.represent_list, diff --git a/lib/ansible/plugins/filter/core.py b/lib/ansible/plugins/filter/core.py index d2ca0f1f535430..9d66e68ecb6b8d 100644 --- a/lib/ansible/plugins/filter/core.py +++ b/lib/ansible/plugins/filter/core.py @@ -53,7 +53,7 @@ from ansible.utils.hashing import md5s, checksum_s from ansible.utils.unicode import unicode_wrap from ansible.utils.vars import merge_hash -from ansible.vars.hostvars import HostVars +from ansible.vars.hostvars import HostVars, HostVarsVars UUID_NAMESPACE_ANSIBLE = uuid.UUID('361E6D51-FAEC-444A-9079-341386DA8E2E') @@ -64,7 +64,7 @@ class AnsibleJSONEncoder(json.JSONEncoder): types like HostVars ''' def default(self, o): - if isinstance(o, HostVars): + if isinstance(o, (HostVars, HostVarsVars)): return dict(o) else: return super(AnsibleJSONEncoder, self).default(o) diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py index b710d47f3befd8..94d662d419b4b0 100644 --- a/lib/ansible/template/__init__.py +++ b/lib/ansible/template/__init__.py @@ -46,6 +46,8 @@ from ansible.template.vars import AnsibleJ2Vars from ansible.vars.unsafe_proxy import UnsafeProxy, wrap_var +from collections import Sequence, Mapping + try: from hashlib import sha1 except ImportError: @@ -324,7 +326,7 @@ def _clean_data(self, orig_data): clean_list.append(self._clean_data(list_item)) ret = clean_list - elif isinstance(orig_data, dict): + elif isinstance(orig_data, (dict, Mapping)): clean_dict = {} for k in orig_data: clean_dict[self._clean_data(k)] = self._clean_data(orig_data[k]) @@ -467,7 +469,7 @@ def template(self, variable, convert_bare=False, preserve_trailing_newlines=True overrides=overrides, disable_lookups=disable_lookups, ) for v in variable] - elif isinstance(variable, dict): + elif isinstance(variable, (dict, Mapping)): d = {} # we don't use iteritems() here to avoid problems if the underlying dict # changes sizes due to the templating, which can happen with hostvars diff --git a/lib/ansible/vars/hostvars.py b/lib/ansible/vars/hostvars.py index 0b34248a54afb3..87bef86838d558 100644 --- a/lib/ansible/vars/hostvars.py +++ b/lib/ansible/vars/hostvars.py @@ -38,7 +38,7 @@ except ImportError: from sha import sha as sha1 -__all__ = ['HostVars'] +__all__ = ['HostVars', 'HostVarsVars'] # Note -- this is a Mapping, not a MutableMapping class HostVars(collections.Mapping): @@ -79,14 +79,9 @@ def raw_get(self, host_name): def __getitem__(self, host_name): data = self.raw_get(host_name) - sha1_hash = sha1(str(data).encode('utf-8')).hexdigest() - if sha1_hash in self._cached_result: - result = self._cached_result[sha1_hash] - else: - templar = Templar(variables=data, loader=self._loader) - result = templar.template(data, fail_on_undefined=False, static_vars=STATIC_VARS) - self._cached_result[sha1_hash] = result - return result + if isinstance(data, Undefined): + return data + return HostVarsVars(data, loader=self._loader) def set_host_variable(self, host, varname, value): self._variable_manager.set_host_variable(host, varname, value) @@ -113,3 +108,28 @@ def __repr__(self): name = host.name out[name] = self.get(name) return repr(out) + + +class HostVarsVars(collections.Mapping): + + def __init__(self, variables, loader): + self._vars = variables + self._loader = loader + + def __getitem__(self, var): + templar = Templar(variables=self._vars, loader=self._loader) + foo = templar.template(self._vars[var], fail_on_undefined=False, static_vars=STATIC_VARS) + return foo + + def __contains__(self, var): + return (var in self._vars) + + def __iter__(self): + for var in self._vars.keys(): + yield var + + def __len__(self): + return len(self._vars.keys()) + + def __repr__(self): + return repr(self._vars)