stratocumulus 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/stratocumulus/database.rb +26 -33
- data/lib/stratocumulus/database/mysql.rb +44 -0
- data/lib/stratocumulus/database/pipe_io.rb +11 -0
- data/lib/stratocumulus/storage.rb +17 -4
- data/lib/stratocumulus/version.rb +1 -1
- data/spec/intergration/database_spec.rb +2 -1
- data/spec/unit/database_spec.rb +48 -0
- data/spec/unit/pipe_io_spec.rb +36 -0
- data/spec/unit/storage_spec.rb +31 -2
- metadata +7 -6
- data/lib/stratocumulus/ext/io.rb +0 -8
- data/spec/ext/io_spec.rb +0 -12
@@ -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
|
-
|
11
|
-
|
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
|
-
|
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
|
27
|
-
|
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
|
41
|
-
|
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
|
45
|
-
|
41
|
+
def dependencies
|
42
|
+
['gzip'] + @backend.dependencies
|
46
43
|
end
|
47
44
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
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
|
-
|
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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
require 'fog'
|
3
|
-
require '
|
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
|
-
|
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
|
@@ -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
|
data/spec/unit/database_spec.rb
CHANGED
@@ -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
|
data/spec/unit/storage_spec.rb
CHANGED
@@ -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:
|
21
|
+
let(:files) { double(:fog_files, create: file) }
|
22
|
+
let(:file) { double }
|
22
23
|
|
23
24
|
let(:database) do
|
24
|
-
double(
|
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.
|
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/
|
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:
|
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:
|
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
|
data/lib/stratocumulus/ext/io.rb
DELETED
data/spec/ext/io_spec.rb
DELETED