mirror of
https://github.com/outbackdingo/patroni.git
synced 2026-09-01 17:19:31 +00:00
Merge pull request #112 from zalando/feature/patronictl_password
Feature/patronictl password
This commit is contained in:
@@ -20,6 +20,7 @@ RUN pip install python-etcd psycopg2
|
||||
ENV PATH /usr/lib/postgresql/${PGVERSION}/bin:$PATH
|
||||
|
||||
ADD patroni.py /patroni.py
|
||||
ADD patronictl.py /patronictl.py
|
||||
ADD patroni/ /patroni
|
||||
|
||||
ENV ETCDVERSION 2.0.13
|
||||
|
||||
@@ -79,7 +79,12 @@ then
|
||||
ETCD_CLUSTER="127.0.0.1:4001"
|
||||
fi
|
||||
|
||||
cat > /patroni/postgres.yml <<__EOF__
|
||||
mkdir -p ~postgres/.config/patroni
|
||||
cat > ~postgres/.config/patroni/patronictl.yaml <<__EOF__
|
||||
{dcs_api: 'etcd://${ETCD_CLUSTER}', namespace: /service/}
|
||||
__EOF__
|
||||
|
||||
cat > /patroni/postgres.yaml <<__EOF__
|
||||
|
||||
ttl: &ttl 30
|
||||
loop_wait: &loop_wait 10
|
||||
@@ -126,7 +131,7 @@ postgresql:
|
||||
hot_standby: "on"
|
||||
__EOF__
|
||||
|
||||
cat /patroni/postgres.yml
|
||||
cat /patroni/postgres.yaml
|
||||
|
||||
if [ ! -z $CHEAT ]
|
||||
then
|
||||
@@ -135,5 +140,5 @@ then
|
||||
sleep 60
|
||||
done
|
||||
else
|
||||
exec python /patroni.py /patroni/postgres.yml
|
||||
exec python /patroni.py /patroni/postgres.yaml
|
||||
fi
|
||||
|
||||
+29
-5
@@ -168,7 +168,9 @@ def watching(w, watch, max_count=None, clear=True):
|
||||
yield 0
|
||||
|
||||
|
||||
def build_connect_parameters(conn_url, connect_parameters={}):
|
||||
def build_connect_parameters(conn_url, connect_parameters=None):
|
||||
if connect_parameters is None:
|
||||
connect_parameters = {}
|
||||
params = connect_parameters.copy()
|
||||
parsed = parseurl(conn_url)
|
||||
params['host'] = parsed['host']
|
||||
@@ -200,7 +202,9 @@ def get_any_member(cluster, role='master', member=None):
|
||||
return None
|
||||
|
||||
|
||||
def get_cursor(cluster, role='master', member=None, connect_parameters={}):
|
||||
def get_cursor(cluster, role='master', member=None, connect_parameters=None):
|
||||
if connect_parameters is None:
|
||||
connect_parameters = {}
|
||||
member = get_any_member(cluster=cluster, role=role, member=member)
|
||||
if member is None:
|
||||
return None
|
||||
@@ -252,6 +256,8 @@ def dsn(cluster_name, config_file, dcs, role, member):
|
||||
@option_format
|
||||
@click.option('--format', help='Output format (pretty, json)', default='tsv')
|
||||
@click.option('--file', '-f', help='Execute the SQL commands from this file', type=click.File('rb'))
|
||||
@click.option('--password', help='force password prompt', is_flag=True)
|
||||
@click.option('-U', '--username', help='database user name', type=str)
|
||||
@option_dcs
|
||||
@option_watch
|
||||
@option_watchrefresh
|
||||
@@ -260,6 +266,7 @@ def dsn(cluster_name, config_file, dcs, role, member):
|
||||
@click.option('--member', '-m', help='Query a specific member', type=str)
|
||||
@click.option('--delimiter', help='The column delimiter', default='\t')
|
||||
@click.option('--command', '-c', help='The SQL commands to execute')
|
||||
@click.option('-d', '--dbname', help='database name to connect to', type=str)
|
||||
def query(
|
||||
cluster_name,
|
||||
config_file,
|
||||
@@ -271,6 +278,9 @@ def query(
|
||||
delimiter,
|
||||
command,
|
||||
file,
|
||||
password,
|
||||
username,
|
||||
dbname,
|
||||
format='tsv',
|
||||
):
|
||||
if role is not None and member is not None:
|
||||
@@ -281,6 +291,17 @@ def query(
|
||||
if file is not None and command is not None:
|
||||
raise PatroniCtlException('--file and --command are mutually exclusive options')
|
||||
|
||||
if file is None and command is None:
|
||||
raise PatroniCtlException('You need to specify either --command or --file')
|
||||
|
||||
connect_parameters = dict()
|
||||
if username:
|
||||
connect_parameters['user'] = username
|
||||
if password:
|
||||
connect_parameters['password'] = click.prompt('Password', hide_input=True, type=str)
|
||||
if dbname:
|
||||
connect_parameters['database'] = dbname
|
||||
|
||||
if file is not None:
|
||||
command = file.read()
|
||||
|
||||
@@ -289,17 +310,20 @@ def query(
|
||||
cursor = None
|
||||
for _ in watching(w, watch, clear=False):
|
||||
|
||||
output, cursor = query_member(cluster=cluster, cursor=cursor, member=member, role=role, command=command)
|
||||
output, cursor = query_member(cluster=cluster, cursor=cursor, member=member, role=role, command=command,
|
||||
connect_parameters=connect_parameters)
|
||||
print_output(None, output, format=format, delimiter=delimiter)
|
||||
|
||||
if cursor is None:
|
||||
cluster = dcs.get_cluster()
|
||||
|
||||
|
||||
def query_member(cluster, cursor, member, role, command):
|
||||
def query_member(cluster, cursor, member, role, command, connect_parameters=None):
|
||||
if connect_parameters is None:
|
||||
connect_parameters = {}
|
||||
try:
|
||||
if cursor is None:
|
||||
cursor = get_cursor(cluster, role=role, member=member)
|
||||
cursor = get_cursor(cluster, role=role, member=member, connect_parameters=connect_parameters)
|
||||
|
||||
if cursor is None:
|
||||
if role is None:
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
from click import ClickException
|
||||
|
||||
|
||||
class PatroniException(Exception):
|
||||
|
||||
"""Parent class for all kind of exceptions related to selected distributed configuration store"""
|
||||
@@ -13,7 +16,7 @@ class PatroniException(Exception):
|
||||
return repr(self.value)
|
||||
|
||||
|
||||
class PatroniCtlException(Exception):
|
||||
class PatroniCtlException(ClickException):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
+27
-18
@@ -103,35 +103,35 @@ y''')
|
||||
result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='''leader
|
||||
other
|
||||
N''')
|
||||
assert 'Aborting failover' in str(result.exception)
|
||||
assert 'Aborting failover' in str(result.output)
|
||||
|
||||
result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='''leader
|
||||
leader
|
||||
y''')
|
||||
assert 'target and source are the same' in str(result.exception)
|
||||
assert 'target and source are the same' in str(result.output)
|
||||
|
||||
result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='''leader
|
||||
Reality
|
||||
y''')
|
||||
assert 'Reality does not exist' in str(result.exception)
|
||||
assert 'Reality does not exist' in str(result.output)
|
||||
|
||||
result = runner.invoke(ctl, ['failover', 'dummy', '--force'])
|
||||
assert 'Failing over to new leader' in result.output
|
||||
|
||||
result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='dummy')
|
||||
assert 'is not the leader of cluster' in str(result.exception)
|
||||
assert 'is not the leader of cluster' in str(result.output)
|
||||
|
||||
with patch('patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_with_only_leader())):
|
||||
result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='''leader
|
||||
other
|
||||
y''')
|
||||
assert 'No candidates found to failover to' in str(result.exception)
|
||||
assert 'No candidates found to failover to' in str(result.output)
|
||||
|
||||
with patch('patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_without_leader())):
|
||||
result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='''leader
|
||||
other
|
||||
y''')
|
||||
assert 'This cluster has no master' in str(result.exception)
|
||||
assert 'This cluster has no master' in str(result.output)
|
||||
|
||||
with patch('patroni.ctl.post_patroni', Mock(side_effect=Exception())):
|
||||
result = runner.invoke(ctl, ['failover', 'dummy', '--dcs', '8.8.8.8'], input='''leader
|
||||
@@ -150,13 +150,13 @@ y''')
|
||||
|
||||
# with patch('patroni.dcs.AbstractDCS.get_cluster', Mock(return_value=get_cluster_initialized_with_leader())):
|
||||
# result = runner.invoke(ctl, ['failover', 'alpha', '--dcs', '8.8.8.8'], input='nonsense')
|
||||
# assert 'is not the leader of cluster' in str(result.exception)
|
||||
# assert 'is not the leader of cluster' in str(result.output)
|
||||
|
||||
# result = runner.invoke(ctl, ['failover', 'alpha', '--dcs', '8.8.8.8', '--master', 'nonsense'])
|
||||
# assert 'is not the leader of cluster' in str(result.exception)
|
||||
# assert 'is not the leader of cluster' in str(result.output)
|
||||
|
||||
# result = runner.invoke(ctl, ['failover', 'alpha', '--dcs', '8.8.8.8'], input='leader\nother\nn')
|
||||
# assert 'Aborting failover' in str(result.exception)
|
||||
# assert 'Aborting failover' in str(result.output)
|
||||
|
||||
# with patch('patroni.ctl.wait_for_leader', Mock(return_value = get_cluster_initialized_with_leader())):
|
||||
# result = runner.invoke(ctl, ['failover', 'alpha', '--dcs', '8.8.8.8'], input='leader\nother\nY')
|
||||
@@ -182,13 +182,19 @@ y''')
|
||||
'--role',
|
||||
'master',
|
||||
])
|
||||
assert 'mutually exclusive' in str(result.exception)
|
||||
assert 'mutually exclusive' in str(result.output)
|
||||
|
||||
with runner.isolated_filesystem():
|
||||
dummy_file = open('dummy', 'w')
|
||||
dummy_file.write('SELECT 1')
|
||||
dummy_file.close()
|
||||
|
||||
result = runner.invoke(ctl, [
|
||||
'query',
|
||||
'alpha'
|
||||
])
|
||||
assert 'You need to specify' in str(result.output)
|
||||
|
||||
result = runner.invoke(ctl, [
|
||||
'query',
|
||||
'alpha',
|
||||
@@ -197,7 +203,7 @@ y''')
|
||||
'--command',
|
||||
'dummy',
|
||||
])
|
||||
assert 'mutually exclusive' in str(result.exception)
|
||||
assert 'mutually exclusive' in str(result.output)
|
||||
|
||||
result = runner.invoke(ctl, ['query', 'alpha', '--file', 'dummy'])
|
||||
|
||||
@@ -206,6 +212,9 @@ y''')
|
||||
result = runner.invoke(ctl, ['query', 'alpha', '--command', 'SELECT 1'])
|
||||
assert 'mock column' in result.output
|
||||
|
||||
result = runner.invoke(ctl, ['query', 'alpha', '--command', 'SELECT 1', '--dbname', 'dummy', '--password', '--username', 'dummy'], input='password\n')
|
||||
assert 'mock column' in result.output
|
||||
|
||||
@patch('patroni.ctl.get_cursor', Mock(return_value=MockConnect().cursor()))
|
||||
def test_query_member(self):
|
||||
rows = query_member(None, None, None, 'master', 'SELECT pg_is_in_recovery()')
|
||||
@@ -243,10 +252,10 @@ y''')
|
||||
'--member',
|
||||
'dummy',
|
||||
])
|
||||
assert 'mutually exclusive' in str(result.exception)
|
||||
assert 'mutually exclusive' in str(result.output)
|
||||
|
||||
result = runner.invoke(ctl, ['dsn', 'alpha', '--member', 'dummy'])
|
||||
assert 'Can not find' in str(result.exception)
|
||||
assert 'Can not find' in str(result.output)
|
||||
|
||||
# result = runner.invoke(ctl, ['dsn', 'alpha', '--dcs', '8.8.8.8', '--role', 'replica'])
|
||||
# assert 'host=127.0.0.1 port=5436' in result.output
|
||||
@@ -270,7 +279,7 @@ y''')
|
||||
'dummy',
|
||||
'--any',
|
||||
], input='y')
|
||||
assert 'not a member' in str(result.exception)
|
||||
assert 'not a member' in str(result.output)
|
||||
|
||||
with patch('requests.post', Mock(return_value=MockResponse())):
|
||||
result = runner.invoke(ctl, ['restart', 'alpha', '--dcs', '8.8.8.8'], input='y')
|
||||
@@ -283,15 +292,15 @@ y''')
|
||||
result = runner.invoke(ctl, ['remove', 'alpha', '--dcs', '8.8.8.8'], input='alpha\nslave')
|
||||
assert 'Please confirm' in result.output
|
||||
assert 'You are about to remove all' in result.output
|
||||
assert 'You did not exactly type' in str(result.exception)
|
||||
assert 'You did not exactly type' in str(result.output)
|
||||
|
||||
result = runner.invoke(ctl, ['remove', 'alpha', '--dcs', '8.8.8.8'], input='''alpha
|
||||
Yes I am aware
|
||||
slave''')
|
||||
assert 'You did not specify the current master of the cluster' in str(result.exception)
|
||||
assert 'You did not specify the current master of the cluster' in str(result.output)
|
||||
|
||||
result = runner.invoke(ctl, ['remove', 'alpha', '--dcs', '8.8.8.8'], input='beta\nleader')
|
||||
assert 'Cluster names specified do not match' in str(result.exception)
|
||||
assert 'Cluster names specified do not match' in str(result.output)
|
||||
|
||||
with patch('patroni.etcd.Etcd.get_cluster', get_cluster_initialized_with_leader):
|
||||
result = runner.invoke(ctl, ['remove', 'alpha', '--dcs', '8.8.8.8'],
|
||||
@@ -305,7 +314,7 @@ leader''')
|
||||
input='''alpha
|
||||
Yes I am aware
|
||||
leader''')
|
||||
assert 'We have not implemented this for DCS of type' in str(result.exception)
|
||||
assert 'We have not implemented this for DCS of type' in str(result.output)
|
||||
|
||||
@patch('patroni.etcd.Etcd.watch', Mock(return_value=None))
|
||||
@patch('patroni.etcd.Etcd.get_cluster', Mock(return_value=get_cluster_initialized_with_leader()))
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@ def false(*args, **kwargs):
|
||||
|
||||
|
||||
def get_cluster(initialize, leader, members, failover):
|
||||
return Cluster(initialize, leader, None, members, failover)
|
||||
return Cluster(initialize, leader, 10, members, failover)
|
||||
|
||||
|
||||
def get_cluster_not_initialized_without_leader():
|
||||
|
||||
Reference in New Issue
Block a user