Skip to content

fix: pin version of libpq to 17.x #1649

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Jun 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions ansible/tasks/setup-postgrest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,28 @@
url: https://www.postgresql.org/media/keys/ACCC4CF8.asc
state: present

- name: PostgREST - add Postgres PPA
- name: PostgREST - add Postgres PPA main
apt_repository:
repo: "deb http://apt.postgresql.org/pub/repos/apt/ focal-pgdg {{ postgresql_major }}"
repo: "deb http://apt.postgresql.org/pub/repos/apt/ focal-pgdg main"
state: present
filename: postgresql-pgdg

- name: PostgREST - update apt cache
- name: PostgREST - install system dependencies
apt:
update_cache: yes

# libpq is a C library that enables user programs to communicate with
# the PostgreSQL database server.
- name: PostgREST - system dependencies
apt:
pkg:
package:
- libpq5
- libnuma-dev
update_cache: yes
state: present

- name: PostgREST - verify libpq5 version
shell: dpkg -l libpq5 | grep '^ii' | awk '{print $3}'
register: libpq5_version
changed_when: false

- name: Show installed libpq5 version
debug:
msg: "Installed libpq5 version: {{ libpq5_version.stdout }}"

- name: PostgREST - remove Postgres PPA gpg key
apt_key:
Expand All @@ -30,7 +36,7 @@

- name: PostgREST - remove Postgres PPA
apt_repository:
repo: "deb http://apt.postgresql.org/pub/repos/apt/ focal-pgdg {{ postgresql_major }}"
repo: "deb http://apt.postgresql.org/pub/repos/apt/ focal-pgdg main"
state: absent

- name: postgis - ensure dependencies do not get autoremoved
Expand Down Expand Up @@ -88,7 +94,6 @@
#! /usr/bin/env bash
set -euo pipefail
set -x

cd "$(dirname "$0")"
cat $@ > merged.conf
dest: /etc/postgrest/merge.sh
Expand Down
6 changes: 3 additions & 3 deletions ansible/vars.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ postgres_major:

# Full version strings for each major version
postgres_release:
postgresorioledb-17: "17.0.1.093-orioledb"
postgres17: "17.4.1.043"
postgres15: "15.8.1.100"
postgresorioledb-17: "17.0.1.094-orioledb"
postgres17: "17.4.1.044"
postgres15: "15.8.1.101"

# Non Postgres Extensions
pgbouncer_release: "1.19.0"
Expand Down
230 changes: 230 additions & 0 deletions testinfra/test_ami_nix.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,3 +507,233 @@ def test_postgrest_ending_empty_key_query_parameter_is_removed(host):
},
)
assert res.ok


def test_postgresql_version(host):
"""Print the PostgreSQL version being tested and ensure it's >= 14."""
result = run_ssh_command(host['ssh'], "sudo -u postgres psql -c 'SELECT version();'")
if result['succeeded']:
print(f"\nPostgreSQL Version:\n{result['stdout']}")
# Extract version number from the output
version_line = result['stdout'].strip().split('\n')[2] # Skip header and get the actual version
# Extract major version number (e.g., "15.8" -> 15)
import re
version_match = re.search(r'PostgreSQL (\d+)\.', version_line)
if version_match:
major_version = int(version_match.group(1))
print(f"PostgreSQL major version: {major_version}")
assert major_version >= 14, f"PostgreSQL version {major_version} is less than 14"
else:
assert False, "Could not parse PostgreSQL version number"
else:
print(f"\nFailed to get PostgreSQL version: {result['stderr']}")
assert False, "Failed to get PostgreSQL version"

# Also get the version from the command line
result = run_ssh_command(host['ssh'], "sudo -u postgres psql --version")
if result['succeeded']:
print(f"PostgreSQL Client Version: {result['stdout'].strip()}")
else:
print(f"Failed to get PostgreSQL client version: {result['stderr']}")

print("✓ PostgreSQL version is >= 14")


def test_libpq5_version(host):
"""Print the libpq5 version installed and ensure it's >= 14."""
# Try different package managers to find libpq5
result = run_ssh_command(host['ssh'], "dpkg -l | grep libpq5 || true")
if result['succeeded'] and result['stdout'].strip():
print(f"\nlibpq5 package info:\n{result['stdout']}")
# Extract version from dpkg output (format: ii libpq5:arm64 17.5-1.pgdg20.04+1)
import re
version_match = re.search(r'libpq5[^ ]* +(\d+)\.', result['stdout'])
if version_match:
major_version = int(version_match.group(1))
print(f"libpq5 major version: {major_version}")
assert major_version >= 14, f"libpq5 version {major_version} is less than 14"
else:
print("Could not parse libpq5 version from dpkg output")
else:
print("\nlibpq5 not found via dpkg")

