Class: ActivePostgres::Configuration

Inherits:
Object
  • Object
show all
Defined in:
lib/active_postgres/configuration.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config_hash, environment = 'development') ⇒ Configuration

Returns a new instance of Configuration.



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/active_postgres/configuration.rb', line 8

def initialize(config_hash, environment = 'development')
  @environment = environment
  env_config = config_hash[environment] || {}

  @skip_deployment = env_config['skip_deployment'] == true

  @version = env_config['version'] || 18
  @user = env_config['user'] || 'ubuntu'
  @ssh_key = File.expand_path(env_config['ssh_key'] || '~/.ssh/id_rsa')
  @ssh_host_key_verification = normalize_ssh_host_key_verification(
    env_config['ssh_host_key_verification'] || env_config['ssh_verify_host_key']
  )

  @primary = env_config['primary'] || {}
  @standbys = env_config['standby'] || []
  @standbys = [@standbys] unless @standbys.is_a?(Array)

  @components = parse_components(env_config['components'] || {})
  @secrets_config = env_config['secrets'] || {}
end

Instance Attribute Details

#componentsObject (readonly)

Returns the value of attribute components.



5
6
7
# File 'lib/active_postgres/configuration.rb', line 5

def components
  @components
end

#database_configObject (readonly)

Returns the value of attribute database_config.



5
6
7
# File 'lib/active_postgres/configuration.rb', line 5

def database_config
  @database_config
end

#environmentObject (readonly)

Returns the value of attribute environment.



5
6
7
# File 'lib/active_postgres/configuration.rb', line 5

def environment
  @environment
end

#primaryObject (readonly)

Returns the value of attribute primary.



5
6
7
# File 'lib/active_postgres/configuration.rb', line 5

def primary
  @primary
end

#secrets_configObject (readonly)

Returns the value of attribute secrets_config.



5
6
7
# File 'lib/active_postgres/configuration.rb', line 5

def secrets_config
  @secrets_config
end

#ssh_host_key_verificationObject (readonly)

Returns the value of attribute ssh_host_key_verification.



5
6
7
# File 'lib/active_postgres/configuration.rb', line 5

def ssh_host_key_verification
  @ssh_host_key_verification
end

#ssh_keyObject (readonly)

Returns the value of attribute ssh_key.



5
6
7
# File 'lib/active_postgres/configuration.rb', line 5

def ssh_key
  @ssh_key
end

#standbysObject (readonly)

Returns the value of attribute standbys.



5
6
7
# File 'lib/active_postgres/configuration.rb', line 5

def standbys
  @standbys
end

#userObject (readonly)

Returns the value of attribute user.



5
6
7
# File 'lib/active_postgres/configuration.rb', line 5

def user
  @user
end

#versionObject (readonly)

Returns the value of attribute version.



5
6
7
# File 'lib/active_postgres/configuration.rb', line 5

def version
  @version
end

Class Method Details

.load(config_path = 'config/postgres.yml', environment = nil) ⇒ Object

Raises:



29
30
31
32
33
34
35
36
# File 'lib/active_postgres/configuration.rb', line 29

def self.load(config_path = 'config/postgres.yml', environment = nil)
  environment ||= ENV['BORING_ENVIRONMENT'] || ENV['RAILS_ENV'] || 'development'

  raise Error, "Config file not found: #{config_path}" unless File.exist?(config_path)

  config_hash = YAML.load_file(config_path, aliases: true)
  new(config_hash, environment)
end

Instance Method Details

#all_hostsObject



38
39
40
# File 'lib/active_postgres/configuration.rb', line 38

def all_hosts
  [primary_host] + standby_hosts
end

#app_databaseObject



168
169
170
171
# File 'lib/active_postgres/configuration.rb', line 168

def app_database
  value = component_config(:core)[:app_database]
  value.nil? || value.to_s.strip.empty? ? "app_#{environment}" : value
end

#app_userObject



163
164
165
166
# File 'lib/active_postgres/configuration.rb', line 163

def app_user
  value = component_config(:core)[:app_user]
  value.nil? || value.to_s.strip.empty? ? 'app' : value
end

#component_config(name) ⇒ Object



54
55
56
# File 'lib/active_postgres/configuration.rb', line 54

def component_config(name)
  @components[name] || {}
end

#component_enabled?(name) ⇒ Boolean

Returns:

  • (Boolean)


50
51
52
# File 'lib/active_postgres/configuration.rb', line 50

def component_enabled?(name)
  @components[name]&.[](:enabled) == true
end

#connection_host_for(host) ⇒ Object

Returns the host to use for direct PostgreSQL connections (private_ip preferred)



72
73
74
75
# File 'lib/active_postgres/configuration.rb', line 72

def connection_host_for(host)
  node = node_config_for(host)
  private_ip_for(node) || host
end

#node_label_for(host) ⇒ Object



85
86
87
88
89
90
91
# File 'lib/active_postgres/configuration.rb', line 85

