active_postgres 0.9.4 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +23 -0
- data/README.md +20 -4
- data/SECURITY.md +21 -0
- data/lib/active_postgres/components/base.rb +2 -0
- data/lib/active_postgres/components/core.rb +11 -4
- data/lib/active_postgres/components/monitoring.rb +2 -2
- data/lib/active_postgres/components/pgbackrest.rb +10 -18
- data/lib/active_postgres/components/pgbouncer.rb +153 -14
- data/lib/active_postgres/components/repmgr.rb +75 -39
- data/lib/active_postgres/configuration.rb +57 -22
- data/lib/active_postgres/direct_executor.rb +3 -9
- data/lib/active_postgres/error_handler.rb +18 -18
- data/lib/active_postgres/health_checker.rb +10 -12
- data/lib/active_postgres/overview.rb +10 -10
- data/lib/active_postgres/secrets.rb +8 -7
- data/lib/active_postgres/ssh_executor.rb +5 -5
- data/lib/active_postgres/version.rb +1 -1
- data/lib/tasks/postgres.rake +26 -26
- data/templates/pgbackrest.conf.erb +2 -1
- data/templates/pgbouncer.ini.erb +2 -1
- data/templates/pgbouncer_follow_dns_primary.sh.erb +39 -0
- data/templates/postgres_log_archive.sh.erb +37 -6
- data/templates/postgresql.conf.erb +5 -0
- data/templates/repmgr_dns_failover.sh.erb +2 -2
- metadata +17 -56
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5d06925c9a64ca3f5b96402257fda9ad13f32bbd0e3ac5377032d737c71fb2ea
|
|
4
|
+
data.tar.gz: 19cde3ee7e86d325378661aa72350d51d1bfebf33a2e6f03a1c90d34c987a997
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2dbcc33b6ad2ffadeeea4145bc5bc36910203002af908030893298df2005089e87fa8e5d465a425ffae11c60ee01b66f301e1ae594e3bb662d747c0c342b20cb
|
|
7
|
+
data.tar.gz: 2772e4a192f87c60d54ccaaeb5ea2952f7a98ce34f3daec5839e77758179f75f3f9ac09a76ab74bc929311b8b417e074e5ae156f286bbba55423c3a923b47d77
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to Active Postgres will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project follows [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
## Unreleased
|
|
9
|
+
|
|
10
|
+
## 0.9.6 - 2026-08-20
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Require Ruby 4.0 or newer and Rails 8.1 or newer for Rails integration.
|
|
15
|
+
- Align runtime and development dependencies with the maintained toolchain.
|
|
16
|
+
- Add blocking test, lint, dependency-audit, and package-build CI checks.
|
|
17
|
+
- Add automated Bundler and GitHub Actions dependency updates.
|
|
18
|
+
- Document network, credential, failover, and recovery responsibilities.
|
|
19
|
+
|
|
20
|
+
## 0.9.5
|
|
21
|
+
|
|
22
|
+
- Current published release. See the repository history for changes included in
|
|
23
|
+
earlier releases.
|
data/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# active_postgres
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
PostgreSQL high-availability automation for Ruby and Rails applications.
|
|
4
|
+
|
|
5
|
+
Active Postgres configures hosts you control over SSH. It is an operations tool,
|
|
6
|
+
not a managed database service: you remain responsible for network isolation,
|
|
7
|
+
capacity planning, monitoring, backups, recovery drills, and failover decisions.
|
|
4
8
|
|
|
5
9
|
## Features
|
|
6
10
|
|
|
@@ -272,11 +276,23 @@ User.all # → Replica
|
|
|
272
276
|
|
|
273
277
|
## Requirements
|
|
274
278
|
|
|
275
|
-
- Ruby
|
|
276
|
-
- PostgreSQL
|
|
279
|
+
- Ruby 4.0+
|
|
280
|
+
- A PostgreSQL version supported by the target Linux distribution
|
|
277
281
|
- Ubuntu 20.04+ / Debian 11+ with systemd
|
|
278
282
|
- SSH key-based authentication (hosts must be in `~/.ssh/known_hosts`)
|
|
279
|
-
- Rails
|
|
283
|
+
- Rails 8.1+ (optional)
|
|
284
|
+
|
|
285
|
+
## Operational safety
|
|
286
|
+
|
|
287
|
+
- Keep PostgreSQL, PgBouncer, exporters, and backup endpoints behind a private
|
|
288
|
+
network or narrowly scoped firewall rules.
|
|
289
|
+
- Test promotion, application reconnection, backup restore, and point-in-time
|
|
290
|
+
recovery in a disposable environment before using them in production.
|
|
291
|
+
- Review generated configuration and package changes before each rollout;
|
|
292
|
+
automatic failover cannot determine whether an application is safe to write
|
|
293
|
+
after a network partition.
|
|
294
|
+
- Store database, replication, monitoring, and backup credentials in a secret
|
|
295
|
+
manager. Do not commit resolved values or generated configuration.
|
|
280
296
|
|
|
281
297
|
### SSH host key verification
|
|
282
298
|
|
data/SECURITY.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Reporting a Vulnerability
|
|
4
|
+
|
|
5
|
+
Please do not open a public issue for a potential vulnerability. Email
|
|
6
|
+
[security@boringcache.com](mailto:security@boringcache.com) or use
|
|
7
|
+
[GitHub private vulnerability reporting](https://github.com/boringcache/active_postgres/security/advisories/new).
|
|
8
|
+
|
|
9
|
+
Include the affected version, expected impact, and enough reproduction detail
|
|
10
|
+
for us to investigate. Do not include database credentials, private keys,
|
|
11
|
+
backups, customer data, or production configuration.
|
|
12
|
+
|
|
13
|
+
## Supported Versions
|
|
14
|
+
|
|
15
|
+
Only the latest published release is supported. Security fixes are normally
|
|
16
|
+
shipped in a new release rather than backported.
|
|
17
|
+
|
|
18
|
+
## Scope
|
|
19
|
+
|
|
20
|
+
Reports about PostgreSQL authentication, replication, backup/restore, SSH host
|
|
21
|
+
verification, secret handling, generated SQL, or release integrity are welcome.
|
|
@@ -136,7 +136,7 @@ module ActivePostgres
|
|
|
136
136
|
end
|
|
137
137
|
|
|
138
138
|
def ensure_standby_compatible(pg_config)
|
|
139
|
-
primary_settings =
|
|
139
|
+
primary_settings = primary_replication_settings
|
|
140
140
|
return pg_config if primary_settings.empty?
|
|
141
141
|
|
|
142
142
|
adjustments = []
|
|
@@ -154,17 +154,24 @@ module ActivePostgres
|
|
|
154
154
|
end
|
|
155
155
|
|
|
156
156
|
if adjustments.any?
|
|
157
|
-
puts
|
|
157
|
+
puts ' ⚠️ Adjusting standby settings to match primary minimum:'
|
|
158
158
|
adjustments.each { |adj| puts " #{adj}" }
|
|
159
159
|
end
|
|
160
160
|
|
|
161
161
|
pg_config
|
|
162
162
|
end
|
|
163
163
|
|
|
164
|
-
def
|
|
164
|
+
def primary_replication_settings
|
|
165
165
|
@primary_replication_settings ||= begin
|
|
166
166
|
settings = {}
|
|
167
|
-
|
|
167
|
+
setting_names = %w[
|
|
168
|
+
max_connections
|
|
169
|
+
max_worker_processes
|
|
170
|
+
max_wal_senders
|
|
171
|
+
max_prepared_transactions
|
|
172
|
+
max_locks_per_transaction
|
|
173
|
+
]
|
|
174
|
+
sql = "SELECT name || '=' || setting FROM pg_settings WHERE name IN (#{setting_names.map { |name| "'#{name}'" }.join(', ')})"
|
|
168
175
|
result = ssh_executor.run_sql(config.primary_host, sql)
|
|
169
176
|
result.strip.split("\n").each do |line|
|
|
170
177
|
name, val = line.split('=')
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
require 'cgi'
|
|
1
|
+
require 'cgi/escape'
|
|
2
2
|
|
|
3
3
|
module ActivePostgres
|
|
4
4
|
module Components
|
|
@@ -83,7 +83,7 @@ module ActivePostgres
|
|
|
83
83
|
sql = build_monitoring_user_sql(monitoring_user, monitoring_password)
|
|
84
84
|
|
|
85
85
|
ssh_executor.run_sql(config.primary_host, sql, postgres_user: 'postgres', port: 5432, tuples_only: false,
|
|
86
|
-
|
|
86
|
+
capture: false)
|
|
87
87
|
end
|
|
88
88
|
|
|
89
89
|
def build_monitoring_user_sql(user, password)
|
|
@@ -3,9 +3,9 @@ require 'shellwords'
|
|
|
3
3
|
module ActivePostgres
|
|
4
4
|
module Components
|
|
5
5
|
class PgBackRest < Base
|
|
6
|
-
LOG_ARCHIVE_CRON_FILE = '/etc/cron.d/postgres-log-archive'
|
|
7
|
-
LOG_ARCHIVE_ENV_FILE = '/etc/active-postgres-log-archive.env'
|
|
8
|
-
LOG_ARCHIVE_SCRIPT = '/usr/local/bin/active-postgres-archive-logs'
|
|
6
|
+
LOG_ARCHIVE_CRON_FILE = '/etc/cron.d/postgres-log-archive'.freeze
|
|
7
|
+
LOG_ARCHIVE_ENV_FILE = '/etc/active-postgres-log-archive.env'.freeze
|
|
8
|
+
LOG_ARCHIVE_SCRIPT = '/usr/local/bin/active-postgres-archive-logs'.freeze
|
|
9
9
|
|
|
10
10
|
def install
|
|
11
11
|
puts 'Installing pgBackRest for backups...'
|
|
@@ -135,9 +135,7 @@ module ActivePostgres
|
|
|
135
135
|
execute :sudo, 'chown', "#{postgres_user}:#{postgres_user}", '/var/spool/pgbackrest'
|
|
136
136
|
|
|
137
137
|
# Only create stanza on primary - standbys share the same backup repo
|
|
138
|
-
if create_stanza
|
|
139
|
-
execute :sudo, '-u', postgres_user, 'pgbackrest', '--stanza=main', 'stanza-create'
|
|
140
|
-
end
|
|
138
|
+
execute :sudo, '-u', postgres_user, 'pgbackrest', '--stanza=main', 'stanza-create' if create_stanza
|
|
141
139
|
end
|
|
142
140
|
|
|
143
141
|
if log_archive_enabled?(pgbackrest_config)
|
|
@@ -167,12 +165,8 @@ module ActivePostgres
|
|
|
167
165
|
schedule_incremental = pgbackrest_config[:schedule_incremental]
|
|
168
166
|
|
|
169
167
|
schedules = []
|
|
170
|
-
if schedule_full
|
|
171
|
-
|
|
172
|
-
end
|
|
173
|
-
if schedule_incremental
|
|
174
|
-
schedules << { type: 'incremental', schedule: schedule_incremental, file: '/etc/cron.d/pgbackrest-backup-incremental' }
|
|
175
|
-
end
|
|
168
|
+
schedules << { type: 'full', schedule: schedule_full, file: '/etc/cron.d/pgbackrest-backup' } if schedule_full
|
|
169
|
+
schedules << { type: 'incr', schedule: schedule_incremental, file: '/etc/cron.d/pgbackrest-backup-incremental' } if schedule_incremental
|
|
176
170
|
|
|
177
171
|
schedules
|
|
178
172
|
end
|
|
@@ -204,15 +198,13 @@ module ActivePostgres
|
|
|
204
198
|
|
|
205
199
|
def setup_log_archive(host, pgbackrest_config)
|
|
206
200
|
log_archive_config = pgbackrest_config[:log_archive] || {}
|
|
207
|
-
unless pgbackrest_config[:repo_type] == 's3'
|
|
208
|
-
raise Error, 'pgbackrest.log_archive requires pgbackrest.repo_type: s3'
|
|
209
|
-
end
|
|
201
|
+
raise Error, 'pgbackrest.log_archive requires pgbackrest.repo_type: s3' unless pgbackrest_config[:repo_type] == 's3'
|
|
210
202
|
|
|
211
203
|
postgres_user = config.postgres_user
|
|
212
204
|
env_content = log_archive_env(pgbackrest_config, log_archive_config, host)
|
|
213
205
|
cron_content = log_archive_cron(log_archive_config)
|
|
214
206
|
|
|
215
|
-
install_apt_packages(host, '
|
|
207
|
+
install_apt_packages(host, 's3cmd')
|
|
216
208
|
|
|
217
209
|
ssh_executor.upload_file(host, env_content, LOG_ARCHIVE_ENV_FILE, mode: '640', owner: "root:#{postgres_user}")
|
|
218
210
|
|
|
@@ -260,13 +252,13 @@ module ActivePostgres
|
|
|
260
252
|
'POSTGRES_LOG_ARCHIVE_NODE' => config.node_label_for(host) || host
|
|
261
253
|
}.compact
|
|
262
254
|
|
|
263
|
-
env.map { |key, value| "#{key}=#{Shellwords.escape(value.to_s)}" }.join("\n")
|
|
255
|
+
"#{env.map { |key, value| "#{key}=#{Shellwords.escape(value.to_s)}" }.join("\n")}\n"
|
|
264
256
|
end
|
|
265
257
|
|
|
266
258
|
def s3_endpoint_url(pgbackrest_config)
|
|
267
259
|
endpoint = pgbackrest_config[:s3_endpoint]
|
|
268
260
|
return nil if endpoint.to_s.empty?
|
|
269
|
-
return endpoint if endpoint.match?(
|
|
261
|
+
return endpoint if endpoint.match?(%r{\Ahttps?://})
|
|
270
262
|
|
|
271
263
|
"https://#{endpoint}"
|
|
272
264
|
end
|
|
@@ -8,12 +8,16 @@ module ActivePostgres
|
|
|
8
8
|
is_standby = config.standby_hosts.include?(host)
|
|
9
9
|
install_on_host(host, is_standby: is_standby)
|
|
10
10
|
end
|
|
11
|
+
|
|
12
|
+
pgbouncer_app_hosts.each do |app_host|
|
|
13
|
+
install_on_app_host(app_host)
|
|
14
|
+
end
|
|
11
15
|
end
|
|
12
16
|
|
|
13
17
|
def uninstall
|
|
14
18
|
puts 'Uninstalling PgBouncer...'
|
|
15
19
|
|
|
16
|
-
config.all_hosts.each do |host|
|
|
20
|
+
(config.all_hosts + pgbouncer_app_hosts.map { |app_host| app_host.fetch('host') }).each do |host|
|
|
17
21
|
ssh_executor.execute_on_host(host) do
|
|
18
22
|
execute :sudo, 'systemctl', 'disable', '--now', 'pgbouncer-follow-primary.timer', '||', 'true'
|
|
19
23
|
execute :sudo, 'rm', '-f', '/usr/local/bin/pgbouncer-follow-primary',
|
|
@@ -29,7 +33,7 @@ module ActivePostgres
|
|
|
29
33
|
def restart
|
|
30
34
|
puts 'Restarting PgBouncer...'
|
|
31
35
|
|
|
32
|
-
config.all_hosts.each do |host|
|
|
36
|
+
(config.all_hosts + pgbouncer_app_hosts.map { |app_host| app_host.fetch('host') }).each do |host|
|
|
33
37
|
ssh_executor.execute_on_host(host) do
|
|
34
38
|
execute :sudo, 'systemctl', 'restart', 'pgbouncer'
|
|
35
39
|
end
|
|
@@ -46,6 +50,15 @@ module ActivePostgres
|
|
|
46
50
|
execute :sudo, 'systemctl', 'reload', 'pgbouncer'
|
|
47
51
|
end
|
|
48
52
|
end
|
|
53
|
+
|
|
54
|
+
pgbouncer_app_hosts.each do |app_host|
|
|
55
|
+
host = app_host.fetch('host')
|
|
56
|
+
create_userlist_from_primary(host)
|
|
57
|
+
|
|
58
|
+
ssh_executor.execute_on_host(host) do
|
|
59
|
+
execute :sudo, 'systemctl', 'reload', 'pgbouncer'
|
|
60
|
+
end
|
|
61
|
+
end
|
|
49
62
|
end
|
|
50
63
|
|
|
51
64
|
def install_on_standby(standby_host)
|
|
@@ -55,6 +68,12 @@ module ActivePostgres
|
|
|
55
68
|
|
|
56
69
|
private
|
|
57
70
|
|
|
71
|
+
def pgbouncer_app_hosts
|
|
72
|
+
return config.pgbouncer_app_hosts if config.respond_to?(:pgbouncer_app_hosts)
|
|
73
|
+
|
|
74
|
+
[]
|
|
75
|
+
end
|
|
76
|
+
|
|
58
77
|
def install_on_host(host, is_standby: false)
|
|
59
78
|
puts " Installing PgBouncer on #{host}..."
|
|
60
79
|
|
|
@@ -62,9 +81,7 @@ module ActivePostgres
|
|
|
62
81
|
|
|
63
82
|
follow_primary = follow_primary_for?(host, is_standby: is_standby, user_config: user_config)
|
|
64
83
|
|
|
65
|
-
if follow_primary && !config.component_enabled?(:repmgr)
|
|
66
|
-
raise Error, 'PgBouncer follow_primary requires repmgr to be enabled'
|
|
67
|
-
end
|
|
84
|
+
raise Error, 'PgBouncer follow_primary requires repmgr to be enabled' if follow_primary && !config.component_enabled?(:repmgr)
|
|
68
85
|
|
|
69
86
|
max_connections = get_postgres_max_connections(host)
|
|
70
87
|
optimal_pool = ConnectionPooler.calculate_optimal_pool_sizes(max_connections)
|
|
@@ -106,6 +123,45 @@ module ActivePostgres
|
|
|
106
123
|
install_follow_primary(host, pgbouncer_config) if follow_primary
|
|
107
124
|
end
|
|
108
125
|
|
|
126
|
+
def install_on_app_host(app_host)
|
|
127
|
+
host = app_host.fetch('host')
|
|
128
|
+
puts " Installing PgBouncer on app host #{host}..."
|
|
129
|
+
|
|
130
|
+
user_config = config.component_config(:pgbouncer)
|
|
131
|
+
max_connections = get_postgres_max_connections(config.primary_host)
|
|
132
|
+
optimal_pool = ConnectionPooler.calculate_optimal_pool_sizes(max_connections)
|
|
133
|
+
|
|
134
|
+
pgbouncer_config = optimal_pool.merge(user_config)
|
|
135
|
+
pgbouncer_config[:database_host] = app_host_value(app_host, :database_host) || pgbouncer_primary_record
|
|
136
|
+
pgbouncer_config[:database_port] = app_host_value(app_host, :database_port) || pgbouncer_config[:database_port] || 5432
|
|
137
|
+
pgbouncer_config[:listen_addr] = app_host_value(app_host, :listen_addr) || pgbouncer_config[:app_listen_addr] || '127.0.0.1'
|
|
138
|
+
pgbouncer_config[:listen_port] = app_host_value(app_host, :listen_port) || pgbouncer_config[:app_listen_port] ||
|
|
139
|
+
pgbouncer_config[:listen_port] || 6432
|
|
140
|
+
ssl_enabled = config.component_enabled?(:ssl)
|
|
141
|
+
has_ca_cert = ssl_enabled && secrets.resolve('ssl_chain')
|
|
142
|
+
secrets_obj = secrets
|
|
143
|
+
_ = pgbouncer_config
|
|
144
|
+
_ = has_ca_cert
|
|
145
|
+
_ = secrets_obj
|
|
146
|
+
|
|
147
|
+
puts " Calculated app pool settings for max_connections=#{max_connections}"
|
|
148
|
+
|
|
149
|
+
install_apt_packages(host, 'pgbouncer', 'postgresql-client')
|
|
150
|
+
|
|
151
|
+
upload_template(host, 'pgbouncer.ini.erb', '/etc/pgbouncer/pgbouncer.ini', binding, mode: '644')
|
|
152
|
+
|
|
153
|
+
setup_app_ssl_certs(host) if ssl_enabled
|
|
154
|
+
|
|
155
|
+
create_userlist_from_primary(host)
|
|
156
|
+
|
|
157
|
+
ssh_executor.execute_on_host(host) do
|
|
158
|
+
execute :sudo, 'systemctl', 'enable', 'pgbouncer'
|
|
159
|
+
execute :sudo, 'systemctl', 'restart', 'pgbouncer'
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
install_follow_dns_primary(host, pgbouncer_config)
|
|
163
|
+
end
|
|
164
|
+
|
|
109
165
|
def follow_primary_for?(host, is_standby:, user_config:)
|
|
110
166
|
if is_standby
|
|
111
167
|
standby_config = config.standby_config_for(host) || {}
|
|
@@ -130,9 +186,33 @@ module ActivePostgres
|
|
|
130
186
|
end
|
|
131
187
|
|
|
132
188
|
ssl_chain = secrets.resolve('ssl_chain')
|
|
133
|
-
|
|
134
|
-
|
|
189
|
+
return unless ssl_chain
|
|
190
|
+
|
|
191
|
+
ssh_executor.upload_file(host, ssl_chain, '/etc/pgbouncer/ca.crt', mode: '644', owner: 'postgres:postgres')
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def setup_app_ssl_certs(host)
|
|
195
|
+
puts ' Setting up SSL certificates for app PgBouncer...'
|
|
196
|
+
|
|
197
|
+
ssl_cert = secrets.resolve('ssl_cert')
|
|
198
|
+
ssl_key = secrets.resolve('ssl_key')
|
|
199
|
+
ssl_chain = secrets.resolve('ssl_chain')
|
|
200
|
+
|
|
201
|
+
raise Error, 'PgBouncer app_hosts require ssl_cert and ssl_key secrets when ssl is enabled' unless ssl_cert && ssl_key
|
|
202
|
+
|
|
203
|
+
full_cert = if ssl_chain
|
|
204
|
+
"#{ssl_cert.strip}\n#{ssl_chain.strip}\n"
|
|
205
|
+
else
|
|
206
|
+
ssl_cert
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
ssh_executor.execute_on_host(host) do
|
|
210
|
+
execute :sudo, 'mkdir', '-p', '/etc/pgbouncer'
|
|
135
211
|
end
|
|
212
|
+
|
|
213
|
+
ssh_executor.upload_file(host, full_cert, '/etc/pgbouncer/server.crt', mode: '644', owner: 'postgres:postgres')
|
|
214
|
+
ssh_executor.upload_file(host, ssl_key, '/etc/pgbouncer/server.key', mode: '600', owner: 'postgres:postgres')
|
|
215
|
+
ssh_executor.upload_file(host, ssl_chain, '/etc/pgbouncer/ca.crt', mode: '644', owner: 'postgres:postgres') if ssl_chain
|
|
136
216
|
end
|
|
137
217
|
|
|
138
218
|
def get_postgres_max_connections(host)
|
|
@@ -154,18 +234,43 @@ module ActivePostgres
|
|
|
154
234
|
def create_userlist(host)
|
|
155
235
|
puts ' Creating PgBouncer userlist with database users...'
|
|
156
236
|
|
|
157
|
-
|
|
158
|
-
app_user = config.app_user
|
|
159
|
-
users_to_add = [postgres_user, (app_user if app_user != postgres_user)].compact
|
|
160
|
-
pgbouncer = self
|
|
237
|
+
component = self
|
|
161
238
|
|
|
162
239
|
ssh_executor.execute_on_host(host) do
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
pgbouncer.send(:write_userlist_file, backend, userlist_entries)
|
|
240
|
+
userlist_entries = component.send(:userlist_entries_from_backend, self)
|
|
241
|
+
component.send(:write_userlist_file, self, userlist_entries)
|
|
166
242
|
end
|
|
167
243
|
end
|
|
168
244
|
|
|
245
|
+
def create_userlist_from_primary(host)
|
|
246
|
+
puts ' Creating app PgBouncer userlist from primary database users...'
|
|
247
|
+
|
|
248
|
+
userlist_entries = nil
|
|
249
|
+
component = self
|
|
250
|
+
|
|
251
|
+
ssh_executor.execute_on_host(config.primary_host) do
|
|
252
|
+
userlist_entries = component.send(:userlist_entries_from_backend, self)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
ssh_executor.execute_on_host(host) do
|
|
256
|
+
component.send(:write_userlist_file, self, userlist_entries || [])
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def userlist_entries_from_backend(backend)
|
|
261
|
+
postgres_user = config.postgres_user
|
|
262
|
+
users_to_add = pgbouncer_database_users
|
|
263
|
+
|
|
264
|
+
users_to_add.filter_map { |user| fetch_user_hash(backend, user, postgres_user) }
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def pgbouncer_database_users
|
|
268
|
+
postgres_user = config.postgres_user
|
|
269
|
+
app_user = config.app_user
|
|
270
|
+
|
|
271
|
+
[postgres_user, (app_user if app_user != postgres_user)].compact
|
|
272
|
+
end
|
|
273
|
+
|
|
169
274
|
def fetch_user_hash(backend, user, postgres_user)
|
|
170
275
|
sql = build_user_hash_sql(user)
|
|
171
276
|
|
|
@@ -241,6 +346,40 @@ module ActivePostgres
|
|
|
241
346
|
execute :sudo, 'systemctl', 'start', 'pgbouncer-follow-primary.service'
|
|
242
347
|
end
|
|
243
348
|
end
|
|
349
|
+
|
|
350
|
+
def install_follow_dns_primary(host, pgbouncer_config)
|
|
351
|
+
interval = pgbouncer_config[:follow_primary_interval] || 5
|
|
352
|
+
interval = interval.to_i
|
|
353
|
+
interval = 5 if interval <= 0
|
|
354
|
+
primary_record = pgbouncer_config[:database_host]
|
|
355
|
+
_ = interval
|
|
356
|
+
_ = primary_record
|
|
357
|
+
_ = pgbouncer_config
|
|
358
|
+
|
|
359
|
+
upload_template(host, 'pgbouncer_follow_dns_primary.sh.erb', '/usr/local/bin/pgbouncer-follow-primary',
|
|
360
|
+
binding, mode: '755', owner: 'root:root')
|
|
361
|
+
upload_template(host, 'pgbouncer-follow-primary.service.erb',
|
|
362
|
+
'/etc/systemd/system/pgbouncer-follow-primary.service', binding, mode: '644', owner: 'root:root')
|
|
363
|
+
upload_template(host, 'pgbouncer-follow-primary.timer.erb',
|
|
364
|
+
'/etc/systemd/system/pgbouncer-follow-primary.timer', binding, mode: '644', owner: 'root:root')
|
|
365
|
+
|
|
366
|
+
ssh_executor.execute_on_host(host) do
|
|
367
|
+
execute :sudo, 'systemctl', 'daemon-reload'
|
|
368
|
+
execute :sudo, 'systemctl', 'enable', '--now', 'pgbouncer-follow-primary.timer'
|
|
369
|
+
execute :sudo, 'systemctl', 'start', 'pgbouncer-follow-primary.service'
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
def pgbouncer_primary_record
|
|
374
|
+
return config.pgbouncer_primary_record if config.respond_to?(:pgbouncer_primary_record)
|
|
375
|
+
|
|
376
|
+
dns_failover = config.component_config(:repmgr)[:dns_failover] || {}
|
|
377
|
+
dns_failover[:primary_record] || config.primary_replication_host
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def app_host_value(app_host, key)
|
|
381
|
+
app_host[key.to_s] || app_host[key]
|
|
382
|
+
end
|
|
244
383
|
end
|
|
245
384
|
end
|
|
246
385
|
end
|