# Also try to find libpq.so files
result = run_ssh_command(host['ssh'], "find /usr -name '*libpq*' -type f 2>/dev/null | head -10")
if result['succeeded'] and result['stdout'].strip():
print(f"\nlibpq files found:\n{result['stdout']}")
else:
print("\nNo libpq files found")

# Check if we can get version from a libpq file
result = run_ssh_command(host['ssh'], "ldd /usr/bin/psql | grep libpq || true")
if result['succeeded'] and result['stdout'].strip():
print(f"\npsql libpq dependency:\n{result['stdout']}")
else:
print("\nCould not find libpq dependency for psql")

# Try to get version from libpq directly
result = run_ssh_command(host['ssh'], "psql --version 2>&1 | head -1")
if result['succeeded'] and result['stdout'].strip():
print(f"\npsql version output: {result['stdout'].strip()}")
# The psql version should match the libpq version
import re
version_match = re.search(r'psql \(PostgreSQL\) (\d+)\.', result['stdout'])
if version_match:
major_version = int(version_match.group(1))
print(f"psql/libpq major version: {major_version}")
assert major_version >= 14, f"psql/libpq version {major_version} is less than 14"
else:
print("Could not parse psql version")

print("✓ libpq5 version is >= 14")


def test_postgrest_read_only_session_attrs(host):
"""Test PostgREST with target_session_attrs=read-only and check for session errors."""
# First, check if PostgreSQL is configured for read-only mode
result = run_ssh_command(host['ssh'], "sudo -u postgres psql -c \"SHOW default_transaction_read_only;\"")
if result['succeeded']:
default_read_only = result['stdout'].strip()
print(f"PostgreSQL default_transaction_read_only: {default_read_only}")
else:
print("Could not check PostgreSQL read-only setting")
default_read_only = "unknown"

# Check if PostgreSQL is in recovery mode (standby)
result = run_ssh_command(host['ssh'], "sudo -u postgres psql -c \"SELECT pg_is_in_recovery();\"")
if result['succeeded']:
in_recovery = result['stdout'].strip()
print(f"PostgreSQL pg_is_in_recovery: {in_recovery}")
else:
print("Could not check PostgreSQL recovery status")
in_recovery = "unknown"

# Find PostgreSQL configuration file
result = run_ssh_command(host['ssh'], "sudo -u postgres psql -c \"SHOW config_file;\"")
if result['succeeded']:
config_file = result['stdout'].strip().split('\n')[2].strip() # Skip header and get the actual path
print(f"PostgreSQL config file: {config_file}")
else:
print("Could not find PostgreSQL config file")
config_file = "/etc/postgresql/15/main/postgresql.conf" # Default fallback

# Backup PostgreSQL config
result = run_ssh_command(host['ssh'], f"sudo cp {config_file} {config_file}.backup")
assert result['succeeded'], "Failed to backup PostgreSQL config"

# Add read-only setting to PostgreSQL config
result = run_ssh_command(host['ssh'], f"echo 'default_transaction_read_only = on' | sudo tee -a {config_file}")
assert result['succeeded'], "Failed to add read-only setting to PostgreSQL config"

# Restart PostgreSQL to apply the new configuration
result = run_ssh_command(host['ssh'], "sudo systemctl restart postgresql")
assert result['succeeded'], "Failed to restart PostgreSQL"

# Wait for PostgreSQL to start up
sleep(5)

# Verify the change took effect
result = run_ssh_command(host['ssh'], "sudo -u postgres psql -c \"SHOW default_transaction_read_only;\"")
if result['succeeded']:
new_default_read_only = result['stdout'].strip()
print(f"PostgreSQL default_transaction_read_only after change: {new_default_read_only}")
else:
print("Could not verify PostgreSQL read-only setting change")

# First, backup the current PostgREST config
result = run_ssh_command(host['ssh'], "sudo cp /etc/postgrest/base.conf /etc/postgrest/base.conf.backup")
assert result['succeeded'], "Failed to backup PostgREST config"

