travis-backup 0.0.3 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +16 -6
- data/lib/config.rb +10 -1
- data/lib/models/branch.rb +7 -0
- data/lib/models/commit.rb +7 -0
- data/lib/models/cron.rb +7 -0
- data/lib/models/pull_request.rb +7 -0
- data/lib/models/repository.rb +2 -0
- data/lib/models/request.rb +10 -0
- data/lib/models/ssl_key.rb +7 -0
- data/lib/models/stage.rb +7 -0
- data/lib/models/tag.rb +7 -0
- data/lib/travis-backup.rb +159 -36
- data/travis-backup.gemspec +1 -1
- metadata +10 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: da5ede447a9a2a1b215d03d8582f62e8c4573e69e3337da91afaaffef07323f3
|
4
|
+
data.tar.gz: 01bc3c92f48f093ae3dd4ff23c07a0f2e9c5516b79c05279700d82570b0a2d40
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 775e8b95e35be972da5194d703d33645bfe21198d91f28efe2a417214d74e4fc71fee5a9c6d7a715ae2819b8d12a0b0bad691bca44b507782acd1dbf48c43d16
|
7
|
+
data.tar.gz: 289107dfc9d91b3cc43479c1a4973fe0fec4db91a519dd9cd8be321c9ce56f65e2170464ae3c88492385af5609d0ea52bfbe9d7510f1a44bcffa8f1dfea81b63
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# README
|
2
2
|
|
3
|
-
*travis-backup* is an application that
|
4
|
-
and exports them (optionally) to json files.
|
3
|
+
*travis-backup* is an application that helps with housekeeping and backup for Travis CI database v2.2 and with migration to v3.0 database.
|
5
4
|
|
6
5
|
### Installation and run
|
7
6
|
|
@@ -27,6 +26,9 @@ All arguments:
|
|
27
26
|
-u, --user_id ID # run only for given user
|
28
27
|
-o, --org_id ID # run only for given organization
|
29
28
|
-r, --repo_id ID # run only for given repository
|
29
|
+
--move_logs # run in move logs mode - move all logs to database at destination_db_url URL
|
30
|
+
--destination_db_url URL # URL for moving logs to
|
31
|
+
--remove_orphans # run in remove orphans mode
|
30
32
|
```
|
31
33
|
|
32
34
|
Or inside your app:
|
@@ -54,9 +56,17 @@ backup.run(org_id: 1)
|
|
54
56
|
backup.run(repo_id: 1)
|
55
57
|
```
|
56
58
|
|
57
|
-
|
59
|
+
#### Special modes
|
58
60
|
|
59
|
-
|
61
|
+
Using `--move_logs` flag you can move all logs to database at `destination_db_url` URL (which is required in this case). When you run gem in this mode no files are created and no other tables are being touched.
|
62
|
+
|
63
|
+
Using `--remove_orphans` flag you can remove all orphaned data from tables. When you run gem in this mode no files are created.
|
64
|
+
|
65
|
+
Using `--dry_run` flag you can check which data would be removed by gem, but without removing them actually. Instead of that reports will be printed on standard output. This flag can be also combined with `--move_logs` or `--remove_orphans`.
|
66
|
+
|
67
|
+
### Configuration options
|
68
|
+
|
69
|
+
Despite of command line arguments, one of the ways you can configure your export is a file `config/settings.yml` that you can place in your app's main directory. The gem expects properties in the following format:
|
60
70
|
|
61
71
|
```
|
62
72
|
backup:
|
@@ -88,9 +98,9 @@ and
|
|
88
98
|
bundle exec rspec
|
89
99
|
```
|
90
100
|
|
91
|
-
To make tests working properly you should also ensure
|
101
|
+
To make tests working properly you should also ensure database connection strings for empty test databases. You can set them as `DATABASE_URL` and `BACKUP_DESTINATION_DB_URL` environment variables or in `config/database.yml`.
|
92
102
|
|
93
|
-
**Warning:
|
103
|
+
**Warning: these databases will be cleaned during tests, so ensure that they include no important data.**
|
94
104
|
|
95
105
|
### Ruby version
|
96
106
|
|
data/lib/config.rb
CHANGED
@@ -12,6 +12,7 @@ class Config
|
|
12
12
|
:repo_id,
|
13
13
|
:org_id,
|
14
14
|
:move_logs,
|
15
|
+
:remove_orphans,
|
15
16
|
:destination_db_url
|
16
17
|
|
17
18
|
def initialize(args={}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
@@ -88,6 +89,13 @@ class Config
|
|
88
89
|
config.dig('backup', 'move_logs'),
|
89
90
|
false
|
90
91
|
)
|
92
|
+
@remove_orphans = first_not_nil(
|
93
|
+
args[:remove_orphans],
|
94
|
+
argv_opts[:remove_orphans],
|
95
|
+
ENV['BACKUP_REMOVE_ORPHANS'],
|
96
|
+
config.dig('backup', 'remove_orphans'),
|
97
|
+
false
|
98
|
+
)
|
91
99
|
@destination_db_url = first_not_nil(
|
92
100
|
args[:destination_db_url],
|
93
101
|
argv_opts[:destination_db_url],
|
@@ -97,7 +105,7 @@ class Config
|
|
97
105
|
end
|
98
106
|
|
99
107
|
def check_values
|
100
|
-
if !@move_logs && !@threshold
|
108
|
+
if !@move_logs && !@remove_orphans && !@threshold
|
101
109
|
message = abort_message("Please provide the threshold argument. Data younger than it will be omitted. " +
|
102
110
|
"Threshold defines number of months from now.")
|
103
111
|
abort message
|
@@ -138,6 +146,7 @@ class Config
|
|
138
146
|
opt.on('-r', '--repo_id X') { |o| options[:repo_id] = o.to_i }
|
139
147
|
opt.on('-o', '--org_id X') { |o| options[:org_id] = o.to_i }
|
140
148
|
opt.on('--move_logs') { |o| options[:move_logs] = o }
|
149
|
+
opt.on('--remove_orphans') { |o| options[:remove_orphans] = o }
|
141
150
|
opt.on('--destination_db_url X') { |o| options[:destination_db_url] = o }
|
142
151
|
end.parse!
|
143
152
|
|
data/lib/models/cron.rb
ADDED
data/lib/models/repository.rb
CHANGED
@@ -2,10 +2,12 @@
|
|
2
2
|
|
3
3
|
require 'models/model'
|
4
4
|
require 'models/build'
|
5
|
+
require 'models/request'
|
5
6
|
|
6
7
|
# Repository model
|
7
8
|
class Repository < Model
|
8
9
|
has_many :builds, -> { order('id') }, foreign_key: :repository_id, dependent: :destroy, class_name: 'Build'
|
10
|
+
has_many :requests, -> { order('id') }, foreign_key: :repository_id, dependent: :destroy, class_name: 'Request'
|
9
11
|
|
10
12
|
self.table_name = 'repositories'
|
11
13
|
end
|
data/lib/models/stage.rb
ADDED
data/lib/models/tag.rb
ADDED
data/lib/travis-backup.rb
CHANGED
@@ -5,17 +5,25 @@ require 'active_support/time'
|
|
5
5
|
require 'config'
|
6
6
|
require 'models/repository'
|
7
7
|
require 'models/log'
|
8
|
+
require 'models/branch'
|
9
|
+
require 'models/tag'
|
10
|
+
require 'models/commit'
|
11
|
+
require 'models/cron'
|
12
|
+
require 'models/pull_request'
|
13
|
+
require 'models/ssl_key'
|
14
|
+
require 'models/request'
|
15
|
+
require 'models/stage'
|
8
16
|
|
9
17
|
# main travis-backup class
|
10
18
|
class Backup
|
11
19
|
attr_accessor :config
|
12
|
-
attr_reader :
|
20
|
+
attr_reader :dry_run_report
|
13
21
|
|
14
22
|
def initialize(config_args={})
|
15
23
|
@config = Config.new(config_args)
|
16
24
|
|
17
25
|
if @config.dry_run
|
18
|
-
@
|
26
|
+
@dry_run_report = {builds: [], jobs: [], logs: [], requests: []}
|
19
27
|
end
|
20
28
|
|
21
29
|
connect_db
|
@@ -26,8 +34,6 @@ class Backup
|
|
26
34
|
end
|
27
35
|
|
28
36
|
def run(args={})
|
29
|
-
return move_logs if @config.move_logs
|
30
|
-
|
31
37
|
user_id = args[:user_id] || @config.user_id
|
32
38
|
repo_id = args[:repo_id] || @config.repo_id
|
33
39
|
org_id = args[:org_id] || @config.org_id
|
@@ -38,32 +44,80 @@ class Backup
|
|
38
44
|
elsif org_id
|
39
45
|
owner_id = org_id
|
40
46
|
owner_type = 'Organization'
|
41
|
-
elsif repo_id
|
42
|
-
repo_id = repo_id
|
43
47
|
end
|
44
48
|
|
45
|
-
if
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
+
if @config.move_logs
|
50
|
+
move_logs
|
51
|
+
elsif @config.remove_orphans
|
52
|
+
remove_orphans
|
53
|
+
elsif owner_id
|
54
|
+
process_repos_for_owner(owner_id, owner_type)
|
49
55
|
elsif repo_id
|
50
|
-
|
56
|
+
process_repo_with_id(repo_id)
|
57
|
+
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|
|
51
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.'
|
52
83
|
else
|
53
|
-
|
54
|
-
|
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)
|
55
88
|
end
|
56
89
|
end
|
90
|
+
end
|
57
91
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
64
116
|
end
|
65
117
|
|
66
118
|
def move_logs
|
119
|
+
return move_logs_dry if config.dry_run
|
120
|
+
|
67
121
|
connect_db(@config.database_url)
|
68
122
|
Log.order(:id).in_groups_of(@config.limit.to_i, false).map do |logs_batch|
|
69
123
|
log_hashes = logs_batch.as_json
|
@@ -80,41 +134,86 @@ class Backup
|
|
80
134
|
end
|
81
135
|
end
|
82
136
|
|
83
|
-
def
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
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
|
+
|
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)).delete_all
|
188
|
+
end
|
90
189
|
end
|
91
190
|
|
92
191
|
private
|
93
192
|
|
94
|
-
def
|
193
|
+
def save_and_destroy_builds_batch(builds_batch, repository)
|
95
194
|
builds_export = export_builds(builds_batch)
|
96
195
|
file_name = "repository_#{repository.id}_builds_#{builds_batch.first.id}-#{builds_batch.last.id}.json"
|
97
196
|
pretty_json = JSON.pretty_generate(builds_export)
|
98
197
|
if save_file(file_name, pretty_json)
|
99
|
-
|
198
|
+
destroy_builds_batch(builds_batch)
|
100
199
|
end
|
101
200
|
builds_export
|
102
201
|
end
|
103
202
|
|
104
|
-
def
|
105
|
-
return
|
203
|
+
def destroy_builds_batch(builds_batch)
|
204
|
+
return destroy_builds_batch_dry(builds_batch) if @config.dry_run
|
106
205
|
|
107
206
|
builds_batch.each(&:destroy)
|
108
207
|
end
|
109
208
|
|
110
|
-
def
|
111
|
-
@
|
209
|
+
def destroy_builds_batch_dry(builds_batch)
|
210
|
+
@dry_run_report[:builds].concat(builds_batch.map(&:id))
|
112
211
|
|
113
212
|
jobs = builds_batch.map do |build|
|
114
213
|
build.jobs.map(&:id) || []
|
115
214
|
end.flatten
|
116
215
|
|
117
|
-
@
|
216
|
+
@dry_run_report[:jobs].concat(jobs)
|
118
217
|
|
119
218
|
logs = builds_batch.map do |build|
|
120
219
|
build.jobs.map do |job|
|
@@ -122,7 +221,27 @@ class Backup
|
|
122
221
|
end.flatten || []
|
123
222
|
end.flatten
|
124
223
|
|
125
|
-
@
|
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))
|
126
245
|
end
|
127
246
|
|
128
247
|
def save_file(file_name, content) # rubocop:disable Metrics/MethodLength
|
@@ -169,9 +288,13 @@ class Backup
|
|
169
288
|
|
170
289
|
def export_logs(logs)
|
171
290
|
logs.map do |log|
|
172
|
-
|
291
|
+
log.attributes
|
292
|
+
end
|
293
|
+
end
|
173
294
|
|
174
|
-
|
295
|
+
def export_requests(requests)
|
296
|
+
requests.map do |request|
|
297
|
+
request.attributes
|
175
298
|
end
|
176
299
|
end
|
177
300
|
end
|
data/travis-backup.gemspec
CHANGED
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.0
|
4
|
+
version: 0.1.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-
|
11
|
+
date: 2021-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -248,12 +248,20 @@ files:
|
|
248
248
|
- db/schema.sql
|
249
249
|
- dump/.keep
|
250
250
|
- lib/config.rb
|
251
|
+
- lib/models/branch.rb
|
251
252
|
- lib/models/build.rb
|
253
|
+
- lib/models/commit.rb
|
254
|
+
- lib/models/cron.rb
|
252
255
|
- lib/models/job.rb
|
253
256
|
- lib/models/log.rb
|
254
257
|
- lib/models/model.rb
|
255
258
|
- lib/models/organization.rb
|
259
|
+
- lib/models/pull_request.rb
|
256
260
|
- lib/models/repository.rb
|
261
|
+
- lib/models/request.rb
|
262
|
+
- lib/models/ssl_key.rb
|
263
|
+
- lib/models/stage.rb
|
264
|
+
- lib/models/tag.rb
|
257
265
|
- lib/models/user.rb
|
258
266
|
- lib/travis-backup.rb
|
259
267
|
- log/.keep
|