backupsss 0.2.0 → 0.3.0

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 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: