backupsss 0.2.0 → 0.3.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
  SHA1:
3
- metadata.gz: 7d8134a309ca7b0fd67955ee59b704417951b648
4
- data.tar.gz: 41d054c4ebe76355d2f6d9de6a52c081daf732f3
3
+ metadata.gz: 473324a2ff4a2affcf336b2ac9adaf8314f3a2a8
4
+ data.tar.gz: efa42347a6d6e9b4bb2d9aa7c0af3a4fac323bb0
5
5
  SHA512:
6
- metadata.gz: bb3d651fa5f156ea578f16a708ee73d6edb19e4e19136e6ab2eb27ac9739a490c9f12cf2a7419f8c6adf97be8fe24efb9cb316ebbf4e9f13e44a15400e3eae8c
7
- data.tar.gz: c738fef618d52401503927e1a14415e6692697f3d321731a46894ba9830d191e1c5b786685dc732bcd55c3928ffff4314d3ad3559e58ade67b97c9f841b21582
6
+ metadata.gz: 970be5e7c287f045ecaed99f506291df4626746ad662ada92ac7654d37c29144c208abc09845afdbe7ea1c24ca5466124187263891edea6f6b65d8d350816227
7
+ data.tar.gz: 9f32195679dcc98f7d9944a8003d5bc0c938ef3a8f06a24c44ccc1e2e32a7cfe716f3fcc72412c3a32e4f9a663fabdf88b8ccea0b3b05b4d762732c7c7368c2f
data/.rubocop.yml CHANGED
@@ -1,2 +1,4 @@
1
1
  AllCops:
2
+ Exclude:
3
+ - '*.gemspec'
2
4
  TargetRubyVersion: 2.1
data/backupsss.gemspec CHANGED
@@ -35,11 +35,12 @@ Gem::Specification.new do |spec|
35
35
  spec.add_development_dependency 'guard-rspec', '~> 4.6.4'
36
36
  spec.add_development_dependency 'guard-rubocop', '~> 1.2'
37
37
  spec.add_development_dependency 'guard-bundler', '~> 2.1'
38
- spec.add_development_dependency 'rubocop', '~> 0.37'
38
+ spec.add_development_dependency 'rubocop', '0.46'
39
39
  spec.add_development_dependency 'simplecov', '~> 0.11.2'
40
40
  spec.add_development_dependency 'simplecov-console', '~> 0.3.0'
41
41
  spec.add_development_dependency 'codeclimate-test-reporter', '~> 0.4'
42
42
 
43
- spec.add_runtime_dependency 'aws-sdk'
44
- spec.add_runtime_dependency 'rufus-scheduler'
43
+ spec.add_runtime_dependency 'aws-sdk', '~> 2.7.0'
44
+ spec.add_runtime_dependency 'parallel', '~> 1.10.0'
45
+ spec.add_runtime_dependency 'rufus-scheduler', '~> 3.3.2'
45
46
  end
@@ -1,25 +1,126 @@
1
+ require 'parallel'
2
+
1
3
  module Backupsss
2
4
  # A class for delivering a tar to S3
3
5
  class Backup
6
+ MAX_FILE_SIZE = 1024 * 1024 * 100 # 100MB
7
+
4
8
  attr_reader :config, :client, :filename
5
9
 
6
10
  def initialize(config, client)
7
- @config = config
8
- @client = client
11
+ @config = config
12
+ @client = client
9
13
  @filename = config[:filename]
10
14
  end
11
15
 
12
16
  def put_file(file)
13
- client.put_object(bucket_opts.merge(body: file))
17
+ large_file(file) ? multi_upload(file) : single_upload(file)
14
18
  end
15
19
 
16
20
  private
17
21
 
22
+ def create_multipart_upload
23
+ $stdout.puts 'Creating a multipart upload'
24
+
25
+ client.create_multipart_upload(bucket_opts)
26
+ end
27
+
28
+ def large_file(file)
29
+ $stdout.puts 'Checking backup size ...'
30
+ is_lg = file.size > MAX_FILE_SIZE
31
+ status = is_lg ? 'greater than' : 'less than or equal to'
32
+ $stdout.puts "Size of backup is #{status} 100MB"
33
+
34
+ is_lg
35
+ end
36
+
37
+ def complete_multipart_upload_request(upload_id, parts)
38
+ bucket_opts.merge(
39
+ upload_id: upload_id, multipart_upload: { parts: parts }
40
+ )
41
+ end
42
+
43
+ def timed_multipart_upload
44
+ s = Time.now
45
+ $stdout.puts "Starting multipart upload at #{s}"
46
+ yield
47
+ e = Time.now
48
+ duration = ((e - s) / 60).round(2)
49
+ output = ["Completed multipart upload at #{e}"]
50
+ output << "Completed in #{duration} minutes."
51
+
52
+ $stdout.puts output.join("\n")
53
+ end
54
+
55
+ def abort_multipart_message(error, upload_id)
56
+ "#{error}\n#{upload_id}: Aborting multipart upload"
57
+ end
58
+
59
+ def multi_upload(file)
60
+ upload_id = create_multipart_upload.upload_id
61
+
62
+ bail_multipart_on_fail(upload_id) do
63
+ timed_multipart_upload do
64
+ parts = upload_parts(file, upload_id).sort do |a, b|
65
+ a[:part_number] <=> b[:part_number]
66
+ end
67
+
68
+ req = complete_multipart_upload_request(upload_id, parts)
69
+ client.complete_multipart_upload(req)
70
+ end
71
+ end
72
+ end
73
+
74
+ def upload_parts(file, upload_id)
75
+ Parallel.map(1..part_count(file), in_threads: 10) do |part|
76
+ bail_upload_part_on_fail(part, upload_id) do
77
+ $stdout.puts "#{upload_id}: Uploading part number #{part}\n"
78
+ r = client.upload_part(upload_part_params(file, part, upload_id))
79
+ success_msg = "#{upload_id}: Completed uploading part number #{part}"
80
+ r.on_success { $stdout.puts success_msg }
81
+
82
+ { etag: r.etag, part_number: part }
83
+ end
84
+ end
85
+ end
86
+
87
+ def bail_multipart_on_fail(upload_id)
88
+ yield
89
+ rescue StandardError => e
90
+ $stdout.puts abort_multipart_message(e, upload_id)
91
+ client.abort_multipart_upload(bucket_opts.merge(upload_id: upload_id))
92
+ end
93
+
94
+ def bail_upload_part_on_fail(part, upload_id)
95
+ yield
96
+ rescue StandardError => e
97
+ output = ["#{upload_id}: Failed to upload part number #{part}"]
98
+ output << "because of #{e.message}"
99
+ output << "#{upload_id}: Aborting remaining parts"
100
+ raise output.join("\n")
101
+ end
102
+
103
+ def upload_part_params(file, part, upload_id)
104
+ start = (part - 1) * MAX_FILE_SIZE
105
+ body = IO.read(file.path, MAX_FILE_SIZE, start)
106
+
107
+ bucket_opts.merge(part_number: part, body: body, upload_id: upload_id)
108
+ end
109
+
110
+ def part_count(file)
111
+ c = (file.size.to_f / MAX_FILE_SIZE.to_f).ceil
112
+ $stdout.puts "Uploading backup as #{c} parts"
113
+
114
+ c
115
+ end
116
+
117
+ def single_upload(file)
118
+ client.put_object(bucket_opts.merge(body: file.read))
119
+ end
120
+
18
121
  def bucket_opts
19
- {
20
- bucket: config[:s3_bucket],
21
- key: "#{config[:s3_bucket_prefix]}/#{filename}"
22
- }
122
+ { bucket: config[:s3_bucket],
123
+ key: "#{config[:s3_bucket_prefix]}/#{filename}" }
23
124
  end
24
125
  end
25
126
  end
data/lib/backupsss/tar.rb CHANGED
@@ -12,21 +12,34 @@ module Backupsss
12
12
  end
13
13
 
14
14
  def make
15
- return nil unless valid_dest? && valid_src?
15
+ return unless valid_dest? && valid_src?
16
16
  _, err, status = Open3.capture3("#{tar_command} #{dest} #{src}")
17
- STDERR.puts "tar command stderr:\n#{err}" unless err.empty?
18
- check_tar_result(status)
19
- File.open(dest)
17
+ File.open(dest) if valid_exit?(status, err) && valid_file?
20
18
  end
21
19
 
22
- def check_tar_result(status)
23
- if status.exitstatus.nonzero?
24
- raise "ERROR: #{tar_command} exited #{status.exitstatus}"
25
- end
26
- unless File.exist?(dest)
27
- raise 'ERROR: Tar destination file does not exist'
28
- end
29
- raise 'ERROR: Tar destionation file is 0 bytes.' if File.size(dest).zero?
20
+ def valid_exit?(status, err)
21
+ output = []
22
+ output << "command.......#{tar_command}"
23
+ output << "stderr........#{err}" unless err.empty?
24
+ output << "status........#{status}"
25
+ output << "exit code.....#{status.to_i}"
26
+ $stdout.puts output.join("\n")
27
+
28
+ return true if success_cases(status.to_i, err)
29
+ raise "ERROR: #{tar_command} exited #{status.to_i}"
30
+ end
31
+
32
+ def valid_file?
33
+ raise messages[:no_file] unless File.exist?(dest)
34
+ raise messages[:zero_byte] if File.size(dest).zero?
35
+ true
36
+ end
37
+
38
+ def messages
39
+ {
40
+ no_file: 'ERROR: Tar destination file does not exist.',
41
+ zero_byte: 'ERROR: Tar destination file is 0 bytes.'
42
+ }
30
43
  end
31
44
 
32
45
  def valid_dest?
@@ -47,6 +60,10 @@ module Backupsss
47
60
 
48
61
  private
49
62
 
63
+ def clean_exit(status)
64
+ status.zero?
65
+ end
66
+
50
67
  def dest_dir
51
68
  File.dirname(dest)
52
69
  end
@@ -56,13 +73,21 @@ module Backupsss
56
73
  end
57
74
 
58
75
  def dir_exists?(dir)
59
- File.exist?(dir) || raise_sys_err(dir, Errno::ENOENT::Errno)
76
+ File.exist?(File.open(dir)) || raise_sys_err(dir, Errno::ENOENT::Errno)
77
+ end
78
+
79
+ def file_changed?(signal_int, err)
80
+ signal_int == 1 && err.match(/file changed as we read it/)
60
81
  end
61
82
 
62
83
  def src_readable?
63
84
  File.readable?(src) || raise_sys_err(src, Errno::EPERM::Errno)
64
85
  end
65
86
 
87
+ def success_cases(signal_int, err)
88
+ clean_exit(signal_int) || file_changed?(signal_int, err)
89
+ end
90
+
66
91
  def raise_sys_err(dir, err)
67
92
  raise SystemCallError.new(dir.to_s, err)
68
93
  end
@@ -1,3 +1,3 @@
1
1
  module Backupsss
2
- VERSION = '0.2.0'.freeze
2
+ VERSION = '0.3.0'.freeze
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: backupsss
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Reppard Walker
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2016-11-23 00:00:00.000000000 Z
13
+ date: 2017-01-30 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: bundler
@@ -114,16 +114,16 @@ dependencies:
114
114
  name: rubocop
115
115
  requirement: !ruby/object:Gem::Requirement
116
116
  requirements:
117
- - - "~>"
117
+ - - '='
118
118
  - !ruby/object:Gem::Version
119
- version: '0.37'
119
+ version: '0.46'
120
120
  type: :development
121
121
  prerelease: false
122
122
  version_requirements: !ruby/object:Gem::Requirement
123
123
  requirements:
124
- - - "~>"
124
+ - - '='
125
125
  - !ruby/object:Gem::Version
126
- version: '0.37'
126
+ version: '0.46'
127
127
  - !ruby/object:Gem::Dependency
128
128
  name: simplecov
129
129
  requirement: !ruby/object:Gem::Requirement
@@ -170,30 +170,44 @@ dependencies:
170
170
  name: aws-sdk
171
171
  requirement: !ruby/object:Gem::Requirement
172
172
  requirements:
173
- - - ">="
173
+ - - "~>"
174
174
  - !ruby/object:Gem::Version
175
- version: '0'
175
+ version: 2.7.0
176
176
  type: :runtime
177
177
  prerelease: false
178
178
  version_requirements: !ruby/object:Gem::Requirement
179
179
  requirements:
180
- - - ">="
180
+ - - "~>"
181
181
  - !ruby/object:Gem::Version
182
- version: '0'
182
+ version: 2.7.0
183
+ - !ruby/object:Gem::Dependency
184
+ name: parallel
185
+ requirement: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - "~>"
188
+ - !ruby/object:Gem::Version
189
+ version: 1.10.0
190
+ type: :runtime
191
+ prerelease: false
192
+ version_requirements: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - "~>"
195
+ - !ruby/object:Gem::Version
196
+ version: 1.10.0
183
197
  - !ruby/object:Gem::Dependency
184
198
  name: rufus-scheduler
185
199
  requirement: !ruby/object:Gem::Requirement
186
200
  requirements:
187
- - - ">="
201
+ - - "~>"
188
202
  - !ruby/object:Gem::Version
189
- version: '0'
203
+ version: 3.3.2
190
204
  type: :runtime
191
205
  prerelease: false
192
206
  version_requirements: !ruby/object:Gem::Requirement
193
207
  requirements:
194
- - - ">="
208
+ - - "~>"
195
209
  - !ruby/object:Gem::Version
196
- version: '0'
210
+ version: 3.3.2
197
211
  description: Backup any file or directory as a tar and push the tar to a specificed
198
212
  S3 bucket.
199
213
  email: