travis-backup 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4165ff1d9cbf20dae8d4e20f8a481b6ae3cc64a93f82fadae2d1159853bcdd31
4
- data.tar.gz: e87df3c1a1dc808308abd0ff46d24fb4479a8cba9407cca38a82948e1e350f8a
3
+ metadata.gz: 85a9fa54e0b9f6637f1371e29d72682ff808bb2a1c7ee5e8d9432634f2961f14
4
+ data.tar.gz: 96f242b4475d5b50f5a46bfb0e2aaac618aad78a7107a89977d4e5ffde103eba
5
5
  SHA512:
6
- metadata.gz: bef72b11c7596d2fa35f618afa684056e4b7a0c9c94217242683c56b62e4e8f8873c23ee6a5ee01ab935da5126cea0fa9894ef06510d2f926471a4004f92e5a3
7
- data.tar.gz: f196c2233ff24dd151d48a7d294a648ea9fee1db24ceb4ee325ccb3efb00675bb809d639403181985041120cc4aadd420a7398aa899228e94562e9acb0b9771b
6
+ metadata.gz: 343866ae70df1f9f7c5fccdbc49f350c4d58ff195c9c527dd13995f3a66710b09ac53e269ab03eacaa36e6c0a17004d58635aec7c5ce0f7fce5d44c1029282de
7
+ data.tar.gz: f6f2a7d430da5bdfb6ece3de27c4c7201061071f6636e4d16f7dc3b70f04b922d1d0e80b87c78bdec33bdf42fa267c7d46ed733dc91eaccb8d094ec1e7005c22
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Backup
4
+ class MoveLogs
5
+ attr_reader :config
6
+
7
+ def initialize(config, db_helper, dry_run_reporter=nil)
8
+ @config = config
9
+ @dry_run_reporter = dry_run_reporter
10
+ @db_helper = db_helper
11
+ end
12
+
13
+ def run
14
+ return run_dry if @config.dry_run
15
+
16
+ @db_helper.connect_db(@config.database_url)
17
+ Log.order(:id).in_batches(of: @config.limit.to_i).map do |logs_batch|
18
+ log_hashes = logs_batch.as_json
19
+ @db_helper.connect_db(@config.destination_db_url)
20
+
21
+ log_hashes.each do |log_hash|
22
+ new_log = Log.new(log_hash)
23
+ new_log.save!
24
+ end
25
+
26
+ @db_helper.connect_db(@config.database_url)
27
+
28
+ logs_batch.each(&:destroy)
29
+ end
30
+ end
31
+
32
+ def run_dry
33
+ ids = Log.order(:id).map(&:id)
34
+ @dry_run_reporter.add_to_report(:logs, *ids)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Backup
4
+ class RemoveOld
5
+ attr_reader :config
6
+
7
+ def initialize(config, dry_run_reporter=nil)
8
+ @config = config
9
+ @dry_run_reporter = dry_run_reporter
10
+ end
11
+
12
+ def dry_run_report
13
+ @dry_run_reporter.report
14
+ end
15
+
16
+ def run(args={})
17
+ user_id = args[:user_id] || @config.user_id
18
+ repo_id = args[:repo_id] || @config.repo_id
19
+ org_id = args[:org_id] || @config.org_id
20
+
21
+ if user_id
22
+ process_repos_for_owner(user_id, 'User')
23
+ elsif org_id
24
+ process_repos_for_owner(org_id, 'Organization')
25
+ elsif repo_id
26
+ process_repo_with_id(repo_id)
27
+ else
28
+ process_all_repos
29
+ end
30
+ end
31
+
32
+ def process_repos_for_owner(owner_id, owner_type)
33
+ Repository.where('owner_id = ? and owner_type = ?', owner_id, owner_type).order(:id).each do |repository|
34
+ process_repo(repository)
35
+ end
36
+ end
37
+
38
+ def process_repo_with_id(repo_id)
39
+ process_repo(Repository.find(repo_id))
40
+ end
41
+
42
+ def process_all_repos
43
+ Repository.order(:id).each do |repository|
44
+ process_repo(repository)
45
+ end
46
+ end
47
+
48
+ def process_repo(repository)
49
+ process_repo_builds(repository)
50
+ process_repo_requests(repository)
51
+ end
52
+
53
+ def process_repo_builds(repository) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
54
+ threshold = @config.threshold.to_i.months.ago.to_datetime
55
+ current_build_id = repository.current_build_id || -1
56
+ repository.builds.where('created_at < ? and id != ?', threshold, current_build_id)
57
+ .in_batches(of: @config.limit.to_i).map do |builds_batch|
58
+ if @config.if_backup
59
+ file_prefix = "repository_#{repository.id}"
60
+ save_and_destroy_builds_batch(builds_batch, file_prefix)
61
+ else
62
+ destroy_builds_batch(builds_batch)
63
+ end
64
+ end.compact.reduce(&:&)
65
+ end
66
+
67
+ def process_repo_requests(repository)
68
+ threshold = @config.threshold.to_i.months.ago.to_datetime
69
+ repository.requests.where('created_at < ?', threshold)
70
+ .in_batches(of: @config.limit.to_i).map do |requests_batch|
71
+ @config.if_backup ? save_and_destroy_requests_batch(requests_batch, repository) : destroy_requests_batch(requests_batch)
72
+ end.compact
73
+ end
74
+
75
+ private
76
+
77
+ def save_and_destroy_builds_batch(builds_batch, file_prefix)
78
+ builds_export = builds_batch.map(&:attributes)
79
+
80
+ dependencies_saved = builds_batch.map do |build|
81
+ save_build_jobs_and_logs(build, file_prefix)
82
+ end.reduce(&:&)
83
+
84
+ if dependencies_saved
85
+ file_name = "#{file_prefix}_builds_#{builds_batch.first.id}-#{builds_batch.last.id}.json"
86
+ pretty_json = JSON.pretty_generate(builds_export)
87
+ save_file(file_name, pretty_json) ? destroy_builds_batch(builds_batch) : false
88
+ else
89
+ false
90
+ end
91
+ end
92
+
93
+ def save_build_jobs_and_logs(build, file_prefix)
94
+ build.jobs.in_batches(of: @config.limit.to_i).map do |jobs_batch|
95
+ file_prefix = "#{file_prefix}_build_#{build.id}"
96
+ save_jobs_batch(jobs_batch, file_prefix)
97
+ end.compact.reduce(&:&)
98
+ end
99
+
100
+ def save_jobs_batch(jobs_batch, file_prefix)
101
+ jobs_export = jobs_batch.map(&:attributes)
102
+
103
+ logs_saved = jobs_batch.map do |job|
104
+ save_job_logs(job, file_prefix)
105
+ end.reduce(&:&)
106
+
107
+ if logs_saved
108
+ file_name = "#{file_prefix}_jobs_#{jobs_batch.first.id}-#{jobs_batch.last.id}.json"
109
+ pretty_json = JSON.pretty_generate(jobs_export)
110
+ save_file(file_name, pretty_json)
111
+ else
112
+ false
113
+ end
114
+ end
115
+
116
+ def save_job_logs(job, file_prefix)
117
+ job.logs.in_batches(of: @config.limit.to_i).map do |logs_batch|
118
+ file_prefix = "#{file_prefix}_job_#{job.id}"
119
+ save_logs_batch(logs_batch, file_prefix)
120
+ end.compact.reduce(&:&)
121
+ end
122
+
123
+ def save_logs_batch(logs_batch, file_prefix)
124
+ logs_export = logs_batch.map(&:attributes)
125
+ file_name = "#{file_prefix}_logs_#{logs_batch.first.id}-#{logs_batch.last.id}.json"
126
+ pretty_json = JSON.pretty_generate(logs_export)
127
+ save_file(file_name, pretty_json)
128
+ end
129
+
130
+ def destroy_builds_batch(builds_batch)
131
+ return destroy_builds_batch_dry(builds_batch) if @config.dry_run
132
+
133
+ builds_batch.each(&:destroy)
134
+ end
135
+
136
+ def destroy_builds_batch_dry(builds_batch)
137
+ @dry_run_reporter.add_to_report(:builds, *builds_batch.map(&:id))
138
+
139
+ jobs_ids = builds_batch.map do |build|
140
+ build.jobs.map(&:id) || []
141
+ end.flatten
142
+
143
+ @dry_run_reporter.add_to_report(:jobs, *jobs_ids)
144
+
145
+ logs_ids = builds_batch.map do |build|
146
+ build.jobs.map do |job|
147
+ job.logs.map(&:id) || []
148
+ end.flatten || []
149
+ end.flatten
150
+
151
+ @dry_run_reporter.add_to_report(:logs, *logs_ids)
152
+ end
153
+
154
+ def save_and_destroy_requests_batch(requests_batch, repository)
155
+ requests_export = export_requests(requests_batch)
156
+ file_name = "repository_#{repository.id}_requests_#{requests_batch.first.id}-#{requests_batch.last.id}.json"
157
+ pretty_json = JSON.pretty_generate(requests_export)
158
+ if save_file(file_name, pretty_json)
159
+ destroy_requests_batch(requests_batch)
160
+ end
161
+ requests_export
162
+ end
163
+
164
+ def destroy_requests_batch(requests_batch)
165
+ return destroy_requests_batch_dry(requests_batch) if @config.dry_run
166
+
167
+ requests_batch.each(&:destroy)
168
+ end
169
+
170
+ def destroy_requests_batch_dry(requests_batch)
171
+ @dry_run_reporter.add_to_report(:requests, *requests_batch.map(&:id))
172
+ end
173
+
174
+ def save_file(file_name, content) # rubocop:disable Metrics/MethodLength
175
+ return true if @config.dry_run
176
+
177
+ saved = false
178
+ begin
179
+ unless File.directory?(@config.files_location)
180
+ FileUtils.mkdir_p(@config.files_location)
181
+ end
182
+
183
+ File.open(file_path(file_name), 'w') do |file|
184
+ file.write(content)
185
+ file.close
186
+ saved = true
187
+ end
188
+ rescue => e
189
+ print "Failed to save #{file_name}, error: #{e.inspect}\n"
190
+ end
191
+ saved
192
+ end
193
+
194
+ def file_path(file_name)
195
+ "#{@config.files_location}/#{file_name}"
196
+ end
197
+
198
+ def export_requests(requests)
199
+ requests.map do |request|
200
+ request.attributes
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Backup
4
+ class RemoveOrphans
5
+ attr_reader :config
6
+
7
+ def initialize(config, dry_run_reporter=nil)
8
+ @config = config
9
+ @dry_run_reporter = dry_run_reporter
10
+ end
11
+
12
+ def dry_run_report
13
+ @dry_run_reporter.report
14
+ end
15
+
16
+ def run
17
+ cases.each do |model_block|
18
+ model_block[:relations].each do |relation|
19
+ process_table(
20
+ main_model: model_block[:main_model],
21
+ related_model: relation[:related_model],
22
+ fk_name: relation[:fk_name],
23
+ method: model_block[:method],
24
+ dry_run_complement: model_block[:dry_run_complement]
25
+ )
26
+ end
27
+ end
28
+ end
29
+
30
+ def cases
31
+ [
32
+ {
33
+ main_model: Repository,
34
+ relations: [
35
+ {related_model: Build, fk_name: 'current_build_id'},
36
+ {related_model: Build, fk_name: 'last_build_id'}
37
+ ]
38
+ }, {
39
+ main_model: Build,
40
+ relations: [
41
+ {related_model: Repository, fk_name: 'repository_id'},
42
+ {related_model: Commit, fk_name: 'commit_id'},
43
+ {related_model: Request, fk_name: 'request_id'},
44
+ {related_model: PullRequest, fk_name: 'pull_request_id'},
45
+ {related_model: Branch, fk_name: 'branch_id'},
46
+ {related_model: Tag, fk_name: 'tag_id'}
47
+ ],
48
+ method: :destroy_all,
49
+ dry_run_complement: -> (ids) { add_builds_dependencies_to_dry_run_report(ids) }
50
+ }, {
51
+ main_model: Job,
52
+ relations: [
53
+ {related_model: Repository, fk_name: 'repository_id'},
54
+ {related_model: Commit, fk_name: 'commit_id'},
55
+ {related_model: Stage, fk_name: 'stage_id'},
56
+ ]
57
+ }, {
58
+ main_model: Branch,
59
+ relations: [
60
+ {related_model: Repository, fk_name: 'repository_id'},
61
+ {related_model: Build, fk_name: 'last_build_id'}
62
+ ]
63
+ }, {
64
+ main_model: Tag,
65
+ relations: [
66
+ {related_model: Repository, fk_name: 'repository_id'},
67
+ {related_model: Build, fk_name: 'last_build_id'}
68
+ ]
69
+ }, {
70
+ main_model: Commit,
71
+ relations: [
72
+ {related_model: Repository, fk_name: 'repository_id'},
73
+ {related_model: Branch, fk_name: 'branch_id'},
74
+ {related_model: Tag, fk_name: 'tag_id'}
75
+ ]
76
+ }, {
77
+ main_model: Cron,
78
+ relations: [
79
+ {related_model: Branch, fk_name: 'branch_id'}
80
+ ]
81
+ }, {
82
+ main_model: PullRequest,
83
+ relations: [
84
+ {related_model: Repository, fk_name: 'repository_id'}
85
+ ]
86
+ }, {
87
+ main_model: SslKey,
88
+ relations: [
89
+ {related_model: Repository, fk_name: 'repository_id'}
90
+ ]
91
+ }, {
92
+ main_model: Request,
93
+ relations: [
94
+ {related_model: Commit, fk_name: 'commit_id'},
95
+ {related_model: PullRequest, fk_name: 'pull_request_id'},
96
+ {related_model: Branch, fk_name: 'branch_id'},
97
+ {related_model: Tag, fk_name: 'tag_id'}
98
+ ]
99
+ }, {
100
+ main_model: Stage,
101
+ relations: [
102
+ {related_model: Build, fk_name: 'build_id'}
103
+ ]
104
+ }
105
+ ]
106
+ end
107
+
108
+ def add_builds_dependencies_to_dry_run_report(ids_for_delete)
109
+ repos_for_delete = Repository.where(current_build_id: ids_for_delete)
110
+ jobs_for_delete = Job.where(source_id: ids_for_delete)
111
+ @dry_run_reporter.add_to_report(:repositories, *repos_for_delete.map(&:id))
112
+ @dry_run_reporter.add_to_report(:jobs, *jobs_for_delete.map(&:id))
113
+ end
114
+
115
+ def process_table(args)
116
+ main_model = args[:main_model]
117
+ related_model = args[:related_model]
118
+ fk_name = args[:fk_name]
119
+ method = args[:method] || :delete_all
120
+ dry_run_complement = args[:dry_run_complement]
121
+
122
+ main_table = main_model.table_name
123
+ related_table = related_model.table_name
124
+
125
+ for_delete = main_model.find_by_sql(%{
126
+ select a.*
127
+ from #{main_table} a
128
+ left join #{related_table} b
129
+ on a.#{fk_name} = b.id
130
+ where
131
+ a.#{fk_name} is not null
132
+ and b.id is null;
133
+ })
134
+
135
+ ids_for_delete = for_delete.map(&:id)
136
+
137
+ if config.dry_run
138
+ key = main_table.to_sym
139
+ @dry_run_reporter.add_to_report(key, *ids_for_delete)
140
+ dry_run_complement.call(ids_for_delete) if dry_run_complement
141
+ else
142
+ main_model.where(id: ids_for_delete).send(method)
143
+ end
144
+ end
145
+ end
146
+ 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,33 @@
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(key, *values)
11
+ report[key] = [] if report[key].nil?
12
+ report[key].concat(values)
13
+ report[key].uniq!
14
+ end
15
+
16
+ def print_report
17
+ if @report.to_a.map(&:second).flatten.empty?
18
+ puts 'Dry run active. No data would be removed in normal run.'
19
+ else
20
+ puts 'Dry run active. The following data would be removed in normal run:'
21
+
22
+ @report.to_a.map(&:first).each do |symbol|
23
+ print_report_line(symbol)
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def print_report_line(symbol)
31
+ puts " - #{symbol}: #{@report[symbol].to_json}" if @report[symbol].any?
32
+ end
33
+ end
data/lib/travis-backup.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require 'active_support/core_ext/array'
4
4
  require 'active_support/time'
