From dae737c8b714f2c6d28663ef0afeab10e6d3a667 Mon Sep 17 00:00:00 2001 From: Andrew Gaffney Date: Thu, 8 Feb 2018 10:17:28 -0700 Subject: [PATCH] Only template each hostvars var on-demand (fixes #33259) --- lib/ansible/parsing/yaml/dumper.py | 7 +++++- lib/ansible/plugins/filter/core.py | 4 ++-- lib/ansible/template/__init__.py | 6 ++--- lib/ansible/vars/hostvars.py | 35 +++++++++++++++++++++++++----- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/lib/ansible/parsing/yaml/dumper.py b/lib/ansible/parsing/yaml/dumper.py index 8d8ffe5701e..155d42986ca 100644 --- a/lib/ansible/parsing/yaml/dumper.py +++ b/lib/ansible/parsing/yaml/dumper.py @@ -24,7 +24,7 @@ import yaml from ansible.module_utils.six import PY3 from ansible.parsing.yaml.objects import AnsibleUnicode, AnsibleSequence, AnsibleMapping, AnsibleVaultEncryptedUnicode from ansible.utils.unsafe_proxy import AnsibleUnsafeText -from ansible.vars.hostvars import HostVars +from ansible.vars.hostvars import HostVars, HostVarsVars class AnsibleDumper(yaml.SafeDumper): @@ -63,6 +63,11 @@ AnsibleDumper.add_representer( 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 d3f2e1424c2..8b9ff7a4c9e 100644 --- a/lib/ansible/plugins/filter/core.py +++ b/lib/ansible/plugins/filter/core.py @@ -55,7 +55,7 @@ from ansible.parsing.yaml.dumper import AnsibleDumper 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') @@ -67,7 +67,7 @@ class AnsibleJSONEncoder(json.JSONEncoder): types like HostVars ''' def default(self, o): - if isinstance(o, HostVars): + if isinstance(o, (HostVars, HostVarsVars)): return dict(o) elif isinstance(o, (datetime.date, datetime.datetime)): return o.isoformat() diff --git a/lib/ansible/template/__init__.py b/lib/ansible/template/__init__.py index 4cd9ee8b3f2..1241ed78432 100644 --- a/lib/ansible/template/__init__.py +++ b/lib/ansible/template/__init__.py @@ -27,7 +27,7 @@ import pwd import re import time -from collections import Sequence +from collections import Sequence, Mapping from functools import wraps from io import StringIO from numbers import Number @@ -356,7 +356,7 @@ class Templar: 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]) @@ -509,7 +509,7 @@ class Templar: 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 d1f1668cca0..4f358737ee2 100644 --- a/lib/ansible/vars/hostvars.py +++ b/lib/ansible/vars/hostvars.py @@ -47,7 +47,7 @@ try: except ImportError: from sha import sha as sha1 -__all__ = ['HostVars'] +__all__ = ['HostVars', 'HostVarsVars'] # Note -- this is a Mapping, not a MutableMapping @@ -86,11 +86,9 @@ class HostVars(collections.Mapping): def __getitem__(self, host_name): data = self.raw_get(host_name) - sha1_hash = sha1(to_bytes(data)).hexdigest() - if sha1_hash not in self._cached_result: - templar = Templar(variables=data, loader=self._loader) - self._cached_result[sha1_hash] = templar.template(data, fail_on_undefined=False, static_vars=STATIC_VARS) - return self._cached_result[sha1_hash] + 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) @@ -117,3 +115,28 @@ class HostVars(collections.Mapping): for host in self._inventory.hosts: out[host] = self.get(host) 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)