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