Skip to content

Commit e91e6b5

Browse files
authored
Add support of sslnegotiation client-side connection option (patroni#3173)
It is available in PostgreSQL 17 Besides that, enable PG17 in behave tests and include PG17 to supported versions in docs.
1 parent 6b68503 commit e91e6b5

File tree

9 files changed

+29
-18
lines changed

9 files changed

+29
-18
lines changed

.github/workflows/install_deps.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ def install_packages(what):
4646
packages['exhibitor'] = packages['zookeeper']
4747
packages = packages.get(what, [])
4848
ver = versions.get(what)
49-
if float(ver) >= 15:
49+
if 15 <= float(ver) < 17:
5050
packages += ['postgresql-{0}-citus-12.1'.format(ver)]
5151
subprocess.call(['sudo', 'apt-get', 'update', '-y'])
5252
return subprocess.call(['sudo', 'apt-get', 'install', '-y', 'postgresql-' + ver, 'expect-dev'] + packages)

.github/workflows/mapping.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
versions = {'etcd': '9.6', 'etcd3': '16', 'consul': '13', 'exhibitor': '12', 'raft': '14', 'kubernetes': '15'}
1+
versions = {'etcd': '9.6', 'etcd3': '16', 'consul': '17', 'exhibitor': '12', 'raft': '14', 'kubernetes': '15'}

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ Patroni is a template for high availability (HA) PostgreSQL solutions using Pyth
1212

1313
We call Patroni a "template" because it is far from being a one-size-fits-all or plug-and-play replication system. It will have its own caveats. Use wisely.
1414

15-
Currently supported PostgreSQL versions: 9.3 to 16.
15+
Currently supported PostgreSQL versions: 9.3 to 17.
1616

1717
**Note to Citus users**: Starting from 3.0 Patroni nicely integrates with the `Citus <https://github.com/citusdata/citus>`__ database extension to Postgres. Please check the `Citus support page <https://github.com/patroni/patroni/blob/master/docs/citus.rst>`__ in the Patroni documentation for more info about how to use Patroni high availability together with a Citus distributed cluster.
1818

docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ Patroni is a template for high availability (HA) PostgreSQL solutions using Pyth
1010

1111
We call Patroni a "template" because it is far from being a one-size-fits-all or plug-and-play replication system. It will have its own caveats. Use wisely. There are many ways to run high availability with PostgreSQL; for a list, see the `PostgreSQL Documentation <https://wiki.postgresql.org/wiki/Replication,_Clustering,_and_Connection_Pooling>`__.
1212

13-
Currently supported PostgreSQL versions: 9.3 to 16.
13+
Currently supported PostgreSQL versions: 9.3 to 17.
1414

1515
**Note to Citus users**: Starting from 3.0 Patroni nicely integrates with the `Citus <https://github.com/citusdata/citus>`__ database extension to Postgres. Please check the :ref:`Citus support page <citus>` in the Patroni documentation for more info about how to use Patroni high availability together with a Citus distributed cluster.
1616

patroni/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@
3434
'sslcrl',
3535
'sslcrldir',
3636
'gssencmode',
37-
'channel_binding'
37+
'channel_binding',
38+
'sslnegotiation'
3839
)
3940

4041

patroni/config_generator.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
'sslcrl': 'PGSSLCRL',
4040
'sslcrldir': 'PGSSLCRLDIR',
4141
'gssencmode': 'PGGSSENCMODE',
42-
'channel_binding': 'PGCHANNELBINDING'
42+
'channel_binding': 'PGCHANNELBINDING',
43+
'sslnegotiation': 'PGSSLNEGOTIATION'
4344
}
4445
NO_VALUE_MSG = '#FIXME'
4546

