stratocumulus 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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