travis-backup-for-v3 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +36 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +215 -0
- data/README.md +100 -0
- data/Rakefile +18 -0
- data/bin/bundle +114 -0
- data/bin/console +8 -0
- data/bin/rails +5 -0
- data/bin/rake +7 -0
- data/bin/setup +36 -0
- data/bin/spring +14 -0
- data/bin/travis_backup_for_v3 +7 -0
- data/bin/yarn +17 -0
- data/config/application.rb +22 -0
- data/config/boot.rb +4 -0
- data/config/cable.yml +10 -0
- data/config/credentials.yml.enc +1 -0
- data/config/database.yml +23 -0
- data/config/environment.rb +5 -0
- data/config/environments/development.rb +76 -0
- data/config/environments/production.rb +120 -0
- data/config/environments/test.rb +60 -0
- data/config/initializers/application_controller_renderer.rb +8 -0
- data/config/initializers/assets.rb +14 -0
- data/config/initializers/backtrace_silencers.rb +8 -0
- data/config/initializers/content_security_policy.rb +30 -0
- data/config/initializers/cookies_serializer.rb +5 -0
- data/config/initializers/filter_parameter_logging.rb +6 -0
- data/config/initializers/inflections.rb +16 -0
- data/config/initializers/mime_types.rb +4 -0
- data/config/initializers/permissions_policy.rb +11 -0
- data/config/initializers/wrap_parameters.rb +14 -0
- data/config/locales/en.yml +33 -0
- data/config/puma.rb +43 -0
- data/config/routes.rb +3 -0
- data/config/settings.yml +7 -0
- data/config/spring.rb +6 -0
- data/config.ru +5 -0
- data/db/schema.sql +5417 -0
- data/dump/.keep +0 -0
- data/lib/backup/remove_specified/remove_heavy_data.rb +99 -0
- data/lib/backup/remove_specified/remove_with_all_dependencies.rb +51 -0
- data/lib/backup/remove_specified/shared.rb +20 -0
- data/lib/backup/remove_specified.rb +68 -0
- data/lib/backup/save_file.rb +43 -0
- data/lib/backup/save_id_hash_to_file.rb +33 -0
- data/lib/backup/save_nullified_rels_to_file.rb +29 -0
- data/lib/config.rb +198 -0
- data/lib/db_helper.rb +27 -0
- data/lib/dry_run_reporter.rb +47 -0
- data/lib/id_hash.rb +97 -0
- data/lib/ids_of_all_dependencies.rb +330 -0
- data/lib/model.rb +86 -0
- data/lib/models/abuse.rb +8 -0
- data/lib/models/beta_migration_request.rb +8 -0
- data/lib/models/branch.rb +18 -0
- data/lib/models/broadcast.rb +7 -0
- data/lib/models/build.rb +36 -0
- data/lib/models/build_config.rb +9 -0
- data/lib/models/cancellation.rb +8 -0
- data/lib/models/commit.rb +16 -0
- data/lib/models/cron.rb +7 -0
- data/lib/models/deleted_build.rb +37 -0
- data/lib/models/deleted_build_config.rb +10 -0
- data/lib/models/deleted_commit.rb +17 -0
- data/lib/models/deleted_job.rb +15 -0
- data/lib/models/deleted_job_config.rb +10 -0
- data/lib/models/deleted_pull_request.rb +12 -0
- data/lib/models/deleted_request.rb +26 -0
- data/lib/models/deleted_request_config.rb +10 -0
- data/lib/models/deleted_request_payload.rb +8 -0
- data/lib/models/deleted_request_raw_config.rb +10 -0
- data/lib/models/deleted_request_raw_configuration.rb +9 -0
- data/lib/models/deleted_request_yaml_config.rb +10 -0
- data/lib/models/deleted_ssl_key.rb +8 -0
- data/lib/models/deleted_stage.rb +10 -0
- data/lib/models/deleted_tag.rb +15 -0
- data/lib/models/email.rb +7 -0
- data/lib/models/email_unsubscribe.rb +8 -0
- data/lib/models/installation.rb +7 -0
- data/lib/models/invoice.rb +7 -0
- data/lib/models/job.rb +16 -0
- data/lib/models/job_config.rb +9 -0
- data/lib/models/job_version.rb +7 -0
- data/lib/models/membership.rb +8 -0
- data/lib/models/message.rb +7 -0
- data/lib/models/organization.rb +26 -0
- data/lib/models/owner_group.rb +7 -0
- data/lib/models/permission.rb +8 -0
- data/lib/models/pull_request.rb +11 -0
- data/lib/models/queueable_job.rb +7 -0
- data/lib/models/repo_count.rb +8 -0
- data/lib/models/repository.rb +39 -0
- data/lib/models/request.rb +25 -0
- data/lib/models/request_config.rb +9 -0
- data/lib/models/request_payload.rb +7 -0
- data/lib/models/request_raw_config.rb +9 -0
- data/lib/models/request_raw_configuration.rb +8 -0
- data/lib/models/request_yaml_config.rb +9 -0
- data/lib/models/ssl_key.rb +7 -0
- data/lib/models/stage.rb +9 -0
- data/lib/models/star.rb +8 -0
- data/lib/models/subscription.rb +9 -0
- data/lib/models/tag.rb +14 -0
- data/lib/models/token.rb +7 -0
- data/lib/models/trial.rb +8 -0
- data/lib/models/trial_allowance.rb +8 -0
- data/lib/models/user.rb +34 -0
- data/lib/models/user_beta_feature.rb +7 -0
- data/lib/models/user_utm_param.rb +7 -0
- data/lib/models.rb +58 -0
- data/lib/nullify_dependencies.rb +42 -0
- data/lib/travis-backup.rb +33 -0
- data/log/.keep +0 -0
- data/package.json +11 -0
- data/tmp/.keep +0 -0
- data/travis-backup-for-v3.gemspec +30 -0
- data/vendor/.keep +0 -0
- data/workspace.code-workspace +8 -0
- 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
|