pghero 2.4.0 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of pghero might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/CHANGELOG.md +84 -55
- data/README.md +19 -8
- data/app/assets/javascripts/pghero/application.js +1 -1
- data/app/controllers/pg_hero/home_controller.rb +78 -18
- data/app/helpers/pg_hero/home_helper.rb +11 -0
- data/app/views/pg_hero/home/_live_queries_table.html.erb +3 -1
- data/app/views/pg_hero/home/connections.html.erb +9 -0
- data/app/views/pg_hero/home/index.html.erb +2 -2
- data/app/views/pg_hero/home/maintenance.html.erb +16 -2
- data/app/views/pg_hero/home/space.html.erb +1 -1
- data/app/views/pg_hero/home/tune.html.erb +2 -1
- data/lib/generators/pghero/templates/config.yml.tt +11 -4
- data/lib/pghero.rb +27 -11
- data/lib/pghero/database.rb +81 -21
- data/lib/pghero/methods/basic.rb +28 -7
- data/lib/pghero/methods/connections.rb +35 -0
- data/lib/pghero/methods/explain.rb +1 -1
- data/lib/pghero/methods/maintenance.rb +3 -1
- data/lib/pghero/methods/queries.rb +6 -2
- data/lib/pghero/methods/query_stats.rb +16 -5
- data/lib/pghero/methods/suggested_indexes.rb +1 -1
- data/lib/pghero/methods/system.rb +227 -15
- data/lib/pghero/methods/users.rb +4 -0
- data/lib/pghero/version.rb +1 -1
- metadata +3 -3
@@ -59,7 +59,7 @@ function initSlider() {
|
|
59
59
|
html = "Now";
|
60
60
|
}
|
61
61
|
} else {
|
62
|
-
html =
|
62
|
+
html = time.getDate() + " " + months[time.getMonth()] + " " + pad(time.getHours()) + ":" + pad(time.getMinutes());
|
63
63
|
}
|
64
64
|
$(selector).html(html);
|
65
65
|
}
|
@@ -13,6 +13,11 @@ module PgHero
|
|
13
13
|
before_action :ensure_query_stats, only: [:queries]
|
14
14
|
|
15
15
|
if PgHero.config["override_csp"]
|
16
|
+
# note: this does not take into account asset hosts
|
17
|
+
# which can be a string with %d or a proc
|
18
|
+
# https://api.rubyonrails.org/classes/ActionView/Helpers/AssetUrlHelper.html
|
19
|
+
# users should set CSP manually if needed
|
20
|
+
# see https://github.com/ankane/pghero/issues/297
|
16
21
|
after_action do
|
17
22
|
response.headers["Content-Security-Policy"] = "default-src 'self' 'unsafe-inline'"
|
18
23
|
end
|
@@ -198,6 +203,11 @@ module PgHero
|
|
198
203
|
"1 week" => {duration: 1.week, period: 30.minutes},
|
199
204
|
"2 weeks" => {duration: 2.weeks, period: 1.hours}
|
200
205
|
}
|
206
|
+
if @database.system_stats_provider == :azure
|
207
|
+
# doesn't support 10, just 5 and 15
|
208
|
+
@periods["1 day"][:period] = 15.minutes
|
209
|
+
end
|
210
|
+
|
201
211
|
@duration = (params[:duration] || 1.hour).to_i
|
202
212
|
@period = (params[:period] || 60.seconds).to_i
|
203
213
|
|
@@ -209,22 +219,36 @@ module PgHero
|
|
209
219
|
end
|
210
220
|
|
211
221
|
def cpu_usage
|
212
|
-
render json: [{name: "CPU", data: @database.cpu_usage(system_params).map { |k, v| [k, v.round] }, library: chart_library_options}]
|
222
|
+
render json: [{name: "CPU", data: @database.cpu_usage(**system_params).map { |k, v| [k, v ? v.round : v] }, library: chart_library_options}]
|
213
223
|
end
|
214
224
|
|
215
225
|
def connection_stats
|
216
|
-
render json: [{name: "Connections", data: @database.connection_stats(system_params), library: chart_library_options}]
|
226
|
+
render json: [{name: "Connections", data: @database.connection_stats(**system_params), library: chart_library_options}]
|
217
227
|
end
|
218
228
|
|
219
229
|
def replication_lag_stats
|
220
|
-
render json: [{name: "Lag", data: @database.replication_lag_stats(system_params), library: chart_library_options}]
|
230
|
+
render json: [{name: "Lag", data: @database.replication_lag_stats(**system_params), library: chart_library_options}]
|
221
231
|
end
|
222
232
|
|
223
233
|
def load_stats
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
234
|
+
stats =
|
235
|
+
case @database.system_stats_provider
|
236
|
+
when :azure
|
237
|
+
[
|
238
|
+
{name: "IO Consumption", data: @database.azure_stats("io_consumption_percent", **system_params), library: chart_library_options}
|
239
|
+
]
|
240
|
+
when :gcp
|
241
|
+
[
|
242
|
+
{name: "Read Ops", data: @database.read_iops_stats(**system_params).map { |k, v| [k, v ? v.round : v] }, library: chart_library_options},
|
243
|
+
{name: "Write Ops", data: @database.write_iops_stats(**system_params).map { |k, v| [k, v ? v.round : v] }, library: chart_library_options}
|
244
|
+
]
|
245
|
+
else
|
246
|
+
[
|
247
|
+
{name: "Read IOPS", data: @database.read_iops_stats(**system_params).map { |k, v| [k, v ? v.round : v] }, library: chart_library_options},
|
248
|
+
{name: "Write IOPS", data: @database.write_iops_stats(**system_params).map { |k, v| [k, v ? v.round : v] }, library: chart_library_options}
|
249
|
+
]
|
250
|
+
end
|
251
|
+
render json: stats
|
228
252
|
end
|
229
253
|
|
230
254
|
def free_space_stats
|
@@ -270,17 +294,48 @@ module PgHero
|
|
270
294
|
|
271
295
|
def connections
|
272
296
|
@title = "Connections"
|
273
|
-
|
274
|
-
|
297
|
+
connections = @database.connections
|
298
|
+
|
299
|
+
@total_connections = connections.count
|
300
|
+
@connection_sources = group_connections(connections, [:database, :user, :source, :ip])
|
301
|
+
@connections_by_database = group_connections_by_key(connections, :database)
|
302
|
+
@connections_by_user = group_connections_by_key(connections, :user)
|
303
|
+
|
304
|
+
if params[:security] && @database.server_version_num >= 90500
|
305
|
+
connections.each do |connection|
|
306
|
+
connection[:ssl_status] =
|
307
|
+
if connection[:ssl]
|
308
|
+
# no way to tell if client used verify-full
|
309
|
+
# so connection may not be actually secure
|
310
|
+
"SSL"
|
311
|
+
else
|
312
|
+
# variety of reasons for no SSL
|
313
|
+
if !connection[:database].present?
|
314
|
+
"Internal Process"
|
315
|
+
elsif !connection[:ip]
|
316
|
+
if connection[:state]
|
317
|
+
"Socket"
|
318
|
+
else
|
319
|
+
# tcp or socket, don't have permission to tell
|
320
|
+
"No SSL"
|
321
|
+
end
|
322
|
+
else
|
323
|
+
# tcp
|
324
|
+
# could separate out localhost since this should be safe
|
325
|
+
"No SSL"
|
326
|
+
end
|
327
|
+
end
|
328
|
+
end
|
275
329
|
|
276
|
-
|
277
|
-
|
330
|
+
@connections_by_ssl_status = group_connections_by_key(connections, :ssl_status)
|
331
|
+
end
|
278
332
|
end
|
279
333
|
|
280
334
|
def maintenance
|
281
335
|
@title = "Maintenance"
|
282
336
|
@maintenance_info = @database.maintenance_info
|
283
337
|
@time_zone = PgHero.time_zone
|
338
|
+
@show_dead_rows = params[:dead_rows]
|
284
339
|
end
|
285
340
|
|
286
341
|
def kill
|
@@ -363,7 +418,8 @@ module PgHero
|
|
363
418
|
def system_params
|
364
419
|
{
|
365
420
|
duration: params[:duration],
|
366
|
-
period: params[:period]
|
421
|
+
period: params[:period],
|
422
|
+
series: true
|
367
423
|
}.delete_if { |_, v| v.nil? }
|
368
424
|
end
|
369
425
|
|
@@ -376,18 +432,22 @@ module PgHero
|
|
376
432
|
@show_details = @historical_query_stats_enabled && @database.supports_query_hash?
|
377
433
|
end
|
378
434
|
|
379
|
-
def group_connections(
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
435
|
+
def group_connections(connections, keys)
|
436
|
+
connections
|
437
|
+
.group_by { |conn| conn.slice(*keys) }
|
438
|
+
.map { |k, v| k.merge(total_connections: v.count) }
|
439
|
+
.sort_by { |v| [-v[:total_connections]] + keys.map { |k| v[k].to_s } }
|
440
|
+
end
|
441
|
+
|
442
|
+
def group_connections_by_key(connections, key)
|
443
|
+
group_connections(connections, [key]).map { |v| [v[key], v[:total_connections]] }.to_h
|
385
444
|
end
|
386
445
|
|
387
446
|
def check_api
|
388
447
|
render_text "No support for Rails API. See https://github.com/pghero/pghero for a standalone app." if Rails.application.config.try(:api_only)
|
389
448
|
end
|
390
449
|
|
450
|
+
# TODO return error status code
|
391
451
|
def render_text(message)
|
392
452
|
render plain: message
|
393
453
|
end
|
@@ -15,5 +15,16 @@ module PgHero
|
|
15
15
|
def pghero_js_var(name, value)
|
16
16
|
"var #{name} = #{json_escape(value.to_json(root: false))};".html_safe
|
17
17
|
end
|
18
|
+
|
19
|
+
def pghero_remove_index(query)
|
20
|
+
if query[:columns]
|
21
|
+
columns = query[:columns].map(&:to_sym)
|
22
|
+
columns = columns.first if columns.size == 1
|
23
|
+
end
|
24
|
+
ret = String.new("remove_index #{query[:table].to_sym.inspect}")
|
25
|
+
ret << ", name: #{(query[:name] || query[:index]).to_s.inspect}"
|
26
|
+
ret << ", column: #{columns.inspect}" if columns
|
27
|
+
ret
|
28
|
+
end
|
18
29
|
end
|
19
30
|
end
|
@@ -30,7 +30,9 @@
|
|
30
30
|
<% end %>
|
31
31
|
</td>
|
32
32
|
<td class="text-right">
|
33
|
-
|
33
|
+
<% unless @database.filter_data %>
|
34
|
+
<%= button_to "Explain", explain_path, params: {query: query[:query]}, form: {target: "_blank"}, class: "btn btn-info" %>
|
35
|
+
<% end %>
|
34
36
|
<%= button_to "Kill", kill_path(pid: query[:pid]), class: "btn btn-danger" %>
|
35
37
|
</td>
|
36
38
|
</tr>
|
@@ -18,6 +18,15 @@
|
|
18
18
|
new Chartkick.PieChart("chart-2", <%= json_escape(@connections_by_user.to_json).html_safe %>);
|
19
19
|
</script>
|
20
20
|
|
21
|
+
<% if @connections_by_ssl_status %>
|
22
|
+
<h3>By Security</h3>
|
23
|
+
|
24
|
+
<div id="chart-3" class="chart" style="height: 260px; line-height: 260px; margin-bottom: 20px;">Loading...</div>
|
25
|
+
<script>
|
26
|
+
new Chartkick.PieChart("chart-3", <%= json_escape(@connections_by_ssl_status.to_json).html_safe %>);
|
27
|
+
</script>
|
28
|
+
<% end %>
|
29
|
+
|
21
30
|
<%= render partial: "connections_table", locals: {connection_sources: @connection_sources} %>
|
22
31
|
<% end %>
|
23
32
|
</div>
|
@@ -387,7 +387,7 @@
|
|
387
387
|
<pre>rails generate migration remove_unneeded_indexes</pre>
|
388
388
|
<p>And paste</p>
|
389
389
|
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @duplicate_indexes.each do |query| %>
|
390
|
-
|
390
|
+
<%= pghero_remove_index(query[:unneeded_index]) %><% end %></pre>
|
391
391
|
</div>
|
392
392
|
|
393
393
|
<table class="table duplicate-indexes">
|
@@ -491,7 +491,7 @@ pg_stat_statements.track = all
|
|
491
491
|
<pre>rails generate migration remove_unused_indexes</pre>
|
492
492
|
<p>And paste</p>
|
493
493
|
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @unused_indexes.each do |query| %>
|
494
|
-
|
494
|
+
<%= pghero_remove_index(query)%><% end %></pre>
|
495
495
|
</div>
|
496
496
|
|
497
497
|
<table class="table">
|
@@ -7,6 +7,9 @@
|
|
7
7
|
<th>Table</th>
|
8
8
|
<th style="width: 20%;">Last Vacuum</th>
|
9
9
|
<th style="width: 20%;">Last Analyze</th>
|
10
|
+
<% if @show_dead_rows %>
|
11
|
+
<th style="width: 20%;">Dead Rows</th>
|
12
|
+
<% end %>
|
10
13
|
</tr>
|
11
14
|
</thead>
|
12
15
|
<tbody>
|
@@ -21,7 +24,7 @@
|
|
21
24
|
<td>
|
22
25
|
<% time = [table[:last_autovacuum], table[:last_vacuum]].compact.max %>
|
23
26
|
<% if time %>
|
24
|
-
<%= time.in_time_zone(@time_zone)
|
27
|
+
<%= l time.in_time_zone(@time_zone), format: :short %>
|
25
28
|
<% else %>
|
26
29
|
<span class="text-muted">Unknown</span>
|
27
30
|
<% end %>
|
@@ -29,11 +32,22 @@
|
|
29
32
|
<td>
|
30
33
|
<% time = [table[:last_autoanalyze], table[:last_analyze]].compact.max %>
|
31
34
|
<% if time %>
|
32
|
-
<%= time.in_time_zone(@time_zone)
|
35
|
+
<%= l time.in_time_zone(@time_zone), format: :short %>
|
33
36
|
<% else %>
|
34
37
|
<span class="text-muted">Unknown</span>
|
35
38
|
<% end %>
|
36
39
|
</td>
|
40
|
+
<% if @show_dead_rows %>
|
41
|
+
<td>
|
42
|
+
<% if table[:live_rows] != 0 %>
|
43
|
+
<%# use live rows only for denominator to make it easier to compare with autovacuum_vacuum_scale_factor %>
|
44
|
+
<%# it's not a true percentage, since it can go above 100% %>
|
45
|
+
<%= (100.0 * table[:dead_rows] / table[:live_rows]).round %>%
|
46
|
+
<% else %>
|
47
|
+
<span class="text-muted">Unknown</span>
|
48
|
+
<% end %>
|
49
|
+
</td>
|
50
|
+
<% end %>
|
37
51
|
</tr>
|
38
52
|
<% end %>
|
39
53
|
</tbody>
|
@@ -33,7 +33,7 @@
|
|
33
33
|
<pre>rails generate migration remove_unused_indexes</pre>
|
34
34
|
<p>And paste</p>
|
35
35
|
<pre style="overflow: scroll; white-space: pre; word-break: normal;"><% @unused_indexes.sort_by { |q| [-q[:size_bytes], q[:index]] }.each do |query| %>
|
36
|
-
|
36
|
+
<%= pghero_remove_index(query) %><% end %></pre>
|
37
37
|
</div>
|
38
38
|
<% end %>
|
39
39
|
|
@@ -18,7 +18,8 @@
|
|
18
18
|
</tbody>
|
19
19
|
</table>
|
20
20
|
|
21
|
-
|
21
|
+
<% version_parts = @database.server_version.split(" ").first.split(".") %>
|
22
|
+
<p>Check out <%= link_to "PgTune", "https://pgtune.leopard.in.ua/", target: "_blank" %> for recommendations. DB version is <%= version_parts[0].to_i >= 10 ? version_parts[0] : version_parts.first(2).join(".") %>.</p>
|
22
23
|
</div>
|
23
24
|
|
24
25
|
<% if @autovacuum_settings %>
|
@@ -3,6 +3,11 @@ databases:
|
|
3
3
|
# Database URL (defaults to app database)
|
4
4
|
# url: <%%= ENV["DATABASE_URL"] %>
|
5
5
|
|
6
|
+
# System stats
|
7
|
+
# aws_db_instance_identifier: my-instance
|
8
|
+
# gcp_database_id: my-project:my-instance
|
9
|
+
# azure_resource_id: my-resource-id
|
10
|
+
|
6
11
|
# Add more databases
|
7
12
|
# other:
|
8
13
|
# url: <%%= ENV["OTHER_DATABASE_URL"] %>
|
@@ -27,13 +32,15 @@ databases:
|
|
27
32
|
|
28
33
|
# Basic authentication
|
29
34
|
# username: admin
|
30
|
-
# password:
|
35
|
+
# password: <%%= ENV["PGHERO_PASSWORD"] %>
|
31
36
|
|
32
37
|
# Stats database URL (defaults to app database)
|
33
38
|
# stats_database_url: <%%= ENV["PGHERO_STATS_DATABASE_URL"] %>
|
34
39
|
|
35
40
|
# AWS configuration (defaults to app AWS config)
|
36
|
-
#
|
37
|
-
#
|
38
|
-
# aws_secret_access_key: ...
|
41
|
+
# aws_access_key_id: <%%= ENV["AWS_ACCESS_KEY_ID"] %>
|
42
|
+
# aws_secret_access_key: <%%= ENV["AWS_SECRET_ACCESS_KEY"] %>
|
39
43
|
# aws_region: us-east-1
|
44
|
+
|
45
|
+
# Filter data from queries (experimental)
|
46
|
+
# filter_data: true
|
data/lib/pghero.rb
CHANGED
@@ -34,24 +34,27 @@ module PgHero
|
|
34
34
|
class Error < StandardError; end
|
35
35
|
class NotEnabled < Error; end
|
36
36
|
|
37
|
+
MUTEX = Mutex.new
|
38
|
+
|
37
39
|
# settings
|
38
40
|
class << self
|
39
|
-
attr_accessor :long_running_query_sec, :slow_query_ms, :slow_query_calls, :explain_timeout_sec, :total_connections_threshold, :cache_hit_rate_threshold, :env, :show_migrations, :config_path
|
41
|
+
attr_accessor :long_running_query_sec, :slow_query_ms, :slow_query_calls, :explain_timeout_sec, :total_connections_threshold, :cache_hit_rate_threshold, :env, :show_migrations, :config_path, :filter_data
|
40
42
|
end
|
41
43
|
self.long_running_query_sec = (ENV["PGHERO_LONG_RUNNING_QUERY_SEC"] || 60).to_i
|
42
44
|
self.slow_query_ms = (ENV["PGHERO_SLOW_QUERY_MS"] || 20).to_i
|
43
45
|
self.slow_query_calls = (ENV["PGHERO_SLOW_QUERY_CALLS"] || 100).to_i
|
44
|
-
self.explain_timeout_sec = (ENV["PGHERO_EXPLAIN_TIMEOUT_SEC"] || 10).
|
46
|
+
self.explain_timeout_sec = (ENV["PGHERO_EXPLAIN_TIMEOUT_SEC"] || 10).to_f
|
45
47
|
self.total_connections_threshold = (ENV["PGHERO_TOTAL_CONNECTIONS_THRESHOLD"] || 500).to_i
|
46
48
|
self.cache_hit_rate_threshold = 99
|
47
49
|
self.env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
48
50
|
self.show_migrations = true
|
49
51
|
self.config_path = ENV["PGHERO_CONFIG_PATH"] || "config/pghero.yml"
|
52
|
+
self.filter_data = ENV["PGHERO_FILTER_DATA"].to_s.size > 0
|
50
53
|
|
51
54
|
class << self
|
52
55
|
extend Forwardable
|
53
56
|
def_delegators :primary_database, :access_key_id, :analyze, :analyze_tables, :autoindex, :autovacuum_danger,
|
54
|
-
:best_index, :blocked_queries, :connection_sources, :connection_states, :connection_stats,
|
57
|
+
:best_index, :blocked_queries, :connections, :connection_sources, :connection_states, :connection_stats,
|
55
58
|
:cpu_usage, :create_user, :database_size, :db_instance_identifier, :disable_query_stats, :drop_user,
|
56
59
|
:duplicate_indexes, :enable_query_stats, :explain, :historical_query_stats_enabled?, :index_caching,
|
57
60
|
:index_hit_rate, :index_usage, :indexes, :invalid_constraints, :invalid_indexes, :kill, :kill_all, :kill_long_running_queries,
|
@@ -116,11 +119,18 @@ module PgHero
|
|
116
119
|
|
117
120
|
if databases.empty?
|
118
121
|
databases["primary"] = {
|
119
|
-
"url" => ENV["PGHERO_DATABASE_URL"] || ActiveRecord::Base.connection_config
|
120
|
-
"db_instance_identifier" => ENV["PGHERO_DB_INSTANCE_IDENTIFIER"]
|
122
|
+
"url" => ENV["PGHERO_DATABASE_URL"] || ActiveRecord::Base.connection_config
|
121
123
|
}
|
122
124
|
end
|
123
125
|
|
126
|
+
if databases.size == 1
|
127
|
+
databases.values.first.merge!(
|
128
|
+
"db_instance_identifier" => ENV["PGHERO_DB_INSTANCE_IDENTIFIER"],
|
129
|
+
"gcp_database_id" => ENV["PGHERO_GCP_DATABASE_ID"],
|
130
|
+
"azure_resource_id" => ENV["PGHERO_AZURE_RESOURCE_ID"]
|
131
|
+
)
|
132
|
+
end
|
133
|
+
|
124
134
|
{
|
125
135
|
"databases" => databases
|
126
136
|
}
|
@@ -128,14 +138,20 @@ module PgHero
|
|
128
138
|
end
|
129
139
|
end
|
130
140
|
|
141
|
+
# ensure we only have one copy of databases
|
142
|
+
# so there's only one connection pool per database
|
131
143
|
def databases
|
132
|
-
@databases
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
144
|
+
unless defined?(@databases)
|
145
|
+
# only use mutex on initialization
|
146
|
+
MUTEX.synchronize do
|
147
|
+
# return if another process initialized while we were waiting
|
148
|
+
return @databases if defined?(@databases)
|
149
|
+
|
150
|
+
@databases = config["databases"].map { |id, c| [id.to_sym, Database.new(id, c)] }.to_h
|
151
|
+
end
|
138
152
|
end
|
153
|
+
|
154
|
+
@databases
|
139
155
|
end
|
140
156
|
|
141
157
|
def primary_database
|
data/lib/pghero/database.rb
CHANGED
@@ -23,6 +23,11 @@ module PgHero
|
|
23
23
|
def initialize(id, config)
|
24
24
|
@id = id
|
25
25
|
@config = config || {}
|
26
|
+
|
27
|
+
# preload model to ensure only one connection pool
|
28
|
+
# this doesn't actually start any connections
|
29
|
+
@adapter_checked = false
|
30
|
+
@connection_model = build_connection_model
|
26
31
|
end
|
27
32
|
|
28
33
|
def name
|
@@ -50,15 +55,16 @@ module PgHero
|
|
50
55
|
end
|
51
56
|
|
52
57
|
def explain_timeout_sec
|
53
|
-
(config["explain_timeout_sec"] || PgHero.config["explain_timeout_sec"] || PgHero.explain_timeout_sec).
|
58
|
+
(config["explain_timeout_sec"] || PgHero.config["explain_timeout_sec"] || PgHero.explain_timeout_sec).to_f
|
54
59
|
end
|
55
60
|
|
56
61
|
def long_running_query_sec
|
57
62
|
(config["long_running_query_sec"] || PgHero.config["long_running_query_sec"] || PgHero.long_running_query_sec).to_i
|
58
63
|
end
|
59
64
|
|
65
|
+
# defaults to 100 megabytes
|
60
66
|
def index_bloat_bytes
|
61
|
-
(config["index_bloat_bytes"] || PgHero.config["index_bloat_bytes"] ||
|
67
|
+
(config["index_bloat_bytes"] || PgHero.config["index_bloat_bytes"] || 104857600).to_i
|
62
68
|
end
|
63
69
|
|
64
70
|
def aws_access_key_id
|
@@ -73,8 +79,43 @@ module PgHero
|
|
73
79
|
config["aws_region"] || PgHero.config["aws_region"] || ENV["PGHERO_REGION"] || ENV["AWS_REGION"] || (defined?(Aws) && Aws.config[:region]) || "us-east-1"
|
74
80
|
end
|
75
81
|
|
82
|
+
# environment variable is only used if no config file
|
76
83
|
def aws_db_instance_identifier
|
77
|
-
@
|
84
|
+
@aws_db_instance_identifier ||= config["aws_db_instance_identifier"] || config["db_instance_identifier"]
|
85
|
+
end
|
86
|
+
|
87
|
+
# environment variable is only used if no config file
|
88
|
+
def gcp_database_id
|
89
|
+
@gcp_database_id ||= config["gcp_database_id"]
|
90
|
+
end
|
91
|
+
|
92
|
+
# environment variable is only used if no config file
|
93
|
+
def azure_resource_id
|
94
|
+
@azure_resource_id ||= config["azure_resource_id"]
|
95
|
+
end
|
96
|
+
|
97
|
+
# must check keys for booleans
|
98
|
+
def filter_data
|
99
|
+
unless defined?(@filter_data)
|
100
|
+
@filter_data =
|
101
|
+
if config.key?("filter_data")
|
102
|
+
config["filter_data"]
|
103
|
+
elsif PgHero.config.key?("filter_data")
|
104
|
+
PgHero.config.key?("filter_data")
|
105
|
+
else
|
106
|
+
PgHero.filter_data
|
107
|
+
end
|
108
|
+
|
109
|
+
if @filter_data
|
110
|
+
begin
|
111
|
+
require "pg_query"
|
112
|
+
rescue LoadError
|
113
|
+
raise Error, "pg_query required for filter_data"
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
@filter_data
|
78
119
|
end
|
79
120
|
|
80
121
|
# TODO remove in next major version
|
@@ -85,27 +126,46 @@ module PgHero
|
|
85
126
|
|
86
127
|
private
|
87
128
|
|
129
|
+
# check adapter lazily
|
88
130
|
def connection_model
|
89
|
-
@
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
raise Error, "
|
95
|
-
url = resolved.config
|
131
|
+
unless @adapter_checked
|
132
|
+
# rough check for Postgres adapter
|
133
|
+
# keep this message generic so it's useful
|
134
|
+
# when empty url set in Docker image pghero.yml
|
135
|
+
unless @connection_model.connection.adapter_name =~ /postg/i
|
136
|
+
raise Error, "Invalid connection URL"
|
96
137
|
end
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
138
|
+
@adapter_checked = true
|
139
|
+
end
|
140
|
+
|
141
|
+
@connection_model
|
142
|
+
end
|
143
|
+
|
144
|
+
# just return the model
|
145
|
+
# do not start a connection
|
146
|
+
def build_connection_model
|
147
|
+
url = config["url"]
|
148
|
+
|
149
|
+
# resolve spec
|
150
|
+
if !url && config["spec"]
|
151
|
+
raise Error, "Spec requires Rails 6+" unless PgHero.spec_supported?
|
152
|
+
resolved = ActiveRecord::Base.configurations.configs_for(env_name: PgHero.env, spec_name: config["spec"], include_replicas: true)
|
153
|
+
raise Error, "Spec not found: #{config["spec"]}" unless resolved
|
154
|
+
url = resolved.config
|
155
|
+
end
|
156
|
+
|
157
|
+
Class.new(PgHero::Connection) do
|
158
|
+
def self.name
|
159
|
+
"PgHero::Connection::Database#{object_id}"
|
160
|
+
end
|
161
|
+
|
162
|
+
case url
|
163
|
+
when String
|
164
|
+
url = "#{url}#{url.include?("?") ? "&" : "?"}connect_timeout=5" unless url.include?("connect_timeout=")
|
165
|
+
when Hash
|
166
|
+
url[:connect_timeout] ||= 5
|
108
167
|
end
|
168
|
+
establish_connection url if url
|
109
169
|
end
|
110
170
|
end
|
111
171
|
end
|