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:
Alexander Kukushkin
2016-06-16 12:16:16 +02:00
parent 5ffc8206b0
commit 9f5276dd2b
2 changed files with 22 additions and 18 deletions
+21 -17
View File
@@ -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
View File
@@ -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)