travis-backup-for-v3 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (122) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +36 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +215 -0
  6. data/README.md +100 -0
  7. data/Rakefile +18 -0
  8. data/bin/bundle +114 -0
  9. data/bin/console +8 -0
  10. data/bin/rails +5 -0
  11. data/bin/rake +7 -0
  12. data/bin/setup +36 -0
  13. data/bin/spring +14 -0
  14. data/bin/travis_backup_for_v3 +7 -0
  15. data/bin/yarn +17 -0
  16. data/config/application.rb +22 -0
  17. data/config/boot.rb +4 -0
  18. data/config/cable.yml +10 -0
  19. data/config/credentials.yml.enc +1 -0
  20. data/config/database.yml +23 -0
  21. data/config/environment.rb +5 -0
  22. data/config/environments/development.rb +76 -0
  23. data/config/environments/production.rb +120 -0
  24. data/config/environments/test.rb +60 -0
  25. data/config/initializers/application_controller_renderer.rb +8 -0
  26. data/config/initializers/assets.rb +14 -0
  27. data/config/initializers/backtrace_silencers.rb +8 -0
  28. data/config/initializers/content_security_policy.rb +30 -0
  29. data/config/initializers/cookies_serializer.rb +5 -0
  30. data/config/initializers/filter_parameter_logging.rb +6 -0
  31. data/config/initializers/inflections.rb +16 -0
  32. data/config/initializers/mime_types.rb +4 -0
  33. data/config/initializers/permissions_policy.rb +11 -0
  34. data/config/initializers/wrap_parameters.rb +14 -0
  35. data/config/locales/en.yml +33 -0
  36. data/config/puma.rb +43 -0
  37. data/config/routes.rb +3 -0
  38. data/config/settings.yml +7 -0
  39. data/config/spring.rb +6 -0
  40. data/config.ru +5 -0
  41. data/db/schema.sql +5417 -0
  42. data/dump/.keep +0 -0
  43. data/lib/backup/remove_specified/remove_heavy_data.rb +99 -0
  44. data/lib/backup/remove_specified/remove_with_all_dependencies.rb +51 -0
  45. data/lib/backup/remove_specified/shared.rb +20 -0
  46. data/lib/backup/remove_specified.rb +68 -0
  47. data/lib/backup/save_file.rb +43 -0
  48. data/lib/backup/save_id_hash_to_file.rb +33 -0
  49. data/lib/backup/save_nullified_rels_to_file.rb +29 -0
  50. data/lib/config.rb +198 -0
  51. data/lib/db_helper.rb +27 -0
  52. data/lib/dry_run_reporter.rb +47 -0
  53. data/lib/id_hash.rb +97 -0
  54. data/lib/ids_of_all_dependencies.rb +330 -0
  55. data/lib/model.rb +86 -0
  56. data/lib/models/abuse.rb +8 -0
  57. data/lib/models/beta_migration_request.rb +8 -0
  58. data/lib/models/branch.rb +18 -0
  59. data/lib/models/broadcast.rb +7 -0
  60. data/lib/models/build.rb +36 -0
  61. data/lib/models/build_config.rb +9 -0
  62. data/lib/models/cancellation.rb +8 -0
  63. data/lib/models/commit.rb +16 -0
  64. data/lib/models/cron.rb +7 -0
  65. data/lib/models/deleted_build.rb +37 -0
  66. data/lib/models/deleted_build_config.rb +10 -0
  67. data/lib/models/deleted_commit.rb +17 -0
  68. data/lib/models/deleted_job.rb +15 -0
  69. data/lib/models/deleted_job_config.rb +10 -0
  70. data/lib/models/deleted_pull_request.rb +12 -0
  71. data/lib/models/deleted_request.rb +26 -0
  72. data/lib/models/deleted_request_config.rb +10 -0
  73. data/lib/models/deleted_request_payload.rb +8 -0
  74. data/lib/models/deleted_request_raw_config.rb +10 -0
  75. data/lib/models/deleted_request_raw_configuration.rb +9 -0
  76. data/lib/models/deleted_request_yaml_config.rb +10 -0
  77. data/lib/models/deleted_ssl_key.rb +8 -0
  78. data/lib/models/deleted_stage.rb +10 -0
  79. data/lib/models/deleted_tag.rb +15 -0
  80. data/lib/models/email.rb +7 -0
  81. data/lib/models/email_unsubscribe.rb +8 -0
  82. data/lib/models/installation.rb +7 -0
  83. data/lib/models/invoice.rb +7 -0
  84. data/lib/models/job.rb +16 -0
  85. data/lib/models/job_config.rb +9 -0
  86. data/lib/models/job_version.rb +7 -0
  87. data/lib/models/membership.rb +8 -0
  88. data/lib/models/message.rb +7 -0
  89. data/lib/models/organization.rb +26 -0
  90. data/lib/models/owner_group.rb +7 -0
  91. data/lib/models/permission.rb +8 -0
  92. data/lib/models/pull_request.rb +11 -0
  93. data/lib/models/queueable_job.rb +7 -0
  94. data/lib/models/repo_count.rb +8 -0
  95. data/lib/models/repository.rb +39 -0
  96. data/lib/models/request.rb +25 -0
  97. data/lib/models/request_config.rb +9 -0
  98. data/lib/models/request_payload.rb +7 -0
  99. data/lib/models/request_raw_config.rb +9 -0
  100. data/lib/models/request_raw_configuration.rb +8 -0
  101. data/lib/models/request_yaml_config.rb +9 -0
  102. data/lib/models/ssl_key.rb +7 -0
  103. data/lib/models/stage.rb +9 -0
  104. data/lib/models/star.rb +8 -0
  105. data/lib/models/subscription.rb +9 -0
  106. data/lib/models/tag.rb +14 -0
  107. data/lib/models/token.rb +7 -0
  108. data/lib/models/trial.rb +8 -0
  109. data/lib/models/trial_allowance.rb +8 -0
  110. data/lib/models/user.rb +34 -0
  111. data/lib/models/user_beta_feature.rb +7 -0
  112. data/lib/models/user_utm_param.rb +7 -0
  113. data/lib/models.rb +58 -0
  114. data/lib/nullify_dependencies.rb +42 -0
  115. data/lib/travis-backup.rb +33 -0
  116. data/log/.keep +0 -0
  117. data/package.json +11 -0
  118. data/tmp/.keep +0 -0
  119. data/travis-backup-for-v3.gemspec +30 -0
  120. data/vendor/.keep +0 -0
  121. data/workspace.code-workspace +8 -0
  122. metadata +366 -0
data/dump/.keep ADDED
File without changes
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'backup/save_file'
4
+ require 'backup/remove_specified/shared'
5
+
6
+ class Backup
7
+ class RemoveSpecified
8
+ module RemoveHeavyData
9
+ include SaveIdHashToFile
10
+ include SaveNullifiedRelsToFile
11
+ include Shared
12
+
13
+ def remove_heavy_data_for_repos_owned_by(owner_id, owner_type)
14
+ Repository.where('owner_id = ? and owner_type = ?', owner_id, owner_type).order(:id).each do |repository|
15
+ remove_heavy_data_for_repo(repository)
16
+ end
17
+ end
18
+
19
+ def remove_heavy_data_for_repo(repository)
20
+ remove_repo_builds(repository)
21
+ remove_repo_requests(repository)
22
+ end
23
+
24
+ def remove_repo_builds(repository) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
25
+ threshold = @config.threshold.to_i.months.ago.to_datetime
26
+ builds_to_remove = repository.builds.where('created_at < ?', threshold)
27
+
28
+ builds_dependencies = builds_to_remove.map do |build|
29
+ result = build.ids_of_all_dependencies(dependencies_to_filter, :without_parents)
30
+ result.add(:build, build.id)
31
+ result
32
+ end.compact
33
+
34
+ ids_to_remove = IdHash.join(*builds_dependencies)
35
+ @subfolder = "repository_#{repository.id}_old_builds_#{current_time_for_subfolder}"
36
+
37
+ unless @config.dry_run
38
+ nullified_rels = builds_to_remove&.map(&:nullify_default_dependencies)&.flatten
39
+ save_nullified_rels_to_file(build: nullified_rels) if @config.if_backup
40
+ end
41
+
42
+ process_ids_to_remove(ids_to_remove)
43
+ end
44
+
45
+ def remove_repo_requests(repository)
46
+ threshold = @config.threshold.to_i.months.ago.to_datetime
47
+ requests_to_remove = repository.requests.where('created_at < ?', threshold)
48
+
49
+ requests_dependencies = requests_to_remove.map do |request|
50
+ hash_with_filtered = request.ids_of_all_dependencies(dependencies_to_filter, :without_parents)
51
+ hash_with_filtered.add(:request, request.id)
52
+ end
53
+
54
+ @subfolder = "repository_#{repository.id}_old_requests_#{current_time_for_subfolder}"
55
+
56
+ unless @config.dry_run
57
+ nullified_rels = requests_to_remove.map do |request|
58
+ nullify_filtered_dependencies(request)
59
+ end.flatten
60
+ save_nullified_rels_to_file(build: nullified_rels) if @config.if_backup
61
+ end
62
+
63
+ ids_to_remove = IdHash.join(*(requests_dependencies))
64
+ process_ids_to_remove(ids_to_remove)
65
+ end
66
+
67
+ private
68
+
69
+ def process_ids_to_remove(ids_to_remove)
70
+ if @config.dry_run
71
+ @dry_run_reporter.add_to_report(ids_to_remove.with_table_symbols)
72
+ else
73
+ save_id_hash_to_file(ids_to_remove) if @config.if_backup
74
+ ids_to_remove.remove_entries_from_db
75
+ end
76
+ end
77
+
78
+ def save_and_destroy_requests_batch(requests_batch, repository)
79
+ requests_export = requests_batch.map(&:attributes)
80
+ file_name = "repository_#{repository.id}_requests_#{requests_batch.first.id}-#{requests_batch.last.id}.json"
81
+ pretty_json = JSON.pretty_generate(requests_export)
82
+ if save_file(file_name, pretty_json)
83
+ destroy_requests_batch(requests_batch)
84
+ end
85
+ requests_export
86
+ end
87
+
88
+ def destroy_requests_batch(requests_batch)
89
+ return destroy_requests_batch_dry(requests_batch) if @config.dry_run
90
+
91
+ requests_batch.each(&:destroy)
92
+ end
93
+
94
+ def destroy_requests_batch_dry(requests_batch)
95
+ @dry_run_reporter.add_to_report(:requests, *requests_batch.map(&:id))
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'backup/save_id_hash_to_file'
4
+ require 'backup/save_nullified_rels_to_file'
5
+ require 'models/user'
6
+ require 'models/repository'
7
+ require 'backup/remove_specified/shared'
8
+
9
+ class Backup
10
+ class RemoveSpecified
11
+ module RemoveWithAllDependencies
12
+ include SaveIdHashToFile
13
+ include SaveNullifiedRelsToFile
14
+ include Shared
15
+
16
+ def remove_user_with_dependencies(user_id)
17
+ remove_entry_with_dependencies(:user, user_id)
18
+ end
19
+
20
+ def remove_org_with_dependencies(org_id)
21
+ remove_entry_with_dependencies(:organization, org_id)
22
+ end
23
+
24
+ def remove_repo_with_dependencies(repo_id)
25
+ remove_entry_with_dependencies(:repository, repo_id)
26
+ end
27
+
28
+ private
29
+
30
+ def remove_entry_with_dependencies(model_name, id)
31
+ @subfolder = "#{model_name}_#{id}_#{current_time_for_subfolder}"
32
+ entry = Model.get_model(model_name).find(id)
33
+ hash_with_filtered = entry.ids_of_all_dependencies_with_filtered(dependencies_to_filter, :without_parents)
34
+ ids_to_remove = hash_with_filtered[:main]
35
+ ids_to_remove.add(model_name, id)
36
+
37
+ return @dry_run_reporter.add_to_report(ids_to_remove) if @config.dry_run
38
+
39
+ nullified_rels = { build: nullify_filtered_dependencies(entry) || [] }
40
+
41
+ if @config.if_backup
42
+ save_nullified_rels_to_file(nullified_rels)
43
+ save_id_hash_to_file(ids_to_remove)
44
+ end
45
+
46
+ ids_to_remove.remove_entries_from_db(as_last: [:build])
47
+ # order important because of foreign key constraint between builds and repos
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,20 @@
1
+ class Backup
2
+ class RemoveSpecified
3
+ module Shared
4
+ private
5
+
6
+ def nullify_filtered_dependencies(entry)
7
+ hash_with_filtered = entry.ids_of_all_dependencies_with_filtered(dependencies_to_filter)
8
+ filtered_builds = hash_with_filtered[:filtered_out]&.[](:build)&.map { |id| Build.find(id) }
9
+ filtered_builds&.map(&:nullify_default_dependencies)&.flatten
10
+ end
11
+
12
+ def dependencies_to_filter
13
+ {
14
+ build: Build.default_dependencies_symbols_to_nullify
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'backup/remove_specified/remove_with_all_dependencies'
4
+ require 'backup/remove_specified/remove_heavy_data'
5
+
6
+ class Backup
7
+ class RemoveSpecified
8
+ include RemoveHeavyData
9
+ include RemoveWithAllDependencies
10
+
11
+ attr_reader :config
12
+
13
+ def initialize(config, dry_run_reporter=nil)
14
+ @config = config
15
+ @dry_run_reporter = dry_run_reporter
16
+ end
17
+
18
+ def dry_run_report
19
+ @dry_run_reporter.report
20
+ end
21
+
22
+ def run(args={})
23
+ user_id = args[:user_id] || @config.user_id
24
+ repo_id = args[:repo_id] || @config.repo_id
25
+ org_id = args[:org_id] || @config.org_id
26
+
27
+ if user_id
28
+ process_user(user_id)
29
+ elsif org_id
30
+ process_organization(org_id)
31
+ elsif repo_id
32
+ process_repo_with_id(repo_id)
33
+ else
34
+ process_all_repos
35
+ end
36
+ end
37
+
38
+ def process_user(user_id)
39
+ if @config.threshold
40
+ remove_heavy_data_for_repos_owned_by(user_id, 'User')
41
+ else
42
+ remove_user_with_dependencies(user_id)
43
+ end
44
+ end
45
+
46
+ def process_organization(org_id)
47
+ if @config.threshold
48
+ remove_heavy_data_for_repos_owned_by(org_id, 'Organization')
49
+ else
50
+ remove_org_with_dependencies(org_id)
51
+ end
52
+ end
53
+
54
+ def process_repo_with_id(repo_id)
55
+ if @config.threshold
56
+ remove_heavy_data_for_repo(Repository.find(repo_id))
57
+ else
58
+ remove_repo_with_dependencies(repo_id)
59
+ end
60
+ end
61
+
62
+ def process_all_repos
63
+ Repository.order(:id).each do |repository|
64
+ remove_heavy_data_for_repo(repository)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SaveFile
4
+ def save_file(file_path, content) # rubocop:disable Metrics/MethodLength
5
+ return true if @config.dry_run
6
+
7
+ saved = false
8
+ begin
9
+ ensure_path(file_path)
10
+
11
+ File.open(full_file_path(file_path), 'w') do |file|
12
+ file.write(content)
13
+ file.close
14
+ saved = true
15
+ end
16
+ rescue => e
17
+ print "Failed to save #{file_path}, error: #{e.inspect}\n"
18
+ end
19
+ saved
20
+ end
21
+
22
+ def current_time_for_subfolder
23
+ Time.now.to_s.parameterize.underscore
24
+ end
25
+
26
+ def ensure_path(file_path)
27
+ path = folder_path(file_path)
28
+
29
+ unless File.directory?(path)
30
+ FileUtils.mkdir_p(path)
31
+ end
32
+ end
33
+
34
+ def full_file_path(file_path)
35
+ "#{@config.files_location}/#{file_path}"
36
+ end
37
+
38
+ def folder_path(file_path)
39
+ result = full_file_path(file_path).split('/')
40
+ result.pop
41
+ result.join('/')
42
+ end
43
+ end
@@ -0,0 +1,33 @@
1
+ require 'backup/save_file'
2
+
3
+ module SaveIdHashToFile
4
+ include SaveFile
5
+
6
+ def save_id_hash_to_file(id_hash)
7
+ id_hash.each do |name, ids|
8
+ ids.sort.each_slice(@config.limit.to_i) do |ids_batch|
9
+ save_ids_batch_to_file(name, ids_batch)
10
+ end
11
+ end
12
+ end
13
+
14
+ def save_ids_batch_to_file(name, ids_batch)
15
+ model = Model.get_model(name)
16
+
17
+ export = {}
18
+ export[:table_name] = model.table_name
19
+ export[:data] = ids_batch.map do |id|
20
+ get_exported_object(model, id)
21
+ end
22
+
23
+ content = JSON.pretty_generate(export)
24
+ file_name = "#{name}_#{ids_batch.first}-#{ids_batch.last}.json"
25
+ file_path = @subfolder.present? ? "#{@subfolder}/#{file_name}" : file_name
26
+ save_file(file_path, content)
27
+ end
28
+
29
+ def get_exported_object(model, id)
30
+ object = model.find(id)
31
+ object.attributes
32
+ end
33
+ end
@@ -0,0 +1,29 @@
1
+ require 'backup/save_file'
2
+
3
+ module SaveNullifiedRelsToFile
4
+ include SaveFile
5
+
6
+ def save_nullified_rels_to_file(rels_hash)
7
+ @file_index = 1
8
+
9
+ rels_hash.each do |name, rels|
10
+ rels&.compact&.each_slice(@config.limit.to_i) do |rels_batch|
11
+ save_rels_batch_to_file(name, rels_batch)
12
+ end
13
+ end
14
+ end
15
+
16
+ def save_rels_batch_to_file(name, rels_batch)
17
+ model = Model.get_model(name)
18
+
19
+ export = {}
20
+ export[:table_name] = model.table_name
21
+ export[:nullified_relationships] = rels_batch
22
+
23
+ content = JSON.pretty_generate(export)
24
+ file_name = "nullified_relationships/build_#{@file_index}.json"
25
+ @file_index += 1
26
+ file_path = @subfolder.present? ? "#{@subfolder}/#{file_name}" : file_name
27
+ save_file(file_path, content)
28
+ end
29
+ end
data/lib/config.rb ADDED
@@ -0,0 +1,198 @@
1
+ # frozen_string_literal: true
2
+ require 'optparse'
3
+
4
+ class Config
5
+ attr_accessor :if_backup,
6
+ :dry_run,
7
+ :limit,
8
+ :threshold,
9
+ :files_location,
10
+ :database_url,
11
+ :user_id,
12
+ :repo_id,
13
+ :org_id,
14
+ :move_logs,
15
+ :remove_orphans,
16
+ :orphans_table,
17
+ :destination_db_url,
18
+ :load_from_files,
19
+ :id_gap
20
+
21
+ def initialize(args={}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
22
+ set_values(args)
23
+ check_values
24
+ end
25
+
26
+ def set_values(args)
27
+ config = yaml_load('config/settings.yml')
28
+ connection_details = yaml_load('config/database.yml')
29
+ argv_opts = argv_options
30
+ @if_backup = first_not_nil(
31
+ args[:if_backup],
32
+ argv_opts[:if_backup],
33
+ ENV['IF_BACKUP'],
34
+ config.dig('backup', 'if_backup'),
35
+ true
36
+ )
37
+ @dry_run = first_not_nil(
38
+ args[:dry_run],
39
+ argv_opts[:dry_run],
40
+ ENV['BACKUP_DRY_RUN'],
41
+ config.dig('backup', 'dry_run'),
42
+ false
43
+ )
44
+ @limit = first_not_nil(
45
+ args[:limit],
46
+ argv_opts[:limit],
47
+ ENV['BACKUP_LIMIT'],
48
+ config.dig('backup', 'limit'),
49
+ 1000
50
+ )
51
+ @threshold = first_not_nil(
52
+ args[:threshold],
53
+ argv_opts[:threshold],
54
+ ENV['BACKUP_THRESHOLD'],
55
+ config.dig('backup', 'threshold')
56
+ )
57
+ @files_location = first_not_nil(
58
+ args[:files_location],
59
+ argv_opts[:files_location],
60
+ ENV['BACKUP_FILES_LOCATION'],
61
+ config.dig('backup', 'files_location'),
62
+ './dump'
63
+ )
64
+ @database_url = first_not_nil(
65
+ args[:database_url],
66
+ argv_opts[:database_url],
67
+ ENV['DATABASE_URL'],
68
+ connection_details.dig(ENV['RAILS_ENV'])
69
+ )
70
+ @user_id = first_not_nil(
71
+ args[:user_id],
72
+ argv_opts[:user_id],
73
+ ENV['BACKUP_USER_ID'],
74
+ config.dig('backup', 'user_id')
75
+ )
76
+ @repo_id = first_not_nil(
77
+ args[:repo_id],
78
+ argv_opts[:repo_id],
79
+ ENV['BACKUP_REPO_ID'],
80
+ config.dig('backup', 'repo_id')
81
+ )
82
+ @org_id = first_not_nil(
83
+ args[:org_id],
84
+ argv_opts[:org_id],
85
+ ENV['BACKUP_ORG_ID'],
86
+ config.dig('backup', 'org_id')
87
+ )
88
+ @move_logs = first_not_nil(
89
+ args[:move_logs],
90
+ argv_opts[:move_logs],
91
+ ENV['BACKUP_MOVE_LOGS'],
92
+ config.dig('backup', 'move_logs'),
93
+ false
94
+ )
95
+ @remove_orphans = first_not_nil(
96
+ args[:remove_orphans],
97
+ argv_opts[:remove_orphans],
98
+ ENV['BACKUP_REMOVE_ORPHANS'],
99
+ config.dig('backup', 'remove_orphans'),
100
+ false
101
+ )
102
+ @orphans_table = first_not_nil(
103
+ args[:orphans_table],
104
+ argv_opts[:orphans_table],
105
+ ENV['BACKUP_ORPHANS_TABLE'],
106
+ config.dig('backup', 'orphans_table'),
107
+ false
108
+ )
109
+ @destination_db_url = first_not_nil(
110
+ args[:destination_db_url],
111
+ argv_opts[:destination_db_url],
112
+ ENV['BACKUP_DESTINATION_DB_URL'],
113
+ connection_details.dig(ENV['RAILS_ENV'], 'destination')
114
+ )
115
+ @load_from_files = first_not_nil(
116
+ args[:load_from_files],
117
+ argv_opts[:load_from_files],
118
+ ENV['BACKUP_LOAD_FROM_FILES'],
119
+ config.dig('backup', 'load_from_files'),
120
+ false
121
+ )
122
+ @id_gap = first_not_nil(
123
+ args[:id_gap],
124
+ argv_opts[:id_gap],
125
+ ENV['BACKUP_ID_GAP'],
126
+ config.dig('backup', 'id_gap'),
127
+ 1000
128
+ )
129
+ end
130
+
131
+ def check_values
132
+ if !@move_logs && !@remove_orphans && !@threshold && !@user_id && !@org_id && !@repo_id && !@load_from_files
133
+ message = abort_message("Please provide the threshold argument. Data younger than it will be omitted. " +
134
+ "Threshold defines number of months from now.")
135
+ abort message
136
+ end
137
+
138
+ if !@database_url
139
+ message = abort_message("Please provide proper database URL.")
140
+ abort message
141
+ end
142
+
143
+ if (@move_logs && !@destination_db_url)
144
+ abort "\nFor moving logs you need to specify your destination database. Example usage:\n" +
145
+ "\n $ bin/travis_backup 'postgres://source_url' --move_logs --destination_db_url 'postgres://destination_url'\n" +
146
+ "\nor using in code:\n" +
147
+ "\n Backup.new(database_url: 'postgres://source_url', destination_db_url: 'postgres://destination_url', move_logs: true)\n" +
148
+ "\nYou can also set it using environment variables or configuration files.\n"
149
+ end
150
+ end
151
+
152
+ def abort_message(intro)
153
+ "\n#{intro}\n\nExample usage:\n"+
154
+ "\n $ bin/travis_backup 'postgres://my_database_url' --threshold 6\n" +
155
+ "\nor using in code:\n" +
156
+ "\n Backup.new(database_url: 'postgres://my_database_url', threshold: 6)\n" +
157
+ "\nYou can also set it using environment variables or configuration files.\n"
158
+ end
159
+
160
+ def argv_options
161
+ argv_copy = ARGV.clone
162
+ options = {}
163
+ OptionParser.new do |opt|
164
+ opt.on('-b', '--backup') { |o| options[:if_backup] = o }
165
+ opt.on('-d', '--dry_run') { |o| options[:dry_run] = o }
166
+ opt.on('-l', '--limit X') { |o| options[:limit] = o.to_i }
167
+ opt.on('-t', '--threshold X') { |o| options[:threshold] = o.to_i }
168
+ opt.on('-f', '--files_location X') { |o| options[:files_location] = o }
169
+ opt.on('-u', '--user_id X') { |o| options[:user_id] = o.to_i }
170
+ opt.on('-r', '--repo_id X') { |o| options[:repo_id] = o.to_i }
171
+ opt.on('-o', '--org_id X') { |o| options[:org_id] = o.to_i }
172
+ opt.on('--move_logs') { |o| options[:move_logs] = o }
173
+ opt.on('--remove_orphans') { |o| options[:remove_orphans] = o }
174
+ opt.on('--orphans_table X') { |o| options[:orphans_table] = o }
175
+ opt.on('--destination_db_url X') { |o| options[:destination_db_url] = o }
176
+ opt.on('--load_from_files') { |o| options[:load_from_files] = o }
177
+ opt.on('--id_gap X') { |o| options[:id_gap] = o.to_i }
178
+ end.parse!
179
+
180
+ options[:database_url] = ARGV.shift if ARGV[0]
181
+ argv_copy.each do |arg|
182
+ ARGV.push(arg)
183
+ end
184
+ options
185
+ end
186
+
187
+ def first_not_nil(*arr)
188
+ arr.compact.first
189
+ end
190
+
191
+ def yaml_load(url)
192
+ begin
193
+ YAML.load(File.open(url))
194
+ rescue => e
195
+ {}
196
+ end
197
+ end
198
+ end
data/lib/db_helper.rb ADDED
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DbHelper
4
+ def initialize(config)
5
+ @config = config
6
+ connect_db
7
+ end
8
+
9
+ def connect_db(config_or_url=@config.database_url)
10
+ ActiveRecord::Base.establish_connection(config_or_url)
11
+ end
12
+
13
+ def do_in_other_db(config_or_url)
14
+ saved_config = ActiveRecord::Base.connection_db_config
15
+ connect_db(config_or_url)
16
+ result = yield
17
+ connect_db(saved_config)
18
+ result
19
+ end
20
+
21
+ def do_without_triggers
22
+ ActiveRecord::Base.connection.execute('set session_replication_role = replica;')
23
+ result = yield
24
+ ActiveRecord::Base.connection.execute('set session_replication_role = default;')
25
+ result
26
+ end
27
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DryRunReporter
4
+ attr_reader :report
5
+
6
+ def initialize
7
+ @report = {}
8
+ end
9
+
10
+ def add_to_report(*args)
11
+ if args.first.is_a?(Hash)
12
+ add_to_report_as_hash(args.first)
13
+ else
14
+ add_to_report_as_key_and_values(*args)
15
+ end
16
+ end
17
+
18
+ def print_report
19
+ if @report.to_a.map(&:second).flatten.empty?
20
+ puts 'Dry run active. No data would be removed in normal run.'
21
+ else
22
+ puts 'Dry run active. The following data would be removed in normal run:'
23
+
24
+ @report.to_a.map(&:first).each do |symbol|
25
+ print_report_line(symbol)
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def add_to_report_as_key_and_values(key, *values)
33
+ report[key] = [] if report[key].nil?
34
+ report[key].concat(values)
35
+ report[key].uniq!
36
+ end
37
+
38
+ def add_to_report_as_hash(hash)
39
+ hash.each do |key, array|
40
+ add_to_report_as_key_and_values(key, *array)
41
+ end
42
+ end
43
+
44
+ def print_report_line(symbol)
45
+ puts " - #{symbol}: #{@report[symbol].to_json}" if @report[symbol].any?
46
+ end
47
+ end