5
5
  require 'config'
6
+ require 'db_helper'
7
+ require 'dry_run_reporter'
6
8
  require 'models/repository'
7
9
  require 'models/log'
8
10
  require 'models/branch'
@@ -13,288 +15,36 @@ require 'models/pull_request'
13
15
  require 'models/ssl_key'
14
16
  require 'models/request'
15
17
  require 'models/stage'
18
+ require 'backup/move_logs'
19
+ require 'backup/remove_orphans'
20
+ require 'backup/remove_old'
16
21
 
17
22
  # main travis-backup class
18
23
  class Backup
19
24
  attr_accessor :config
20
- attr_reader :dry_run_report
21
25
 
22
26
  def initialize(config_args={})
23
27
  @config = Config.new(config_args)
28
+ @db_helper = DbHelper.new(@config)
24
29
 
25
30
  if @config.dry_run
26
- @dry_run_report = {builds: [], jobs: [], logs: [], requests: []}
31
+ @dry_run_reporter = DryRunReporter.new
27
32
  end
28
-
29
- connect_db
30
33
  end
31
34
 
32
- def connect_db(url=@config.database_url)
33
- ActiveRecord::Base.establish_connection(url)
35
+ def dry_run_report
36
+ @dry_run_reporter.report
34
37
  end
