schema-tools 1.0.0
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 +7 -0
- data/LICENSE +201 -0
- data/README.md +305 -0
- data/bin/integrate +136 -0
- data/bin/setup +23 -0
- data/lib/schema_tools/api_aware_mappings_diff.rb +79 -0
- data/lib/schema_tools/catchup.rb +23 -0
- data/lib/schema_tools/client.rb +472 -0
- data/lib/schema_tools/close.rb +28 -0
- data/lib/schema_tools/config.rb +46 -0
- data/lib/schema_tools/delete.rb +28 -0
- data/lib/schema_tools/diff.rb +263 -0
- data/lib/schema_tools/download.rb +114 -0
- data/lib/schema_tools/json_diff.rb +234 -0
- data/lib/schema_tools/migrate/migrate.rb +90 -0
- data/lib/schema_tools/migrate/migrate_breaking_change.rb +373 -0
- data/lib/schema_tools/migrate/migrate_new.rb +33 -0
- data/lib/schema_tools/migrate/migrate_non_breaking_change.rb +74 -0
- data/lib/schema_tools/migrate/migrate_verify.rb +19 -0
- data/lib/schema_tools/migrate/migration_step.rb +36 -0
- data/lib/schema_tools/migrate/rollback.rb +211 -0
- data/lib/schema_tools/new_alias.rb +165 -0
- data/lib/schema_tools/painless_scripts_delete.rb +21 -0
- data/lib/schema_tools/painless_scripts_download.rb +26 -0
- data/lib/schema_tools/painless_scripts_upload.rb +31 -0
- data/lib/schema_tools/rake_tasks.rb +15 -0
- data/lib/schema_tools/schema_files.rb +53 -0
- data/lib/schema_tools/seed.rb +64 -0
- data/lib/schema_tools/settings_diff.rb +64 -0
- data/lib/schema_tools/settings_filter.rb +27 -0
- data/lib/seeder/seeder.rb +539 -0
- data/lib/tasks/schema.rake +150 -0
- data/lib/tasks/test.rake +8 -0
- metadata +190 -0
@@ -0,0 +1,373 @@
|
|
1
|
+
require_relative '../schema_files'
|
2
|
+
require_relative 'migration_step'
|
3
|
+
require_relative 'migrate_verify'
|
4
|
+
require_relative '../diff'
|
5
|
+
require_relative 'rollback'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module SchemaTools
|
9
|
+
# Custom logger that uses the migration's log() method
|
10
|
+
class MigrationLogger
|
11
|
+
attr_writer :migration_log_index
|
12
|
+
|
13
|
+
def initialize(migration_log_index, client)
|
14
|
+
@migration_log_index = migration_log_index
|
15
|
+
@client = client
|
16
|
+
end
|
17
|
+
|
18
|
+
def info(message)
|
19
|
+
log(message)
|
20
|
+
end
|
21
|
+
|
22
|
+
def warn(message)
|
23
|
+
log(message)
|
24
|
+
end
|
25
|
+
|
26
|
+
def error(message)
|
27
|
+
log(message)
|
28
|
+
end
|
29
|
+
|
30
|
+
def log(message)
|
31
|
+
puts message
|
32
|
+
log_to_log_index(message)
|
33
|
+
end
|
34
|
+
|
35
|
+
def log_to_log_index(message)
|
36
|
+
return unless @migration_log_index
|
37
|
+
doc = {
|
38
|
+
timestamp: Time.now.iso8601,
|
39
|
+
message: message.is_a?(String) ? message : message.to_json
|
40
|
+
}
|
41
|
+
@client.post("/#{@migration_log_index}/_doc", doc, suppress_logging: true)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class MigrateBreakingChange
|
46
|
+
def self.migrate(alias_name:, client:)
|
47
|
+
new(alias_name: alias_name, client: client).migrate
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize(alias_name:, client:)
|
51
|
+
@alias_name = alias_name
|
52
|
+
@client = client
|
53
|
+
@migration_log_index = nil
|
54
|
+
@current_step = nil
|
55
|
+
@rollback_attempted = false
|
56
|
+
|
57
|
+
@logger = MigrationLogger.new(nil, client)
|
58
|
+
@client.instance_variable_set(:@logger, @logger)
|
59
|
+
end
|
60
|
+
|
61
|
+
def migrate
|
62
|
+
log "=" * 60
|
63
|
+
log "Breaking Change Migration for #{@alias_name}"
|
64
|
+
log "=" * 60
|
65
|
+
|
66
|
+
begin
|
67
|
+
setup
|
68
|
+
migration_steps.each do |step|
|
69
|
+
@current_step = step
|
70
|
+
step.execute(self)
|
71
|
+
end
|
72
|
+
SchemaTools.verify_migration(@alias_name, @client)
|
73
|
+
rescue => e
|
74
|
+
log("Migration failed: #{e.message}")
|
75
|
+
raise e
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def setup
|
80
|
+
unless @client.alias_exists?(@alias_name)
|
81
|
+
raise "Alias '#{@alias_name}' does not exist"
|
82
|
+
end
|
83
|
+
|
84
|
+
indices = @client.get_alias_indices(@alias_name)
|
85
|
+
if indices.length != 1
|
86
|
+
log "ERROR: Alias '#{@alias_name}' must point to exactly one index"
|
87
|
+
log " Currently points to: #{indices.join(', ')}"
|
88
|
+
raise "Alias '#{@alias_name}' must point to exactly one index"
|
89
|
+
end
|
90
|
+
|
91
|
+
@new_timestamp = Time.now.strftime('%Y%m%d%H%M%S')
|
92
|
+
@migration_log_index = "#{@alias_name}-#{@new_timestamp}-migration-log"
|
93
|
+
log "Creating log index: #{@migration_log_index}"
|
94
|
+
@client.create_index(@migration_log_index, {}, {})
|
95
|
+
@logger.migration_log_index = @migration_log_index
|
96
|
+
log "Logging to '#{@migration_log_index}'"
|
97
|
+
|
98
|
+
@current_index = indices.first
|
99
|
+
log "Alias '#{@alias_name}' points to index '#{@current_index}'"
|
100
|
+
|
101
|
+
@new_index = "#{@alias_name}-#{@new_timestamp}"
|
102
|
+
log "new_index: #{@new_index}"
|
103
|
+
|
104
|
+
@catchup1_index = "#{@new_index}-catchup-1"
|
105
|
+
log "catchup1_index: #{@catchup1_index}"
|
106
|
+
|
107
|
+
@catchup2_index = "#{@new_index}-catchup-2"
|
108
|
+
log "catchup2_index: #{@catchup2_index}"
|
109
|
+
|
110
|
+
# Use current index settings and mappings when creating catchup indexes
|
111
|
+
# so that any reindex painless script logic will apply correctly to them.
|
112
|
+
@current_settings = @client.get_index_settings(@current_index)
|
113
|
+
@current_mappings = @client.get_index_mappings(@current_index)
|
114
|
+
raise "Schema files not found for #{@current_index}" unless @current_settings && @current_mappings
|
115
|
+
# Filter read-only settings
|
116
|
+
@current_settings = SettingsFilter.filter_internal_settings(@current_settings)
|
117
|
+
log "Current settings: #{JSON.generate(@current_settings)}"
|
118
|
+
log "Current mappings: #{JSON.generate(@current_mappings)}"
|
119
|
+
|
120
|
+
@new_settings = SchemaFiles.get_settings(@alias_name)
|
121
|
+
@new_mappings = SchemaFiles.get_mappings(@alias_name)
|
122
|
+
raise "Schema files not found for #{@alias_name}" unless @new_settings && @new_mappings
|
123
|
+
log "New settings: #{JSON.generate(@new_settings)}"
|
124
|
+
log "New mappings: #{JSON.generate(@new_mappings)}"
|
125
|
+
|
126
|
+
@reindex_script = SchemaFiles.get_reindex_script(@alias_name)
|
127
|
+
if @reindex_script
|
128
|
+
log "Using reindex painless script defined for #{@alias_name}"
|
129
|
+
log "reindex.painless script: #{@reindex_script}"
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def log(message)
|
134
|
+
@logger.info(message)
|
135
|
+
end
|
136
|
+
|
137
|
+
def migration_steps
|
138
|
+
[
|
139
|
+
MigrationStep.new(
|
140
|
+
name: "STEP 1: Create catchup-1 index",
|
141
|
+
run: ->(logger) { step1_create_catchup1 }
|
142
|
+
),
|
143
|
+
MigrationStep.new(
|
144
|
+
name: "STEP 2: Configure alias for write to catchup-1",
|
145
|
+
run: ->(logger) { step2_configure_alias_write_catchup1_read_both }
|
146
|
+
),
|
147
|
+
MigrationStep.new(
|
148
|
+
name: "STEP 3: Reindex to new index",
|
149
|
+
run: ->(logger) { step3_reindex_to_new_index }
|
150
|
+
),
|
151
|
+
MigrationStep.new(
|
152
|
+
name: "STEP 4: Create catchup-2 index",
|
153
|
+
run: ->(logger) { step4_create_catchup2 }
|
154
|
+
),
|
155
|
+
MigrationStep.new(
|
156
|
+
name: "STEP 5: Configure alias for write to catchup-2",
|
157
|
+
run: ->(logger) { step5_configure_alias_write_catchup2_read_all }
|
158
|
+
),
|
159
|
+
MigrationStep.new(
|
160
|
+
name: "STEP 6: Merge catchup-1 to new index",
|
161
|
+
run: ->(logger) { step6_merge_catchup1_to_new }
|
162
|
+
),
|
163
|
+
MigrationStep.new(
|
164
|
+
name: "STEP 7: Configure alias with no write indexes",
|
165
|
+
run: ->(logger) { step7_configure_alias_no_write }
|
166
|
+
),
|
167
|
+
MigrationStep.new(
|
168
|
+
name: "STEP 8: Merge catchup-2 to new index",
|
169
|
+
run: ->(logger) { step8_merge_catchup2_to_new }
|
170
|
+
),
|
171
|
+
MigrationStep.new(
|
172
|
+
name: "STEP 9: Configure alias to new index only",
|
173
|
+
run: ->(logger) { step9_configure_alias_final }
|
174
|
+
),
|
175
|
+
MigrationStep.new(
|
176
|
+
name: "STEP 10: Close unused indexes",
|
177
|
+
run: ->(logger) { step10_close_unused_indexes }
|
178
|
+
)
|
179
|
+
]
|
180
|
+
end
|
181
|
+
|
182
|
+
def step1_create_catchup1
|
183
|
+
@client.create_index(@catchup1_index, @current_settings, @current_mappings)
|
184
|
+
log "Created catchup-1 index: #{@catchup1_index}"
|
185
|
+
end
|
186
|
+
|
187
|
+
def step2_configure_alias_write_catchup1_read_both
|
188
|
+
actions = [
|
189
|
+
{
|
190
|
+
add: {
|
191
|
+
index: @catchup1_index,
|
192
|
+
alias: @alias_name,
|
193
|
+
is_write_index: true
|
194
|
+
}
|
195
|
+
},
|
196
|
+
{
|
197
|
+
add: {
|
198
|
+
index: @current_index,
|
199
|
+
alias: @alias_name,
|
200
|
+
is_write_index: false
|
201
|
+
}
|
202
|
+
}
|
203
|
+
]
|
204
|
+
update_aliases(actions)
|
205
|
+
log "Configured alias #{@alias_name} to write to #{@catchup1_index} and read from both indexes"
|
206
|
+
end
|
207
|
+
|
208
|
+
def update_aliases(actions)
|
209
|
+
response = @client.update_aliases(actions)
|
210
|
+
if response['errors']
|
211
|
+
log "ERROR: Failed to update aliases"
|
212
|
+
log actions
|
213
|
+
log response
|
214
|
+
raise "Failed to update aliases"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def step3_reindex_to_new_index
|
219
|
+
@client.create_index(@new_index, @new_settings, @new_mappings)
|
220
|
+
begin
|
221
|
+
reindex(@current_index, @new_index, @reindex_script)
|
222
|
+
rescue => e
|
223
|
+
attempt_rollback(e)
|
224
|
+
raise e # Re-raise the error after rollback
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
def reindex(current_index, new_index, reindex_script)
|
229
|
+
response = @client.reindex(current_index, new_index, reindex_script)
|
230
|
+
log response
|
231
|
+
|
232
|
+
if response['took']
|
233
|
+
log "Reindex task complete. Took: #{response['took']}"
|
234
|
+
return true
|
235
|
+
end
|
236
|
+
|
237
|
+
task_id = response['task']
|
238
|
+
if !task_id
|
239
|
+
raise "No task ID from reindex. Reindex incomplete."
|
240
|
+
end
|
241
|
+
|
242
|
+
log "Reindex task started at #{Time.now}. task_id is #{task_id}. Fetch task status with GET #{@client.url}/_tasks/#{task_id}"
|
243
|
+
|
244
|
+
timeout = 604800 # 1 week
|
245
|
+
@client.wait_for_task(response['task'], timeout)
|
246
|
+
log "Reindex complete"
|
247
|
+
end
|
248
|
+
|
249
|
+
def step4_create_catchup2
|
250
|
+
@client.create_index(@catchup2_index, @current_settings, @current_mappings)
|
251
|
+
log "Created catchup-2 index: #{@catchup2_index}"
|
252
|
+
end
|
253
|
+
|
254
|
+
def step5_configure_alias_write_catchup2_read_all
|
255
|
+
actions = [
|
256
|
+
# keep reading from current_index and catchup1_index
|
257
|
+
# add a new catchup2_index for writes
|
258
|
+
{
|
259
|
+
add: {
|
260
|
+
index: @catchup2_index,
|
261
|
+
alias: @alias_name,
|
262
|
+
is_write_index: true
|
263
|
+
}
|
264
|
+
},
|
265
|
+
{
|
266
|
+
add: {
|
267
|
+
index: @catchup1_index,
|
268
|
+
alias: @alias_name,
|
269
|
+
is_write_index: false
|
270
|
+
}
|
271
|
+
},
|
272
|
+
{
|
273
|
+
add: {
|
274
|
+
index: @current_index,
|
275
|
+
alias: @alias_name,
|
276
|
+
is_write_index: false
|
277
|
+
}
|
278
|
+
}
|
279
|
+
]
|
280
|
+
update_aliases(actions)
|
281
|
+
log "Configured alias #{@alias_name} to write to #{@catchup2_index} and continue reading from current and catchup1 indexes"
|
282
|
+
end
|
283
|
+
|
284
|
+
def step6_merge_catchup1_to_new
|
285
|
+
reindex(@catchup1_index, @new_index, @reindex_script)
|
286
|
+
log "Catchup-1 merged to new index"
|
287
|
+
end
|
288
|
+
|
289
|
+
def step7_configure_alias_no_write
|
290
|
+
actions = [
|
291
|
+
{
|
292
|
+
add: {
|
293
|
+
index: @catchup2_index,
|
294
|
+
alias: @alias_name,
|
295
|
+
is_write_index: false
|
296
|
+
}
|
297
|
+
},
|
298
|
+
{
|
299
|
+
add: {
|
300
|
+
index: @catchup1_index,
|
301
|
+
alias: @alias_name,
|
302
|
+
is_write_index: false
|
303
|
+
}
|
304
|
+
},
|
305
|
+
{
|
306
|
+
add: {
|
307
|
+
index: @current_index,
|
308
|
+
alias: @alias_name,
|
309
|
+
is_write_index: false
|
310
|
+
}
|
311
|
+
}
|
312
|
+
]
|
313
|
+
update_aliases(actions)
|
314
|
+
log "Configured alias #{@alias_name} with NO write indexes - writes will fail temporarily"
|
315
|
+
end
|
316
|
+
|
317
|
+
def step8_merge_catchup2_to_new
|
318
|
+
reindex_script = SchemaFiles.get_reindex_script(@alias_name)
|
319
|
+
reindex(@catchup2_index, @new_index, reindex_script)
|
320
|
+
end
|
321
|
+
|
322
|
+
def step9_configure_alias_final
|
323
|
+
actions = [
|
324
|
+
{
|
325
|
+
remove: {
|
326
|
+
index: @catchup2_index,
|
327
|
+
alias: @alias_name
|
328
|
+
}
|
329
|
+
},
|
330
|
+
{
|
331
|
+
remove: {
|
332
|
+
index: @catchup1_index,
|
333
|
+
alias: @alias_name
|
334
|
+
}
|
335
|
+
},
|
336
|
+
{
|
337
|
+
remove: {
|
338
|
+
index: @current_index,
|
339
|
+
alias: @alias_name
|
340
|
+
}
|
341
|
+
},
|
342
|
+
{
|
343
|
+
add: {
|
344
|
+
index: @new_index,
|
345
|
+
alias: @alias_name,
|
346
|
+
is_write_index: true
|
347
|
+
}
|
348
|
+
}
|
349
|
+
]
|
350
|
+
update_aliases(actions)
|
351
|
+
log "Configured alias #{@alias_name} to write and read from #{@new_index} only"
|
352
|
+
end
|
353
|
+
|
354
|
+
def step10_close_unused_indexes
|
355
|
+
[@current_index, @catchup1_index, @catchup2_index, @migration_log_index].each do |index|
|
356
|
+
if @client.index_exists?(index)
|
357
|
+
log "Closing index: #{index}"
|
358
|
+
@client.close_index(index)
|
359
|
+
end
|
360
|
+
end
|
361
|
+
@migration_log_index = nil
|
362
|
+
@logger.migration_log_index = nil
|
363
|
+
end
|
364
|
+
|
365
|
+
private
|
366
|
+
|
367
|
+
def attempt_rollback(original_error)
|
368
|
+
rollback = Migrate::Rollback.new(@alias_name, @current_index, @catchup1_index, @new_index, @client, self)
|
369
|
+
rollback.attempt_rollback(original_error)
|
370
|
+
end
|
371
|
+
|
372
|
+
end
|
373
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require_relative '../schema_files'
|
2
|
+
require_relative 'migrate_breaking_change'
|
3
|
+
require_relative '../diff'
|
4
|
+
require_relative '../settings_diff'
|
5
|
+
require_relative '../api_aware_mappings_diff'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module SchemaTools
|
9
|
+
def self.migrate_to_new_alias(alias_name, client)
|
10
|
+
timestamp = Time.now.strftime("%Y%m%d%H%M%S")
|
11
|
+
new_index_name = "#{alias_name}-#{timestamp}"
|
12
|
+
|
13
|
+
settings = SchemaFiles.get_settings(alias_name)
|
14
|
+
mappings = SchemaFiles.get_mappings(alias_name)
|
15
|
+
|
16
|
+
if settings.nil? || mappings.nil?
|
17
|
+
schema_path = File.join(Config.schemas_path, alias_name)
|
18
|
+
puts "ERROR: Could not load schema files for #{alias_name}"
|
19
|
+
puts " Make sure settings.json and mappings.json exist in #{schema_path}"
|
20
|
+
raise "Could not load schema files for #{alias_name}"
|
21
|
+
end
|
22
|
+
|
23
|
+
puts "Creating new index '#{new_index_name}' with provided schema..."
|
24
|
+
client.create_index(new_index_name, settings, mappings)
|
25
|
+
puts "✓ Index '#{new_index_name}' created"
|
26
|
+
|
27
|
+
puts "Creating alias '#{alias_name}' pointing to '#{new_index_name}'..."
|
28
|
+
client.create_alias(alias_name, new_index_name)
|
29
|
+
puts "✓ Alias '#{alias_name}' created and configured"
|
30
|
+
|
31
|
+
puts "Migration completed successfully!"
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require_relative '../schema_files'
|
2
|
+
require_relative 'migrate_verify'
|
3
|
+
require_relative '../diff'
|
4
|
+
require_relative '../settings_diff'
|
5
|
+
require_relative '../api_aware_mappings_diff'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module SchemaTools
|
9
|
+
def self.attempt_non_breaking_migration(alias_name:, index_name:, client:)
|
10
|
+
settings = SchemaFiles.get_settings(alias_name)
|
11
|
+
mappings = SchemaFiles.get_mappings(alias_name)
|
12
|
+
|
13
|
+
if settings.nil? || mappings.nil?
|
14
|
+
schema_path = File.join(Config.schemas_path, alias_name)
|
15
|
+
puts "ERROR: Could not load schema files for #{alias_name}"
|
16
|
+
puts " Make sure settings.json and mappings.json exist in #{schema_path}"
|
17
|
+
raise "Could not load schema files for #{alias_name}"
|
18
|
+
end
|
19
|
+
|
20
|
+
puts "Checking for differences between local schema and live alias..."
|
21
|
+
diff_result = Diff.generate_schema_diff(alias_name, client)
|
22
|
+
|
23
|
+
if diff_result[:status] == :no_changes
|
24
|
+
puts "✓ No differences detected between local schema and live alias"
|
25
|
+
puts "✓ Migration skipped - index is already up to date"
|
26
|
+
return
|
27
|
+
end
|
28
|
+
|
29
|
+
puts "Showing diff between local schema and live alias before migration:"
|
30
|
+
puts "-" * 60
|
31
|
+
Diff.print_schema_diff(diff_result)
|
32
|
+
puts "-" * 60
|
33
|
+
|
34
|
+
puts "Attempting to update index '#{index_name}' in place with new schema as a non-breaking change..."
|
35
|
+
begin
|
36
|
+
remote_settings = client.get_index_settings(index_name)
|
37
|
+
filtered_remote_settings = SettingsFilter.filter_internal_settings(remote_settings)
|
38
|
+
|
39
|
+
settings_diff = SettingsDiff.new(settings, filtered_remote_settings)
|
40
|
+
minimal_settings_changes = settings_diff.generate_minimal_changes
|
41
|
+
|
42
|
+
if minimal_settings_changes.empty?
|
43
|
+
puts "✓ No settings changes needed - settings are already up to date"
|
44
|
+
else
|
45
|
+
puts "Applying minimal settings changes"
|
46
|
+
client.update_index_settings(index_name, minimal_settings_changes)
|
47
|
+
puts "✓ Settings updated successfully"
|
48
|
+
end
|
49
|
+
|
50
|
+
remote_mappings = client.get_index_mappings(index_name)
|
51
|
+
mappings_diff = ApiAwareMappingsDiff.new(mappings, remote_mappings)
|
52
|
+
minimal_mappings_changes = mappings_diff.generate_minimal_changes
|
53
|
+
|
54
|
+
if minimal_mappings_changes.empty?
|
55
|
+
puts "✓ No mappings changes needed - mappings are already up to date"
|
56
|
+
else
|
57
|
+
puts "Applying minimal mappings changes"
|
58
|
+
client.update_index_mappings(index_name, minimal_mappings_changes)
|
59
|
+
puts "✓ Mappings updated successfully"
|
60
|
+
end
|
61
|
+
|
62
|
+
puts "✓ Index '#{index_name}' updated successfully"
|
63
|
+
|
64
|
+
SchemaTools.verify_migration(alias_name, client)
|
65
|
+
rescue => e
|
66
|
+
if e.message.include?("no settings to update")
|
67
|
+
puts "✓ No settings changes needed - index is already up to date"
|
68
|
+
puts "Migration completed successfully!"
|
69
|
+
else
|
70
|
+
raise e
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require_relative '../diff'
|
2
|
+
|
3
|
+
module SchemaTools
|
4
|
+
def self.verify_migration(alias_name, client)
|
5
|
+
puts "Verifying migration by comparing local schema with remote index..."
|
6
|
+
diff_result = Diff.generate_schema_diff(alias_name, client)
|
7
|
+
|
8
|
+
if diff_result[:status] == :no_changes
|
9
|
+
puts "✓ Migration verification successful - no differences detected"
|
10
|
+
puts "Migration completed successfully!"
|
11
|
+
else
|
12
|
+
puts "⚠️ Migration verification failed - differences detected:"
|
13
|
+
puts "-" * 60
|
14
|
+
Diff.print_schema_diff(diff_result)
|
15
|
+
puts "-" * 60
|
16
|
+
raise "Migration verification failed - local schema does not match remote index after migration"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module SchemaTools
|
2
|
+
class MigrationStep
|
3
|
+
attr_reader :name, :before_actions, :run_actions, :after_actions
|
4
|
+
|
5
|
+
def initialize(name:, run:)
|
6
|
+
@name = name
|
7
|
+
@before_actions = []
|
8
|
+
@run_actions = [run]
|
9
|
+
@after_actions = []
|
10
|
+
add_default_logging
|
11
|
+
end
|
12
|
+
|
13
|
+
def add_before(action)
|
14
|
+
@before_actions << action
|
15
|
+
self
|
16
|
+
end
|
17
|
+
|
18
|
+
def add_after(action)
|
19
|
+
@after_actions << action
|
20
|
+
self
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute(logger)
|
24
|
+
@before_actions.each { |action| action.call(logger) }
|
25
|
+
@run_actions.each { |action| action.call(logger) }
|
26
|
+
@after_actions.each { |action| action.call(logger) }
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def add_default_logging
|
32
|
+
@before_actions << ->(logger) { logger.log("\nSTARTING: #{@name}") }
|
33
|
+
@after_actions << ->(logger) { logger.log("COMPLETED: #{@name}") }
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|