mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-08-25 14:53:37 +00:00
patronictl will send authorization header if it is configured
username:password can be configured in the 'restapi' section of config file or via environment
This commit is contained in:
+21
-17
@@ -2,6 +2,7 @@
|
|||||||
Patroni Control
|
Patroni Control
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
import base64
|
||||||
import click
|
import click
|
||||||
import datetime
|
import datetime
|
||||||
import dateutil
|
import dateutil
|
||||||
@@ -102,11 +103,19 @@ def get_dcs(config, scope):
|
|||||||
raise PatroniCtlException(str(e))
|
raise PatroniCtlException(str(e))
|
||||||
|
|
||||||
|
|
||||||
|
def auth_header(config):
|
||||||
|
if config.get('restapi', {}).get('auth', ''):
|
||||||
|
return {'Authorization': 'Basic ' + base64.b64encode(config['restapi']['auth'].encode('utf-8')).decode('utf-8')}
|
||||||
|
|
||||||
|
|
||||||
def post_patroni(member, endpoint, content, headers=None):
|
def post_patroni(member, endpoint, content, headers=None):
|
||||||
|
headers = headers or {}
|
||||||
url = urlparse(member.api_url)
|
url = urlparse(member.api_url)
|
||||||
logging.debug(url)
|
logging.debug(url)
|
||||||
|
if 'Content-Type' not in headers:
|
||||||
|
headers['Content-Type'] = 'application/json'
|
||||||
return requests.post('{0}://{1}/{2}'.format(url.scheme, url.netloc, endpoint),
|
return requests.post('{0}://{1}/{2}'.format(url.scheme, url.netloc, endpoint),
|
||||||
headers=headers or {'Content-Type': 'application/json'},
|
headers=headers,
|
||||||
data=json.dumps(content), timeout=60)
|
data=json.dumps(content), timeout=60)
|
||||||
|
|
||||||
|
|
||||||
@@ -122,10 +131,7 @@ def print_output(columns, rows=None, alignment=None, fmt='pretty', header=True,
|
|||||||
return
|
return
|
||||||
|
|
||||||
if fmt == 'json':
|
if fmt == 'json':
|
||||||
elements = list()
|
elements = [dict(zip(columns, r)) for r in rows]
|
||||||
for r in rows:
|
|
||||||
elements.append(dict(zip(columns, r)))
|
|
||||||
|
|
||||||
click.echo(json.dumps(elements))
|
click.echo(json.dumps(elements))
|
||||||
|
|
||||||
if fmt == 'tsv':
|
if fmt == 'tsv':
|
||||||
@@ -382,17 +388,15 @@ def wait_for_leader(dcs, timeout=30):
|
|||||||
raise PatroniCtlException('Timeout occured')
|
raise PatroniCtlException('Timeout occured')
|
||||||
|
|
||||||
|
|
||||||
def empty_post_to_members(cluster, member_names, force, endpoint):
|
def empty_post_to_members(cluster, member_names, force, endpoint, headers=None):
|
||||||
candidates = dict()
|
candidates = {m.name: m for m in cluster.members}
|
||||||
for m in cluster.members:
|
|
||||||
candidates[m.name] = m
|
|
||||||
|
|
||||||
if not member_names:
|
if not member_names:
|
||||||
member_names = [click.prompt('Which member do you want to {0} [{1}]?'.format(endpoint,
|
member_names = [click.prompt('Which member do you want to {0} [{1}]?'.format(endpoint,
|
||||||
', '.join(candidates.keys())), type=str, default='')]
|
', '.join(candidates.keys())), type=str, default='')]
|
||||||
|
|
||||||
for mn in member_names:
|
for mn in member_names:
|
||||||
if mn not in candidates.keys():
|
if mn not in candidates:
|
||||||
raise PatroniCtlException('{0} is not a member of cluster'.format(mn))
|
raise PatroniCtlException('{0} is not a member of cluster'.format(mn))
|
||||||
|
|
||||||
if not force:
|
if not force:
|
||||||
@@ -401,7 +405,7 @@ def empty_post_to_members(cluster, member_names, force, endpoint):
|
|||||||
raise PatroniCtlException('Aborted {0}'.format(endpoint))
|
raise PatroniCtlException('Aborted {0}'.format(endpoint))
|
||||||
|
|
||||||
for mn in member_names:
|
for mn in member_names:
|
||||||
r = post_patroni(candidates[mn], endpoint, '')
|
r = post_patroni(candidates[mn], endpoint, '', headers)
|
||||||
if r.status_code != 200:
|
if r.status_code != 200:
|
||||||
click.echo('{0} failed for member {1}, status code={2}, ({3})'.format(endpoint, mn, r.status_code, r.text))
|
click.echo('{0} failed for member {1}, status code={2}, ({3})'.format(endpoint, mn, r.status_code, r.text))
|
||||||
else:
|
else:
|
||||||
@@ -426,7 +430,7 @@ def ctl_load_config(cluster_name, config_file, dcs):
|
|||||||
@option_force
|
@option_force
|
||||||
@option_dcs
|
@option_dcs
|
||||||
def restart(cluster_name, member_names, config_file, dcs, force, role, p_any):
|
def restart(cluster_name, member_names, config_file, dcs, force, role, p_any):
|
||||||
_, dcs, cluster = ctl_load_config(cluster_name, config_file, dcs)
|
config, dcs, cluster = ctl_load_config(cluster_name, config_file, dcs)
|
||||||
|
|
||||||
role_names = [m.name for m in get_all_members(cluster, role)]
|
role_names = [m.name for m in get_all_members(cluster, role)]
|
||||||
|
|
||||||
@@ -440,7 +444,7 @@ def restart(cluster_name, member_names, config_file, dcs, force, role, p_any):
|
|||||||
member_names = member_names[:1]
|
member_names = member_names[:1]
|
||||||
|
|
||||||
output_members(cluster, cluster_name)
|
output_members(cluster, cluster_name)
|
||||||
empty_post_to_members(cluster, member_names, force, 'restart')
|
empty_post_to_members(cluster, member_names, force, 'restart', auth_header(config))
|
||||||
|
|
||||||
|
|
||||||
@ctl.command('reinit', help='Reinitialize cluster member')
|
@ctl.command('reinit', help='Reinitialize cluster member')
|
||||||
@@ -450,8 +454,8 @@ def restart(cluster_name, member_names, config_file, dcs, force, role, p_any):
|
|||||||
@option_force
|
@option_force
|
||||||
@option_dcs
|
@option_dcs
|
||||||
def reinit(cluster_name, member_names, config_file, dcs, force):
|
def reinit(cluster_name, member_names, config_file, dcs, force):
|
||||||
_, dcs, cluster = ctl_load_config(cluster_name, config_file, dcs)
|
config, dcs, cluster = ctl_load_config(cluster_name, config_file, dcs)
|
||||||
empty_post_to_members(cluster, member_names, force, 'reinitialize')
|
empty_post_to_members(cluster, member_names, force, 'reinitialize', auth_header(config))
|
||||||
|
|
||||||
|
|
||||||
@ctl.command('failover', help='Failover to a replica')
|
@ctl.command('failover', help='Failover to a replica')
|
||||||
@@ -471,7 +475,7 @@ def failover(config_file, cluster_name, master, candidate, force, dcs, scheduled
|
|||||||
If so, we trigger a failover and keep the client up to date.
|
If so, we trigger a failover and keep the client up to date.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
_, dcs, cluster = ctl_load_config(cluster_name, config_file, dcs)
|
config, dcs, cluster = ctl_load_config(cluster_name, config_file, dcs)
|
||||||
|
|
||||||
if cluster.leader is None:
|
if cluster.leader is None:
|
||||||
raise PatroniCtlException('This cluster has no master')
|
raise PatroniCtlException('This cluster has no master')
|
||||||
@@ -533,7 +537,7 @@ def failover(config_file, cluster_name, master, candidate, force, dcs, scheduled
|
|||||||
|
|
||||||
r = None
|
r = None
|
||||||
try:
|
try:
|
||||||
r = post_patroni(cluster.leader.member, 'failover', failover_value)
|
r = post_patroni(cluster.leader.member, 'failover', failover_value, auth_header(config))
|
||||||
if r.status_code in (200, 202):
|
if r.status_code in (200, 202):
|
||||||
logging.debug(r)
|
logging.debug(r)
|
||||||
cluster = dcs.get_cluster()
|
cluster = dcs.get_cluster()
|
||||||
|
|||||||
+1
-1
@@ -43,7 +43,7 @@ def test_rw_config():
|
|||||||
load_config(CONFIG_FILE_PATH, None)
|
load_config(CONFIG_FILE_PATH, None)
|
||||||
|
|
||||||
|
|
||||||
@patch('patroni.ctl.load_config', Mock(return_value={'etcd': {'host': 'localhost:4001'}}))
|
@patch('patroni.ctl.load_config', Mock(return_value={'restapi': {'auth': 'u:p'}, 'etcd': {'host': 'localhost:4001'}}))
|
||||||
class TestCtl(unittest.TestCase):
|
class TestCtl(unittest.TestCase):
|
||||||
|
|
||||||
@patch('socket.getaddrinfo', socket_getaddrinfo)
|
@patch('socket.getaddrinfo', socket_getaddrinfo)
|
||||||
|
|||||||
Reference in New Issue
Block a user