35
38
 
36
39
  def run(args={})
37
- user_id = args[:user_id] || @config.user_id
38
- repo_id = args[:repo_id] || @config.repo_id
39
- org_id = args[:org_id] || @config.org_id
40
-
41
- if user_id
42
- owner_id = user_id
43
- owner_type = 'User'
44
- elsif org_id
45
- owner_id = org_id
46
- owner_type = 'Organization'
47
- end
48
-
49
40
  if @config.move_logs
50
- move_logs
41
+ Backup::MoveLogs.new(@config, @db_helper, @dry_run_reporter).run
51
42
  elsif @config.remove_orphans
52
- remove_orphans
53
- elsif owner_id
54
- process_repos_for_owner(owner_id, owner_type)
55
- elsif repo_id
56
- process_repo_with_id(repo_id)
43
+ Backup::RemoveOrphans.new(@config, @dry_run_reporter).run
57
44
  else
58
- process_all_repos
59
- end
60
-
61
- print_dry_run_report if @config.dry_run
62
- end
63
-
64
- def process_repos_for_owner(owner_id, owner_type)
65
- Repository.where('owner_id = ? and owner_type = ?', owner_id, owner_type).order(:id).each do |repository|
66
- process_repo(repository)
67
- end
68
- end
69
-
70
- def process_repo_with_id(repo_id)
71
- process_repo(Repository.find(repo_id))
72
- end
73
-
74
- def process_all_repos
75
- Repository.order(:id).each do |repository|
76
- process_repo(repository)
77
- end
78
- end
79
-
80
- def print_dry_run_report
81
- if @dry_run_report.to_a.map(&:second).flatten.empty?
82
- puts 'Dry run active. No data would be removed in normal run.'
83
- else
84
- puts 'Dry run active. The following data would be removed in normal run:'
85
-
86
- @dry_run_report.to_a.map(&:first).each do |symbol|
87
- print_dry_run_report_line(symbol)
88
- end
89
- end
90
- end
91
-
92
- def print_dry_run_report_line(symbol)
93
- puts " - #{symbol}: #{@dry_run_report[symbol].to_json}" if @dry_run_report[symbol].any?
94
- end
95
-
96
- def process_repo(repository)
97
- process_repo_builds(repository)
98
- process_repo_requests(repository)
99
- end
100
-
101
- def process_repo_builds(repository) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
102
- threshold = @config.threshold.to_i.months.ago.to_datetime
103
- current_build_id = repository.current_build_id || -1
104
- repository.builds.where('created_at < ? and id != ?', threshold, current_build_id)
105
- .in_groups_of(@config.limit.to_i, false).map do |builds_batch|
106
- @config.if_backup ? save_and_destroy_builds_batch(builds_batch, repository) : destroy_builds_batch(builds_batch)
107
- end.compact
108
- end
109
-
110
- def process_repo_requests(repository)
111
- threshold = @config.threshold.to_i.months.ago.to_datetime
112
- repository.requests.where('created_at < ?', threshold)
113
- .in_groups_of(@config.limit.to_i, false).map do |requests_batch|
114
- @config.if_backup ? save_and_destroy_requests_batch(requests_batch, repository) : destroy_requests_batch(requests_batch)
115
- end.compact
116
- end
117
-
118
- def move_logs
119
- return move_logs_dry if config.dry_run
120
-
121
- connect_db(@config.database_url)
122
- Log.order(:id).in_groups_of(@config.limit.to_i, false).map do |logs_batch|
123
- log_hashes = logs_batch.as_json
124
- connect_db(@config.destination_db_url)
125
-
126
- log_hashes.each do |log_hash|
127
- new_log = Log.new(log_hash)
128
- new_log.save!
129
- end
130
-
131
- connect_db(@config.database_url)
132
-
133
- logs_batch.each(&:destroy)
45
+ Backup::RemoveOld.new(@config, @dry_run_reporter).run(args)
134
46
  end