patroni/postgresql/config.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,13 +121,15 @@ def parse_dsn(value: str) -> Optional[Dict[str, str]]:
121121
122122
>>> r = parse_dsn('postgresql://u%2Fse:pass@:%2f123,[::1]/db%2Fsdf?application_name=mya%2Fpp&ssl=true')
123123
>>> r == {'application_name': 'mya/pp', 'dbname': 'db/sdf', 'host': ',::1', 'sslmode': 'require',\
124-
'password': 'pass', 'port': '/123,', 'user': 'u/se', 'gssencmode': 'prefer', 'channel_binding': 'prefer'}
124+
'password': 'pass', 'port': '/123,', 'user': 'u/se', 'gssencmode': 'prefer',\
125+
'channel_binding': 'prefer', 'sslnegotiation': 'postgres'}
125126
True
126127
>>> r = parse_dsn(" host = 'host' dbname = db\\\\ name requiressl=1 ")
127128
>>> r == {'dbname': 'db name', 'host': 'host', 'sslmode': 'require',\
128-
'gssencmode': 'prefer', 'channel_binding': 'prefer'}
129+
'gssencmode': 'prefer', 'channel_binding': 'prefer', 'sslnegotiation': 'postgres'}
129130
True
130-
>>> parse_dsn('requiressl = 0\\\\') == {'sslmode': 'prefer', 'gssencmode': 'prefer', 'channel_binding': 'prefer'}
131+
>>> parse_dsn('requiressl = 0\\\\') == {'sslmode': 'prefer', 'gssencmode': 'prefer',\
132+
'channel_binding': 'prefer', 'sslnegotiation': 'postgres'}
131133
True
132134
>>> parse_dsn("host=a foo = '") is None
133135
True
@@ -151,6 +153,7 @@ def parse_dsn(value: str) -> Optional[Dict[str, str]]:
151153
ret.setdefault('sslmode', 'prefer')
152154
ret.setdefault('gssencmode', 'prefer')
153155
ret.setdefault('channel_binding', 'prefer')
156+
ret.setdefault('sslnegotiation', 'postgres')
154157
return ret
155158

156159

@@ -587,6 +590,8 @@ def primary_conninfo_params(self, member: Union[Leader, Member, None]) -> Option
587590
ret.setdefault('gssencmode', 'prefer')
588591
if self._postgresql.major_version >= 130000:
589592
ret.setdefault('channel_binding', 'prefer')
593+
if self._postgresql.major_version >= 170000:
594+
ret.setdefault('sslnegotiation', 'postgres')
590595
if self._krbsrvname:
591596
ret['krbsrvname'] = self._krbsrvname
592597
if not ret.get('dbname'):
@@ -607,7 +612,7 @@ def format_dsn(self, params: Dict[str, Any]) -> str:
607612
keywords = ('dbname', 'user', 'passfile' if params.get('passfile') else 'password', 'host', 'port',
608613
'sslmode', 'sslcompression', 'sslcert', 'sslkey', 'sslpassword', 'sslrootcert', 'sslcrl',
609614
'sslcrldir', 'application_name', 'krbsrvname', 'gssencmode', 'channel_binding',
610-
'target_session_attrs')
615+
'target_session_attrs', 'sslnegotiation')
611616

612617
def escape(value: Any) -> str:
613618
return re.sub(r'([\'\\ ])', r'\\\1', str(value))

tests/test_config_generator.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def mock_open(*args, **kwargs):
2929

3030
@patch('patroni.psycopg.connect', psycopg_connect)
3131
@patch('builtins.open', MagicMock())
32-
@patch('subprocess.check_output', Mock(return_value=b"postgres (PostgreSQL) 16.2"))
32+
@patch('subprocess.check_output', Mock(return_value=b"postgres (PostgreSQL) 17.0"))
3333
@patch('psutil.Process.exe', Mock(return_value='/bin/dir/from/running/postgres'))
3434
@patch('psutil.Process.__init__', Mock(return_value=None))
3535
@patch('patroni.config_generator.get_address', Mock(return_value=(HOSTNAME, IP)))
@@ -128,7 +128,8 @@ def _set_running_instance_config_vals(self):
128128
'password': 'qwerty',
129129
'channel_binding': 'prefer',
130130
'gssencmode': 'prefer',
131-
'sslmode': 'prefer'
131+
'sslmode': 'prefer',
132+
'sslnegotiation': 'postgres'
132133
},
133134
'replication': {
134135
'username': NO_VALUE_MSG,
@@ -177,7 +178,7 @@ def test_generate_sample_config_pre_13_dir_creation(self, mock_makedir):
177178
'--version'])
178179

