pg_sql_triggers 1.0.0 → 1.0.1
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/.erb_lint.yml +47 -0
- data/.rubocop.yml +4 -1
- data/CHANGELOG.md +29 -1
- data/Goal.md +408 -123
- data/README.md +47 -215
- data/app/controllers/pg_sql_triggers/application_controller.rb +46 -0
- data/app/controllers/pg_sql_triggers/generator_controller.rb +10 -4
- data/app/controllers/pg_sql_triggers/migrations_controller.rb +18 -0
- data/app/models/pg_sql_triggers/trigger_registry.rb +20 -2
- data/app/views/layouts/pg_sql_triggers/application.html.erb +34 -1
- data/app/views/pg_sql_triggers/dashboard/index.html.erb +70 -30
- data/app/views/pg_sql_triggers/generator/new.html.erb +4 -4
- data/app/views/pg_sql_triggers/generator/preview.html.erb +14 -6
- data/app/views/pg_sql_triggers/shared/_confirmation_modal.html.erb +189 -0
- data/app/views/pg_sql_triggers/shared/_kill_switch_status.html.erb +40 -0
- data/app/views/pg_sql_triggers/tables/index.html.erb +0 -2
- data/app/views/pg_sql_triggers/tables/show.html.erb +3 -4
- data/db/migrate/20251222000001_create_pg_sql_triggers_tables.rb +1 -1
- data/docs/README.md +66 -0
- data/docs/api-reference.md +663 -0
- data/docs/configuration.md +541 -0
- data/docs/getting-started.md +135 -0
- data/docs/kill-switch.md +586 -0
- data/docs/screenshots/.gitkeep +1 -0
- data/docs/screenshots/Generate Trigger.png +0 -0
- data/docs/screenshots/Triggers Page.png +0 -0
- data/docs/screenshots/kill error.png +0 -0
- data/docs/screenshots/kill modal for migration down.png +0 -0
- data/docs/usage-guide.md +420 -0
- data/docs/web-ui.md +339 -0
- data/lib/generators/pg_sql_triggers/templates/create_pg_sql_triggers_tables.rb +1 -1
- data/lib/generators/pg_sql_triggers/templates/initializer.rb +36 -2
- data/lib/pg_sql_triggers/generator/service.rb +1 -1
- data/lib/pg_sql_triggers/migration.rb +1 -1
- data/lib/pg_sql_triggers/migrator.rb +27 -3
- data/lib/pg_sql_triggers/registry/manager.rb +6 -6
- data/lib/pg_sql_triggers/sql/kill_switch.rb +300 -0
- data/lib/pg_sql_triggers/testing/dry_run.rb +5 -7
- data/lib/pg_sql_triggers/testing/safe_executor.rb +23 -11
- data/lib/pg_sql_triggers/version.rb +1 -1
- data/lib/pg_sql_triggers.rb +12 -0
- data/lib/tasks/trigger_migrations.rake +33 -0
- metadata +35 -5
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PgSqlTriggers
|
|
4
|
+
module SQL
|
|
5
|
+
# KillSwitch is a centralized safety gate that prevents dangerous operations
|
|
6
|
+
# from being executed in protected environments (typically production).
|
|
7
|
+
#
|
|
8
|
+
# It operates on three levels:
|
|
9
|
+
# 1. Configuration Level: Environment-based activation via PgSqlTriggers.kill_switch_enabled
|
|
10
|
+
# 2. Runtime Level: ENV variable override support (KILL_SWITCH_OVERRIDE)
|
|
11
|
+
# 3. Explicit Confirmation Level: Typed confirmation text for critical operations
|
|
12
|
+
#
|
|
13
|
+
# @example Basic usage in a dangerous operation
|
|
14
|
+
# KillSwitch.check!(
|
|
15
|
+
# operation: :migrate_up,
|
|
16
|
+
# environment: Rails.env,
|
|
17
|
+
# confirmation: params[:confirmation_text],
|
|
18
|
+
# actor: { type: 'UI', id: current_user.email }
|
|
19
|
+
# )
|
|
20
|
+
#
|
|
21
|
+
# @example Using override block
|
|
22
|
+
# KillSwitch.override(confirmation: "EXECUTE MIGRATE_UP") do
|
|
23
|
+
# # dangerous operation here
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# @example CLI usage with ENV override
|
|
27
|
+
# KILL_SWITCH_OVERRIDE=true CONFIRMATION_TEXT="EXECUTE MIGRATE_UP" rake pg_sql_triggers:migrate
|
|
28
|
+
#
|
|
29
|
+
# rubocop:disable Metrics/ModuleLength
|
|
30
|
+
module KillSwitch
|
|
31
|
+
# Thread-local storage key for override state
|
|
32
|
+
OVERRIDE_KEY = :pg_sql_triggers_kill_switch_override
|
|
33
|
+
|
|
34
|
+
class << self
|
|
35
|
+
# Checks if the kill switch is active for the given environment and operation.
|
|
36
|
+
#
|
|
37
|
+
# @param environment [String, Symbol, nil] The environment to check (defaults to current environment)
|
|
38
|
+
# @param operation [String, Symbol, nil] The operation being performed (for logging)
|
|
39
|
+
# @return [Boolean] true if kill switch is active, false otherwise
|
|
40
|
+
def active?(environment: nil, operation: nil)
|
|
41
|
+
# Check if kill switch is globally disabled
|
|
42
|
+
return false unless kill_switch_enabled?
|
|
43
|
+
|
|
44
|
+
# Detect environment
|
|
45
|
+
env = detect_environment(environment)
|
|
46
|
+
|
|
47
|
+
# Check if this environment is protected
|
|
48
|
+
is_active = protected_environment?(env)
|
|
49
|
+
|
|
50
|
+
# Log the check if logger is available and operation is provided
|
|
51
|
+
if logger && operation
|
|
52
|
+
logger.debug "[KILL_SWITCH] Check: operation=#{operation} environment=#{env} active=#{is_active}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
is_active
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Checks if an operation should be blocked by the kill switch.
|
|
59
|
+
# Raises KillSwitchError if the operation is blocked.
|
|
60
|
+
#
|
|
61
|
+
# @param operation [String, Symbol] The operation being performed
|
|
62
|
+
# @param environment [String, Symbol, nil] The environment (defaults to current)
|
|
63
|
+
# @param confirmation [String, nil] The confirmation text provided by the user
|
|
64
|
+
# @param actor [Hash, nil] Information about who is performing the operation
|
|
65
|
+
# @raise [PgSqlTriggers::KillSwitchError] if the operation is blocked
|
|
66
|
+
# @return [void]
|
|
67
|
+
def check!(operation:, environment: nil, confirmation: nil, actor: nil)
|
|
68
|
+
env = detect_environment(environment)
|
|
69
|
+
|
|
70
|
+
# Check if kill switch is active for this environment
|
|
71
|
+
unless active?(environment: env, operation: operation)
|
|
72
|
+
log_allowed(operation: operation, environment: env, actor: actor, reason: "not_protected_environment")
|
|
73
|
+
return
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Check for thread-local override
|
|
77
|
+
if thread_override_active?
|
|
78
|
+
log_override(operation: operation, environment: env, actor: actor, source: "thread_local")
|
|
79
|
+
return
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Check for ENV override
|
|
83
|
+
if env_override_active?
|
|
84
|
+
# If ENV override is present, check confirmation if required
|
|
85
|
+
if confirmation_required?
|
|
86
|
+
validate_confirmation!(confirmation, operation)
|
|
87
|
+
log_override(operation: operation, environment: env, actor: actor, source: "env_with_confirmation",
|
|
88
|
+
confirmation: confirmation)
|
|
89
|
+
else
|
|
90
|
+
log_override(operation: operation, environment: env, actor: actor, source: "env_without_confirmation")
|
|
91
|
+
end
|
|
92
|
+
return
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# If confirmation is provided, validate it
|
|
96
|
+
unless confirmation.nil?
|
|
97
|
+
validate_confirmation!(confirmation, operation)
|
|
98
|
+
log_override(operation: operation, environment: env, actor: actor, source: "explicit_confirmation",
|
|
99
|
+
confirmation: confirmation)
|
|
100
|
+
return
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# No override mechanism satisfied - block the operation
|
|
104
|
+
log_blocked(operation: operation, environment: env, actor: actor)
|
|
105
|
+
raise_blocked_error(operation: operation, environment: env)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Temporarily overrides the kill switch for the duration of the block.
|
|
109
|
+
# Uses thread-local storage to ensure thread safety.
|
|
110
|
+
#
|
|
111
|
+
# @param confirmation [String, nil] Optional confirmation text
|
|
112
|
+
# @yield The block to execute with kill switch overridden
|
|
113
|
+
# @return The return value of the block
|
|
114
|
+
def override(confirmation: nil)
|
|
115
|
+
raise ArgumentError, "Block required for kill switch override" unless block_given?
|
|
116
|
+
|
|
117
|
+
# Validate confirmation if provided and required
|
|
118
|
+
if confirmation.present? && confirmation_required?
|
|
119
|
+
# NOTE: We can't validate against a specific operation here since we don't know it
|
|
120
|
+
# The block itself will call check! with the operation, which will see the override
|
|
121
|
+
logger&.info "[KILL_SWITCH] Override block initiated with confirmation: #{confirmation}"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Set thread-local override
|
|
125
|
+
previous_value = Thread.current[OVERRIDE_KEY]
|
|
126
|
+
Thread.current[OVERRIDE_KEY] = true
|
|
127
|
+
|
|
128
|
+
begin
|
|
129
|
+
yield
|
|
130
|
+
ensure
|
|
131
|
+
# Restore previous value
|
|
132
|
+
Thread.current[OVERRIDE_KEY] = previous_value
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Validates the confirmation text against the expected pattern for the operation.
|
|
137
|
+
#
|
|
138
|
+
# @param confirmation [String, nil] The confirmation text to validate
|
|
139
|
+
# @param operation [String, Symbol] The operation being confirmed
|
|
140
|
+
# @raise [PgSqlTriggers::KillSwitchError] if confirmation is invalid
|
|
141
|
+
# @return [void]
|
|
142
|
+
def validate_confirmation!(confirmation, operation)
|
|
143
|
+
expected = expected_confirmation(operation)
|
|
144
|
+
|
|
145
|
+
if confirmation.nil? || confirmation.strip.empty?
|
|
146
|
+
raise PgSqlTriggers::KillSwitchError,
|
|
147
|
+
"Confirmation text required. Expected: '#{expected}'"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
return if confirmation.strip == expected
|
|
151
|
+
|
|
152
|
+
raise PgSqlTriggers::KillSwitchError,
|
|
153
|
+
"Invalid confirmation text. Expected: '#{expected}', got: '#{confirmation.strip}'"
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private
|
|
157
|
+
|
|
158
|
+
# Checks if kill switch is globally enabled in configuration
|
|
159
|
+
def kill_switch_enabled?
|
|
160
|
+
return true unless PgSqlTriggers.respond_to?(:kill_switch_enabled)
|
|
161
|
+
|
|
162
|
+
# Default to true (fail-safe) if not configured
|
|
163
|
+
value = PgSqlTriggers.kill_switch_enabled
|
|
164
|
+
value.nil? || value
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Checks if the given environment is protected by the kill switch
|
|
168
|
+
def protected_environment?(environment)
|
|
169
|
+
return false if environment.nil?
|
|
170
|
+
|
|
171
|
+
protected_envs = if PgSqlTriggers.respond_to?(:kill_switch_environments)
|
|
172
|
+
value = PgSqlTriggers.kill_switch_environments
|
|
173
|
+
value.nil? ? %i[production staging] : value
|
|
174
|
+
else
|
|
175
|
+
%i[production staging]
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
protected_envs = Array(protected_envs).map(&:to_s)
|
|
179
|
+
protected_envs.include?(environment.to_s)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# Detects the current environment
|
|
183
|
+
def detect_environment(environment)
|
|
184
|
+
return environment.to_s if environment.present?
|
|
185
|
+
|
|
186
|
+
# Try Rails environment
|
|
187
|
+
return Rails.env.to_s if defined?(Rails) && Rails.respond_to?(:env)
|
|
188
|
+
|
|
189
|
+
# Try PgSqlTriggers default_environment
|
|
190
|
+
if PgSqlTriggers.respond_to?(:default_environment) && PgSqlTriggers.default_environment.respond_to?(:call)
|
|
191
|
+
begin
|
|
192
|
+
return PgSqlTriggers.default_environment.call.to_s
|
|
193
|
+
rescue NameError, NoMethodError # rubocop:disable Lint/ShadowedException
|
|
194
|
+
# Fall through to next option if default_environment proc references undefined constants
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Fall back to RAILS_ENV or RACK_ENV
|
|
199
|
+
ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Checks if thread-local override is active
|
|
203
|
+
def thread_override_active?
|
|
204
|
+
Thread.current[OVERRIDE_KEY] == true
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Checks if ENV override is active
|
|
208
|
+
def env_override_active?
|
|
209
|
+
ENV["KILL_SWITCH_OVERRIDE"]&.downcase == "true"
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Checks if confirmation is required for overrides
|
|
213
|
+
def confirmation_required?
|
|
214
|
+
return true unless PgSqlTriggers.respond_to?(:kill_switch_confirmation_required)
|
|
215
|
+
|
|
216
|
+
# Default to true (safer) if not configured
|
|
217
|
+
value = PgSqlTriggers.kill_switch_confirmation_required
|
|
218
|
+
value.nil? || value
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
# Generates the expected confirmation text for an operation
|
|
222
|
+
def expected_confirmation(operation)
|
|
223
|
+
if PgSqlTriggers.respond_to?(:kill_switch_confirmation_pattern) &&
|
|
224
|
+
PgSqlTriggers.kill_switch_confirmation_pattern.respond_to?(:call)
|
|
225
|
+
PgSqlTriggers.kill_switch_confirmation_pattern.call(operation)
|
|
226
|
+
else
|
|
227
|
+
# Default pattern
|
|
228
|
+
"EXECUTE #{operation.to_s.upcase}"
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Returns the configured logger
|
|
233
|
+
def logger
|
|
234
|
+
if PgSqlTriggers.respond_to?(:kill_switch_logger)
|
|
235
|
+
PgSqlTriggers.kill_switch_logger
|
|
236
|
+
elsif defined?(Rails) && Rails.respond_to?(:logger)
|
|
237
|
+
Rails.logger
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
# Logs an allowed operation
|
|
242
|
+
def log_allowed(operation:, environment:, actor:, reason:)
|
|
243
|
+
actor_info = format_actor(actor)
|
|
244
|
+
logger&.info "[KILL_SWITCH] ALLOWED: operation=#{operation} environment=#{environment} " \
|
|
245
|
+
"actor=#{actor_info} reason=#{reason}"
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# Logs an overridden operation
|
|
249
|
+
def log_override(operation:, environment:, actor:, source:, confirmation: nil)
|
|
250
|
+
actor_info = format_actor(actor)
|
|
251
|
+
conf_info = confirmation ? " confirmation=#{confirmation}" : ""
|
|
252
|
+
logger&.warn "[KILL_SWITCH] OVERRIDDEN: operation=#{operation} environment=#{environment} " \
|
|
253
|
+
"actor=#{actor_info} source=#{source}#{conf_info}"
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Logs a blocked operation
|
|
257
|
+
def log_blocked(operation:, environment:, actor:)
|
|
258
|
+
actor_info = format_actor(actor)
|
|
259
|
+
logger&.error "[KILL_SWITCH] BLOCKED: operation=#{operation} environment=#{environment} actor=#{actor_info}"
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Formats actor information for logging
|
|
263
|
+
def format_actor(actor)
|
|
264
|
+
return "unknown" if actor.nil?
|
|
265
|
+
return actor.to_s unless actor.is_a?(Hash)
|
|
266
|
+
|
|
267
|
+
"#{actor[:type] || 'unknown'}:#{actor[:id] || 'unknown'}"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
# Raises a kill switch error with helpful message
|
|
271
|
+
def raise_blocked_error(operation:, environment:)
|
|
272
|
+
expected = expected_confirmation(operation)
|
|
273
|
+
|
|
274
|
+
message = <<~ERROR
|
|
275
|
+
Kill switch is active for #{environment} environment.
|
|
276
|
+
Operation '#{operation}' has been blocked for safety.
|
|
277
|
+
|
|
278
|
+
To override this protection, you must provide confirmation.
|
|
279
|
+
|
|
280
|
+
For CLI/rake tasks, use:
|
|
281
|
+
KILL_SWITCH_OVERRIDE=true CONFIRMATION_TEXT="#{expected}" rake your:task
|
|
282
|
+
|
|
283
|
+
For console operations, use:
|
|
284
|
+
PgSqlTriggers::SQL::KillSwitch.override(confirmation: "#{expected}") do
|
|
285
|
+
# your dangerous operation here
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
For UI operations, enter the confirmation text: #{expected}
|
|
289
|
+
|
|
290
|
+
This protection prevents accidental destructive operations in production.
|
|
291
|
+
Make sure you understand the implications before proceeding.
|
|
292
|
+
ERROR
|
|
293
|
+
|
|
294
|
+
raise PgSqlTriggers::KillSwitchError, message
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
# rubocop:enable Metrics/ModuleLength
|
|
299
|
+
end
|
|
300
|
+
end
|
|
@@ -27,14 +27,12 @@ module PgSqlTriggers
|
|
|
27
27
|
trigger_timing = "BEFORE" # Could be configurable
|
|
28
28
|
trigger_level = "ROW" # Could be configurable
|
|
29
29
|
|
|
30
|
-
trigger_sql =
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
FOR EACH #{trigger_level}
|
|
34
|
-
SQL
|
|
30
|
+
trigger_sql = "CREATE TRIGGER #{@trigger.trigger_name} " \
|
|
31
|
+
"#{trigger_timing} #{events} ON #{@trigger.table_name} " \
|
|
32
|
+
"FOR EACH #{trigger_level}"
|
|
35
33
|
|
|
36
|
-
trigger_sql += "WHEN (#{@trigger.condition})
|
|
37
|
-
trigger_sql += "EXECUTE FUNCTION #{definition['function_name']}();"
|
|
34
|
+
trigger_sql += " WHEN (#{@trigger.condition})" if @trigger.condition.present?
|
|
35
|
+
trigger_sql += " EXECUTE FUNCTION #{definition['function_name']}();"
|
|
38
36
|
|
|
39
37
|
sql_parts << {
|
|
40
38
|
type: "CREATE TRIGGER",
|
|
@@ -26,21 +26,33 @@ module PgSqlTriggers
|
|
|
26
26
|
end
|
|
27
27
|
|
|
28
28
|
# Step 2: Create trigger
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
29
|
+
begin
|
|
30
|
+
sql_parts = DryRun.new(@trigger).generate_sql[:sql_parts]
|
|
31
|
+
trigger_part = sql_parts.find { |p| p[:type] == "CREATE TRIGGER" }
|
|
32
|
+
if trigger_part && trigger_part[:sql]
|
|
33
|
+
ActiveRecord::Base.connection.execute(trigger_part[:sql])
|
|
34
|
+
results[:trigger_created] = true
|
|
35
|
+
results[:output] << "✓ Trigger created successfully"
|
|
36
|
+
else
|
|
37
|
+
results[:errors] << "Could not find CREATE TRIGGER SQL in generated SQL parts"
|
|
38
|
+
end
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
results[:errors] << "Error generating trigger SQL: #{e.message}"
|
|
41
|
+
end
|
|
34
42
|
|
|
35
43
|
# Step 3: Test with sample data (if provided)
|
|
36
|
-
if test_data
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
if test_data && results[:trigger_created]
|
|
45
|
+
begin
|
|
46
|
+
test_sql = build_test_insert(test_data)
|
|
47
|
+
ActiveRecord::Base.connection.execute(test_sql)
|
|
48
|
+
results[:test_insert_executed] = true
|
|
49
|
+
results[:output] << "✓ Test insert executed successfully"
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
results[:errors] << "Error executing test insert: #{e.message}"
|
|
52
|
+
end
|
|
41
53
|
end
|
|
42
54
|
|
|
43
|
-
results[:success] =
|
|
55
|
+
results[:success] = results[:errors].empty?
|
|
44
56
|
rescue ActiveRecord::StatementInvalid => e
|
|
45
57
|
results[:success] = false
|
|
46
58
|
results[:errors] << e.message
|
data/lib/pg_sql_triggers.rb
CHANGED
|
@@ -14,6 +14,18 @@ module PgSqlTriggers
|
|
|
14
14
|
mattr_accessor :kill_switch_enabled
|
|
15
15
|
self.kill_switch_enabled = true
|
|
16
16
|
|
|
17
|
+
mattr_accessor :kill_switch_environments
|
|
18
|
+
self.kill_switch_environments = %i[production staging]
|
|
19
|
+
|
|
20
|
+
mattr_accessor :kill_switch_confirmation_required
|
|
21
|
+
self.kill_switch_confirmation_required = true
|
|
22
|
+
|
|
23
|
+
mattr_accessor :kill_switch_confirmation_pattern
|
|
24
|
+
self.kill_switch_confirmation_pattern = ->(operation) { "EXECUTE #{operation.to_s.upcase}" }
|
|
25
|
+
|
|
26
|
+
mattr_accessor :kill_switch_logger
|
|
27
|
+
self.kill_switch_logger = nil # Will default to Rails.logger if available
|
|
28
|
+
|
|
17
29
|
mattr_accessor :default_environment
|
|
18
30
|
self.default_environment = -> { Rails.env }
|
|
19
31
|
|
|
@@ -1,8 +1,23 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# Helper method to check kill switch before dangerous operations
|
|
4
|
+
def check_kill_switch!(operation)
|
|
5
|
+
PgSqlTriggers::SQL::KillSwitch.check!(
|
|
6
|
+
operation: operation,
|
|
7
|
+
environment: Rails.env,
|
|
8
|
+
confirmation: ENV.fetch("CONFIRMATION_TEXT", nil),
|
|
9
|
+
actor: { type: "CLI", id: ENV.fetch("USER", "unknown") }
|
|
10
|
+
)
|
|
11
|
+
rescue PgSqlTriggers::KillSwitchError => e
|
|
12
|
+
puts "\n#{e.message}\n"
|
|
13
|
+
exit 1
|
|
14
|
+
end
|
|
15
|
+
|
|
3
16
|
namespace :trigger do
|
|
4
17
|
desc "Migrate trigger migrations (options: VERSION=x, VERBOSE=false)"
|
|
5
18
|
task migrate: :environment do
|
|
19
|
+
check_kill_switch!(:trigger_migrate)
|
|
20
|
+
|
|
6
21
|
PgSqlTriggers::Migrator.ensure_migrations_table!
|
|
7
22
|
|
|
8
23
|
target_version = ENV["VERSION"]&.to_i
|
|
@@ -20,6 +35,8 @@ namespace :trigger do
|
|
|
20
35
|
|
|
21
36
|
desc "Rollback trigger migrations (specify steps w/ STEP=n)"
|
|
22
37
|
task rollback: :environment do
|
|
38
|
+
check_kill_switch!(:trigger_rollback)
|
|
39
|
+
|
|
23
40
|
PgSqlTriggers::Migrator.ensure_migrations_table!
|
|
24
41
|
|
|
25
42
|
steps = ENV["STEP"] ? ENV["STEP"].to_i : 1
|
|
@@ -64,6 +81,8 @@ namespace :trigger do
|
|
|
64
81
|
|
|
65
82
|
desc "Runs the 'up' for a given migration VERSION"
|
|
66
83
|
task "migrate:up" => :environment do
|
|
84
|
+
check_kill_switch!(:trigger_migrate_up)
|
|
85
|
+
|
|
67
86
|
version = ENV.fetch("VERSION", nil)
|
|
68
87
|
raise "VERSION is required" unless version
|
|
69
88
|
|
|
@@ -74,6 +93,8 @@ namespace :trigger do
|
|
|
74
93
|
|
|
75
94
|
desc "Runs the 'down' for a given migration VERSION"
|
|
76
95
|
task "migrate:down" => :environment do
|
|
96
|
+
check_kill_switch!(:trigger_migrate_down)
|
|
97
|
+
|
|
77
98
|
version = ENV.fetch("VERSION", nil)
|
|
78
99
|
raise "VERSION is required" unless version
|
|
79
100
|
|
|
@@ -84,6 +105,8 @@ namespace :trigger do
|
|
|
84
105
|
|
|
85
106
|
desc "Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x)"
|
|
86
107
|
task "migrate:redo" => :environment do
|
|
108
|
+
check_kill_switch!(:trigger_migrate_redo)
|
|
109
|
+
|
|
87
110
|
PgSqlTriggers::Migrator.ensure_migrations_table!
|
|
88
111
|
|
|
89
112
|
if ENV["VERSION"]
|
|
@@ -127,6 +150,8 @@ end
|
|
|
127
150
|
namespace :db do
|
|
128
151
|
desc "Migrate the database schema and triggers (options: VERSION=x, VERBOSE=false)"
|
|
129
152
|
task "migrate:with_triggers" => :environment do
|
|
153
|
+
check_kill_switch!(:db_migrate_with_triggers)
|
|
154
|
+
|
|
130
155
|
verbose = ENV["VERBOSE"] != "false"
|
|
131
156
|
|
|
132
157
|
puts "Running schema and trigger migrations..." if verbose
|
|
@@ -140,6 +165,8 @@ namespace :db do
|
|
|
140
165
|
|
|
141
166
|
desc "Rollback schema and trigger migrations (specify steps w/ STEP=n)"
|
|
142
167
|
task "rollback:with_triggers" => :environment do
|
|
168
|
+
check_kill_switch!(:db_rollback_with_triggers)
|
|
169
|
+
|
|
143
170
|
ENV["STEP"] ? ENV["STEP"].to_i : 1
|
|
144
171
|
|
|
145
172
|
# Determine which type of migration was last run
|
|
@@ -171,6 +198,8 @@ namespace :db do
|
|
|
171
198
|
|
|
172
199
|
desc "Runs the 'up' for a given migration VERSION (schema or trigger)"
|
|
173
200
|
task "migrate:up:with_triggers" => :environment do
|
|
201
|
+
check_kill_switch!(:db_migrate_up_with_triggers)
|
|
202
|
+
|
|
174
203
|
version = ENV.fetch("VERSION", nil)
|
|
175
204
|
raise "VERSION is required" unless version
|
|
176
205
|
|
|
@@ -201,6 +230,8 @@ namespace :db do
|
|
|
201
230
|
|
|
202
231
|
desc "Runs the 'down' for a given migration VERSION (schema or trigger)"
|
|
203
232
|
task "migrate:down:with_triggers" => :environment do
|
|
233
|
+
check_kill_switch!(:db_migrate_down_with_triggers)
|
|
234
|
+
|
|
204
235
|
version = ENV.fetch("VERSION", nil)
|
|
205
236
|
raise "VERSION is required" unless version
|
|
206
237
|
|
|
@@ -228,6 +259,8 @@ namespace :db do
|
|
|
228
259
|
|
|
229
260
|
desc "Rollbacks the database one migration and re migrate up (options: STEP=x, VERSION=x)"
|
|
230
261
|
task "migrate:redo:with_triggers" => :environment do
|
|
262
|
+
check_kill_switch!(:db_migrate_redo_with_triggers)
|
|
263
|
+
|
|
231
264
|
if ENV["VERSION"]
|
|
232
265
|
Rake::Task["db:migrate:down:with_triggers"].invoke
|
|
233
266
|
Rake::Task["db:migrate:up:with_triggers"].invoke
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pg_sql_triggers
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- samaswin87
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-12-
|
|
11
|
+
date: 2025-12-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: pg
|
|
@@ -30,14 +30,14 @@ dependencies:
|
|
|
30
30
|
requirements:
|
|
31
31
|
- - ">="
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version: '6.
|
|
33
|
+
version: '6.1'
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
38
|
- - ">="
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version: '6.
|
|
40
|
+
version: '6.1'
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
42
|
name: database_cleaner-active_record
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -52,6 +52,20 @@ dependencies:
|
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '2.0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: erb_lint
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - "~>"
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0.9'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - "~>"
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0.9'
|
|
55
69
|
- !ruby/object:Gem::Dependency
|
|
56
70
|
name: factory_bot_rails
|
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -172,6 +186,7 @@ executables: []
|
|
|
172
186
|
extensions: []
|
|
173
187
|
extra_rdoc_files: []
|
|
174
188
|
files:
|
|
189
|
+
- ".erb_lint.yml"
|
|
175
190
|
- ".rspec"
|
|
176
191
|
- ".rubocop.yml"
|
|
177
192
|
- CHANGELOG.md
|
|
@@ -193,10 +208,24 @@ files:
|
|
|
193
208
|
- app/views/pg_sql_triggers/dashboard/index.html.erb
|
|
194
209
|
- app/views/pg_sql_triggers/generator/new.html.erb
|
|
195
210
|
- app/views/pg_sql_triggers/generator/preview.html.erb
|
|
211
|
+
- app/views/pg_sql_triggers/shared/_confirmation_modal.html.erb
|
|
212
|
+
- app/views/pg_sql_triggers/shared/_kill_switch_status.html.erb
|
|
196
213
|
- app/views/pg_sql_triggers/tables/index.html.erb
|
|
197
214
|
- app/views/pg_sql_triggers/tables/show.html.erb
|
|
198
215
|
- config/routes.rb
|
|
199
216
|
- db/migrate/20251222000001_create_pg_sql_triggers_tables.rb
|
|
217
|
+
- docs/README.md
|
|
218
|
+
- docs/api-reference.md
|
|
219
|
+
- docs/configuration.md
|
|
220
|
+
- docs/getting-started.md
|
|
221
|
+
- docs/kill-switch.md
|
|
222
|
+
- docs/screenshots/.gitkeep
|
|
223
|
+
- docs/screenshots/Generate Trigger.png
|
|
224
|
+
- docs/screenshots/Triggers Page.png
|
|
225
|
+
- docs/screenshots/kill error.png
|
|
226
|
+
- docs/screenshots/kill modal for migration down.png
|
|
227
|
+
- docs/usage-guide.md
|
|
228
|
+
- docs/web-ui.md
|
|
200
229
|
- lib/generators/pg_sql_triggers/install_generator.rb
|
|
201
230
|
- lib/generators/pg_sql_triggers/templates/README
|
|
202
231
|
- lib/generators/pg_sql_triggers/templates/create_pg_sql_triggers_tables.rb
|
|
@@ -221,6 +250,7 @@ files:
|
|
|
221
250
|
- lib/pg_sql_triggers/registry/manager.rb
|
|
222
251
|
- lib/pg_sql_triggers/registry/validator.rb
|
|
223
252
|
- lib/pg_sql_triggers/sql.rb
|
|
253
|
+
- lib/pg_sql_triggers/sql/kill_switch.rb
|
|
224
254
|
- lib/pg_sql_triggers/testing.rb
|
|
225
255
|
- lib/pg_sql_triggers/testing/dry_run.rb
|
|
226
256
|
- lib/pg_sql_triggers/testing/function_tester.rb
|
|
@@ -246,7 +276,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
246
276
|
requirements:
|
|
247
277
|
- - ">="
|
|
248
278
|
- !ruby/object:Gem::Version
|
|
249
|
-
version:
|
|
279
|
+
version: 3.0.0
|
|
250
280
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
251
281
|
requirements:
|
|
252
282
|
- - ">="
|