mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
* FEATURE: cache/redis: add redis sentinel support
* FEATURE: add changelog fragment
* STYLE: fix sanity
* DOCS: update fragment, specify redis cache plugin
* STYLE: spelling
Co-authored-by: Andrew Klychkov <aaklychkov@mail.ru>
* STYLE: spelling
* FIX: import to_native
* FIX: remove kw args to prevent secrets leak
Co-authored-by: Benjamin Pereto <benjamin@sandchaschte.ch>
Co-authored-by: Andrew Klychkov <aaklychkov@mail.ru>
(cherry picked from commit a6c950a44b
)
Co-authored-by: Benjamin Pereto <dev@sandchaschte.ch>
This commit is contained in:
parent
bb2ad10eef
commit
5571a0cdf8
2 changed files with 58 additions and 2 deletions
3
changelogs/fragments/1055-redis-cache-sentinel.yaml
Normal file
3
changelogs/fragments/1055-redis-cache-sentinel.yaml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
minor_changes:
|
||||||
|
- redis cache plugin - add redis sentinel functionality to cache plugin
|
||||||
|
(https://github.com/ansible-collections/community.general/pull/1055).
|
53
plugins/cache/redis.py
vendored
53
plugins/cache/redis.py
vendored
|
@ -18,6 +18,7 @@ DOCUMENTATION = '''
|
||||||
- A colon separated string of connection information for Redis.
|
- A colon separated string of connection information for Redis.
|
||||||
- The format is C(host:port:db:password), for example C(localhost:6379:0:changeme).
|
- The format is C(host:port:db:password), for example C(localhost:6379:0:changeme).
|
||||||
- To use encryption in transit, prefix the connection with C(tls://), as in C(tls://localhost:6379:0:changeme).
|
- To use encryption in transit, prefix the connection with C(tls://), as in C(tls://localhost:6379:0:changeme).
|
||||||
|
- To use redis sentinel, use separator C(;), for example C(localhost:26379;localhost:26379;0:changeme). Requires redis>=2.9.0.
|
||||||
required: True
|
required: True
|
||||||
env:
|
env:
|
||||||
- name: ANSIBLE_CACHE_PLUGIN_CONNECTION
|
- name: ANSIBLE_CACHE_PLUGIN_CONNECTION
|
||||||
|
@ -41,6 +42,14 @@ DOCUMENTATION = '''
|
||||||
- key: fact_caching_redis_keyset_name
|
- key: fact_caching_redis_keyset_name
|
||||||
section: defaults
|
section: defaults
|
||||||
version_added: 1.3.0
|
version_added: 1.3.0
|
||||||
|
_sentinel_service_name:
|
||||||
|
description: The redis sentinel service name (or referenced as cluster name).
|
||||||
|
env:
|
||||||
|
- name: ANSIBLE_CACHE_REDIS_SENTINEL
|
||||||
|
ini:
|
||||||
|
- key: fact_caching_redis_sentinel
|
||||||
|
section: defaults
|
||||||
|
version_added: 1.3.0
|
||||||
_timeout:
|
_timeout:
|
||||||
default: 86400
|
default: 86400
|
||||||
description: Expiration timeout in seconds for the cache plugin data. Set to 0 to never expire
|
description: Expiration timeout in seconds for the cache plugin data. Set to 0 to never expire
|
||||||
|
@ -57,6 +66,7 @@ import json
|
||||||
|
|
||||||
from ansible import constants as C
|
from ansible import constants as C
|
||||||
from ansible.errors import AnsibleError
|
from ansible.errors import AnsibleError
|
||||||
|
from ansible.module_utils._text import to_native
|
||||||
from ansible.parsing.ajson import AnsibleJSONEncoder, AnsibleJSONDecoder
|
from ansible.parsing.ajson import AnsibleJSONEncoder, AnsibleJSONDecoder
|
||||||
from ansible.plugins.cache import BaseCacheModule
|
from ansible.plugins.cache import BaseCacheModule
|
||||||
from ansible.utils.display import Display
|
from ansible.utils.display import Display
|
||||||
|
@ -78,6 +88,8 @@ class CacheModule(BaseCacheModule):
|
||||||
to expire keys. This mechanism is used or a pattern matched 'scan' for
|
to expire keys. This mechanism is used or a pattern matched 'scan' for
|
||||||
performance.
|
performance.
|
||||||
"""
|
"""
|
||||||
|
_sentinel_service_name = None
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
uri = ''
|
uri = ''
|
||||||
|
|
||||||
|
@ -88,6 +100,7 @@ class CacheModule(BaseCacheModule):
|
||||||
self._timeout = float(self.get_option('_timeout'))
|
self._timeout = float(self.get_option('_timeout'))
|
||||||
self._prefix = self.get_option('_prefix')
|
self._prefix = self.get_option('_prefix')
|
||||||
self._keys_set = self.get_option('_keyset_name')
|
self._keys_set = self.get_option('_keyset_name')
|
||||||
|
self._sentinel_service_name = self.get_option('_sentinel_service_name')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
display.deprecated('Rather than importing CacheModules directly, '
|
display.deprecated('Rather than importing CacheModules directly, '
|
||||||
'use ansible.plugins.loader.cache_loader',
|
'use ansible.plugins.loader.cache_loader',
|
||||||
|
@ -100,14 +113,54 @@ class CacheModule(BaseCacheModule):
|
||||||
|
|
||||||
self._cache = {}
|
self._cache = {}
|
||||||
kw = {}
|
kw = {}
|
||||||
|
|
||||||
|
# tls connection
|
||||||
tlsprefix = 'tls://'
|
tlsprefix = 'tls://'
|
||||||
if uri.startswith(tlsprefix):
|
if uri.startswith(tlsprefix):
|
||||||
kw['ssl'] = True
|
kw['ssl'] = True
|
||||||
uri = uri[len(tlsprefix):]
|
uri = uri[len(tlsprefix):]
|
||||||
|
|
||||||
|
# redis sentinel connection
|
||||||
|
if self._sentinel_service_name:
|
||||||
|
self._db = self._get_sentinel_connection(uri, kw)
|
||||||
|
# normal connection
|
||||||
|
else:
|
||||||
connection = uri.split(':')
|
connection = uri.split(':')
|
||||||
self._db = StrictRedis(*connection, **kw)
|
self._db = StrictRedis(*connection, **kw)
|
||||||
|
|
||||||
|
display.vv('Redis connection: %s' % self._db)
|
||||||
|
|
||||||
|
def _get_sentinel_connection(self, uri, kw):
|
||||||
|
"""
|
||||||
|
get sentinel connection details from _uri
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
from redis.sentinel import Sentinel
|
||||||
|
except ImportError:
|
||||||
|
raise AnsibleError("The 'redis' python module (version 2.9.0 or newer) is required to use redis sentinel.")
|
||||||
|
|
||||||
|
if ';' not in uri:
|
||||||
|
raise AnsibleError('_uri does not have sentinel syntax.')
|
||||||
|
|
||||||
|
# format: "localhost:26379;localhost2:26379;0:changeme"
|
||||||
|
connections = uri.split(';')
|
||||||
|
connection_args = connections.pop(-1)
|
||||||
|
if len(connection_args) > 0: # hanle if no db nr is given
|
||||||
|
connection_args = connection_args.split(':')
|
||||||
|
kw['db'] = connection_args.pop(0)
|
||||||
|
try:
|
||||||
|
kw['password'] = connection_args.pop(0)
|
||||||
|
except IndexError:
|
||||||
|
pass # password is optional
|
||||||
|
|
||||||
|
sentinels = [tuple(shost.split(':')) for shost in connections]
|
||||||
|
display.vv('\nUsing redis sentinels: %s' % sentinels)
|
||||||
|
scon = Sentinel(sentinels, **kw)
|
||||||
|
try:
|
||||||
|
return scon.master_for(self._sentinel_service_name, socket_timeout=0.2)
|
||||||
|
except Exception as exc:
|
||||||
|
raise AnsibleError('Could not connect to redis sentinel: %s' % to_native(exc))
|
||||||
|
|
||||||
def _make_key(self, key):
|
def _make_key(self, key):
|
||||||
return self._prefix + key
|
return self._prefix + key
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue