stratocumulus 0.0.3 → 0.0.4

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.
@@ -1,20 +1,25 @@
1
1
  # encoding: UTF-8
2
+ require 'stratocumulus/database/mysql'
3
+ require 'stratocumulus/database/pipe_io'
4
+ require 'English'
2
5
 
3
6
  module Stratocumulus
4
7
  class Database
5
- def initialize(options = {})
6
- @username = options['username'] || 'root'
7
- @password = options['password']
8
+ def initialize(options = {}, backend_class = nil)
8
9
  @name = options['name']
9
-
10
- @host = options['host']
11
- @port = options['port']
12
-
13
- check_dependencies(options['type'])
10
+ fail 'database name not specified' unless @name
11
+ setup_backend(backend_class, options)
12
+ check_dependencies
14
13
  end
15
14
 
16
15
  def dump
17
- IO.popen("bash -c '#{pipefail} #{mysqldump_command} | gzip'")
16
+ @dump ||= PipeIO.popen("bash -c '#{pipefail} #{@backend.command} | gzip'")
17
+ end
18
+
19
+ def success?
20
+ dump.read
21
+ dump.close
22
+ $CHILD_STATUS.success?
18
23
  end
19
24
 
20
25
  def filename
@@ -23,37 +28,25 @@ module Stratocumulus
23
28
 
24
29
  private
25
30
 
26
- def mysqldump_command
27
- command = 'mysqldump '
28
- command << '--single-transaction '
29
- command << "-u#{@username} "
30
- command << "-h#{host} " unless socket?
31
- command << "-P#{port} " unless socket?
32
- command << "-p#{@password} " if @password
33
- command << @name
34
- end
35
-
36
- def host
37
- @host || 'localhost'
31
+ def pipefail
32
+ 'set -o pipefail;'
38
33
  end
39
34
 
40
- def port
41
- @port || 3306
35
+ def check_dependencies
36
+ dependencies.each do |cmd|
37
+ fail "#{cmd} not available" unless system("which #{cmd} >/dev/null")
38
+ end
42
39
  end
43
40
 
44
- def socket?
45
- !@host && !@port
41
+ def dependencies
42
+ ['gzip'] + @backend.dependencies
46
43
  end
47
44
 
48
- def pipefail
49
- 'set -o pipefail;'
50
- end
51
-
52
- def check_dependencies(type)
53
- fail 'database name not specified' unless @name
45
+ def setup_backend(backend_class, options)
46
+ backend_class ||= MySQL
47
+ type = options['type']
54
48
  fail "#{type} is not a supported database" unless type == 'mysql'
55
- fail 'mysqldump not available' unless system('which mysqldump >/dev/null')
56
- fail 'gzip not available' unless system('which gzip >/dev/null')
49
+ @backend = backend_class.new(options)
57
50
  end
58
51
  end
59
52
  end
@@ -0,0 +1,44 @@
1
+ # encoding: UTF-8
2
+
3
+ module Stratocumulus
4
+ class Database
5
+ class MySQL
6
+ def initialize(options = {})
7
+ @username = options['username'] || 'root'
8
+ @password = options['password']
9
+ @name = options['name']
10
+
11
+ @host = options['host']
12
+ @port = options['port']
13
+ end
14
+
15
+ def command
16
+ command = 'mysqldump '
17
+ command << '--single-transaction '
18
+ command << "-u#{@username} "
19
+ command << "-h#{host} " unless socket?
20
+ command << "-P#{port} " unless socket?
21
+ command << "-p#{@password} " if @password
22
+ command << @name
23
+ end
24
+
25
+ def dependencies
26
+ ['mysqldump']
27
+ end
28
+
29
+ private
30
+
31
+ def host
32
+ @host || 'localhost'
33
+ end
34
+
35
+ def port
36
+ @port || 3306
37
+ end
38
+
39
+ def socket?
40
+ !@host && !@port
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,11 @@
1
+ # encoding: UTF-8
2
+
3
+ # Rewind is undefined so fog won't try to call rewind on a pipe
4
+
5
+ module Stratocumulus
6
+ class Database
7
+ class PipeIO < IO
8
+ undef rewind
9
+ end
10
+ end
11
+ end
@@ -1,6 +1,6 @@
1
1
  # encoding: UTF-8
2
2
  require 'fog'
3
- require 'stratocumulus/ext/io'
3
+ require 'logger'
4
4
 
5
5
  module Stratocumulus
6
6
  class Storage
@@ -14,7 +14,18 @@ module Stratocumulus
14
14
 
15
15
  def upload(database)
16
16
  return unless @retention.upload_today?
17
- add_expiry_rule(database.filename)
17
+ file = store_file(database)
18
+ if database.success?
19
+ add_expiry_rule(database.filename)
20
+ else
21
+ log.error("there was an error generating #{database.filename}")
22
+ file.destroy
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def store_file(database)
18
29
  files.create(
19
30
  key: database.filename,
20
31
  body: database.dump,
@@ -23,8 +34,6 @@ module Stratocumulus
23
34
  )
24
35
  end
25
36
 
26
- private
27
-
28
37
  def connection
29
38
  Fog::Storage.new(
30
39
  provider: 'AWS',
@@ -62,5 +71,9 @@ module Stratocumulus
62
71
  rescue Excon::Errors::NotFound
63
72
  []
64
73
  end
74
+
75
+ def log
76
+ Logger.new(STDERR)
77
+ end
65
78
  end
66
79
  end
@@ -1,4 +1,4 @@
1
1
  # encoding: UTF-8
2
2
  module Stratocumulus
3
- VERSION = '0.0.3'
3
+ VERSION = '0.0.4'
4
4
  end
@@ -21,9 +21,10 @@ describe Stratocumulus::Database do
21
21
 
22
22
  let(:dump) { Zlib::GzipReader.new(subject.dump).read }
23
23
 
24
- it 'dumps a gziped copy of the database' do
24
+ it 'sucessfully dumps a gziped copy of the database' do
25
25
  expect(dump).to include('CREATE TABLE `widgets`')
26
26
  expect(dump).to include("INSERT INTO `widgets` VALUES (1,'Foo',3,1,2)")
27
+ expect(subject).to be_success
27
28
  end
28
29
  end
29
30
  end
@@ -208,4 +208,52 @@ describe Stratocumulus::Database do
208
208
  expect(subject.filename).to eq filename
209
209
  end
210
210
  end
211
+
212
+ describe '#success?' do
213
+ subject do
214
+ described_class.new(
215
+ { 'name' => 'foo', 'type' => 'mysql' },
216
+ backend
217
+ )
218
+ end
219
+
220
+ context 'the backend fails' do
221
+ let(:backend) { FailingBackend }
222
+
223
+ it 'returns false' do
224
+ expect(subject).to_not be_success
225
+ end
226
+
227
+ end
228
+
229
+ context 'the backend is sucessfull' do
230
+ let(:backend) { SucessBackend }
231
+
232
+ it 'returns true' do
233
+ expect(subject).to be_success
234
+ end
235
+
236
+ end
237
+
238
+ class FakeBackend
239
+ def initialize(*)
240
+ end
241
+
242
+ def dependencies
243
+ []
244
+ end
245
+ end
246
+
247
+ class FailingBackend < FakeBackend
248
+ def command
249
+ 'exit 127'
250
+ end
251
+ end
252
+
253
+ class SucessBackend < FakeBackend
254
+ def command
255
+ 'exit 0'
256
+ end
257
+ end
258
+ end
211
259
  end
@@ -0,0 +1,36 @@
1
+ # encoding: UTF-8
2
+ require 'spec_helper'
3
+
4
+ describe Stratocumulus::Database::PipeIO do
5
+ subject do
6
+ described_class.popen('echo foo')
7
+ end
8
+
9
+ after do
10
+ subject.close
11
+ end
12
+
13
+ it 'behaves like IO' do
14
+ expect(subject.read).to eq "foo\n"
15
+ end
16
+
17
+ describe '#rewind' do
18
+ it 'is not defined' do
19
+ expect(subject).to_not respond_to(:rewind)
20
+ end
21
+ end
22
+ end
23
+
24
+ describe IO do
25
+ subject do
26
+ described_class.open(
27
+ described_class.sysopen('spec/support/test_config_file.yml')
28
+ )
29
+ end
30
+
31
+ it 'is not affected' do
32
+ subject.read
33
+ subject.rewind
34
+ expect(subject.read).to_not be_nil
35
+ end
36
+ end
@@ -18,10 +18,16 @@ describe Stratocumulus::Storage do
18
18
  describe '#upload' do
19
19
  let(:connection) { double(:fog_conn, directories: directories) }
20
20
  let(:directories) { double(:fog_dirs, get: double(files: files)) }
21
- let(:files) { double(:fog_files, create: true) }
21
+ let(:files) { double(:fog_files, create: file) }
22
+ let(:file) { double }
22
23
 
23
24
  let(:database) do
24
- double(:database, filename: 'foo.sql.gz', dump: :database_dump)
25
+ double(
26
+ :database,
27
+ filename: 'foo.sql.gz',
28
+ dump: :database_dump,
29
+ success?: true
30
+ )
25
31
  end
26
32
 
27
33
  before do
@@ -95,6 +101,29 @@ describe Stratocumulus::Storage do
95
101
  end
96
102
  end
97
103
 
104
+ context 'when the database is not sucessfull' do
105
+ let(:database) do
106
+ double(
107
+ :database,
108
+ filename: 'foo.sql.gz',
109
+ dump: :database_dump,
110
+ success?: false
111
+ )
112
+ end
113
+
114
+ before do
115
+ allow(file).to receive(:destroy)
116
+ end
117
+
118
+ it 'destroys the failing dump' do
119
+ expect(file).to receive(:destroy)
120
+ end
121
+
122
+ it 'does not create a expiry rule' do
123
+ expect(service).to_not receive(:put_bucket_lifecycle)
124
+ end
125
+ end
126
+
98
127
  context 'rules allready set on the bucket' do
99
128
  let(:files) { [existing_file] }
100
129
  let(:existing_file) { double(:fog_file, key: 'bar.sql.gz') }
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stratocumulus
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -160,18 +160,19 @@ files:
160
160
  - lib/stratocumulus.rb
161
161
  - lib/stratocumulus/cli.rb
162
162
  - lib/stratocumulus/database.rb
163
- - lib/stratocumulus/ext/io.rb
163
+ - lib/stratocumulus/database/mysql.rb
164
+ - lib/stratocumulus/database/pipe_io.rb
164
165
  - lib/stratocumulus/retention.rb
165
166
  - lib/stratocumulus/runner.rb
166
167
  - lib/stratocumulus/storage.rb
167
168
  - lib/stratocumulus/version.rb
168
- - spec/ext/io_spec.rb
169
169
  - spec/intergration/database_spec.rb
170
170
  - spec/spec_helper.rb
171
171
  - spec/support/test.sql
172
172
  - spec/support/test_config_file.yml
173
173
  - spec/unit/cli_spec.rb
174
174
  - spec/unit/database_spec.rb
175
+ - spec/unit/pipe_io_spec.rb
175
176
  - spec/unit/retention_spec.rb
176
177
  - spec/unit/runner_spec.rb
177
178
  - spec/unit/storage_spec.rb
@@ -191,7 +192,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
191
192
  version: '0'
192
193
  segments:
193
194
  - 0
194
- hash: -4542051147236532735
195
+ hash: 815129825214945472
195
196
  required_rubygems_version: !ruby/object:Gem::Requirement
196
197
  none: false
197
198
  requirements:
@@ -200,7 +201,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
200
201
  version: '0'
201
202
  segments:
202
203
  - 0
203
- hash: -4542051147236532735
204
+ hash: 815129825214945472
204
205
  requirements: []
205
206
  rubyforge_project:
206
207
  rubygems_version: 1.8.25
@@ -208,13 +209,13 @@ signing_key:
208
209
  specification_version: 3
209
210
  summary: Backup Databases to Cloud Storage
210
211
  test_files:
211
- - spec/ext/io_spec.rb
212
212
  - spec/intergration/database_spec.rb
213
213
  - spec/spec_helper.rb
214
214
  - spec/support/test.sql
215
215
  - spec/support/test_config_file.yml
216
216
  - spec/unit/cli_spec.rb
217
217
  - spec/unit/database_spec.rb
218
+ - spec/unit/pipe_io_spec.rb
218
219
  - spec/unit/retention_spec.rb
219
220
  - spec/unit/runner_spec.rb
220
221
  - spec/unit/storage_spec.rb
@@ -1,8 +0,0 @@
1
- # encoding: UTF-8
2
-
3
- # Rewind is undefined so fog won't try to call rewind on a pipe
4
- # This should be a refinement, but we are supporting 1.9.3 (for now)
5
-
6
- class IO
7
- undef rewind
8
- end
data/spec/ext/io_spec.rb DELETED
@@ -1,12 +0,0 @@
1
- # encoding: UTF-8
2
- require 'spec_helper'
3
-
4
- describe IO do
5
- subject { IO.new(IO.sysopen('/dev/null', 'w')) }
6
-
7
- describe '#rewind' do
8
- it 'is not defined' do
9
- expect(subject).to_not respond_to(:rewind)
10
- end
11
- end
12
- end