backup_mongo_s3 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 6818188f3168c9c81bb4872cb8879696dd6bc659
4
+ data.tar.gz: 7e64a7949927fc492e5f37b7f1da076bfbda157b
5
+ SHA512:
6
+ metadata.gz: b37923f6e3c23e4b62322db449e8452989f6488b22598aa7c5497301ef018d8c2bb92659783e77fb93f9468287b37801def3de49723de1ee2949bdc3a6256909
7
+ data.tar.gz: c1af757aae23d51381d98d5071fc440e7fd07845dd324958e407a306b2ee66e180170ff45509fd14a7b18683ff7a536e6eca59b3a7b52e383a0684121bef6406
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ *.gem
2
+ *.rbc
3
+ *.idea
4
+ .bundle
5
+ .config
6
+ .yardoc
7
+ Gemfile.lock
8
+ InstalledFiles
9
+ _yardoc
10
+ coverage
11
+ doc/
12
+ lib/bundler/man
13
+ pkg
14
+ rdoc
15
+ spec/reports
16
+ test/tmp
17
+ test/version_tmp
18
+ tmp
19
+ *.bundle
20
+ *.so
21
+ *.o
22
+ *.a
23
+ mkmf.log
24
+ *.yml
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gem 'aws-sdk'
data/README.md ADDED
@@ -0,0 +1,17 @@
1
+ # BackupMongoS3
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'backup_mongo_s3'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install backup_mongo_s3
@@ -0,0 +1,17 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = 'backup_mongo_s3'
3
+ spec.version = '0.0.2'
4
+ spec.authors = ['Yakupov Dima']
5
+ spec.email = ['yakupov.dima@mail.ru']
6
+ spec.summary = "Some summary"
7
+ spec.description = "Some description"
8
+ spec.homepage = ''
9
+ spec.license = 'MIT'
10
+
11
+ spec.files = `git ls-files -z`.split("\x0")
12
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
13
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
14
+ spec.require_paths = ["lib"]
15
+
16
+ spec.add_development_dependency 'bundler', '~> 1.6'
17
+ end
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/backup_mongo_s3'
4
+
5
+ begin
6
+ BackupMongoS3::Application.new(ARGV).run
7
+ rescue SystemExit
8
+ # do nothing
9
+ rescue Exception => err
10
+ abort "[abort] #{err.message}"
11
+ end
@@ -0,0 +1,284 @@
1
+ module BackupMongoS3
2
+ class Application
3
+
4
+ def initialize(argv)
5
+ @parser = OptionParser.new
6
+
7
+ @params = parse_options(argv)
8
+ @config = parse_config(@params[:config])
9
+
10
+ @db = Db.new(@config[:mongo])
11
+ @storage = Storage.new(@config[:s3])
12
+ end
13
+
14
+ public
15
+
16
+ def run
17
+
18
+ case
19
+ when @params[:backups_list] # BACKUPS_LIST
20
+ show_backups_list(@params[:backups_list])
21
+
22
+ when @params[:backup_all] # BACKUP_ALL
23
+ dbs_str = @config[:backup][:dbs]
24
+
25
+ if dbs_str.nil? || dbs_str.empty?
26
+ raise 'config.yml::backup.dbs is empty'
27
+ end
28
+
29
+ dbs_name = dbs_str.split(',').each { |db_name| db_name.strip! }
30
+
31
+ backup(dbs_name)
32
+
33
+ when @params[:backup] # BACKUP
34
+ backup([@params[:backup]])
35
+
36
+ when @params[:restore] # RESTORE
37
+ if @params[:backup_date].nil?
38
+ raise 'param --date BACKUP_DATE is not specified'
39
+ end
40
+
41
+ restore(@params[:restore], @params[:backup_date])
42
+
43
+ when @params[:cron_update] || @params[:cron_clear] # CRON
44
+ cron_options =
45
+ {
46
+ update: @params[:cron_update],
47
+ clear: @params[:cron_clear],
48
+ config: @params[:config],
49
+ time: @config[:backup][:cron_time],
50
+ }
51
+
52
+ Scheduler.new(cron_options).execute
53
+
54
+ else
55
+ puts "\n#{@parser}\n"
56
+ exit
57
+ end
58
+
59
+ end
60
+
61
+ private
62
+
63
+ def show_backups_list(db_name)
64
+
65
+ backups = @storage.get_backups_list("#{db_name}")
66
+
67
+ if backups.empty?
68
+ puts 'Backups not found'
69
+ else
70
+
71
+ puts sprintf('%-30s %-15s %-20s', 'name', 'size, MB', 'last_modified')
72
+
73
+ backups.each do |backup|
74
+
75
+ backup_name = File.join(File.dirname(backup.key), File.basename(backup.key, '.backup'))
76
+ backup_size = backup.content_length / 1024 / 1024
77
+ backup_last_modified = backup.last_modified
78
+
79
+ puts sprintf('%-30s %-15s %-20s', backup_name, backup_size, backup_last_modified)
80
+ end
81
+
82
+ end
83
+ end
84
+
85
+ def backup(dbs_name)
86
+
87
+ history_days_str = @config[:backup][:history_days]
88
+
89
+ begin
90
+ history_days = history_days_str.to_i
91
+ rescue
92
+ raise 'config.yml::backup.history_days is not integer'
93
+ end
94
+
95
+ dbs_name.each do |db_name|
96
+
97
+ puts "backup db #{db_name.upcase}:"
98
+
99
+ tmp_dir = get_temp_dir
100
+
101
+ begin
102
+
103
+ dump_path = File.join(tmp_dir, db_name)
104
+
105
+ puts "\t dump db..."
106
+ @db.dump(db_name, tmp_dir)
107
+
108
+ if Dir["#{dump_path}/*"].empty?
109
+ puts "\t [skip] db is empty"
110
+
111
+ else
112
+
113
+ puts "\t compress..."
114
+ system("zip -6 -r '#{dump_path}.zip' '#{dump_path}/' -j > /dev/null")
115
+ raise 'Error zip' unless $?.exitstatus.zero?
116
+
117
+ FileUtils.rm_rf(dump_path)
118
+
119
+ zip_file_path = "#{dump_path}.zip"
120
+
121
+ if File.exists?(zip_file_path)
122
+ puts "\t upload backup to s3..."
123
+ @storage.upload(db_name, zip_file_path)
124
+
125
+ puts "\t delete old backups from s3..."
126
+ @storage.delete_old_backups(db_name, history_days)
127
+
128
+ File.delete(zip_file_path)
129
+ end
130
+
131
+ end
132
+
133
+ ensure
134
+ FileUtils.remove_entry_secure(tmp_dir)
135
+ end
136
+
137
+ end
138
+
139
+ puts '[done] backup'
140
+ end
141
+
142
+
143
+ def restore(db_name, date)
144
+
145
+ puts "restore db #{db_name.upcase}:"
146
+
147
+ tmp_dir = get_temp_dir
148
+
149
+ begin
150
+
151
+ dump_path = File.join(tmp_dir, db_name)
152
+
153
+ zip_file_path = "#{dump_path}.zip"
154
+
155
+ puts "\t download file from s3..."
156
+ @storage.download(db_name, date, zip_file_path)
157
+
158
+ if File.exists?(zip_file_path)
159
+ puts "\t uncompress..."
160
+ system("unzip '#{zip_file_path}' -d '#{dump_path}' > /dev/null")
161
+ raise 'Error unzip' unless $?.exitstatus.zero?
162
+
163
+ File.delete(zip_file_path)
164
+
165
+ puts "\t restore db..."
166
+ @db.restore(db_name, dump_path)
167
+ end
168
+
169
+ ensure
170
+ FileUtils.remove_entry_secure(tmp_dir)
171
+ end
172
+
173
+ puts '[done] restore'
174
+ end
175
+
176
+ def create_config(path)
177
+
178
+ path = '.' if path.nil? || path == ''
179
+
180
+ file = File.join(path, 'config.yml')
181
+
182
+ if File.exists?(file)
183
+ raise "create_config: '#{file}' already exists"
184
+ elsif File.exists?(file.downcase)
185
+ raise "create_config: '#{file.downcase}' exists, which could conflict with '#{file}'"
186
+ elsif !File.exists?(File.dirname(file))
187
+ raise "create_config: directory '#{File.dirname(file)}' does not exist"
188
+ else
189
+ file_template = File.join(BackupMongoS3.root_path, 'lib/helpers/config.yml.template')
190
+
191
+ FileUtils.cp file_template, file
192
+ end
193
+
194
+ puts "[done] file #{file} was created"
195
+ end
196
+
197
+ def parse_options(argv)
198
+ params = {}
199
+
200
+ @parser.on('--backup_all', 'Backup databases specified in config.yml and upload to S3 bucket') do
201
+ params[:backup_all] = true
202
+ end
203
+ @parser.on('--backup DB_NAME', String, 'Backup database and upload to S3 bucket') do |db_name|
204
+ params[:backup] = db_name
205
+ end
206
+ @parser.on('-r', '--restore DB_NAME', String, 'Restore database from BACKUP_DATE backup') do |db_name|
207
+ params[:restore] = db_name
208
+ end
209
+ @parser.on('-d', '--date BACKUP_DATE', String, 'Restore date YYYYMMDD') do |backup_date|
210
+ params[:backup_date] = backup_date
211
+ end
212
+ @parser.on('-l', '--list_backups [DB_NAME]', String, 'Show list of available backups') do |db_name|
213
+ params[:backups_list] = db_name || ''
214
+ end
215
+ @parser.on('--write_cron', 'Add/update backup_all job') do
216
+ params[:cron_update] = true
217
+ end
218
+ @parser.on('--clear_cron', 'Clear backup_all job') do
219
+ params[:cron_clear] = true
220
+ end
221
+ @parser.on('-c', '--config PATH', String, 'Path to config *.yml. Default ./config.yml') do |path|
222
+ params[:config] = path || ''
223
+ end
224
+ @parser.on('--create_config [PATH]', String, 'Create template config.yml in current/PATH directory') do |path|
225
+ create_config(path)
226
+ exit
227
+ end
228
+ @parser.on('-h', '--help', 'Show help') do
229
+ puts "\n#{@parser}\n"
230
+ exit
231
+ end
232
+
233
+ begin
234
+ @parser.parse!(argv)
235
+
236
+ rescue OptionParser::ParseError => err
237
+ puts "#{err.message}\n\n#{@parser}"
238
+ exit
239
+ end
240
+
241
+ if [params[:backup_all], params[:backup], params[:restore], params[:backups_list], params[:cron_update], params[:cron_clear]].compact.length > 1
242
+ raise 'Can only backup_all, backup, restore, backups_list, cron_update or cron_clear. Choose one.'
243
+ end
244
+
245
+ if params[:config].nil? || params[:config] == ''
246
+ params[:config] = './config.yml'
247
+ end
248
+
249
+ params[:config] = File.absolute_path(params[:config])
250
+
251
+ params
252
+ end
253
+
254
+ def parse_config(config_path)
255
+
256
+ begin
257
+ config = YAML.load(File.read(config_path))
258
+ rescue Errno::ENOENT
259
+ raise "Could not find config file '#{config_path}'"
260
+ rescue ArgumentError => err
261
+ raise "Could not parse config file '#{config_path}' - #{err}"
262
+ end
263
+
264
+ config.deep_symbolize_keys!
265
+
266
+ raise 'config.yml. Section <backup> not found' if config[:backup].nil?
267
+ raise 'config.yml. Section <mongo> not found' if config[:mongo].nil?
268
+ raise 'config.yml. Section <s3> not found' if config[:s3].nil?
269
+
270
+ config
271
+ end
272
+
273
+ def get_temp_dir
274
+ temp_dir = @config[:backup][:temp_directory]
275
+
276
+ if temp_dir.nil? || temp_dir == ''
277
+ temp_dir = Dir.tmpdir
278
+ end
279
+
280
+ Dir.mktmpdir(nil, temp_dir)
281
+ end
282
+
283
+ end
284
+ end
@@ -0,0 +1,45 @@
1
+ module BackupMongoS3
2
+ class Db
3
+
4
+ def initialize(options)
5
+ @connection_options = connection(options)
6
+ end
7
+
8
+ private
9
+ def connection(options)
10
+
11
+ host = (options[:host].nil? || options[:host].empty?) ? 'localhost' : options[:host]
12
+ port = options[:port].nil? ? 27017 : options[:port]
13
+ username = options[:username]
14
+ password = options[:password]
15
+
16
+ auth_options = ''
17
+
18
+ unless username.nil? || username.empty? || password.nil? || password.empty?
19
+ auth_options = "-u '#{username}' -p '#{password}'"
20
+ end
21
+
22
+ "--host '#{host}' --port '#{port}' #{auth_options}"
23
+ end
24
+
25
+ public
26
+ def dump(db_name, backup_path)
27
+ command = "mongodump --dumpDbUsersAndRoles #{@connection_options} --db '#{db_name}' --out '#{backup_path}'"
28
+ command << ' > /dev/null'
29
+
30
+ system(command)
31
+ raise "Error mongodump '#{db_name}'" unless $?.exitstatus.zero?
32
+ end
33
+
34
+ public
35
+ def restore(db_name, backup_path)
36
+ command = "mongorestore --restoreDbUsersAndRoles #{@connection_options} --db '#{db_name}' '#{backup_path}'"
37
+ command << ' > /dev/null'
38
+
39
+ system(command)
40
+ raise "Error mongodump '#{db_name}'" unless $?.exitstatus.zero?
41
+ end
42
+
43
+
44
+ end
45
+ end
@@ -0,0 +1,102 @@
1
+ module BackupMongoS3
2
+ class Scheduler
3
+
4
+ def initialize(options = {})
5
+ @options = options
6
+
7
+ if [@options[:write], @options[:clear]].compact.length > 1
8
+ raise 'cron: Can only write or clear. Choose one.'
9
+ end
10
+
11
+ unless @options[:time] =~ /\A[*\-,0-9]+ [*\-,0-9]+ [*\-,0-9]+ [*\-,0-9]+ [*\-,0-6]+\z/
12
+ raise 'config.yml: cron_time is not valid'
13
+ end
14
+ end
15
+
16
+ def execute
17
+ write_crontab(updated_crontab)
18
+ end
19
+
20
+ private
21
+
22
+ def read_crontab
23
+ return @read_crontab if @read_crontab
24
+
25
+ command = 'crontab -l'
26
+
27
+ command_results = %x[#{command} 2> /dev/null]
28
+
29
+ @read_crontab = $?.exitstatus.zero? ? prepare(command_results) : ''
30
+ end
31
+
32
+ def prepare(contents)
33
+ # Some cron implementations require all non-comment lines to be newline-
34
+ # terminated. (issue #95) Strip all newlines and replace with the default
35
+ # platform record seperator ($/)
36
+ contents.gsub!(/\s+$/, $/)
37
+ end
38
+
39
+ def write_crontab(contents)
40
+ command = 'crontab -'
41
+
42
+ IO.popen(command, 'r+') do |crontab|
43
+ crontab.write(contents)
44
+ crontab.close_write
45
+ end
46
+
47
+ success = $?.exitstatus.zero?
48
+
49
+ if success
50
+ action = @options[:update] ? 'updated' : 'cleared'
51
+ puts "[done] crontab file #{action}"
52
+ exit(0)
53
+ else
54
+ raise "Couldn't write crontab"
55
+ end
56
+ end
57
+
58
+ def updated_crontab
59
+ # Check for unopened or unclosed identifier blocks
60
+ if read_crontab =~ Regexp.new("^#{comment_open}\s*$") && (read_crontab =~ Regexp.new("^#{comment_close}\s*$")).nil?
61
+ raise "Unclosed indentifier; Your crontab file contains '#{comment_open}', but no '#{comment_close}'"
62
+ elsif (read_crontab =~ Regexp.new("^#{comment_open}\s*$")).nil? && read_crontab =~ Regexp.new("^#{comment_close}\s*$")
63
+ raise "Unopened indentifier; Your crontab file contains '#{comment_close}', but no '#{comment_open}'"
64
+ end
65
+
66
+ # If an existing identier block is found, replace it with the new cron entries
67
+ if read_crontab =~ Regexp.new("^#{comment_open}\s*$") && read_crontab =~ Regexp.new("^#{comment_close}\s*$")
68
+ # If the existing crontab file contains backslashes they get lost going through gsub.
69
+ # .gsub('\\', '\\\\\\') preserves them. Go figure.
70
+ read_crontab.gsub(Regexp.new("^#{comment_open}\s*$.+^#{comment_close}\s*$", Regexp::MULTILINE), crontab_task.chomp.gsub('\\', '\\\\\\'))
71
+ else # Otherwise, append the new cron entries after any existing ones
72
+ [read_crontab, crontab_task].join("\n\n")
73
+ end.gsub(/\n{3,}/, "\n\n") # More than two newlines becomes just two.
74
+ end
75
+
76
+ def crontab_task
77
+ return '' if @options[:clear]
78
+ [comment_open, crontab_job, comment_close].compact.join("\n") + "\n"
79
+ end
80
+
81
+ def crontab_job
82
+ job = [@options[:time]]
83
+ job << BackupMongoS3.name
84
+ job << '--backup_all'
85
+ job << "--config #{@options[:config]}"
86
+
87
+ job.join(' ')
88
+ end
89
+
90
+ def comment_base
91
+ "#{BackupMongoS3.name} task"
92
+ end
93
+
94
+ def comment_open
95
+ "# Begin #{comment_base}"
96
+ end
97
+
98
+ def comment_close
99
+ "# End #{comment_base}"
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,110 @@
1
+ module BackupMongoS3
2
+ class Storage
3
+
4
+ def initialize(options)
5
+ @s3 = AWS::S3.new({access_key_id: options[:access_key_id], secret_access_key: options[:secret_access_key]})
6
+ @bucket = get_bucket(options[:bucket])
7
+ end
8
+
9
+ private
10
+ def get_bucket(bucket_name)
11
+ bucket = @s3.buckets[bucket_name]
12
+
13
+ unless bucket.exists?
14
+ raise "Bucket #{bucket_name} doesn't not exists. Please create it"
15
+ end
16
+
17
+ bucket
18
+ end
19
+
20
+ public
21
+ def upload(storage_path, file_name)
22
+ key = File.join(storage_path, Time.now.utc.strftime('%Y%m%d') + '.backup')
23
+
24
+ checksum = get_signature(file_name)
25
+
26
+ begin
27
+
28
+ file = File.open(file_name, 'rb')
29
+
30
+ obj = @bucket.objects[key]
31
+
32
+ obj.write(:content_length => file.size, metadata: {checksum: checksum}) do |buffer, bytes|
33
+ buffer.write(file.read(bytes))
34
+ end
35
+
36
+ file.close
37
+
38
+ rescue Exception => err
39
+ raise "Error upload file <#{file_name}> to s3 <#{key}>: #{err.message}"
40
+ end
41
+
42
+ end
43
+
44
+ public
45
+ def download(storage_path, storage_file_name, file_name)
46
+ key = File.join(storage_path, storage_file_name + '.backup')
47
+
48
+ begin
49
+
50
+ file = File.open(file_name, 'wb')
51
+
52
+ obj = @bucket.objects[key]
53
+
54
+ response = obj.read do |chunk|
55
+ file.write(chunk)
56
+ end
57
+
58
+ file.close
59
+
60
+ checksum = get_signature(file_name)
61
+
62
+ if checksum != response[:meta]['checksum']
63
+ raise 'Backup signature is not valid'
64
+ end
65
+
66
+ rescue Exception => err
67
+ raise "Error download file <#{key}> from s3 to <#{file_name}>: #{err.message}"
68
+ end
69
+
70
+ end
71
+
72
+ public
73
+ def get_backups_list(prefix = '', limit = 100)
74
+ result =[]
75
+
76
+ @bucket.objects.with_prefix(prefix).each(:limit => limit) do |object|
77
+ if File.extname(object.key) == '.backup'
78
+ result << object
79
+ end
80
+ end
81
+
82
+ result
83
+ end
84
+
85
+ public
86
+ def delete_old_backups(prefix, history_days)
87
+
88
+ old_backups = []
89
+
90
+ backups_time_limit = Time.now.utc.midnight - history_days.days
91
+
92
+ backups = get_backups_list(prefix)
93
+
94
+ backups.each do |backup|
95
+ if backup.last_modified.utc < backups_time_limit
96
+ old_backups << backup
97
+ end
98
+ end
99
+
100
+ @bucket.delete(old_backups) unless old_backups.empty?
101
+
102
+ end
103
+
104
+ private
105
+ def get_signature(file_name)
106
+ Digest::MD5.hexdigest(File.size(file_name).to_s)
107
+ end
108
+
109
+ end
110
+ end
@@ -0,0 +1,28 @@
1
+ require 'optparse'
2
+ require 'aws-sdk'
3
+ require 'fileutils'
4
+ require 'digest/md5'
5
+ require 'tmpdir'
6
+
7
+ require_relative 'backup_mongo_s3/application'
8
+ require_relative 'backup_mongo_s3/db'
9
+ require_relative 'backup_mongo_s3/storage'
10
+ require_relative 'backup_mongo_s3/scheduler'
11
+
12
+ require_relative 'helpers/fixnum'
13
+ require_relative 'helpers/hash'
14
+ require_relative 'helpers/time'
15
+
16
+ module BackupMongoS3
17
+
18
+ def self.name
19
+ File.basename( __FILE__, '.rb')
20
+ end
21
+
22
+ def self.root_path
23
+ @root_path if @root_path
24
+ spec = Gem::Specification.find_by_name(self.name)
25
+ @root_path = spec.gem_dir
26
+ end
27
+ end
28
+
@@ -0,0 +1,23 @@
1
+ backup:
2
+ # List dbs for backup when run backup_all
3
+ dbs: db1, db2
4
+
5
+ # All backups older than [today - history_days] will be deleted when run backup/backup_all
6
+ history_days: 5
7
+
8
+ # Temporary directory for dump files. Default system temp directory
9
+ temp_directory:
10
+
11
+ # [minute] [hour] [day] [month] [weekday]
12
+ cron_time: 0 0 * * *
13
+
14
+ mongo:
15
+ host: 'localhost'
16
+ port: 27017
17
+ username:
18
+ password:
19
+
20
+ s3:
21
+ access_key_id: 'access_key_id'
22
+ secret_access_key: 'secret_access_key'
23
+ bucket: 'backup_mongo_s3-backups'
@@ -0,0 +1,11 @@
1
+ class Fixnum
2
+ SECONDS_IN_DAY = 24 * 60 * 60
3
+
4
+ def days
5
+ self * SECONDS_IN_DAY
6
+ end
7
+
8
+ def ago
9
+ Time.now - self
10
+ end
11
+ end
@@ -0,0 +1,26 @@
1
+ class Hash
2
+
3
+ def symbolize_keys!
4
+ transform_keys! { |key| key.to_sym rescue key }
5
+ end
6
+
7
+ def deep_symbolize_keys!
8
+ deep_transform_keys!{ |key| key.to_sym rescue key }
9
+ end
10
+
11
+ def transform_keys!
12
+ keys.each do |key|
13
+ self[yield(key)] = delete(key)
14
+ end
15
+ self
16
+ end
17
+
18
+ def deep_transform_keys!(&block)
19
+ keys.each do |key|
20
+ value = delete(key)
21
+ self[yield(key)] = value.is_a?(Hash) ? value.deep_transform_keys!(&block) : value
22
+ end
23
+ self
24
+ end
25
+
26
+ end
@@ -0,0 +1,25 @@
1
+ class Time
2
+
3
+ def midnight
4
+ change(:hour => 0)
5
+ end
6
+
7
+ def change(options)
8
+ new_year = options.fetch(:year, year)
9
+ new_month = options.fetch(:month, month)
10
+ new_day = options.fetch(:day, day)
11
+ new_hour = options.fetch(:hour, hour)
12
+ new_min = options.fetch(:min, options[:hour] ? 0 : min)
13
+ new_sec = options.fetch(:sec, (options[:hour] || options[:min]) ? 0 : sec)
14
+ new_usec = options.fetch(:usec, (options[:hour] || options[:min] || options[:sec]) ? 0 : Rational(nsec, 1000))
15
+
16
+ if utc?
17
+ ::Time.utc(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
18
+ elsif zone
19
+ ::Time.local(new_year, new_month, new_day, new_hour, new_min, new_sec, new_usec)
20
+ else
21
+ ::Time.new(new_year, new_month, new_day, new_hour, new_min, new_sec + (new_usec.to_r / 1000000), utc_offset)
22
+ end
23
+ end
24
+
25
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: backup_mongo_s3
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Yakupov Dima
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: '1.6'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: '1.6'
27
+ description: Some description
28
+ email:
29
+ - yakupov.dima@mail.ru
30
+ executables:
31
+ - backup_mongo_s3
32
+ extensions: []
33
+ extra_rdoc_files: []
34
+ files:
35
+ - .gitignore
36
+ - Gemfile
37
+ - README.md
38
+ - backup_mongo_s3.gemspec
39
+ - bin/backup_mongo_s3
40
+ - lib/backup_mongo_s3.rb
41
+ - lib/backup_mongo_s3/application.rb
42
+ - lib/backup_mongo_s3/db.rb
43
+ - lib/backup_mongo_s3/scheduler.rb
44
+ - lib/backup_mongo_s3/storage.rb
45
+ - lib/helpers/config.yml.template
46
+ - lib/helpers/fixnum.rb
47
+ - lib/helpers/hash.rb
48
+ - lib/helpers/time.rb
49
+ homepage: ''
50
+ licenses:
51
+ - MIT
52
+ metadata: {}
53
+ post_install_message:
54
+ rdoc_options: []
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - '>='
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements: []
68
+ rubyforge_project:
69
+ rubygems_version: 2.4.2
70
+ signing_key:
71
+ specification_version: 4
72
+ summary: Some summary
73
+ test_files: []