1# Copyright: (c) 2014, Michael DeHaan <michael.dehaan@gmail.com> 2# Copyright: (c) 2018, Ansible Project 3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 4 5 6from __future__ import (absolute_import, division, print_function) 7__metaclass__ = type 8 9 10from ansible import constants as C 11from ansible.errors import AnsibleError 12from ansible.module_utils.common._collections_compat import MutableMapping 13from ansible.plugins.loader import cache_loader 14from ansible.utils.display import Display 15 16 17display = Display() 18 19 20class FactCache(MutableMapping): 21 22 def __init__(self, *args, **kwargs): 23 24 self._plugin = cache_loader.get(C.CACHE_PLUGIN) 25 if not self._plugin: 26 raise AnsibleError('Unable to load the facts cache plugin (%s).' % (C.CACHE_PLUGIN)) 27 28 super(FactCache, self).__init__(*args, **kwargs) 29 30 def __getitem__(self, key): 31 if not self._plugin.contains(key): 32 raise KeyError 33 return self._plugin.get(key) 34 35 def __setitem__(self, key, value): 36 self._plugin.set(key, value) 37 38 def __delitem__(self, key): 39 self._plugin.delete(key) 40 41 def __contains__(self, key): 42 return self._plugin.contains(key) 43 44 def __iter__(self): 45 return iter(self._plugin.keys()) 46 47 def __len__(self): 48 return len(self._plugin.keys()) 49 50 def copy(self): 51 """ Return a primitive copy of the keys and values from the cache. """ 52 return dict(self) 53 54 def keys(self): 55 return self._plugin.keys() 56 57 def flush(self): 58 """ Flush the fact cache of all keys. """ 59 self._plugin.flush() 60 61 def first_order_merge(self, key, value): 62 host_facts = {key: value} 63 64 try: 65 host_cache = self._plugin.get(key) 66 if host_cache: 67 host_cache.update(value) 68 host_facts[key] = host_cache 69 except KeyError: 70 pass 71 72 super(FactCache, self).update(host_facts) 73 74 def update(self, *args): 75 """ 76 Backwards compat shim 77 78 We thought we needed this to ensure we always called the plugin's set() method but 79 MutableMapping.update() will call our __setitem__() just fine. It's the calls to update 80 that we need to be careful of. This contains a bug:: 81 82 fact_cache[host.name].update(facts) 83 84 It retrieves a *copy* of the facts for host.name and then updates the copy. So the changes 85 aren't persisted. 86 87 Instead we need to do:: 88 89 fact_cache.update({host.name, facts}) 90 91 Which will use FactCache's update() method. 92 93 We currently need this shim for backwards compat because the update() method that we had 94 implemented took key and value as arguments instead of taking a dict. We can remove the 95 shim in 2.12 as MutableMapping.update() should do everything that we need. 96 """ 97 if len(args) == 2: 98 # Deprecated. Call the new function with this name 99 display.deprecated('Calling FactCache().update(key, value) is deprecated. Use' 100 ' FactCache().first_order_merge(key, value) if you want the old' 101 ' behaviour or use FactCache().update({key: value}) if you want' 102 ' dict-like behaviour.', version='2.12', collection_name='ansible.builtin') 103 return self.first_order_merge(*args) 104 105 elif len(args) == 1: 106 host_facts = args[0] 107 108 else: 109 raise TypeError('update expected at most 1 argument, got {0}'.format(len(args))) 110 111 super(FactCache, self).update(host_facts) 112