diff --git a/ansible.cfg b/ansible.cfg index 5b23c72be8df5131da8eb4c28469284e1f0a4b45..85718531b43ae6f125ba4919ca9c4cdaa5396da4 100644 --- a/ansible.cfg +++ b/ansible.cfg @@ -50,5 +50,8 @@ use_cpasswords = True cache = jsonfile # Time in second before the cache expired. 0 means never expire cache. -# Default is 120 seconds. -timeout = 120 +# Default is 24 hours. +timeout = 86400 + +# Default is 12 hours. +timeout_token = 43200 diff --git a/lookup_plugins/re2oapi.py b/lookup_plugins/re2oapi.py index 53d235554113bc6d37b807b8bd984746c243465e..e1f1041ba3733a2a966d6248e0e61a41a45392ca 100644 --- a/lookup_plugins/re2oapi.py +++ b/lookup_plugins/re2oapi.py @@ -30,38 +30,67 @@ from ansible.config.manager import ConfigManager # Ansible Logger to stdout display = Display() -# Number of seconds before expiration where renewing the token is done -TIME_FOR_RENEW = 120 # Default name of the file to store tokens. Path $HOME/{DEFAUlt_TOKEN_FILENAME} DEFAULT_TOKEN_FILENAME = '.re2o.token' +# If no plugin is used, then use this as token timeout. +# Overriden by key timeout_token from ansible configuration. +TIME_FOR_RENEW = 43200 # 12 jours class Client: """ Class based client to contact re2o API. """ - def __init__(self, hostname, username, password, use_tls=True): + def __init__(self, hostname, username, password, + use_tls=True, cachetoken=None): """ :arg hostname: The hostname of the Re2o instance to use. :arg username: The username to use. :arg password: The password to use. :arg use_tls: A boolean to specify whether the client should use a a TLS connection. Default is True. Please, keep it. + :arg cachetoken: The cache to use to manage authentication token. + If it is None, then store the token in a file. """ self.use_tls = use_tls self.hostname = hostname self._username = username self._password = password - - self.token_file = Path.home() / DEFAULT_TOKEN_FILENAME + self._cachetoken = cachetoken + self.token_file = None + if self._cachetoken is None: + self.token_file = Path.home() / DEFAULT_TOKEN_FILENAME + display.vvv("Setting token file to {}".format(self.token_file)) + else: + try: + display.vvv("Using {} as cache plugin" + .format(self._cachetoken.plugin_name)) + except AttributeError: + # Happens when plugin_name is not implemented... + # For example with memcached + display.vvv("Using cache plugin specified in configuration.") display.v("Connecting to {hostname} as user {user}".format( hostname=to_native(self.hostname), user=to_native(self._username))) - try: - self.token = self._get_token_from_file() - except AnsibleFileNotFound: - display.vv("Force renew the token") - self._force_renew_token() + + @property + def token(self): + if self._cachetoken: + display.vvv("Trying to get token from cache.") + if self._cachetoken.contains("auth_token"): + display.vvv("Found token in cache.") + return self._cachetoken.get("auth_token") + else: + display.vvv("Token not found. Forcing renew.") + return self._force_renew_token() + else: + try: + token = self._get_token_from_file() + if token['expiration'] < datetime.datetime.now() + \ + datetime.timedelta(seconds=TIME_FOR_RENEW): + return self._force_renew_token() + except AnsibleError: + return self._force_renew_token() def _get_token_from_file(self): display.vv("Trying to fetch token from {}".format(self.token_file)) @@ -95,13 +124,18 @@ class Client: ) ) else: - display.vv("""Token successfully retreived from - file {token}""".format(token=self.token_file)) + display.vv("Token successfully retreived from " + "file {token}".format(token=self.token_file)) return ret def _force_renew_token(self): - self.token = self._get_token_from_server() - self._save_token_to_file() + token = self._get_token_from_server() + if self._cachetoken: + display.vvv("Storing authentication token in cache") + self._cachetoken.set("auth_token", token.get('token')) + else: + self._save_token_to_file(token) + return token.get('token') def _get_token_from_server(self): display.vv("Requesting a new token for {user}@{host}".format( @@ -141,7 +175,7 @@ class Client: def _parse_date(self, date, date_format="%Y-%m-%dT%H:%M:%S"): return datetime.datetime.strptime(date.split('.')[0], date_format) - def _save_token_to_file(self): + def _save_token_to_file(self, token): display.vv("Saving token to file {}".format(self.token_file)) try: # Read previous data to avoid erasures @@ -155,8 +189,8 @@ class Client: if self.hostname not in data.keys(): data[self.hostname] = {} data[self.hostname][self._username] = { - 'token': self.token['token'], - 'expiration': self.token['expiration'].isoformat(), + 'token': token['token'], + 'expiration': token['expiration'].isoformat(), } try: @@ -171,22 +205,6 @@ class Client: display.vv("Token successfully written to file {}" .format(self.token_file)) - def get_token(self): - """ - Retrieves the token to use for the current connection. - Automatically renewed if needed. - """ - if self.need_renew_token: - self._force_renew_token() - - return self.token['token'] - - @property - def need_renew_token(self): - return self.token['expiration'] < \ - datetime.datetime.now() + \ - datetime.timedelta(seconds=TIME_FOR_RENEW) - def _request(self, method, url, headers={}, params={}, *args, **kwargs): display.vv("Building the {method} request to {url}.".format( method=method.upper(), @@ -194,9 +212,9 @@ class Client: )) # Force the 'Authorization' field with the right token. - display.vvv("Forcing authentication token.") + display.vvv("Forcing authentication token in headers.") headers.update({ - 'Authorization': 'Token {}'.format(self.get_token()) + 'Authorization': 'Token {}'.format(self.token) }) # Use a json format unless the user already specified something @@ -215,10 +233,10 @@ class Client: # Force re-login to the server (case of a wrong token but valid # credentials) and then retry the request without catching errors. display.vv("Token refused. Trying to refresh the token.") - self._force_renew_token() + token = self._force_renew_token() headers.update({ - 'Authorization': 'Token {}'.format(self.get_token()) + 'Authorization': 'Token {}'.format(token) }) display.vv("Re-performing the request {method} {url}".format( method=method.upper(), @@ -342,11 +360,11 @@ class LookupModule(LookupBase): - debug: var=dnszones """ - def _readconfig(self, section="re2o", key=None, boolean=False, - integer=False): + def _readconfig(self, section="re2o", key=None, default=None, + boolean=False, integer=False): config = self._config if not config: - return None + return default else: if config.has_option(section, key): display.vvv("Found key {} in configuration file".format(key)) @@ -373,7 +391,9 @@ class LookupModule(LookupBase): self._use_cpasswords = None self._cache_plugin = None self._cache = None - self._timeout = 120 + self._timeout = 86400 # 1 day + self._cachetoken = None + self._timeouttoken = TIME_FOR_RENEW # 12 hours if self._config.has_section("re2o"): display.vvv("Found section re2o in configuration file") @@ -382,7 +402,11 @@ class LookupModule(LookupBase): self._use_cpasswords = self._readconfig(key="use_cpasswords", boolean=True) self._cache_plugin = self._readconfig(key="cache") - self._timeout = self._readconfig(key="timeout", integer=True) + self._timeout = self._readconfig(key="timeout", integer=True, + default=86400) + self._timeouttoken = self._readconfig(key="timeout_token", + integer=True, + default=TIME_FOR_RENEW) if self._cache_plugin is not None: display.vvv("Using {} as cache plugin".format(self._cache_plugin)) @@ -450,8 +474,8 @@ class LookupModule(LookupBase): 'You must specify a valid password to connect to re2oAPI' )) - api_client = Client(api_hostname, api_username, - api_password, use_tls=True) + api_client = Client(api_hostname, api_username, api_password, + use_tls=True, cachetoken=self._cachetoken) res = [] dterms = collections.deque(terms)