def node_label_for(host)
  if host == primary_host
    @primary['label']
  else
    standby_config_for(host)&.dig('label')
  end
end

#pgbouncer_userObject



159
160
161
# File 'lib/active_postgres/configuration.rb', line 159

def pgbouncer_user
  component_config(:pgbouncer)[:user] || 'pgbouncer'
end

#postgres_userObject

Database and user configuration helpers from components



143
144
145
# File 'lib/active_postgres/configuration.rb', line 143

def postgres_user
  component_config(:core)[:postgres_user] || 'postgres'
end

#primary_connection_hostObject



77
78
79
# File 'lib/active_postgres/configuration.rb', line 77

def primary_connection_host
  connection_host_for(primary_host)
end

#primary_hostObject



42
43
44
# File 'lib/active_postgres/configuration.rb', line 42

def primary_host
  @primary['host']
end

#primary_replication_hostObject



62
63
64
# File 'lib/active_postgres/configuration.rb', line 62

def primary_replication_host
  replication_host_for(primary_host)
end

#replication_host_for(host) ⇒ Object



66
67
68
69
# File 'lib/active_postgres/configuration.rb', line 66

def replication_host_for(host)
  node = node_config_for(host)
  private_ip_for(node) || host
end

#replication_userObject



155
156
157
# File 'lib/active_postgres/configuration.rb', line 155

def replication_user
  component_config(:repmgr)[:replication_user] || 'replication'
end

#repmgr_databaseObject



151
152
153
# File 'lib/active_postgres/configuration.rb', line 151

def repmgr_database
  component_config(:repmgr)[:database] || 'repmgr'
end

#repmgr_userObject



147
148
149
# File 'lib/active_postgres/configuration.rb', line 147

def repmgr_user
  component_config(:repmgr)[:user] || 'repmgr'
end

#skip_deployment?Boolean

Returns:

  • (Boolean)


58
59
60
# File 'lib/active_postgres/configuration.rb', line 58

def skip_deployment?
  @skip_deployment
end

#standby_config_for(host) ⇒ Object



81
82
83
# File 'lib/active_postgres/configuration.rb', line 81

def standby_config_for(host)
  @standbys.find { |s| s['host'] == host }
end

#standby_hostsObject



46
47
48
# File 'lib/active_postgres/configuration.rb', line 46

def standby_hosts
  @standbys.map { |s| s['host'] }
end

#validate!Object

Raises:



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/active_postgres/configuration.rb', line 93

def validate!
  raise Error, 'No primary host defined' unless primary_host

  # Validate required secrets if components are enabled
  raise Error, 'Missing replication_password secret' if component_enabled?(:repmgr) && !secrets_config['replication_password']
  raise Error, 'Missing monitoring_password secret' if component_enabled?(:monitoring) && !secrets_config['monitoring_password']
  if component_enabled?(:monitoring)
    grafana_config = component_config(:monitoring)[:grafana] || {}
    if grafana_config[:enabled] && !secrets_config['grafana_admin_password']
      raise Error, 'Missing grafana_admin_password secret'
    end
    if grafana_config[:enabled] && grafana_config[:host].to_s.strip.empty?
      raise Error, 'monitoring.grafana.host is required when grafana is enabled'
    end
  end
  if component_enabled?(:pgbackrest)
    pg_config = component_config(:pgbackrest)
    retention_full = pg_config[:retention_full]
    retention_archive = pg_config[:retention_archive]
    if retention_full && retention_archive && retention_archive.to_i < retention_full.to_i
      raise Error, 'pgbackrest.retention_archive must be >= retention_full for PITR safety'
    end
  end

    if component_enabled?(:repmgr)
      dns_failover = component_config(:repmgr)[:dns_failover]
      if dns_failover && dns_failover[:enabled]
        domains = Array(dns_failover[:domains] || dns_failover[:domain]).map(&:to_s).map(&:strip).reject(&:empty?)
        servers = Array(dns_failover[:dns_servers])
        provider = (dns_failover[:provider] || 'dnsmasq').to_s.strip

        raise Error, 'dns_failover.domain or dns_failover.domains is required when enabled' if domains.empty?
        raise Error, 'dns_failover.dns_servers is required when enabled' if servers.empty?
        raise Error, "Unsupported dns_failover provider '#{provider}'" unless provider == 'dnsmasq'

        servers.each do |server|
          next unless server.is_a?(Hash)

          ssh_host = server['ssh_host'] || server[:ssh_host] || server['host'] || server[:host]
          private_ip = server['private_ip'] || server[:private_ip] || server['ip'] || server[:ip]
          raise Error, 'dns_failover.dns_servers entries must include host/ssh_host or private_ip' if
            (ssh_host.nil? || ssh_host.to_s.strip.empty?) && (private_ip.nil? || private_ip.to_s.strip.empty?)
        end
      end
    end

  true
end