mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-25 14:53:37 +00:00
Advanced configuration for Consul (#506)
* possibility to specify client certs and cacert * possibility to specify token * compatibility with python-consul-0.7.1
This commit is contained in:
committed by
GitHub
parent
4f87ea96ca
commit
5ef01cfdfa
@@ -25,6 +25,14 @@ Example: defining ``PATRONI_admin_PASSWORD=strongpasswd`` and ``PATRONI_admin_OP
|
||||
Consul
|
||||
------
|
||||
- **PATRONI\_CONSUL\_HOST**: the host:port for the Consul endpoint.
|
||||
- **PATRONI\_CONSUL\_URL**: url for the Consul, in format: http(s)://host:port
|
||||
- **PATRONI\_CONSUL\_PORT**: (optional) Consul port
|
||||
- **PATRONI\_CONSUL\_SCHEME**: (optional) **http** or **https**, defaults to **http**
|
||||
- **PATRONI\_CONSUL\_TOKEN**: (optional) ACL token
|
||||
- **PATRONI\_CONSUL\_VERIFY**: (optional) whether to verify the SSL certificate for HTTPS requests
|
||||
- **PATRONI\_CONSUL\_CACERT**: (optional) The ca certificate. If pressent it will enable validation.
|
||||
- **PATRONI\_CONSUL\_CERT**: (optional) File with the client certificate
|
||||
- **PATRONI\_CONSUL\_KEY**: (optional) File with the client key. Can be empty if the key is part of certificate.
|
||||
|
||||
Etcd
|
||||
----
|
||||
|
||||
+10
-1
@@ -45,7 +45,16 @@ Bootstrap configuration
|
||||
|
||||
Consul
|
||||
------
|
||||
- **host**: the host:port for the Consul endpoint.
|
||||
Most of the parameters are optional, but you have to specify one of the **host** or **url**
|
||||
- **host**: the host:port for the Consul endpoint, in format: http(s)://host:port
|
||||
- **url**: url for the Consul endpoint
|
||||
- **port**: (optional) Consul port
|
||||
- **scheme**: (optional) **http** or **https**, defaults to **http**
|
||||
- **token**: (optional) ACL token
|
||||
- **verify** (optional) whether to verify the SSL certificate for HTTPS requests
|
||||
- **cacert**: (optional) The ca certificate. If pressent it will enable validation.
|
||||
- **cert**: (optional) file with the client certificate
|
||||
- **key**: (optional) file with the client key. Can be empty if the key is part of **cert**.
|
||||
|
||||
Etcd
|
||||
----
|
||||
|
||||
+2
-2
@@ -242,8 +242,8 @@ class Config(object):
|
||||
name, suffix = (param[8:].rsplit('_', 1) + [''])[:2]
|
||||
if name and suffix:
|
||||
# PATRONI_(ETCD|CONSUL|ZOOKEEPER|EXHIBITOR|...)_(HOSTS?|PORT|..)
|
||||
if suffix in ('HOST', 'HOSTS', 'PORT', 'SRV', 'URL', 'PROXY', 'CACERT', 'CERT', 'KEY') \
|
||||
and '_' not in name:
|
||||
if suffix in ('HOST', 'HOSTS', 'PORT', 'SRV', 'URL', 'PROXY', 'CACERT', 'CERT', 'KEY',
|
||||
'VERIFY', 'TOKEN') and '_' not in name:
|
||||
value = os.environ.pop(param)
|
||||
if suffix == 'PORT':
|
||||
value = value and parse_int(value)
|
||||
|
||||
+57
-15
@@ -2,15 +2,16 @@ from __future__ import absolute_import
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import ssl
|
||||
import time
|
||||
import urllib3
|
||||
|
||||
from consul import ConsulException, NotFound, base
|
||||
from patroni.dcs import AbstractDCS, ClusterConfig, Cluster, Failover, Leader, Member, SyncState
|
||||
from patroni.exceptions import DCSError
|
||||
from patroni.utils import Retry, RetryFailedError
|
||||
from patroni.utils import parse_bool, Retry, RetryFailedError
|
||||
from urllib3.exceptions import HTTPError
|
||||
from six.moves.urllib.parse import urlencode
|
||||
from six.moves.urllib.parse import urlencode, urlparse
|
||||
from six.moves.http_client import HTTPException
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -26,14 +27,23 @@ class ConsulInternalError(ConsulException):
|
||||
|
||||
class HTTPClient(object):
|
||||
|
||||
def __init__(self, host='127.0.0.1', port=8500, scheme='http', verify=True, timeout=10):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.scheme = scheme
|
||||
self.verify = verify
|
||||
self.set_read_timeout(timeout)
|
||||
self.base_uri = '{0}://{1}:{2}'.format(self.scheme, self.host, self.port)
|
||||
self.http = urllib3.PoolManager(num_pools=10)
|
||||
def __init__(self, host='127.0.0.1', port=8500, scheme='http', verify=True, cert=None, ca_cert=None):
|
||||
self._read_timeout = 10
|
||||
self.base_uri = '{0}://{1}:{2}'.format(scheme, host, port)
|
||||
kwargs = {}
|
||||
if cert:
|
||||
if isinstance(cert, tuple):
|
||||
# Key and cert are separate
|
||||
kwargs['cert_file'] = cert[0]
|
||||
kwargs['key_file'] = cert[1]
|
||||
else:
|
||||
# combined certificate
|
||||
kwargs['cert_file'] = cert
|
||||
if ca_cert:
|
||||
kwargs['ca_certs'] = ca_cert
|
||||
if verify or ca_cert:
|
||||
kwargs['cert_reqs'] = ssl.CERT_REQUIRED
|
||||
self.http = urllib3.PoolManager(num_pools=10, **kwargs)
|
||||
self._ttl = None
|
||||
|
||||
def set_read_timeout(self, timeout):
|
||||
@@ -78,9 +88,18 @@ class HTTPClient(object):
|
||||
|
||||
class ConsulClient(base.Consul):
|
||||
|
||||
@staticmethod
|
||||
def connect(host, port, scheme, verify=True):
|
||||
return HTTPClient(host, port, scheme, verify)
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._cert = kwargs.pop('cert', None)
|
||||
self._ca_cert = kwargs.pop('ca_cert', None)
|
||||
super(ConsulClient, self).__init__(*args, **kwargs)
|
||||
|
||||
def connect(self, *args, **kwargs):
|
||||
kwargs.update(dict(zip(['host', 'port', 'scheme', 'verify'], args)))
|
||||
if self._cert:
|
||||
kwargs['cert'] = self._cert
|
||||
if self._ca_cert:
|
||||
kwargs['ca_cert'] = self._ca_cert
|
||||
return HTTPClient(**kwargs)
|
||||
|
||||
|
||||
def catch_consul_errors(func):
|
||||
@@ -104,8 +123,31 @@ class Consul(AbstractDCS):
|
||||
HTTPError, socket.error, socket.timeout))
|
||||
|
||||
self._my_member_data = None
|
||||
host, port = config.get('host', '127.0.0.1:8500').split(':')
|
||||
self._client = ConsulClient(host=host, port=port)
|
||||
kwargs = {}
|
||||
if 'url' in config:
|
||||
r = urlparse(config['url'])
|
||||
config.update({'scheme': r.scheme, 'host': r.hostname, 'port': r.port or 8500})
|
||||
elif 'host' in config:
|
||||
host, port = (config.get('host', '127.0.0.1:8500') + ':8500').split(':')[:2]
|
||||
config['host'] = host
|
||||
if 'port' not in config:
|
||||
config['port'] = int(port)
|
||||
|
||||
if config.get('cacert'):
|
||||
config['ca_cert'] = config.pop('cacert')
|
||||
|
||||
if config.get('key') and config.get('cert'):
|
||||
config['cert'] = (config['cert'], config['key'])
|
||||
|
||||
kwargs = {p: config.get(p) for p in ('host', 'port', 'token', 'scheme', 'cert', 'ca_cert') if config.get(p)}
|
||||
|
||||
verify = config.get('verify')
|
||||
if not isinstance(verify, bool):
|
||||
verify = parse_bool(verify)
|
||||
if isinstance(verify, bool):
|
||||
kwargs['verify'] = verify
|
||||
|
||||
self._client = ConsulClient(**kwargs)
|
||||
self.set_retry_timeout(config['retry_timeout'])
|
||||
self.set_ttl(config.get('ttl') or 30)
|
||||
self._last_session_refresh = 0
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ requests
|
||||
six >= 1.7
|
||||
kazoo==2.2.1
|
||||
python-etcd>=0.4.3,<0.5
|
||||
python-consul==0.7.0
|
||||
python-consul>=0.7.0
|
||||
click>=4.1
|
||||
prettytable>=0.7
|
||||
tzlocal
|
||||
|
||||
@@ -69,6 +69,10 @@ class TestConsul(unittest.TestCase):
|
||||
@patch.object(consul.Consul.KV, 'get', kv_get)
|
||||
@patch.object(consul.Consul.KV, 'delete', Mock())
|
||||
def setUp(self):
|
||||
Consul({'ttl': 30, 'scope': 't', 'name': 'p', 'url': 'https://l:1', 'retry_timeout': 10,
|
||||
'verify': 'on', 'key': 'foo', 'cert': 'bar', 'cacert': 'buz'})
|
||||
Consul({'ttl': 30, 'scope': 't', 'name': 'p', 'url': 'https://l:1', 'retry_timeout': 10,
|
||||
'verify': 'on', 'cert': 'bar', 'cacert': 'buz'})
|
||||
self.c = Consul({'ttl': 30, 'scope': 'test', 'name': 'postgresql1', 'host': 'localhost:1', 'retry_timeout': 10})
|
||||
self.c._base_path = '/service/good'
|
||||
self.c._load_cluster()
|
||||
|
||||
Reference in New Issue
Block a user