135
- end
136
-
137
- def move_logs_dry
138
- dry_run_report[:logs].concat(Log.order(:id).map(&:id))
139
- end
140
-
141
- def remove_orphans
142
- remove_orphans_for_table(Repository, 'repositories', 'builds', 'current_build_id')
143
- remove_orphans_for_table(Repository, 'repositories', 'builds', 'last_build_id')
144
- remove_orphans_for_table(Build, 'builds', 'repositories', 'repository_id')
145
- remove_orphans_for_table(Build, 'builds', 'commits', 'commit_id')
146
- remove_orphans_for_table(Build, 'builds', 'requests', 'request_id')
147
- remove_orphans_for_table(Build, 'builds', 'pull_requests', 'pull_request_id')
148
- remove_orphans_for_table(Build, 'builds', 'branches', 'branch_id')
149
- remove_orphans_for_table(Build, 'builds', 'tags', 'tag_id')
150
- remove_orphans_for_table(Job, 'jobs', 'repositories', 'repository_id')
151
- remove_orphans_for_table(Job, 'jobs', 'commits', 'commit_id')
152
- remove_orphans_for_table(Job, 'jobs', 'stages', 'stage_id')
153
- remove_orphans_for_table(Branch, 'branches', 'repositories', 'repository_id')
154
- remove_orphans_for_table(Branch, 'branches', 'builds', 'last_build_id')
155
- remove_orphans_for_table(Tag, 'tags', 'repositories', 'repository_id')
156
- remove_orphans_for_table(Tag, 'tags', 'builds', 'last_build_id')
157
- remove_orphans_for_table(Commit, 'commits', 'repositories', 'repository_id')
158
- remove_orphans_for_table(Commit, 'commits', 'branches', 'branch_id')
159
- remove_orphans_for_table(Commit, 'commits', 'tags', 'tag_id')
160
- remove_orphans_for_table(Cron, 'crons', 'branches', 'branch_id')
161
- remove_orphans_for_table(PullRequest, 'pull_requests', 'repositories', 'repository_id')
162
- remove_orphans_for_table(SslKey, 'ssl_keys', 'repositories', 'repository_id')
163
- remove_orphans_for_table(Request, 'requests', 'commits', 'commit_id')
164
- remove_orphans_for_table(Request, 'requests', 'pull_requests', 'pull_request_id')
165
- remove_orphans_for_table(Request, 'requests', 'branches', 'branch_id')
166
- remove_orphans_for_table(Request, 'requests', 'tags', 'tag_id')
167
- remove_orphans_for_table(Stage, 'stages', 'builds', 'build_id')
168
- end
169
-
170
- def remove_orphans_for_table(model_class, table_a_name, table_b_name, fk_name)
171
- for_delete = model_class.find_by_sql(%{
172
- select a.*
173
- from #{table_a_name} a
174
- left join #{table_b_name} b
175
- on a.#{fk_name} = b.id
176
- where
177
- a.#{fk_name} is not null
178
- and b.id is null;
179
- })
180
47
 
181
- if config.dry_run
182
- key = table_a_name.to_sym
183
- dry_run_report[key] = [] if dry_run_report[key].nil?
184
- dry_run_report[key].concat(for_delete.map(&:id))
185
- dry_run_report[key].uniq!
186
- else
187
- model_class.where(id: for_delete.map(&:id)).destroy_all
188
- end
189
- end
190
-
191
- private
192
-
193
- def save_and_destroy_builds_batch(builds_batch, repository)
194
- builds_export = export_builds(builds_batch)
195
- file_name = "repository_#{repository.id}_builds_#{builds_batch.first.id}-#{builds_batch.last.id}.json"
196
- pretty_json = JSON.pretty_generate(builds_export)
197
- if save_file(file_name, pretty_json)
198
- destroy_builds_batch(builds_batch)
199
- end
200
- builds_export
201
- end
202
-
203
- def destroy_builds_batch(builds_batch)
204
- return destroy_builds_batch_dry(builds_batch) if @config.dry_run
205
-
206
- builds_batch.each(&:destroy)
207
- end
208
-
209
- def destroy_builds_batch_dry(builds_batch)
210
- @dry_run_report[:builds].concat(builds_batch.map(&:id))
211
-
212
- jobs = builds_batch.map do |build|
213
- build.jobs.map(&:id) || []
214
- end.flatten
215
-
216
- @dry_run_report[:jobs].concat(jobs)
217
-
218
- logs = builds_batch.map do |build|
219
- build.jobs.map do |job|
220
- job.logs.map(&:id) || []
221
- end.flatten || []
222
- end.flatten
223
-
224
- @dry_run_report[:logs].concat(logs)
225
- end
226
-
227
- def save_and_destroy_requests_batch(requests_batch, repository)
228
- requests_export = export_requests(requests_batch)
229
- file_name = "repository_#{repository.id}_requests_#{requests_batch.first.id}-#{requests_batch.last.id}.json"
230
- pretty_json = JSON.pretty_generate(requests_export)
231
- if save_file(file_name, pretty_json)
232
- destroy_requests_batch(requests_batch)
233
- end
234
- requests_export
235
- end
236
-
237
- def destroy_requests_batch(requests_batch)
238
- return destroy_requests_batch_dry(requests_batch) if @config.dry_run
239
-
240
- requests_batch.each(&:destroy)
241
- end
242
-
243
- def destroy_requests_batch_dry(requests_batch)
244
- @dry_run_report[:requests].concat(requests_batch.map(&:id))
245
- end
246
-
247
- def save_file(file_name, content) # rubocop:disable Metrics/MethodLength
248
- return true if @config.dry_run
249
-
250
- saved = false
251
- begin
252
- unless File.directory?(@config.files_location)
253
- FileUtils.mkdir_p(@config.files_location)
254
- end
255
-
256
- File.open(file_path(file_name), 'w') do |file|
257
- file.write(content)
258
- file.close
259
- saved = true
260
- end
261
- rescue => e
262
- print "Failed to save #{file_name}, error: #{e.inspect}\n"
263
- end
264
- saved
265
- end
266
-
267
- def file_path(file_name)
268
- "#{@config.files_location}/#{file_name}"
269
- end
270
-
271
- def export_builds(builds)
272
- builds.map do |build|
273
- build_export = build.attributes
274
- build_export[:jobs] = export_jobs(build.jobs)
275
-
276
- build_export
277
- end
278
- end
279
-
280
- def export_jobs(jobs)
281
- jobs.map do |job|
282
- job_export = job.attributes
283
- job_export[:logs] = export_logs(job.logs)
284
-
285
- job_export
286
- end
287
- end
288
-
289
- def export_logs(logs)
290
- logs.map do |log|
291
- log.attributes
292
- end
293
- end
294
-
295
- def export_requests(requests)
296
- requests.map do |request|
297
- request.attributes
298
- end
48
+ @dry_run_reporter.print_report if @config.dry_run
299
49
  end
300
50
  end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'travis-backup'
3
- s.version = '0.1.3'
3
+ s.version = '0.2.0'
4
4
  s.summary = 'Travis CI backup tool'
5
5
  s.authors = ['Karol Selak']
6
6
  s.required_ruby_version = Gem::Requirement.new(">= 2.3.0")
@@ -26,4 +26,5 @@ Gem::Specification.new do |s|
26
26
  s.add_development_dependency 'listen'
27
27
  s.add_development_dependency 'rubocop', '~> 0.75.1'
28
28
  s.add_development_dependency 'rubocop-rspec'
29
+ s.add_development_dependency 'database_cleaner-active_record'
29
30
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: travis-backup
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Karol Selak
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-13 00:00:00.000000000 Z
11
+ date: 2021-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -192,6 +192,20 @@ dependencies:
192
192
  - - ">="
193
193
  - !ruby/object:Gem::Version
194
194
  version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: database_cleaner-active_record
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
195
209
  description:
196
210
  email:
197
211
  executables:
@@ -209,7 +223,6 @@ files:
209
223
  - ".gitignore"
210
224
  - ".travis.yml"
211
225
  - Gemfile
212
- - Gemfile.lock
213
226
  - README.md
214
227
  - Rakefile
215
228
  - bin/bundle
@@ -247,7 +260,12 @@ files:
247
260
  - config/spring.rb
248
261
  - db/schema.sql
249
262
  - dump/.keep
263
+ - lib/backup/move_logs.rb
264
+ - lib/backup/remove_old.rb
265
+ - lib/backup/remove_orphans.rb
250
266
  - lib/config.rb
267
+ - lib/db_helper.rb
268
+ - lib/dry_run_reporter.rb
251
269
  - lib/models/branch.rb
252
270
  - lib/models/build.rb
253
271
  - lib/models/commit.rb
data/Gemfile.lock DELETED
@@ -1,212 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- travis-backup (0.1.3)
5
- activerecord
6
- bootsnap
7
- pg
8
- pry
9
- rails
10
- tzinfo-data
11
-
12
- GEM
13
- remote: https://rubygems.org/
14
- specs:
15
- actioncable (6.1.4.1)
16
- actionpack (= 6.1.4.1)
17
- activesupport (= 6.1.4.1)
18
- nio4r (~> 2.0)
19
- websocket-driver (>= 0.6.1)
20
- actionmailbox (6.1.4.1)
21
- actionpack (= 6.1.4.1)
22
- activejob (= 6.1.4.1)
23
- activerecord (= 6.1.4.1)
24
- activestorage (= 6.1.4.1)
25
- activesupport (= 6.1.4.1)
26
- mail (>= 2.7.1)
27
- actionmailer (6.1.4.1)
28
- actionpack (= 6.1.4.1)
29
- actionview (= 6.1.4.1)
30
- activejob (= 6.1.4.1)
31
- activesupport (= 6.1.4.1)
32
- mail (~> 2.5, >= 2.5.4)
33
- rails-dom-testing (~> 2.0)
34
- actionpack (6.1.4.1)
35
- actionview (= 6.1.4.1)
36
- activesupport (= 6.1.4.1)
37
- rack (~> 2.0, >= 2.0.9)
38
- rack-test (>= 0.6.3)
39
- rails-dom-testing (~> 2.0)
40
- rails-html-sanitizer (~> 1.0, >= 1.2.0)
41
- actiontext (6.1.4.1)
42
- actionpack (= 6.1.4.1)
43
- activerecord (= 6.1.4.1)
44
- activestorage (= 6.1.4.1)
45
- activesupport (= 6.1.4.1)
46
- nokogiri (>= 1.8.5)
47
- actionview (6.1.4.1)
48
- activesupport (= 6.1.4.1)
49
- builder (~> 3.1)
50
- erubi (~> 1.4)
51
- rails-dom-testing (~> 2.0)
52
- rails-html-sanitizer (~> 1.1, >= 1.2.0)
53
- activejob (6.1.4.1)
54
- activesupport (= 6.1.4.1)
55
- globalid (>= 0.3.6)
56
- activemodel (6.1.4.1)
57
- activesupport (= 6.1.4.1)
58
- activerecord (6.1.4.1)
59
- activemodel (= 6.1.4.1)
60
- activesupport (= 6.1.4.1)
61
- activestorage (6.1.4.1)
62
- actionpack (= 6.1.4.1)
63
- activejob (= 6.1.4.1)
64
- activerecord (= 6.1.4.1)
65
- activesupport (= 6.1.4.1)
66
- marcel (~> 1.0.0)
67
- mini_mime (>= 1.1.0)
68
- activesupport (6.1.4.1)
69
- concurrent-ruby (~> 1.0, >= 1.0.2)
70
- i18n (>= 1.6, < 2)
71
- minitest (>= 5.1)
72
- tzinfo (~> 2.0)
73
- zeitwerk (~> 2.3)
74
- ast (2.4.2)
75
- bootsnap (1.7.7)
76
- msgpack (~> 1.0)
77
- brakeman (5.1.1)
78
- builder (3.2.4)
79
- byebug (11.1.3)
80
- coderay (1.1.3)
81
- concurrent-ruby (1.1.9)
82
- crass (1.0.6)
83
- diff-lcs (1.4.4)
84
- erubi (1.10.0)
85
- factory_bot (6.2.0)
86
- activesupport (>= 5.0.0)
87
- ffi (1.15.3)
88
- globalid (0.5.2)
89
- activesupport (>= 5.0)
90
- i18n (1.8.10)
91
- concurrent-ruby (~> 1.0)
92
- jaro_winkler (1.5.4)
93
- listen (3.7.0)
94
- rb-fsevent (~> 0.10, >= 0.10.3)
95
- rb-inotify (~> 0.9, >= 0.9.10)
96
- loofah (2.12.0)
97
- crass (~> 1.0.2)
98
- nokogiri (>= 1.5.9)
99
- mail (2.7.1)
100
- mini_mime (>= 0.1.1)
101
- marcel (1.0.1)
102
- method_source (1.0.0)
103
- mini_mime (1.1.1)
104
- mini_portile2 (2.6.1)
105
- minitest (5.14.4)
106
- msgpack (1.4.2)
107
- nio4r (2.5.8)
108
- nokogiri (1.12.3)
109
- mini_portile2 (~> 2.6.1)
110
- racc (~> 1.4)
111
- parallel (1.20.1)
112
- parser (3.0.2.0)
113
- ast (~> 2.4.1)
114
- pg (1.2.3)
115
- pry (0.14.1)
116
- coderay (~> 1.1)
117
- method_source (~> 1.0)
118
- racc (1.5.2)
119
- rack (2.2.3)
120
- rack-test (1.1.0)
121
- rack (>= 1.0, < 3)
122
- rails (6.1.4.1)
123
- actioncable (= 6.1.4.1)
124
- actionmailbox (= 6.1.4.1)
125
- actionmailer (= 6.1.4.1)
126
- actionpack (= 6.1.4.1)
127
- actiontext (= 6.1.4.1)
128
- actionview (= 6.1.4.1)
129
- activejob (= 6.1.4.1)
130
- activemodel (= 6.1.4.1)
131
- activerecord (= 6.1.4.1)
132
- activestorage (= 6.1.4.1)
133
- activesupport (= 6.1.4.1)
134
- bundler (>= 1.15.0)
135
- railties (= 6.1.4.1)
136
- sprockets-rails (>= 2.0.0)
137
- rails-dom-testing (2.0.3)
138
- activesupport (>= 4.2.0)
139
- nokogiri (>= 1.6)
140
- rails-html-sanitizer (1.4.1)
141
- loofah (~> 2.3)
142
- railties (6.1.4.1)
143
- actionpack (= 6.1.4.1)
144
- activesupport (= 6.1.4.1)
145
- method_source
146
- rake (>= 0.13)
147
- thor (~> 1.0)
148
- rainbow (3.0.0)
149
- rake (13.0.6)
150
- rb-fsevent (0.11.0)
151
- rb-inotify (0.10.1)
152
- ffi (~> 1.0)
153
- rspec-core (3.10.1)
154
- rspec-support (~> 3.10.0)
155
- rspec-expectations (3.10.1)
156
- diff-lcs (>= 1.2.0, < 2.0)
157
- rspec-support (~> 3.10.0)
158
- rspec-mocks (3.10.2)
159
- diff-lcs (>= 1.2.0, < 2.0)
160
- rspec-support (~> 3.10.0)
161
- rspec-rails (5.0.2)
162
- actionpack (>= 5.2)
163
- activesupport (>= 5.2)
164
- railties (>= 5.2)
165
- rspec-core (~> 3.10)
166
- rspec-expectations (~> 3.10)
167
- rspec-mocks (~> 3.10)
168
- rspec-support (~> 3.10)
169
- rspec-support (3.10.2)
170
- rubocop (0.75.1)
171
- jaro_winkler (~> 1.5.1)
172
- parallel (~> 1.10)
173
- parser (>= 2.6)
174
- rainbow (>= 2.2.2, < 4.0)
175
- ruby-progressbar (~> 1.7)
176
- unicode-display_width (>= 1.4.0, < 1.7)
177
- rubocop-rspec (1.41.0)
178
- rubocop (>= 0.68.1)
179
- ruby-progressbar (1.11.0)
180
- sprockets (4.0.2)
181
- concurrent-ruby (~> 1.0)
182
- rack (> 1, < 3)
183
- sprockets-rails (3.2.2)
184
- actionpack (>= 4.0)
185
- activesupport (>= 4.0)
186
- sprockets (>= 3.0.0)
187
- thor (1.1.0)
188
- tzinfo (2.0.4)
189
- concurrent-ruby (~> 1.0)
190
- tzinfo-data (1.2021.1)
191
- tzinfo (>= 1.0.0)
192
- unicode-display_width (1.6.1)
193
- websocket-driver (0.7.5)
194
- websocket-extensions (>= 0.1.0)
195
- websocket-extensions (0.1.5)
196
- zeitwerk (2.4.2)
197
-
198
- PLATFORMS
199
- ruby
200
-
201
- DEPENDENCIES
202
- brakeman
203
- byebug
204
- factory_bot
205
- listen
206
- rspec-rails
207
- rubocop (~> 0.75.1)
208
- rubocop-rspec
209
- travis-backup!
210
-
211
- BUNDLED WITH
212
- 2.1.4