Use passfile in the primary_conninfo instead of password (#1194)

Fixed a few minor issues related to the #1134 and #1122
Close https://github.com/zalando/patroni/issues/1185
This commit is contained in:
Alexander Kukushkin
2019-10-09 18:04:14 +02:00
committed by GitHub
parent 86ee22efab
commit 1572c02ced
9 changed files with 162 additions and 121 deletions
+6 -6
View File
@@ -15,14 +15,14 @@ from requests.structures import CaseInsensitiveDict
logger = logging.getLogger(__name__)
AUTH_ALLOWED_PARAMETERS = (
_AUTH_ALLOWED_PARAMETERS = (
'username',
'password',
'sslmode',
'sslcert',
'sslkey',
'sslrootcert',
'sslcrl',
'sslcrl'
)
@@ -260,9 +260,9 @@ class Config(object):
if value:
ret['log']['loggers'] = value
def _get_auth(name):
def _get_auth(name, params=None):
ret = {}
for param in AUTH_ALLOWED_PARAMETERS:
for param in params or _AUTH_ALLOWED_PARAMETERS[:2]:
value = _popenv(name + '_' + param)
if value:
ret[param] = value
@@ -274,7 +274,7 @@ class Config(object):
authentication = {}
for user_type in ('replication', 'superuser', 'rewind'):
entry = _get_auth(user_type)
entry = _get_auth(user_type, _AUTH_ALLOWED_PARAMETERS)
if entry:
authentication[user_type] = entry
@@ -366,7 +366,7 @@ class Config(object):
# handle setting additional connection parameters that may be available
# in the configuration file, such as SSL connection parameters
for name, value in pg_config['authentication'].items():
pg_config['authentication'][name] = {n: v for n, v in value.items() if n in AUTH_ALLOWED_PARAMETERS}
pg_config['authentication'][name] = {n: v for n, v in value.items() if n in _AUTH_ALLOWED_PARAMETERS}
# no 'name' in config
if 'name' not in config and 'name' in pg_config:
+1 -2
View File
@@ -161,8 +161,7 @@ class Member(namedtuple('Member', 'index,name,session,data')):
if auth and isinstance(auth, dict):
ret.update(auth)
if 'username' in auth:
ret['user'] = auth['username']
del ret['username']
ret['user'] = ret.pop('username')
return ret
@property
+5 -21
View File
@@ -77,7 +77,6 @@ class Postgresql(object):
self.slots_handler = SlotsHandler(self)
self._pgpass = config.get('pgpass') or os.path.join(os.path.expanduser('~'), 'pgpass')
self._callback_executor = CallbackExecutor()
self.__cb_called = False
self.__cb_pending = None
@@ -258,19 +257,6 @@ class Postgresql(object):
def data_directory_empty(self):
return not os.path.exists(self._data_dir) or os.listdir(self._data_dir) == []
def write_pgpass(self, record):
if 'user' not in record or 'password' not in record:
return os.environ.copy()
with open(self._pgpass, 'w') as f:
if os.name != 'nt':
os.fchmod(f.fileno(), 0o600)
f.write('{host}:{port}:*:{user}:{password}\n'.format(**record))
env = os.environ.copy()
env['PGPASSFILE'] = self._pgpass
return env
def replica_method_options(self, method):
return deepcopy(self.config.get(method, {}))
@@ -645,12 +631,10 @@ class Postgresql(object):
@contextmanager
def get_replication_connection_cursor(self, host='localhost', port=5432, database=None, **kwargs):
replication = self.config.replication
extra_kwargs = {k: v for k, v in replication.items()
if k not in ('username', 'password', 'connect_timeout', 'options')}
with get_connection_cursor(host=host, port=int(port), database=database or self._database, replication=1,
user=replication['username'], password=replication.get('password'),
connect_timeout=3, options='-c statement_timeout=2000', **extra_kwargs) as cur:
conn_kwargs = self.config.replication.copy()
conn_kwargs.update(host=host, port=int(port), database=database or self._database, connect_timeout=3,
user=conn_kwargs.pop('username'), replication=1, options='-c statement_timeout=2000')
with get_connection_cursor(**conn_kwargs) as cur:
yield cur
def get_local_timeline_lsn_from_replication_connection(self):
@@ -696,7 +680,7 @@ class Postgresql(object):
min_apply_delay = is_remote_master and member.recovery_min_apply_delay
archive_cleanup = is_remote_master and member.archive_cleanup_command
primary_conninfo = self.config.primary_conninfo(member)
primary_conninfo = self.config.primary_conninfo_params(member)
change_role = self.cb_called and (self.role in ('master', 'demoted') or
not {'standby_leader', 'replica'} - {self.role, role})
+3 -11
View File
@@ -135,16 +135,8 @@ class Bootstrap(object):
# (pghost empty or the default socket directory) connections coming from the local machine.
r['host'] = 'localhost' # set it to localhost to write into pgpass
if 'user' in r:
user = r['user']
else:
user = ''
if 'password' in r:
import getpass
r.setdefault('user', os.environ.get('PGUSER', getpass.getuser()))
connstring = uri('postgres', (host, r['port']), r['database'], user)
env = self._postgresql.write_pgpass(r) if 'password' in r else None
connstring = uri('postgres', (host, r['port']), r['database'], r.get('user'))
env = self._postgresql.config.write_pgpass(r) if 'password' in r else None
try:
ret = self._postgresql.cancellable.call(shlex.split(cmd) + [connstring], env=env)
@@ -178,7 +170,7 @@ class Bootstrap(object):
r = clone_member.conn_kwargs(self._postgresql.config.replication)
connstring = uri('postgres', (r['host'], r['port']), r['database'], r['user'])
# add the credentials to connect to the replica origin to pgpass.
env = self._postgresql.write_pgpass(r)
env = self._postgresql.config.write_pgpass(r)
else:
connstring = ''
env = os.environ.copy()
+127 -55
View File
@@ -154,6 +154,37 @@ def mtime(filename):
return None
class ConfigWriter(object):
def __init__(self, filename):
self._filename = filename
self._fd = None
def __enter__(self):
self._fd = open(self._filename, 'w')
self.writeline('# Do not edit this file manually!\n# It will be overwritten by Patroni!')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
if self._fd:
self._fd.close()
def writeline(self, line):
self._fd.write(line)
self._fd.write('\n')
def writelines(self, lines):
for line in lines:
self.writeline(line)
@staticmethod
def escape(value): # Escape (by doubling) any single quotes or backslashes in given string
return re.sub(r'([\'\\])', r'\1\1', str(value))
def write_param(self, param, value):
self.writeline("{0} = '{1}'".format(param, self.escape(value)))
class ConfigHandler(object):
# List of parameters which must be always passed to postmaster as command line options
@@ -187,23 +218,6 @@ class ConfigHandler(object):
'wal_log_hints': ('on', lambda _: False, 90400)
})
# A list of keywords that can be found in a conninfo string. Follows what
# is acceptable by libpq
_CONNINFO_KEYWORDS = (
'user',
'password',
'host',
'port',
'sslmode',
'sslcompression',
'sslcert',
'sslkey',
'sslrootcert',
'sslcrl',
'application_name',
'krbsrvname',
)
_RECOVERY_PARAMETERS = {
'archive_cleanup_command',
'restore_command',
@@ -223,8 +237,6 @@ class ConfigHandler(object):
'trigger_file'
}
_CONFIG_WARNING_HEADER = '# Do not edit this file manually!\n# It will be overwritten by Patroni!\n'
def __init__(self, postgresql, config):
self._postgresql = postgresql
self._config_dir = os.path.abspath(config.get('config_dir') or postgresql.data_dir)
@@ -241,6 +253,9 @@ class ConfigHandler(object):
self._standby_signal = os.path.join(postgresql.data_dir, 'standby.signal')
self._auto_conf = os.path.join(postgresql.data_dir, 'postgresql.auto.conf')
self._auto_conf_mtime = None
self._pgpass = config.get('pgpass') or os.path.join(os.path.expanduser('~'), 'pgpass')
self._passfile = None
self._passfile_mtime = None
self._synchronous_standby_names = None
self._postmaster_ctime = None
self._primary_conninfo = None
@@ -300,28 +315,26 @@ class ConfigHandler(object):
if 'custom_conf' not in self._config and not os.path.exists(self._postgresql_base_conf):
os.rename(self._postgresql_conf, self._postgresql_base_conf)
with open(self._postgresql_conf, 'w') as f:
os.chmod(self._postgresql_conf, stat.S_IWRITE | stat.S_IREAD)
f.write(self._CONFIG_WARNING_HEADER)
f.write("include '{0}'\n\n".format(self._config.get('custom_conf') or self._postgresql_base_conf_name))
with ConfigWriter(self._postgresql_conf) as f:
include = self._config.get('custom_conf') or self._postgresql_base_conf_name
f.writeline("include '{0}'\n".format(ConfigWriter.escape(include)))
for name, value in sorted((configuration or self._server_parameters).items()):
if (not self._postgresql.bootstrap.running_custom_bootstrap or name != 'hba_file') \
and name not in self._RECOVERY_PARAMETERS:
f.write("{0} = '{1}'\n".format(name, value))
f.write_param(name, value)
# when we are doing custom bootstrap we assume that we don't know superuser password
# and in order to be able to change it, we are opening trust access from a certain address
# therefore we need to make sure that hba_file is not overriden
# after changing superuser password we will "revert" all these "changes"
if self._postgresql.bootstrap.running_custom_bootstrap or 'hba_file' not in self._server_parameters:
f.write("hba_file = '{0}'\n".format(self._pg_hba_conf.replace('\\', '\\\\')))
f.write_param('hba_file', self._pg_hba_conf)
if 'ident_file' not in self._server_parameters:
f.write("ident_file = '{0}'\n".format(self._pg_ident_conf.replace('\\', '\\\\')))
f.write_param('ident_file', self._pg_ident_conf)
if self._postgresql.major_version >= 120000:
if self._recovery_params:
f.write('\n# recovery.conf\n')
for name, value in sorted(self._recovery_params.items()):
f.write("{0} = '{1}'\n".format(name, value))
f.writeline('\n# recovery.conf')
self._write_recovery_params(f, self._recovery_params)
if not self._postgresql.bootstrap.keep_existing_recovery_conf:
self._sanitize_auto_conf()
@@ -349,18 +362,15 @@ class ConfigHandler(object):
self.local_replication_address['host'], self.local_replication_address['port'],
0, socket.SOCK_STREAM, socket.IPPROTO_TCP)})
with open(self._pg_hba_conf, 'w') as f:
f.write(self._CONFIG_WARNING_HEADER)
with ConfigWriter(self._pg_hba_conf) as f:
for address, t in addresses.items():
f.write((
f.writeline((
'{0}\treplication\t{1}\t{3}\ttrust\n'
'{0}\tall\t{2}\t{3}\ttrust\n'
'{0}\tall\t{2}\t{3}\ttrust'
).format(t, self.replication['username'], self._superuser.get('username') or 'all', address))
elif not self.hba_file and self._config.get('pg_hba'):
with open(self._pg_hba_conf, 'w') as f:
f.write(self._CONFIG_WARNING_HEADER)
for line in self._config['pg_hba']:
f.write('{0}\n'.format(line))
with ConfigWriter(self._pg_hba_conf) as f:
f.writelines(self._config['pg_hba'])
return True
def replace_pg_ident(self):
@@ -372,29 +382,44 @@ class ConfigHandler(object):
"""
if not self._server_parameters.get('ident_file') and self._config.get('pg_ident'):
with open(self._pg_ident_conf, 'w') as f:
f.write(self._CONFIG_WARNING_HEADER)
for line in self._config['pg_ident']:
f.write('{0}\n'.format(line))
with ConfigWriter(self._pg_ident_conf) as f:
f.writelines(self._config['pg_ident'])
return True
def primary_conninfo_params(self, member):
name = self._postgresql.name
if not (member and member.conn_url) or member.name == name:
if not (member and member.conn_url) or member.name == self._postgresql.name:
return None
ret = member.conn_kwargs(self.replication)
ret.update(application_name=name, sslmode='prefer')
ret['application_name'] = self._postgresql.name
ret.setdefault('sslmode', 'prefer')
if self._krbsrvname:
ret['krbsrvname'] = self._krbsrvname
if 'database' in ret:
del ret['database']
return ret
def primary_conninfo(self, member):
r = self.primary_conninfo_params(member)
if not r:
return None
return ' '.join('{0}={{{0}}}'.format(kw) for kw in self._CONNINFO_KEYWORDS if r.get(kw)).format(**r)
def format_dsn(self, params, include_dbname=False):
# A list of keywords that can be found in a conninfo string. Follows what is acceptable by libpq
keywords = ('user', 'passfile', 'host', 'port', 'sslmode', 'sslcompression', 'sslcert',
'sslkey', 'sslrootcert', 'sslcrl', 'application_name', 'krbsrvname')
if include_dbname:
params['dbname'] = params.get('database') or self._postgresql.database
keywords = ('dbname',) + keywords
def escape(value):
return re.sub(r'([\'\\ ])', r'\\\1', str(value))
return ' '.join('{0}={1}'.format(kw, escape(params[kw])) for kw in keywords if params.get(kw) is not None)
def _write_recovery_params(self, fd, recovery_params):
for name, value in sorted(recovery_params.items()):
if name == 'primary_conninfo':
if 'password' in value:
self.write_pgpass(value)
value['passfile'] = self._passfile = self._pgpass
self._passfile_mtime = mtime(self._pgpass)
value = self.format_dsn(value)
fd.write_param(name, value)
def recovery_conf_exists(self):
if self._postgresql.major_version >= 120000:
@@ -404,12 +429,13 @@ class ConfigHandler(object):
def _read_primary_conninfo(self):
pg_conf_mtime = mtime(self._postgresql_conf)
auto_conf_mtime = mtime(self._auto_conf)
passfile_mtime = mtime(self._passfile) if self._passfile else False
postmaster_ctime = self._postgresql.is_running()
if postmaster_ctime:
postmaster_ctime = postmaster_ctime.create_time()
if self._postgresql_conf_mtime == pg_conf_mtime and self._auto_conf_mtime == auto_conf_mtime \
and self._postmaster_ctime == postmaster_ctime:
and self._passfile_mtime == passfile_mtime and self._postmaster_ctime == postmaster_ctime:
return None, False
try:
@@ -423,7 +449,8 @@ class ConfigHandler(object):
def _read_primary_conninfo_pre_v12(self):
recovery_conf_mtime = mtime(self._recovery_conf)
if recovery_conf_mtime == self._recovery_conf_mtime:
passfile_mtime = mtime(self._passfile) if self._passfile else False
if recovery_conf_mtime == self._recovery_conf_mtime and passfile_mtime == self._passfile_mtime:
return None, False
primary_conninfo = ''
@@ -439,6 +466,27 @@ class ConfigHandler(object):
self._recovery_conf_mtime = recovery_conf_mtime
return primary_conninfo, True
def _check_passfile(self, wanted_primary_conninfo):
# If there is a passfile in the primary_conninfo try to figure out that
# the passfile contains the line allowing connection to the given node.
# We assume that the passfile was created by Patroni and therefore doing
# the full match and not covering cases when host, port or user are set to '*'
passfile = self._primary_conninfo['passfile']
passfile_mtime = mtime(passfile)
if passfile_mtime:
try:
with open(passfile) as f:
wanted_line = self._pgpass_line(wanted_primary_conninfo).strip()
for raw_line in f:
if raw_line.strip() == wanted_line:
self._primary_conninfo['password'] = wanted_primary_conninfo['password']
self._passfile = passfile
self._passfile_mtime = passfile_mtime
return True
except Exception:
logger.info('Failed to read %s', passfile)
return False
def check_recovery_conf(self, member): # Name is confusing. In fact it checks the value of primary_conninfo
# TODO: recovery.conf could be stale, would be nice to detect that.
if self._postgresql.major_version >= 120000:
@@ -480,6 +528,10 @@ class ConfigHandler(object):
elif not self._primary_conninfo:
return False
if 'passfile' in self._primary_conninfo and 'password' in wanted_primary_conninfo and \
'password' not in self._primary_conninfo and not self._check_passfile(wanted_primary_conninfo):
return False
return all(self._primary_conninfo.get(p) == str(v) for p, v in wanted_primary_conninfo.items())
@staticmethod
@@ -487,6 +539,28 @@ class ConfigHandler(object):
if os.path.isfile(name) or os.path.islink(name):
os.unlink(name)
@staticmethod
def _pgpass_line(record):
if 'password' in record:
def escape(value):
return re.sub(r'([:\\])', r'\\\1', str(value))
record = {n: escape(record.get(n, '*')) for n in ('host', 'port', 'user', 'password')}
return '{host}:{port}:*:{user}:{password}'.format(**record)
def write_pgpass(self, record):
line = self._pgpass_line(record)
if not line:
return os.environ.copy()
with open(self._pgpass, 'w') as f:
os.chmod(self._pgpass, stat.S_IWRITE | stat.S_IREAD)
f.write(line)
env = os.environ.copy()
env['PGPASSFILE'] = self._pgpass
return env
def write_recovery_conf(self, recovery_params):
if self._postgresql.major_version >= 120000:
if parse_bool(recovery_params.pop('standby_mode', None)):
@@ -496,10 +570,9 @@ class ConfigHandler(object):
open(self._recovery_signal, 'w').close()
self._recovery_params = recovery_params
else:
with open(self._recovery_conf, 'w') as f:
with ConfigWriter(self._recovery_conf) as f:
os.chmod(self._recovery_conf, stat.S_IWRITE | stat.S_IREAD)
for name, value in recovery_params.items():
f.write("{0} = '{1}'\n".format(name, value))
self._write_recovery_params(f, recovery_params)
def remove_recovery_conf(self):
for name in (self._recovery_conf, self._standby_signal, self._recovery_signal):
@@ -678,7 +751,6 @@ class ConfigHandler(object):
self._postgresql.set_pending_restart(pending_restart)
self._server_parameters = server_parameters
self._adjust_recovery_parameters()
self._connect_address = config.get('connect_address')
self._krbsrvname = config.get('krbsrvname')
# for not so obvious connection attempts that may happen outside of pyscopg2
+2 -10
View File
@@ -145,17 +145,9 @@ class Rewind(object):
def pg_rewind(self, r):
# prepare pg_rewind connection
env = self._postgresql.write_pgpass(r)
env = self._postgresql.config.write_pgpass(r)
env['PGOPTIONS'] = '-c statement_timeout=0'
dsn_attrs = [
('user', r.get('user')),
('host', r.get('host')),
('port', r.get('port')),
('dbname', r.get('database') or self._postgresql.database),
('sslmode', 'prefer'),
('sslcompression', '1'),
]
dsn = " ".join("{0}={1}".format(k, v) for k, v in dsn_attrs if v is not None)
dsn = self._postgresql.config.format_dsn(r, True)
logger.info('running pg_rewind from %s', dsn)
try:
return self._postgresql.cancellable.call([self._postgresql.pgcommand('pg_rewind'), '-D',
+1 -2
View File
@@ -5,7 +5,6 @@ import shutil
import unittest
from mock import Mock, patch
from tempfile import gettempdir
import psycopg2
import requests
@@ -183,7 +182,7 @@ class PostgresInit(unittest.TestCase):
data_dir = 'data/test0'
self.p = Postgresql({'name': 'postgresql0', 'scope': 'batman', 'data_dir': data_dir,
'config_dir': data_dir, 'retry_timeout': 10,
'krbsrvname': 'postgres', 'pgpass': os.path.join(gettempdir(), 'pgpass0'),
'krbsrvname': 'postgres', 'pgpass': os.path.join(data_dir, 'pgpass0'),
'listen': '127.0.0.2, 127.0.0.3:5432', 'connect_address': '127.0.0.2:5432',
'authentication': {'superuser': {'username': 'foo', 'password': 'test'},
'replication': {'username': '', 'password': 'rep-pass'}},
+1 -1
View File
@@ -152,7 +152,7 @@ def run_async(self, func, args=()):
@patch.object(Postgresql, 'controldata', Mock(return_value={'Database system identifier': SYSID}))
@patch.object(SlotsHandler, 'sync_replication_slots', Mock())
@patch.object(ConfigHandler, 'append_pg_hba', Mock())
@patch.object(Postgresql, 'write_pgpass', Mock(return_value={}))
@patch.object(ConfigHandler, 'write_pgpass', Mock(return_value={}))
@patch.object(ConfigHandler, 'write_recovery_conf', Mock())
@patch.object(Postgresql, 'query', Mock())
@patch.object(Postgresql, 'checkpoint', Mock())
+16 -13
View File
@@ -186,10 +186,11 @@ class TestPostgresql(BaseTestPostgresql):
self.assertFalse(self.p.restart())
self.assertEqual(self.p.state, 'restart failed (restarting)')
@patch('os.chmod', Mock())
@patch.object(builtins, 'open', MagicMock())
def test_write_pgpass(self):
self.p.write_pgpass({'host': 'localhost', 'port': '5432', 'user': 'foo'})
self.p.write_pgpass({'host': 'localhost', 'port': '5432', 'user': 'foo', 'password': 'bar'})
self.p.config.write_pgpass({'host': 'localhost', 'port': '5432', 'user': 'foo'})
self.p.config.write_pgpass({'host': 'localhost', 'port': '5432', 'user': 'foo', 'password': 'bar'})
def test_checkpoint(self):
with patch.object(MockCursor, 'fetchone', Mock(return_value=(True, ))):
@@ -199,29 +200,31 @@ class TestPostgresql(BaseTestPostgresql):
self.assertEqual(self.p.checkpoint(), 'not accessible or not healty')
@patch('patroni.postgresql.config.mtime', mock_mtime)
@patch.object(MockCursor, 'fetchone', Mock(side_effect=[('foo=bar',), ('',), ('',), ('a=b',)]))
def test_check_recovery_conf(self):
@patch.object(MockCursor, 'fetchone')
def test_check_recovery_conf(self, mock_fetchone):
mock_fetchone.side_effect = [('foo=bar',), ('',), ('foo',), ('host=1 passfile=' + self.p.config._pgpass,)]
conninfo = {'host': '1', 'password': 'bar'}
for version in (120000, 100000):
with patch.object(Postgresql, 'major_version', PropertyMock(return_value=version)):
self.p.config.write_recovery_conf({'standby_mode': 'on', 'primary_conninfo': 'foo'})
self.p.config.write_recovery_conf({'standby_mode': 'on', 'primary_conninfo': {'sslmode': 'prefer'}})
self.assertFalse(self.p.config.check_recovery_conf(None))
self.p.config.write_recovery_conf({'primary_conninfo': 'foo'})
self.p.config.write_recovery_conf({'primary_conninfo': {'sslmode': 'prefer'}})
self.p.config.write_postgresql_conf()
self.assertFalse(self.p.config.check_recovery_conf(None))
self.p.config.write_recovery_conf({'standby_mode': 'on'})
self.assertTrue(self.p.config.check_recovery_conf(None))
with patch('patroni.postgresql.config.ConfigHandler.primary_conninfo_params',
Mock(return_value={'a': 'b'})):
Mock(return_value=conninfo.copy())):
self.assertFalse(self.p.config.check_recovery_conf(None))
self.p.config.write_recovery_conf({'standby_mode': 'on', 'primary_conninfo': 'a=b'})
self.assertTrue(self.p.config.check_recovery_conf(None))
self.p.config.write_recovery_conf({'standby_mode': 'on', 'primary_conninfo': conninfo.copy()})
self.p.config.check_recovery_conf(None)
@patch.object(Postgresql, 'major_version', PropertyMock(return_value=120000))
@patch.object(Postgresql, 'is_running', MockPostmaster)
@patch.object(MockPostmaster, 'create_time', Mock(return_value=1234567), create=True)
@patch.object(MockCursor, 'fetchone', Mock(return_value=('',)))
def test__read_primary_conninfo(self):
self.p.config.write_recovery_conf({'standby_mode': 'on'})
self.p.config.write_recovery_conf({'standby_mode': 'on', 'primary_conninfo': {'password': 'foo'}})
self.p.config.write_postgresql_conf()
self.assertTrue(self.p.config.check_recovery_conf(None))
self.assertTrue(self.p.config.check_recovery_conf(None))
@@ -231,9 +234,9 @@ class TestPostgresql(BaseTestPostgresql):
@patch.object(Postgresql, 'major_version', PropertyMock(return_value=100000))
def test__read_primary_conninfo_pre_v12(self):
self.p.config.write_recovery_conf({'standby_mode': 'on'})
self.assertTrue(self.p.config.check_recovery_conf(None))
self.assertTrue(self.p.config.check_recovery_conf(None))
self.p.config.write_recovery_conf({'standby_mode': 'on', 'primary_conninfo': {'password': 'foo'}})
self.assertFalse(self.p.config.check_recovery_conf(None))
self.assertFalse(self.p.config.check_recovery_conf(None))
def test_write_postgresql_and_sanitize_auto_conf(self):
read_data = 'primary_conninfo = foo\nfoo = bar\n'