179180
@patch('os.makedirs', Mock())
180-
def test_generate_sample_config_16(self):
181+
def test_generate_sample_config_17(self):
181182
conf = {
182183
'bootstrap': {
183184
'dcs': {
@@ -213,7 +214,7 @@ def test_generate_sample_config_16(self):
213214

214215
@patch('os.makedirs', Mock())
215216
@patch('sys.stdout')
216-
def test_generate_config_running_instance_16(self, mock_sys_stdout):
217+
def test_generate_config_running_instance_17(self, mock_sys_stdout):
217218
self._set_running_instance_config_vals()
218219

219220
with patch('builtins.open', Mock(side_effect=self._get_running_instance_open_res())), \
@@ -226,11 +227,13 @@ def test_generate_config_running_instance_16(self, mock_sys_stdout):
226227

227228
@patch('os.makedirs', Mock())
228229
@patch('sys.stdout')
229-
def test_generate_config_running_instance_16_connect_from_env(self, mock_sys_stdout):
230+
def test_generate_config_running_instance_17_connect_from_env(self, mock_sys_stdout):
230231
self._set_running_instance_config_vals()
231232
# su auth params and connect host from env
232233
os.environ['PGCHANNELBINDING'] = \
233234
self.config['postgresql']['authentication']['superuser']['channel_binding'] = 'disable'
235+
os.environ['PGSSLNEGOTIATION'] = \
236+
self.config['postgresql']['authentication']['superuser']['sslnegotiation'] = 'direct'
234237

235238
conf = {
236239
'scope': 'my_cluster',
@@ -265,7 +268,7 @@ def test_generate_config_running_instance_16_connect_from_env(self, mock_sys_std
265268

266269
with patch('builtins.open', Mock(side_effect=self._get_running_instance_open_res())), \
267270
patch('sys.argv', ['patroni.py', '--generate-config']), \
268-
patch.object(MockConnect, 'server_version', PropertyMock(return_value=160000)), \
271+
patch.object(MockConnect, 'server_version', PropertyMock(return_value=170000)), \
269272
self.assertRaises(SystemExit) as e:
270273
_main()
271274
self.assertEqual(e.exception.code, 0)

tests/test_postgresql.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ def test_write_postgresql_and_sanitize_auto_conf(self):
364364

365365
@patch.object(Postgresql, 'is_running', Mock(return_value=False))
366366
@patch.object(Postgresql, 'start', Mock())
367+
@patch.object(Postgresql, 'major_version', PropertyMock(return_value=170000))
367368
def test_follow(self):
368369
self.p.call_nowait(CallbackAction.ON_START)
369370
m = RemoteMember('1', {'restore_command': '2', 'primary_slot_name': 'foo', 'conn_kwargs': {'host': 'foo,bar'}})
@@ -1169,5 +1170,5 @@ def test_load_current_server_parameters(self):
11691170
self.p.config.load_current_server_parameters()
11701171
self.assertTrue(all(self.p.config._server_parameters[name] == value for name, value in keep_values.items()))
11711172
self.assertEqual(dict(self.p.config._recovery_params),
1172-
{'primary_conninfo': {'host': 'a', 'port': '5433', 'passfile': '/blabla',
1173-
'gssencmode': 'prefer', 'sslmode': 'prefer', 'channel_binding': 'prefer'}})
1173+
{'primary_conninfo': {'host': 'a', 'port': '5433', 'passfile': '/blabla', 'sslmode': 'prefer',
1174+
'gssencmode': 'prefer', 'channel_binding': 'prefer', 'sslnegotiation': 'postgres'}})

0 commit comments

Comments
 (0)