s3_rotate 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +1 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +55 -0
- data/LICENSE +21 -0
- data/README.md +241 -0
- data/examples/example.rb +7 -0
- data/lib/s3_rotate.rb +4 -0
- data/lib/s3_rotate/aws/s3_client.rb +93 -0
- data/lib/s3_rotate/core/backup_manager.rb +62 -0
- data/lib/s3_rotate/core/backup_rotator.rb +239 -0
- data/lib/s3_rotate/core/backup_uploader.rb +60 -0
- data/lib/s3_rotate/utils/file_utils.rb +65 -0
- data/s3_rotate.gemspec +15 -0
- data/spec/s3_rotate/aws/s3_client_spec.rb +128 -0
- data/spec/s3_rotate/core/backup_manager_spec.rb +56 -0
- data/spec/s3_rotate/core/backup_rotator_spec.rb +681 -0
- data/spec/s3_rotate/core/backup_uploader_spec.rb +145 -0
- data/spec/s3_rotate/utils/file_utils_spec.rb +61 -0
- data/spec/s3_rotate/utils/mock/backup-2020-01-02.test +0 -0
- data/spec/s3_rotate/utils/mock/backup-2020-01-03.test +0 -0
- data/spec/s3_rotate/utils/mock/backup-2020-02-01.test +0 -0
- data/spec/s3_rotate/utils/mock/backup-2021-01-01.test +0 -0
- data/spec/spec_helper.rb +15 -0
- metadata +76 -0
@@ -0,0 +1,62 @@
|
|
1
|
+
require 's3_rotate/core/backup_uploader'
|
2
|
+
require 's3_rotate/core/backup_rotator'
|
3
|
+
|
4
|
+
module S3Rotate
|
5
|
+
|
6
|
+
class BackupManager
|
7
|
+
|
8
|
+
# attributes
|
9
|
+
attr_accessor :s3_client
|
10
|
+
attr_accessor :uploader
|
11
|
+
attr_accessor :rotator
|
12
|
+
|
13
|
+
#
|
14
|
+
# Initialize a new BackupManager instance.
|
15
|
+
#
|
16
|
+
# @param key String representing the AWS ACCESS KEY ID.
|
17
|
+
# @param secret String representing the AWS ACCESS KEY SECRET.
|
18
|
+
# @param bucket String representing the name of the bucket ot use.
|
19
|
+
# @param region String representing the region to conect to.
|
20
|
+
#
|
21
|
+
# @return the newly instanciated object.
|
22
|
+
#
|
23
|
+
def initialize(key, secret, bucket, region)
|
24
|
+
@s3_client = S3Client.new(key, secret, bucket, region)
|
25
|
+
@uploader = BackupUploader.new(@s3_client)
|
26
|
+
@rotator = BackupRotator.new(@s3_client)
|
27
|
+
end
|
28
|
+
|
29
|
+
#
|
30
|
+
# Upload local backup files to AWS S3
|
31
|
+
# Only uploads new backups
|
32
|
+
# Only uploads backups as daily backups: use `rotate` to generate the weekly & monthly files
|
33
|
+
#
|
34
|
+
# @param backup_name String containing the name of the backup to upload
|
35
|
+
# @param local_backups_path String containing the path to the directory containing the backups
|
36
|
+
# @param date_regex Regex returning the date contained in the filename of each backup
|
37
|
+
#
|
38
|
+
# @return nothing
|
39
|
+
#
|
40
|
+
def upload(backup_name, local_backups_path, date_regex=/\d{4}-\d{2}-\d{2}/)
|
41
|
+
@uploader.upload(backup_name, local_backups_path, date_regex)
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Rotate files (local, daily, weekly, monthly) and apply maximum limits for each type
|
46
|
+
#
|
47
|
+
# @param backup_name String containing the name of the backup to rotate
|
48
|
+
# @param local_backups_path String containing the path to the directory containing the backups
|
49
|
+
# @param max_local Integer specifying the maximum number of local backups to keep
|
50
|
+
# @param max_daily Integer specifying the maximum number of daily backups to keep
|
51
|
+
# @param max_weekly Integer specifying the maximum number of weekly backups to keep
|
52
|
+
# @param max_monthly Integer specifying the maximum number of monthly backups to keep
|
53
|
+
#
|
54
|
+
# @return nothing
|
55
|
+
#
|
56
|
+
def rotate(backup_name, local_backups_dir, max_local=3, max_daily=7, max_weekly=4, max_monthly=3)
|
57
|
+
@rotator.rotate(backup_name, local_backups_dir, max_local, max_daily, max_weekly, max_monthly)
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
# s3_rotate
|
2
|
+
require 's3_rotate/utils/file_utils'
|
3
|
+
|
4
|
+
module S3Rotate
|
5
|
+
|
6
|
+
#
|
7
|
+
# BackupRotator Class
|
8
|
+
# Handles backup rotation locally and on S3
|
9
|
+
#
|
10
|
+
class BackupRotator
|
11
|
+
|
12
|
+
# attributes
|
13
|
+
attr_accessor :s3_client
|
14
|
+
|
15
|
+
#
|
16
|
+
# Initialize a new BackupRotator instance.
|
17
|
+
#
|
18
|
+
# @param s3_client S3Client instance
|
19
|
+
#
|
20
|
+
# @return the newly instanciated object.
|
21
|
+
#
|
22
|
+
def initialize(s3_client)
|
23
|
+
@s3_client = s3_client
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Rotate files (local, daily, weekly, monthly) and apply maximum limits for each type
|
28
|
+
#
|
29
|
+
# @param backup_name String containing the name of the backup to rotate
|
30
|
+
# @param local_backups_path String containing the path to the directory containing the backups
|
31
|
+
# @param max_local Integer specifying the maximum number of local backups to keep
|
32
|
+
# @param max_daily Integer specifying the maximum number of daily backups to keep
|
33
|
+
# @param max_weekly Integer specifying the maximum number of weekly backups to keep
|
34
|
+
# @param max_monthly Integer specifying the maximum number of monthly backups to keep
|
35
|
+
#
|
36
|
+
# @return nothing
|
37
|
+
#
|
38
|
+
def rotate(backup_name, local_backups_dir, max_local=3, max_daily=7, max_weekly=4, max_monthly=3)
|
39
|
+
rotate_local(local_backups_dir, max_local)
|
40
|
+
rotate_daily(backup_name, max_daily)
|
41
|
+
rotate_weekly(backup_name, max_weekly)
|
42
|
+
rotate_monthly(backup_name, max_monthly)
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Rotate daily files
|
47
|
+
#
|
48
|
+
# @param backup_name String containing the name of the backup being rotated
|
49
|
+
# @param max_daily Integer specifying the maximum number of daily backups to keep
|
50
|
+
# - If there are less than `max_daily` daily files: do nothing
|
51
|
+
# - If there are more than `max_daily` daily files: delete the oldest files to leave `max_daily` files
|
52
|
+
#
|
53
|
+
# The rotation works as follows:
|
54
|
+
# - Less than 7 days datediff between the oldest daily file and the most recent weekly file: do nothing
|
55
|
+
# - More than 7 days datediff between the oldest daily file and the most recent weekly file: promote the oldest daily file to weekly file
|
56
|
+
# - In both cases, apply the `max_daily`
|
57
|
+
#
|
58
|
+
# @return nothing
|
59
|
+
#
|
60
|
+
def rotate_daily(backup_name, max_daily)
|
61
|
+
# get backup files
|
62
|
+
daily_backups = @s3_client.remote_backups(backup_name, "daily").files
|
63
|
+
weekly_backups = @s3_client.remote_backups(backup_name, "weekly").files
|
64
|
+
|
65
|
+
# get most recent weekly file
|
66
|
+
recent_weekly_file = weekly_backups.last
|
67
|
+
|
68
|
+
# look through daily backups to find which oness should be promoted
|
69
|
+
daily_backups.each do |backup|
|
70
|
+
# promote to weekly if applicable
|
71
|
+
if should_promote_daily_to_weekly?(backup.key, recent_weekly_file&.key)
|
72
|
+
recent_weekly_file = promote(backup_name, backup.key, backup.body, "weekly")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# cleanup old files
|
77
|
+
if daily_backups.length > max_daily
|
78
|
+
daily_backups.each_with_index { |backup, i| backup.destroy if i < daily_backups.length - max_daily }
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Rotate weekly files
|
84
|
+
#
|
85
|
+
# @param backup_name String containing the name of the backup being rotated
|
86
|
+
# @param max_weekly Integer specifying the maximum number of weekly backups to keep
|
87
|
+
# - If there are less than `max_weekly` weekly files: do nothing
|
88
|
+
# - If there are more than `max_weekly` weekly files: delete the oldest files to leave `max_weekly` files
|
89
|
+
#
|
90
|
+
# The rotation works as follows:
|
91
|
+
# - Less than 1 month datediff between the oldest weekly file and the most recent monthly file: do nothing
|
92
|
+
# - More than 1 month datediff between the oldest weekly file and the most recent monthly file: promote the oldest daily file to weekly file
|
93
|
+
# - In both cases, apply the `max_weekly`
|
94
|
+
#
|
95
|
+
# @return nothing
|
96
|
+
#
|
97
|
+
def rotate_weekly(backup_name, max_weekly)
|
98
|
+
# get backup files
|
99
|
+
weekly_backups = @s3_client.remote_backups(backup_name, "weekly").files
|
100
|
+
monthly_backups = @s3_client.remote_backups(backup_name, "monthly").files
|
101
|
+
|
102
|
+
# get most recent monthly file
|
103
|
+
recent_monthly_file = monthly_backups.last
|
104
|
+
|
105
|
+
# look through weekly backups to find which oness should be promoted
|
106
|
+
weekly_backups.each do |backup|
|
107
|
+
# promote to monthly if applicable
|
108
|
+
if should_promote_weekly_to_monthly?(backup.key, recent_monthly_file&.key)
|
109
|
+
recent_monthly_file = promote(backup_name, backup.key, backup.body, "monthly")
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# cleanup old files
|
114
|
+
if weekly_backups.length > max_weekly
|
115
|
+
weekly_backups.each_with_index { |backup, i| backup.destroy if i < weekly_backups.length - max_weekly }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
#
|
120
|
+
# Rotate monthly files
|
121
|
+
#
|
122
|
+
# @param backup_name String containing the name of the backup being rotated
|
123
|
+
# @param max_monthly Integer specifying the maximum number of month backups to keep
|
124
|
+
# - If there are less than `max_monthly` monthly files: do nothing
|
125
|
+
# - If there are more than `max_monthly` monthly files: delete the oldest files to leave `max_monthly` files
|
126
|
+
#
|
127
|
+
# @return nothing
|
128
|
+
#
|
129
|
+
def rotate_monthly(backup_name, max_monthly)
|
130
|
+
# get backup files
|
131
|
+
monthly_backups = @s3_client.remote_backups(backup_name, "monthly").files
|
132
|
+
|
133
|
+
# cleanup old files
|
134
|
+
if monthly_backups.length > max_monthly
|
135
|
+
monthly_backups.each_with_index { |backup, i| backup.destroy if i < monthly_backups.length - max_monthly }
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
#
|
140
|
+
# Rotate local files
|
141
|
+
#
|
142
|
+
# @param local_backups_path String containing the path to the directory containing the backups
|
143
|
+
# @param max_local Integer specifying the maximum number of local backups to keep
|
144
|
+
# - If there are less than `max_local` local files: do nothing
|
145
|
+
# - If there are more than `max_local` local files: delete the oldest files to leave `max_local` files
|
146
|
+
#
|
147
|
+
# @return nothing
|
148
|
+
#
|
149
|
+
def rotate_local(local_backups_path, max_local)
|
150
|
+
# get backup files
|
151
|
+
local_backups = FileUtils::files_in_directory(local_backups_path)
|
152
|
+
|
153
|
+
# cleanup old files
|
154
|
+
if local_backups.length > max_local
|
155
|
+
local_backups[0..(local_backups.length - max_local - 1)].each { |backup| File.delete("#{local_backups_path}/#{backup}") }
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
#
|
160
|
+
# Check whether `daily_file` should be promoted into a weekly file
|
161
|
+
# Only promote a daily file if the most recent weekly backup is one week old
|
162
|
+
#
|
163
|
+
# @param daily_file String, filename of the daily backup to be checked for promotion
|
164
|
+
# @param weekly_file String, filename of the most recent weekly backup
|
165
|
+
#
|
166
|
+
# @return Boolean, True or False, whether the file should be promoted
|
167
|
+
#
|
168
|
+
def should_promote_daily_to_weekly?(daily_file, weekly_file)
|
169
|
+
# never promote if no daily file
|
170
|
+
return false if not daily_file
|
171
|
+
|
172
|
+
# always promote if no weekly file
|
173
|
+
return true if not weekly_file
|
174
|
+
|
175
|
+
# retrieve the date of each file
|
176
|
+
begin
|
177
|
+
date_daily_file = FileUtils::date_from_filename(daily_file)
|
178
|
+
date_weekly_file = FileUtils::date_from_filename(weekly_file)
|
179
|
+
rescue
|
180
|
+
print "Wrong date (Date.parse in should_promote_daily_to_weekly)."
|
181
|
+
return false
|
182
|
+
end
|
183
|
+
|
184
|
+
# perform date comparison
|
185
|
+
return (date_daily_file - date_weekly_file).abs >= 7
|
186
|
+
end
|
187
|
+
|
188
|
+
#
|
189
|
+
# Check whether `weekly_file` should be promoted into a monthly file
|
190
|
+
# Only promote a weekly file if the most recent monthly backup is one month old
|
191
|
+
#
|
192
|
+
# @param weekly_file String, filename of the weekly backup to be checked for promotion
|
193
|
+
# @param monthly_file String, filename of the most recent monthly backup
|
194
|
+
#
|
195
|
+
# @return Boolean, True or False, whether the file should be promoted
|
196
|
+
#
|
197
|
+
def should_promote_weekly_to_monthly?(weekly_file, monthly_file)
|
198
|
+
# never promote if no weekly file
|
199
|
+
return false if not weekly_file
|
200
|
+
|
201
|
+
# always promote if no monthly file
|
202
|
+
return true if not monthly_file
|
203
|
+
|
204
|
+
# retrieve the date of each file
|
205
|
+
begin
|
206
|
+
date_weekly_file = FileUtils::date_from_filename(weekly_file)
|
207
|
+
date_monthly_file = FileUtils::date_from_filename(monthly_file)
|
208
|
+
rescue
|
209
|
+
print "Wrong date (Date.parse in should_promote_weekly_to_monthly)."
|
210
|
+
return false
|
211
|
+
end
|
212
|
+
|
213
|
+
# perform date comparison
|
214
|
+
return date_weekly_file.prev_month >= date_monthly_file
|
215
|
+
end
|
216
|
+
|
217
|
+
#
|
218
|
+
# Promote a daily backup into a weekly backup
|
219
|
+
# This operation keeps the original daily file, and creates a new weekly backup
|
220
|
+
#
|
221
|
+
# @param backup_name String containing the name of the backup being updated
|
222
|
+
# @param filename String, filename of the backup you want to promote
|
223
|
+
# @param body String, body of the file you want to promote
|
224
|
+
# @param type String representing the type of backup being uploaded, one of "daily", "weekly" or "monthly"
|
225
|
+
#
|
226
|
+
# @return created S3 Bucket File
|
227
|
+
#
|
228
|
+
def promote(backup_name, filename, body, type)
|
229
|
+
# parse the date & extension
|
230
|
+
backup_date = FileUtils::date_from_filename(filename)
|
231
|
+
backup_extension = FileUtils::extension_from_filename(filename)
|
232
|
+
|
233
|
+
# upload
|
234
|
+
@s3_client.upload(backup_name, backup_date, type, backup_extension, body)
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# s3_rotate
|
2
|
+
require 's3_rotate/utils/file_utils'
|
3
|
+
|
4
|
+
module S3Rotate
|
5
|
+
|
6
|
+
#
|
7
|
+
# BackupUploader Class
|
8
|
+
# Handles backup uploads with the right format
|
9
|
+
#
|
10
|
+
class BackupUploader
|
11
|
+
|
12
|
+
# attributes
|
13
|
+
attr_accessor :s3_client
|
14
|
+
|
15
|
+
#
|
16
|
+
# Initialize a new BackupUploader instance.
|
17
|
+
#
|
18
|
+
# @param s3_client S3Client instance
|
19
|
+
#
|
20
|
+
# @return the newly instanciated object.
|
21
|
+
#
|
22
|
+
def initialize(s3_client)
|
23
|
+
@s3_client = s3_client
|
24
|
+
end
|
25
|
+
|
26
|
+
#
|
27
|
+
# Upload local backup files to AWS S3
|
28
|
+
# Only uploads new backups
|
29
|
+
# Only uploads backups as daily backups: use `rotate` to generate the weekly & monthly files
|
30
|
+
#
|
31
|
+
# @param backup_name String containing the name of the backup to upload
|
32
|
+
# @param local_backups_path String containing the path to the directory containing the backups
|
33
|
+
# @param date_regex Regex returning the date contained in the filename of each backup
|
34
|
+
#
|
35
|
+
# @return nothing
|
36
|
+
#
|
37
|
+
def upload(backup_name, local_backups_path, date_regex=/\d{4}-\d{2}-\d{2}/)
|
38
|
+
# get backup files
|
39
|
+
local_backups = FileUtils::files_in_directory(local_backups_path).reverse
|
40
|
+
|
41
|
+
# upload local backups until we find one backup already uploaded
|
42
|
+
local_backups.each do |local_backup|
|
43
|
+
# parse the date & extension
|
44
|
+
backup_date = FileUtils::date_from_filename(local_backup, date_regex)
|
45
|
+
backup_extension = FileUtils::extension_from_filename(local_backup)
|
46
|
+
|
47
|
+
# skip invalid files
|
48
|
+
next if not backup_date
|
49
|
+
|
50
|
+
# stop uploading once we reach a file already uploaded
|
51
|
+
break if @s3_client.exists?(backup_name, backup_date, "daily", extension=backup_extension)
|
52
|
+
|
53
|
+
# upload file
|
54
|
+
@s3_client.upload_local_backup_to_s3(backup_name, backup_date, "daily", backup_extension, File.open(local_backup))
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module S3Rotate
|
4
|
+
|
5
|
+
module FileUtils
|
6
|
+
|
7
|
+
#
|
8
|
+
# Parse the date in a filename
|
9
|
+
# Date can be any format recognize by Date.parse, or be a timestamp
|
10
|
+
#
|
11
|
+
# @param filename String containing the filename to be parsed.
|
12
|
+
# @param date_regex Regex returning the date contained in the filename
|
13
|
+
#
|
14
|
+
# @return Date instance, representing the parsed date
|
15
|
+
#
|
16
|
+
def FileUtils.date_from_filename(filename, date_regex=/\d{4}-\d{2}-\d{2}/)
|
17
|
+
# match the date in the filename
|
18
|
+
match = filename.match(date_regex)
|
19
|
+
date_str = match&.captures&.first || match&.to_s
|
20
|
+
|
21
|
+
# if nothing could be match, immediately fail
|
22
|
+
raise "Invalid date_regex or filename format" if not date_str
|
23
|
+
|
24
|
+
# regular date
|
25
|
+
begin
|
26
|
+
if date_str.include?("-")
|
27
|
+
Date.parse(date_str)
|
28
|
+
# timestamp
|
29
|
+
else
|
30
|
+
DateTime.strptime(date_str, "%s").to_date
|
31
|
+
end
|
32
|
+
rescue
|
33
|
+
raise "Date format not supported"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
#
|
38
|
+
# Parse the extension in a filename
|
39
|
+
#
|
40
|
+
# @param filename String containing the filename to be parsed
|
41
|
+
#
|
42
|
+
# @return String containing the extension of the filename if relevant, None otherwise
|
43
|
+
#
|
44
|
+
def FileUtils.extension_from_filename(filename)
|
45
|
+
if filename.include?('.')
|
46
|
+
'.' + filename.split('/').last.split('.')[1..-1].join('.')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Get the list of files in the specified directory
|
52
|
+
#
|
53
|
+
# @param directory String containing the path to the directory
|
54
|
+
#
|
55
|
+
# @return array of filenames, in ascending date order
|
56
|
+
#
|
57
|
+
def FileUtils.files_in_directory(directory)
|
58
|
+
Dir.entries(directory).select { |f| !File.directory? f }.sort
|
59
|
+
rescue
|
60
|
+
raise "Invalid directory #{directory}"
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
data/s3_rotate.gemspec
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 's3_rotate'
|
5
|
+
s.version = '1.0.0'
|
6
|
+
s.homepage = 'https://github.com/Whova/s3_rotate'
|
7
|
+
s.date = Date.today.to_s
|
8
|
+
s.summary = "AWS S3 upload with rotation mechanism"
|
9
|
+
s.description = s.summary
|
10
|
+
s.authors = ["Simon Ninon"]
|
11
|
+
s.email = 'simon.ninon@gmail.com'
|
12
|
+
s.files = `git ls-files`.split("\n")
|
13
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
s.license = 'MIT'
|
15
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
# standard
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
# 3rd part
|
5
|
+
require 'fog-aws'
|
6
|
+
|
7
|
+
# s3_rotate
|
8
|
+
require File.expand_path("../../../../lib/s3_rotate/aws/s3_client", __FILE__)
|
9
|
+
|
10
|
+
describe S3Rotate::S3Client do
|
11
|
+
|
12
|
+
before :each do
|
13
|
+
@client = S3Rotate::S3Client.new('key', 'secret', 'bucket', 'region')
|
14
|
+
end
|
15
|
+
|
16
|
+
describe '#initialize' do
|
17
|
+
|
18
|
+
it 'sets the access_key' do
|
19
|
+
expect(@client.access_key).to eq 'key'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'sets the access_secret' do
|
23
|
+
expect(@client.access_secret).to eq 'secret'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'sets the bucket_name' do
|
27
|
+
expect(@client.bucket_name).to eq 'bucket'
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'sets the region' do
|
31
|
+
expect(@client.region).to eq 'region'
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#connection' do
|
37
|
+
|
38
|
+
it 'sets the connection when unset' do
|
39
|
+
# mock
|
40
|
+
@client.connection = nil
|
41
|
+
|
42
|
+
# perform test
|
43
|
+
expect(@client.connection).not_to eq nil
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'does not set the connection when set' do
|
47
|
+
# mock
|
48
|
+
@client.connection = "some connection"
|
49
|
+
|
50
|
+
# perform test
|
51
|
+
expect(@client.connection).to eq "some connection"
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#bucket' do
|
57
|
+
|
58
|
+
it 'gets the bucket' do
|
59
|
+
expect(@client.bucket).not_to eq nil
|
60
|
+
expect(@client.bucket.key).to eq 'bucket'
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
describe '#remote_backups' do
|
66
|
+
|
67
|
+
before do
|
68
|
+
@client.connection.directories.get('bucket').files.create(key: '/backup_name/daily/2020-01-01.tgz', body: 'some data')
|
69
|
+
@client.connection.directories.get('bucket').files.create(key: '/backup_name/daily/2020-01-02.tgz', body: 'some data')
|
70
|
+
@client.connection.directories.get('bucket').files.create(key: '/backup_name/daily/2020-02-03.tgz', body: 'some data')
|
71
|
+
@client.connection.directories.get('bucket').files.create(key: '/backup_name/daily/2021-02-04.tgz', body: 'some data')
|
72
|
+
@client.connection.directories.get('bucket').files.create(key: '/backup_name/weekly/2020-01-03.tgz', body: 'some data')
|
73
|
+
@client.connection.directories.get('bucket').files.create(key: '/backup_name/monthly/2020-01-04.tgz', body: 'some data')
|
74
|
+
@client.connection.directories.get('bucket').files.create(key: '/other_backup_name/daily/2020-01-05.tgz', body: 'some data')
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'gets the remote backups' do
|
78
|
+
expect(@client.remote_backups('backup_name', 'daily')).not_to eq nil
|
79
|
+
expect(@client.remote_backups('backup_name', 'daily').files).not_to eq nil
|
80
|
+
expect(@client.remote_backups('backup_name', 'daily').files.length).to eq 4
|
81
|
+
expect(@client.remote_backups('backup_name', 'daily').files[0].key).to eq "/backup_name/daily/2020-01-01.tgz"
|
82
|
+
expect(@client.remote_backups('backup_name', 'daily').files[1].key).to eq "/backup_name/daily/2020-01-02.tgz"
|
83
|
+
expect(@client.remote_backups('backup_name', 'daily').files[2].key).to eq "/backup_name/daily/2020-02-03.tgz"
|
84
|
+
expect(@client.remote_backups('backup_name', 'daily').files[3].key).to eq "/backup_name/daily/2021-02-04.tgz"
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
describe '#exists?' do
|
90
|
+
|
91
|
+
before do
|
92
|
+
@client.connection.directories.get('bucket').files.create(key: '/backup_name/daily/2020-01-01.tgz', body: 'some data')
|
93
|
+
end
|
94
|
+
|
95
|
+
it 'returns true for existing backups' do
|
96
|
+
expect(@client.exists?('backup_name', Date.new(2020, 1, 1), 'daily', '.tgz')).to eq true
|
97
|
+
end
|
98
|
+
|
99
|
+
it 'returns false for wrong extension' do
|
100
|
+
expect(@client.exists?('backup_name', Date.new(2020, 1, 1), 'daily', '.tar.gz')).to eq false
|
101
|
+
end
|
102
|
+
|
103
|
+
it 'returns false for wrong type' do
|
104
|
+
expect(@client.exists?('backup_name', Date.new(2020, 1, 1), 'weekly', '.tgz')).to eq false
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'returns false for wrong date' do
|
108
|
+
expect(@client.exists?('backup_name', Date.new(2020, 1, 2), 'daily', '.tgz')).to eq false
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'returns false for backup name' do
|
112
|
+
expect(@client.exists?('other_backup_name', Date.new(2020, 1, 1), 'daily', '.tgz')).to eq false
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
describe '#upload' do
|
118
|
+
|
119
|
+
it 'uploads files' do
|
120
|
+
@client.upload('backup_name', Date.new(2020, 1, 1), 'daily', '.tgz', 'hello world')
|
121
|
+
expect(@client.connection.directories.get('bucket', prefix: '/backup_name/daily/2020-01-01.tgz').files.length).to eq 1
|
122
|
+
expect(@client.connection.directories.get('bucket', prefix: '/backup_name/daily/2020-01-01.tgz').files.first.key).to eq '/backup_name/daily/2020-01-01.tgz'
|
123
|
+
expect(@client.connection.directories.get('bucket', prefix: '/backup_name/daily/2020-01-01.tgz').files.first.body).to eq 'hello world'
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|