active_postgres 0.9.7 → 0.9.8

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f7bf86efa4e32bcb1543e317bd6219983f9f42eb0ed0bf8787ff75b6a573a48
4
- data.tar.gz: 8a2b2f0ec44bdfd94552dfa0fd84145b64ded472b4f421f216c86af71e2d859e
3
+ metadata.gz: c3d6f986ee2ac96f5cdd8b69541a5acae87733f506b10efb94c1046082b990a9
4
+ data.tar.gz: f64bb049c6601cf853de16c6cfeff8293ebb06d622db9633330298f02ef82135
5
5
  SHA512:
6
- metadata.gz: 1d4a26f7284e14c84bcaa4d1e982da9162a25a098b15f9423b4923e7b7a9ab007112620761e5184a1da514f40495edd00647e8c2a269bbbb0511e2611e44118f
7
- data.tar.gz: 4dfb7b6e19345e3324d38a798a82bdfa111001cb94736348aa88ba6146f9c3a6bc47fed8977039faefc271d412529f61995ad36f42be93b96a8789437187423e
6
+ metadata.gz: b07c93095ab642d39d6d658e93ef82c7ca993a30cf06b3f320d02e20dc5f45a546ffcdb2196f5bae0d1ab2051fb3a453a0a445e3e3f49b89ec1e3be9ee19f042
7
+ data.tar.gz: aeae944d32c7ee5f661470eaee9f66c01ef47c1bb4f578bbae0fd8d3e50f81044d24ddf49f85304a5aa6437de207aa22600cd3cc6cf7167713c5f87204e3cee9
data/CHANGELOG.md CHANGED
@@ -7,6 +7,13 @@ and this project follows [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ## Unreleased
9
9
 
10
+ ## 0.9.8 - 2026-10-02
11
+
12
+ ### Fixed
13
+
14
+ - Support explicit node IDs so repmgr identities survive primary changes and standby reordering.
15
+ - Reject duplicate or invalid node IDs before deployment.
16
+
10
17
  ## 0.9.7 - 2026-09-01
11
18
 
12
19
  ### Added
data/README.md CHANGED
@@ -76,6 +76,12 @@ manual-promotion while the ordinary standby retains automatic failover.
76
76
  settings before repmgr registration, avoiding a full base-backup stream from
77
77
  the primary. The `pgbackrest` component must be enabled.
78
78
 
79
+ Set a unique positive integer `node_id` on every primary and standby entry
80
+ before permanently changing their roles or reordering standbys. Match the
81
+ existing IDs in `repmgr cluster show`, and keep each ID with its host after a
82
+ switchover. Without explicit IDs, the primary defaults to 1 and standbys use
83
+ their list position starting at 2. Duplicate or invalid IDs are rejected.
84
+
79
85
  Add credentials (`rails credentials:edit`):
80
86
 
81
87
  ```yaml
@@ -28,7 +28,7 @@ module ActivePostgres
28
28
  end
29
29
 
30
30
  def uninstall_from(host)
31
- node_id = host == config.primary_host ? 1 : (config.standby_hosts.index(host) || 997) + 2
31
+ node_id = config.node_id_for(host)
32
32
 
33
33
  ssh_executor.execute_on_host(host) do
34
34
  cluster_output = begin
@@ -98,6 +98,7 @@ module ActivePostgres
98
98
  puts ' Setting up primary with repmgr...'
99
99
 
100
100
  host = config.primary_host
101
+ primary_node_id = config.node_id_for(host)
101
102
  repmgr_config = config.repmgr_config_for(host)
102
103
  version = config.version
103
104
  repmgr_password = normalize_repmgr_password(secrets.resolve('repmgr_password'))
@@ -208,7 +209,7 @@ module ActivePostgres
208
209
  unless cluster_show&.match?(/primary/i)
209
210
  # Fallback: verify via repmgr metadata in the repmgr database
210
211
  db_check = executor.run_sql_on_backend(self,
211
- 'SELECT type FROM repmgr.nodes WHERE node_id = 1;',
212
+ "SELECT type FROM repmgr.nodes WHERE node_id = #{primary_node_id};",
212
213
  postgres_user: 'postgres',
213
214
  database: repmgr_db,
214
215
  tuples_only: true,
@@ -250,7 +251,7 @@ module ActivePostgres
250
251
  _ = repmgr_config
251
252
  _ = secrets_obj
252
253
 
253
- node_id = config.standby_hosts.index(standby_host) + 2
254
+ node_id = config.node_id_for(standby_host)
254
255
  repmgr_password = normalize_repmgr_password(secrets_obj.resolve('repmgr_password'))
255
256
  replication_password = normalize_replication_password(secrets_obj.resolve('replication_password'))
256
257
  if replication_user == repmgr_user && replication_password != repmgr_password
@@ -679,6 +680,7 @@ module ActivePostgres
679
680
  puts 'Verifying PostgreSQL HA cluster health...'
680
681
 
681
682
  primary_host = config.primary_host
683
+ primary_node_id = config.node_id_for(primary_host)
682
684
  standby_hosts = config.standby_hosts
683
685
  version = config.version
684
686
  postgres_user = config.postgres_user
@@ -709,12 +711,12 @@ module ActivePostgres
709
711
  cluster_output = capture(:sudo, '-u', 'postgres',
710
712
  'repmgr', '-f', '/etc/repmgr.conf', 'cluster', 'show',
711
713
  raise_on_non_zero_exit: false).to_s
712
- # Primary always has node_id=1, check if it's registered and running
713
- if cluster_output.match?(/\s+1\s+\|.*primary.*\*\s+running/i)
714
+ # Check the configured node identity, which survives role changes.
715
+ if cluster_output.match?(/\s+#{primary_node_id}\s+\|.*primary.*\*\s+running/i)
714
716
  info '✓ Primary is registered with repmgr'
715
717
  else
716
718
  db_check = executor.run_sql_on_backend(self,
717
- 'SELECT type FROM repmgr.nodes WHERE node_id = 1 AND active IS TRUE;',
719
+ "SELECT type FROM repmgr.nodes WHERE node_id = #{primary_node_id} AND active IS TRUE;",
718
720
  postgres_user: postgres_user,
719
721
  database: repmgr_db,
720
722
  tuples_only: true,
@@ -951,7 +953,7 @@ module ActivePostgres
951
953
 
952
954
  def ensure_primary_registered
953
955
  host = config.primary_host
954
- config.version
956
+ primary_node_id = config.node_id_for(host)
955
957
  is_registered = false
956
958
 
957
959
  ssh_executor.execute_on_host(host) do
@@ -965,8 +967,8 @@ module ActivePostgres
965
967
  ''
966
968
  end
967
969
 
968
- if cluster_output.include?('| 1') && cluster_output.include?('primary')
969
- info '✓ Primary is registered (node_id=1)'
970
+ if cluster_output.match?(/\b#{primary_node_id}\s+\|.*primary/)
971
+ info "✓ Primary is registered (node_id=#{primary_node_id})"
970
972
  else
971
973
  warn "⚠ Primary not found in cluster, assuming it's registered"
972
974
  end
@@ -0,0 +1,22 @@
1
+ module ActivePostgres
2
+ class Configuration
3
+ module NodeIdentity
4
+ def node_id_for(host)
5
+ node = node_config_for(host)
6
+ raise Error, "Unknown PostgreSQL host: #{host}" unless node
7
+
8
+ value = node.fetch('node_id') { host == primary_host ? 1 : standby_hosts.index(host) + 2 }
9
+ raise Error, "node_id for #{host} must be a positive 32-bit integer" unless value.is_a?(Integer) && value.between?(1, 2_147_483_647)
10
+
11
+ value
12
+ end
13
+
14
+ private
15
+
16
+ def validate_node_ids!
17
+ node_ids = all_hosts.map { |host| node_id_for(host) }
18
+ raise Error, 'PostgreSQL node_id values must be unique' unless node_ids.uniq.size == node_ids.size
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,11 +1,13 @@
1
1
  require 'yaml'
2
2
  require_relative 'configuration/pgbackrest_policy'
3
3
  require_relative 'configuration/standby_policy'
4
+ require_relative 'configuration/node_identity'
4
5
 
5
6
  module ActivePostgres
6
7
  class Configuration
7
8
  include PgBackRestPolicy
8
9
  include StandbyPolicy
10
+ include NodeIdentity
9
11
 
10
12
  attr_reader :environment, :version, :user, :ssh_key, :ssh_known_hosts_file, :ssh_host_key_verification,
11
13
  :primary, :standbys, :jump_hosts, :components, :secrets_config, :database_config
@@ -147,6 +149,8 @@ module ActivePostgres
147
149
  def validate!
148
150
  raise Error, 'No primary host defined' unless primary_host
149
151
 
152
+ validate_node_ids!
153
+
150
154
  # Validate required secrets if components are enabled
151
155
  raise Error, 'Missing replication_password secret' if component_enabled?(:repmgr) && !secrets_config['replication_password']
152
156
  raise Error, 'Missing monitoring_password secret' if component_enabled?(:monitoring) && !secrets_config['monitoring_password']
@@ -1,3 +1,3 @@
1
1
  module ActivePostgres
2
- VERSION = '0.9.7'.freeze
2
+ VERSION = '0.9.8'.freeze
3
3
  end
@@ -9,7 +9,7 @@
9
9
  end
10
10
  end
11
11
  %>
12
- node_id=<%= host == config.primary_host ? 1 : config.standby_hosts.index(host) + 2 %>
12
+ node_id=<%= config.node_id_for(host) %>
13
13
  node_name='<%= node_label %>'
14
14
  <%
15
15
  # conninfo describes how OTHER nodes connect to THIS node
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_postgres
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.7
4
+ version: 0.9.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - BoringCache
@@ -126,6 +126,7 @@ files:
126
126
  - lib/active_postgres/components/repmgr.rb
127
127
  - lib/active_postgres/components/ssl.rb
128
128
  - lib/active_postgres/configuration.rb
129
+ - lib/active_postgres/configuration/node_identity.rb
129
130
  - lib/active_postgres/configuration/pgbackrest_policy.rb
130
131
  - lib/active_postgres/configuration/standby_policy.rb
131
132
  - lib/active_postgres/connection_pooler.rb