try:
# Read the current config to get the db-uri
result = run_ssh_command(host['ssh'], "sudo cat /etc/postgrest/base.conf | grep '^db-uri'")
assert result['succeeded'], "Failed to read current db-uri"

current_db_uri = result['stdout'].strip()
print(f"Current db-uri: {current_db_uri}")

# Extract just the URI part (remove the db-uri = " prefix and trailing quote)
uri_start = current_db_uri.find('"') + 1
uri_end = current_db_uri.rfind('"')
base_uri = current_db_uri[uri_start:uri_end]

# Modify the URI to add target_session_attrs=read-only
if '?' in base_uri:
# URI already has parameters, add target_session_attrs
modified_uri = base_uri + "&target_session_attrs=read-only"
else:
# URI has no parameters, add target_session_attrs
modified_uri = base_uri + "?target_session_attrs=read-only"

print(f"Modified URI: {modified_uri}")

# Use awk to replace the db-uri line more reliably
result = run_ssh_command(host['ssh'], f"sudo awk '{{if ($1 == \"db-uri\") print \"db-uri = \\\"{modified_uri}\\\"\"; else print $0}}' /etc/postgrest/base.conf > /tmp/new_base.conf && sudo mv /tmp/new_base.conf /etc/postgrest/base.conf")
assert result['succeeded'], "Failed to update db-uri in config"

# Verify the change was made correctly
result = run_ssh_command(host['ssh'], "sudo cat /etc/postgrest/base.conf | grep '^db-uri'")
print(f"Updated db-uri line: {result['stdout'].strip()}")

# Also show the full config to debug
result = run_ssh_command(host['ssh'], "sudo cat /etc/postgrest/base.conf")
print(f"Full config after change:\n{result['stdout']}")

# Restart PostgREST to apply the new configuration
result = run_ssh_command(host['ssh'], "sudo systemctl restart postgrest")
assert result['succeeded'], "Failed to restart PostgREST"

# Wait a moment for PostgREST to start up
sleep(5)

# Check if PostgREST is running
result = run_ssh_command(host['ssh'], "sudo systemctl is-active postgrest")
if not (result['succeeded'] and result['stdout'].strip() == 'active'):
# If PostgREST failed to start, check the logs to see why
log_result = run_ssh_command(host['ssh'], "sudo journalctl -u postgrest --since '5 seconds ago' --no-pager")
print(f"PostgREST failed to start. Recent logs:\n{log_result['stdout']}")
assert False, "PostgREST failed to start after config change"

# Make a test request to trigger any potential session errors
try:
response = requests.get(
f"http://{host['ip']}/rest/v1/",
headers={"apikey": anon_key, "authorization": f"Bearer {anon_key}"},
timeout=10
)
print(f"Test request status: {response.status_code}")
except Exception as e:
print(f"Test request failed: {str(e)}")

# Check PostgREST logs for "session is not read-only" errors
result = run_ssh_command(host['ssh'], "sudo journalctl -u postgrest --since '5 seconds ago' | grep -i 'session is not read-only' || true")

if result['stdout'].strip():
print(f"\nFound 'session is not read-only' errors in PostgREST logs:\n{result['stdout']}")
assert False, "PostgREST logs contain 'session is not read-only' errors even though PostgreSQL is configured for read-only mode"
else:
print("\nNo 'session is not read-only' errors found in PostgREST logs")

finally:
# Restore the original configuration
result = run_ssh_command(host['ssh'], "sudo cp /etc/postgrest/base.conf.backup /etc/postgrest/base.conf")
if result['succeeded']:
result = run_ssh_command(host['ssh'], "sudo systemctl restart postgrest")
if result['succeeded']:
print("Restored original PostgREST configuration")
else:
print("Warning: Failed to restart PostgREST after restoring config")
else:
print("Warning: Failed to restore original PostgREST configuration")

# Restore PostgreSQL to original configuration
result = run_ssh_command(host['ssh'], f"sudo cp {config_file}.backup {config_file}")
if result['succeeded']:
result = run_ssh_command(host['ssh'], "sudo systemctl restart postgresql")
if result['succeeded']:
print("Restored PostgreSQL to original configuration")
else:
print("Warning: Failed to restart PostgreSQL after restoring config")
else:
print("Warning: Failed to restore PostgreSQL configuration")