stratocumulus 0.0.4 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +10 -0
- data/lib/stratocumulus/database.rb +42 -18
- data/lib/stratocumulus/database/mysql.rb +23 -33
- data/lib/stratocumulus/database/postgresql.rb +34 -0
- data/lib/stratocumulus/database/rethinkdb.rb +39 -0
- data/lib/stratocumulus/runner.rb +1 -1
- data/lib/stratocumulus/storage.rb +1 -1
- data/lib/stratocumulus/version.rb +1 -1
- data/spec/intergration/database_spec.rb +74 -9
- data/spec/spec_helper.rb +11 -0
- data/spec/support/{test.sql → mysql.sql} +0 -0
- data/spec/support/psql.sql +52 -0
- data/spec/support/rethinkdb.tar.gz +0 -0
- data/spec/unit/database_spec.rb +47 -171
- data/spec/unit/mysql_spec.rb +104 -0
- data/spec/unit/postgresql_spec.rb +104 -0
- data/spec/unit/storage_spec.rb +13 -1
- metadata +16 -6
data/.travis.yml
CHANGED
@@ -3,3 +3,13 @@ rvm:
|
|
3
3
|
- 2.1.2
|
4
4
|
- 2.0.0
|
5
5
|
- 1.9.3
|
6
|
+
before_install:
|
7
|
+
- source /etc/lsb-release && echo "deb http://download.rethinkdb.com/apt $DISTRIB_CODENAME main" | sudo tee /etc/apt/sources.list.d/rethinkdb.list
|
8
|
+
- wget -O- http://download.rethinkdb.com/apt/pubkey.gpg | sudo apt-key add -
|
9
|
+
- sudo apt-get update -qq
|
10
|
+
install:
|
11
|
+
- sudo apt-get install -y rethinkdb
|
12
|
+
- sudo pip install rethinkdb
|
13
|
+
- sudo cp /etc/rethinkdb/default.conf.sample /etc/rethinkdb/instances.d/instance1.conf
|
14
|
+
- sudo /etc/init.d/rethinkdb restart
|
15
|
+
- bundle install
|
@@ -1,19 +1,42 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
require 'stratocumulus/database/mysql'
|
3
2
|
require 'stratocumulus/database/pipe_io'
|
3
|
+
require 'stratocumulus/database/mysql'
|
4
|
+
require 'stratocumulus/database/postgresql'
|
5
|
+
require 'stratocumulus/database/rethinkdb'
|
4
6
|
require 'English'
|
5
7
|
|
6
8
|
module Stratocumulus
|
7
9
|
class Database
|
8
|
-
def
|
10
|
+
def self.build(options = {})
|
11
|
+
backend_class = backends[options['type']]
|
12
|
+
fail "#{options['type']} is not a supported database" unless backend_class
|
13
|
+
backend_class.new(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.backends
|
17
|
+
{
|
18
|
+
'psql' => PostgreSQL,
|
19
|
+
'mysql' => MySQL,
|
20
|
+
'rethinkdb' => RethinkDB
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(options = {})
|
25
|
+
check_dependencies
|
26
|
+
@username = options['username']
|
27
|
+
@password = options['password']
|
9
28
|
@name = options['name']
|
10
29
|
fail 'database name not specified' unless @name
|
11
|
-
|
12
|
-
|
30
|
+
@host = options['host']
|
31
|
+
@port = options['port']
|
13
32
|
end
|
14
33
|
|
15
34
|
def dump
|
16
|
-
@dump ||= PipeIO.popen("bash -c '#{pipefail} #{
|
35
|
+
@dump ||= PipeIO.popen("bash -c '#{pipefail} #{command} | gzip'")
|
36
|
+
end
|
37
|
+
|
38
|
+
def filename
|
39
|
+
@name + '/' + file
|
17
40
|
end
|
18
41
|
|
19
42
|
def success?
|
@@ -22,31 +45,32 @@ module Stratocumulus
|
|
22
45
|
$CHILD_STATUS.success?
|
23
46
|
end
|
24
47
|
|
25
|
-
def
|
26
|
-
|
48
|
+
def dependencies
|
49
|
+
['gzip']
|
27
50
|
end
|
28
51
|
|
29
52
|
private
|
30
53
|
|
31
|
-
def pipefail
|
32
|
-
'set -o pipefail;'
|
33
|
-
end
|
34
|
-
|
35
54
|
def check_dependencies
|
36
55
|
dependencies.each do |cmd|
|
37
56
|
fail "#{cmd} not available" unless system("which #{cmd} >/dev/null")
|
38
57
|
end
|
39
58
|
end
|
40
59
|
|
41
|
-
def
|
42
|
-
|
60
|
+
def file
|
61
|
+
@file ||= Time.now.utc.strftime("#{@name}.%Y%m%d%H%M#{suffix}")
|
62
|
+
end
|
63
|
+
|
64
|
+
def suffix
|
65
|
+
'.sql.gz'
|
66
|
+
end
|
67
|
+
|
68
|
+
def pipefail
|
69
|
+
'set -o pipefail;'
|
43
70
|
end
|
44
71
|
|
45
|
-
def
|
46
|
-
|
47
|
-
type = options['type']
|
48
|
-
fail "#{type} is not a supported database" unless type == 'mysql'
|
49
|
-
@backend = backend_class.new(options)
|
72
|
+
def socket?
|
73
|
+
!@host && !@port
|
50
74
|
end
|
51
75
|
end
|
52
76
|
end
|
@@ -1,44 +1,34 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
+
require 'stratocumulus/database'
|
2
3
|
|
3
4
|
module Stratocumulus
|
4
|
-
class Database
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
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
|
5
|
+
class MySQL < Database
|
6
|
+
def command
|
7
|
+
command = 'mysqldump '
|
8
|
+
command << '--single-transaction '
|
9
|
+
command << "-u#{username} "
|
10
|
+
command << "-h#{host} " unless socket?
|
11
|
+
command << "-P#{port} " unless socket?
|
12
|
+
command << "-p#{@password} " if @password
|
13
|
+
command << @name
|
14
|
+
end
|
24
15
|
|
25
|
-
|
26
|
-
|
27
|
-
|
16
|
+
def dependencies
|
17
|
+
super + ['mysqldump']
|
18
|
+
end
|
28
19
|
|
29
|
-
|
20
|
+
private
|
30
21
|
|
31
|
-
|
32
|
-
|
33
|
-
|
22
|
+
def username
|
23
|
+
@username || 'root'
|
24
|
+
end
|
34
25
|
|
35
|
-
|
36
|
-
|
37
|
-
|
26
|
+
def host
|
27
|
+
@host || 'localhost'
|
28
|
+
end
|
38
29
|
|
39
|
-
|
40
|
-
|
41
|
-
end
|
30
|
+
def port
|
31
|
+
@port || 3306
|
42
32
|
end
|
43
33
|
end
|
44
34
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'stratocumulus/database'
|
3
|
+
|
4
|
+
module Stratocumulus
|
5
|
+
class PostgreSQL < Database
|
6
|
+
def command
|
7
|
+
command = ''
|
8
|
+
command << %Q(PGPASSWORD="#{@password}" ) if @password
|
9
|
+
command << 'pg_dump '
|
10
|
+
command << "-U#{username} "
|
11
|
+
command << "-h#{host} " unless socket?
|
12
|
+
command << "-p#{port} " unless socket?
|
13
|
+
command << @name
|
14
|
+
end
|
15
|
+
|
16
|
+
def dependencies
|
17
|
+
super + ['pg_dump']
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def username
|
23
|
+
@username || 'postgres'
|
24
|
+
end
|
25
|
+
|
26
|
+
def host
|
27
|
+
@host || 'localhost'
|
28
|
+
end
|
29
|
+
|
30
|
+
def port
|
31
|
+
@port || 5432
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'stratocumulus/database'
|
3
|
+
require 'tmpdir'
|
4
|
+
|
5
|
+
module Stratocumulus
|
6
|
+
class RethinkDB < Database
|
7
|
+
def dump
|
8
|
+
`#{command}`
|
9
|
+
@success = $CHILD_STATUS.success?
|
10
|
+
File.open(path)
|
11
|
+
end
|
12
|
+
|
13
|
+
def command
|
14
|
+
command = 'rethinkdb dump '
|
15
|
+
command << "-c #{host}:#{port} " unless socket?
|
16
|
+
command << "-f #{path} "
|
17
|
+
command << "-e #{@name}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def success?
|
21
|
+
File.delete(path)
|
22
|
+
@success
|
23
|
+
end
|
24
|
+
|
25
|
+
def dependencies
|
26
|
+
['rethinkdb-dump']
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def path
|
32
|
+
Dir.tmpdir + '/' + file
|
33
|
+
end
|
34
|
+
|
35
|
+
def suffix
|
36
|
+
'.tar.gz'
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/stratocumulus/runner.rb
CHANGED
@@ -1,30 +1,95 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
require 'spec_helper'
|
3
|
+
require 'zlib'
|
4
|
+
require 'rubygems/package'
|
3
5
|
|
4
6
|
describe Stratocumulus::Database do
|
5
7
|
|
6
8
|
subject do
|
7
|
-
described_class.
|
9
|
+
described_class.build(config)
|
8
10
|
end
|
9
11
|
|
10
12
|
let('config') do
|
11
13
|
{
|
12
14
|
'name' => 'stratocumulus_test',
|
13
|
-
'type' =>
|
15
|
+
'type' => type
|
14
16
|
}
|
15
17
|
end
|
16
18
|
|
19
|
+
let(:dump) { Zlib::GzipReader.new(subject.dump).read }
|
20
|
+
|
17
21
|
describe '#dump' do
|
18
|
-
|
19
|
-
|
22
|
+
context 'MySQL' do
|
23
|
+
before do
|
24
|
+
`mysql -u root < spec/support/mysql.sql`
|
25
|
+
end
|
26
|
+
|
27
|
+
let(:type) { 'mysql' }
|
28
|
+
|
29
|
+
it 'sucessfully dumps a gziped copy of the database' do
|
30
|
+
expect(dump).to include('CREATE TABLE `widgets`')
|
31
|
+
expect(dump).to include("INSERT INTO `widgets` VALUES (1,'Foo',3,1,2)")
|
32
|
+
expect(subject).to be_success
|
33
|
+
end
|
20
34
|
end
|
21
35
|
|
22
|
-
|
36
|
+
context 'PostgreSQL' do
|
37
|
+
before do
|
38
|
+
`psql -U postgres < spec/support/psql.sql`
|
39
|
+
end
|
40
|
+
|
41
|
+
let(:type) { 'psql' }
|
42
|
+
|
43
|
+
it 'sucessfully dumps a gziped copy of the database' do
|
44
|
+
expect(dump).to include('CREATE TABLE widgets')
|
45
|
+
expect(dump).to include(
|
46
|
+
'COPY widgets (id, name, leavers, pivots, fulcrums) FROM stdin;'
|
47
|
+
)
|
48
|
+
expect(dump).to include('1 Foo 3 1 2')
|
49
|
+
expect(subject).to be_success
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'RethinkDB' do
|
54
|
+
before do
|
55
|
+
`rethinkdb restore --force spec/support/rethinkdb.tar.gz`
|
56
|
+
end
|
57
|
+
|
58
|
+
let(:type) { 'rethinkdb' }
|
59
|
+
|
60
|
+
let(:tarball) { Zlib::GzipReader.new(subject.dump) }
|
61
|
+
|
62
|
+
let(:dump) do
|
63
|
+
Gem::Package::TarReader.new(tarball).each do |tarfile|
|
64
|
+
next unless tarfile.full_name =~ /widgets.json/
|
65
|
+
return JSON.load(tarfile.read)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
let(:expected_data) do
|
70
|
+
[
|
71
|
+
# rubocop:disable Style/LineLength
|
72
|
+
{ 'pivots' => 5, 'fulcrums' => 3, 'leavers' => 8, 'id' => '1c66308d-492e-48ee-bfc5-83de96a15236', 'name' => 'Plugh' },
|
73
|
+
{ 'pivots' => 2, 'fulcrums' => 3, 'leavers' => 1, 'id' => '224ba4b1-2184-4905-b81b-ac6368480f43', 'name' => 'Thud' },
|
74
|
+
{ 'pivots' => 2, 'fulcrums' => 3, 'leavers' => 1, 'id' => '25b68062-7e9f-4fca-92bf-672287961096', 'name' => 'Garply' },
|
75
|
+
{ 'pivots' => 1, 'fulcrums' => 2, 'leavers' => 3, 'id' => '380c2a12-62d8-4faa-968e-79b2919bf9ad', 'name' => 'Foo' },
|
76
|
+
{ 'pivots' => 5, 'fulcrums' => 4, 'leavers' => 8, 'id' => '46cd4d4a-2cca-4ae2-835e-19c94c53ce02', 'name' => 'Quux' },
|
77
|
+
{ 'pivots' => 2, 'fulcrums' => 7, 'leavers' => 8, 'id' => '63441f79-cfe4-4460-8fc7-bda802e93646', 'name' => 'Corge' },
|
78
|
+
{ 'pivots' => 3, 'fulcrums' => 4, 'leavers' => 7, 'id' => '7d83f209-b4b1-4463-9eec-42dbf9217a39', 'name' => 'Grault' },
|
79
|
+
{ 'pivots' => 5, 'fulcrums' => 6, 'leavers' => 4, 'id' => '8e1623c8-e28d-48f9-8d74-dc5bf459cff5', 'name' => 'Qux' },
|
80
|
+
{ 'pivots' => 2, 'fulcrums' => 0, 'leavers' => 2, 'id' => 'a59c4505-c078-4186-a1d5-b0120dba4aa1', 'name' => 'Bar' },
|
81
|
+
{ 'pivots' => 3, 'fulcrums' => 3, 'leavers' => 3, 'id' => 'b535529c-c822-4c08-af65-f3662d981046', 'name' => 'Xyzzy' },
|
82
|
+
{ 'pivots' => 0, 'fulcrums' => 0, 'leavers' => 0, 'id' => 'f47bcc2a-ae2a-4d5f-9bbf-df71c8d81a7a', 'name' => 'Waldo' },
|
83
|
+
{ 'pivots' => 6, 'fulcrums' => 4, 'leavers' => 5, 'id' => 'f6745b27-b0c8-40ca-a472-dbe45310ee19', 'name' => 'Baz' },
|
84
|
+
{ 'pivots' => 1, 'fulcrums' => 1, 'leavers' => 1, 'id' => 'f88a0196-faaa-4695-88f1-808555a68ffa', 'name' => 'Fred' }
|
85
|
+
# rubocop:enable Style/LineLength
|
86
|
+
]
|
87
|
+
end
|
23
88
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
89
|
+
it 'sucessfully dumps a gziped copy of the database' do
|
90
|
+
expect(dump).to eq expected_data
|
91
|
+
expect(subject).to be_success
|
92
|
+
end
|
28
93
|
end
|
29
94
|
end
|
30
95
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# encoding: UTF-8
|
2
2
|
require 'simplecov'
|
3
3
|
require 'coveralls'
|
4
|
+
require 'stringio'
|
4
5
|
|
5
6
|
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
6
7
|
SimpleCov::Formatter::HTMLFormatter,
|
@@ -32,3 +33,13 @@ RSpec.configure do |config|
|
|
32
33
|
mocks.syntax = :expect
|
33
34
|
end
|
34
35
|
end
|
36
|
+
|
37
|
+
def capture_stderr
|
38
|
+
old_stderr = $stderr
|
39
|
+
fake_stderr = StringIO.new
|
40
|
+
$stderr = fake_stderr
|
41
|
+
yield
|
42
|
+
fake_stderr.string
|
43
|
+
ensure
|
44
|
+
$stdout = old_stderr
|
45
|
+
end
|
File without changes
|
@@ -0,0 +1,52 @@
|
|
1
|
+
DROP DATABASE stratocumulus_test;
|
2
|
+
CREATE DATABASE stratocumulus_test
|
3
|
+
WITH OWNER = postgres
|
4
|
+
ENCODING = 'UTF8'
|
5
|
+
TABLESPACE = pg_default
|
6
|
+
CONNECTION LIMIT = -1;
|
7
|
+
\c stratocumulus_test
|
8
|
+
SET statement_timeout = 0;
|
9
|
+
SET lock_timeout = 0;
|
10
|
+
SET client_encoding = 'UTF8';
|
11
|
+
SET standard_conforming_strings = on;
|
12
|
+
SET check_function_bodies = false;
|
13
|
+
SET client_min_messages = warning;
|
14
|
+
CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog;
|
15
|
+
COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language';
|
16
|
+
SET search_path = public, pg_catalog;
|
17
|
+
SET default_tablespace = '';
|
18
|
+
SET default_with_oids = false;
|
19
|
+
CREATE TABLE widgets (
|
20
|
+
id integer NOT NULL,
|
21
|
+
name text,
|
22
|
+
leavers integer,
|
23
|
+
pivots integer,
|
24
|
+
fulcrums integer
|
25
|
+
);
|
26
|
+
ALTER TABLE public.widgets OWNER TO postgres;
|
27
|
+
CREATE SEQUENCE widgets_id_seq
|
28
|
+
START WITH 1
|
29
|
+
INCREMENT BY 1
|
30
|
+
NO MINVALUE
|
31
|
+
NO MAXVALUE
|
32
|
+
CACHE 1;
|
33
|
+
ALTER TABLE public.widgets_id_seq OWNER TO postgres;
|
34
|
+
ALTER SEQUENCE widgets_id_seq OWNED BY widgets.id;
|
35
|
+
ALTER TABLE ONLY widgets ALTER COLUMN id SET DEFAULT nextval('widgets_id_seq'::regclass);
|
36
|
+
COPY widgets (id, name, leavers, pivots, fulcrums) FROM stdin;
|
37
|
+
1 Foo 3 1 2
|
38
|
+
2 Bar 2 2 0
|
39
|
+
3 Baz 5 6 4
|
40
|
+
4 Qux 4 5 6
|
41
|
+
5 Quux 8 5 4
|
42
|
+
6 Corge 8 2 7
|
43
|
+
7 Grault 7 3 4
|
44
|
+
8 Garply 1 2 3
|
45
|
+
9 Waldo 0 0 0
|
46
|
+
10 Fred 1 1 1
|
47
|
+
11 Xyzzy 3 3 3
|
48
|
+
12 Thud 1 2 3
|
49
|
+
\.
|
50
|
+
SELECT pg_catalog.setval('widgets_id_seq', 12, true);
|
51
|
+
ALTER TABLE ONLY widgets
|
52
|
+
ADD CONSTRAINT widgets_pkey PRIMARY KEY (id);
|
Binary file
|
data/spec/unit/database_spec.rb
CHANGED
@@ -4,7 +4,7 @@ require 'spec_helper'
|
|
4
4
|
describe Stratocumulus::Database do
|
5
5
|
|
6
6
|
subject do
|
7
|
-
described_class.
|
7
|
+
described_class.build(config)
|
8
8
|
end
|
9
9
|
|
10
10
|
let(:config) { base_config }
|
@@ -12,19 +12,23 @@ describe Stratocumulus::Database do
|
|
12
12
|
let(:base_config) do
|
13
13
|
{
|
14
14
|
'name' => 'stratocumulus_test',
|
15
|
-
'type' =>
|
15
|
+
'type' => type
|
16
16
|
}
|
17
17
|
end
|
18
18
|
|
19
|
+
let(:type) { 'mysql' }
|
20
|
+
|
19
21
|
before do
|
20
|
-
allow_any_instance_of(
|
22
|
+
allow_any_instance_of(Stratocumulus::Database).to(
|
23
|
+
receive(:system).and_return(true)
|
24
|
+
)
|
21
25
|
end
|
22
26
|
|
23
27
|
describe '.new' do
|
24
|
-
context 'the database type is not
|
28
|
+
context 'the database type is not supported' do
|
25
29
|
let(:config) { base_config.merge('type' => 'nosqlioid') }
|
26
30
|
|
27
|
-
it 'thows an error unless the type is
|
31
|
+
it 'thows an error unless the type is supported' do
|
28
32
|
expect { subject }.to raise_error(
|
29
33
|
RuntimeError,
|
30
34
|
'nosqlioid is not a supported database'
|
@@ -32,18 +36,35 @@ describe Stratocumulus::Database do
|
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
35
|
-
|
36
|
-
|
37
|
-
|
39
|
+
context 'mysql' do
|
40
|
+
it 'throws an error if mysqldump is not installed' do
|
41
|
+
allow_any_instance_of(Stratocumulus::Database).to(
|
42
|
+
receive(:system).with(/which mysqldump/)
|
43
|
+
)
|
38
44
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
45
|
+
expect { subject }.to raise_error(
|
46
|
+
RuntimeError,
|
47
|
+
'mysqldump not available'
|
48
|
+
)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context 'postgresql' do
|
53
|
+
let(:type) { 'psql' }
|
54
|
+
|
55
|
+
it 'throws an error if pg_dump is not installed' do
|
56
|
+
allow_any_instance_of(Stratocumulus::Database)
|
57
|
+
.to receive(:system).with(/which pg_dump/)
|
58
|
+
|
59
|
+
expect { subject }.to raise_error(
|
60
|
+
RuntimeError,
|
61
|
+
'pg_dump not available'
|
62
|
+
)
|
63
|
+
end
|
43
64
|
end
|
44
65
|
|
45
66
|
it 'throws an error if gzip is not installed' do
|
46
|
-
allow_any_instance_of(
|
67
|
+
allow_any_instance_of(Stratocumulus::Database)
|
47
68
|
.to receive(:system).with(/which gzip/)
|
48
69
|
|
49
70
|
expect { subject }.to raise_error(
|
@@ -68,133 +89,6 @@ describe Stratocumulus::Database do
|
|
68
89
|
end
|
69
90
|
end
|
70
91
|
|
71
|
-
describe 'dump' do
|
72
|
-
|
73
|
-
after do
|
74
|
-
subject.dump
|
75
|
-
end
|
76
|
-
|
77
|
-
describe 'username' do
|
78
|
-
context 'default' do
|
79
|
-
it 'uses root' do
|
80
|
-
expect(IO).to receive(:popen) do |command|
|
81
|
-
expect(command).to include(' -uroot ')
|
82
|
-
end
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
context 'setting the username' do
|
87
|
-
let(:config) { base_config.merge('username' => 'susan') }
|
88
|
-
|
89
|
-
it 'uses the correct username' do
|
90
|
-
expect(IO).to receive(:popen) do |command|
|
91
|
-
expect(command).to include(' -ususan ')
|
92
|
-
end
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
describe 'password' do
|
98
|
-
context 'default' do
|
99
|
-
it 'uses no password' do
|
100
|
-
expect(IO).to receive(:popen) do |command|
|
101
|
-
expect(command).to_not include('-p')
|
102
|
-
end
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
|
-
context 'setting the passsword' do
|
107
|
-
let(:config) { base_config.merge('password' => 'sekret') }
|
108
|
-
|
109
|
-
it 'uses the correct password' do
|
110
|
-
expect(IO).to receive(:popen) do |command|
|
111
|
-
expect(command).to include(' -psekret ')
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
describe 'host' do
|
118
|
-
context 'default with the port set' do
|
119
|
-
let(:config) { base_config.merge('port' => '3306') }
|
120
|
-
|
121
|
-
it 'uses localhost' do
|
122
|
-
expect(IO).to receive(:popen) do |command|
|
123
|
-
expect(command).to include(' -hlocalhost ')
|
124
|
-
end
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
context 'default' do
|
129
|
-
it 'uses the default socket' do
|
130
|
-
expect(IO).to receive(:popen) do |command|
|
131
|
-
expect(command).to_not include(' -hlocalhost ')
|
132
|
-
end
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
context 'setting the host' do
|
137
|
-
let(:config) { base_config.merge('host' => 'db.awesome-server.net') }
|
138
|
-
|
139
|
-
it 'uses the correct hostname' do
|
140
|
-
expect(IO).to receive(:popen) do |command|
|
141
|
-
expect(command).to include(' -hdb.awesome-server.net ')
|
142
|
-
end
|
143
|
-
end
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
describe 'port' do
|
148
|
-
context 'default with the host set' do
|
149
|
-
let(:config) { base_config.merge('host' => 'db.awesome-server.net') }
|
150
|
-
|
151
|
-
it 'uses 3306' do
|
152
|
-
expect(IO).to receive(:popen) do |command|
|
153
|
-
expect(command).to include(' -P3306 ')
|
154
|
-
end
|
155
|
-
end
|
156
|
-
end
|
157
|
-
|
158
|
-
context 'default' do
|
159
|
-
it 'uses the default socket' do
|
160
|
-
expect(IO).to receive(:popen) do |command|
|
161
|
-
expect(command).to_not include(' -P3306 ')
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
context 'setting the port' do
|
167
|
-
let(:config) { base_config.merge('port' => '4306') }
|
168
|
-
|
169
|
-
it 'uses the correct port' do
|
170
|
-
expect(IO).to receive(:popen) do |command|
|
171
|
-
expect(command).to include(' -P4306 ')
|
172
|
-
end
|
173
|
-
end
|
174
|
-
end
|
175
|
-
end
|
176
|
-
|
177
|
-
describe 'the dump command' do
|
178
|
-
it 'sets pipefail' do
|
179
|
-
expect(IO).to receive(:popen) do |command|
|
180
|
-
expect(command).to include("bash -c 'set -o pipefail;")
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
it 'uses mysqldump --single-transaction option to not lock tables' do
|
185
|
-
expect(IO).to receive(:popen) do |command|
|
186
|
-
expect(command).to include(' mysqldump --single-transaction ')
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
it 'pipes the output of mysql to gzip' do
|
191
|
-
expect(IO).to receive(:popen) do |command|
|
192
|
-
expect(command).to match(/.*mysqldump.*\| gzip/)
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|
196
|
-
end
|
197
|
-
|
198
92
|
describe '#filename' do
|
199
93
|
it 'calculates a filename based on the name and timestamp' do
|
200
94
|
timestamp = Time.now.utc.strftime('%Y%m%d%H%M')
|
@@ -210,50 +104,32 @@ describe Stratocumulus::Database do
|
|
210
104
|
end
|
211
105
|
|
212
106
|
describe '#success?' do
|
213
|
-
|
214
|
-
|
215
|
-
{ 'name' => 'foo', 'type' => 'mysql' },
|
216
|
-
backend
|
217
|
-
)
|
218
|
-
end
|
219
|
-
|
220
|
-
context 'the backend fails' do
|
221
|
-
let(:backend) { FailingBackend }
|
107
|
+
describe 'the dump fails' do
|
108
|
+
subject { FailingDatabase.new('name' => 'test_database') }
|
222
109
|
|
223
110
|
it 'returns false' do
|
224
111
|
expect(subject).to_not be_success
|
225
112
|
end
|
226
|
-
|
227
113
|
end
|
228
114
|
|
229
|
-
|
230
|
-
|
115
|
+
describe 'the dump is sucessfull' do
|
116
|
+
subject { SucessDatabase.new('name' => 'test_database') }
|
231
117
|
|
232
|
-
it 'returns
|
118
|
+
it 'returns false' do
|
233
119
|
expect(subject).to be_success
|
234
120
|
end
|
235
|
-
|
236
|
-
end
|
237
|
-
|
238
|
-
class FakeBackend
|
239
|
-
def initialize(*)
|
240
|
-
end
|
241
|
-
|
242
|
-
def dependencies
|
243
|
-
[]
|
244
|
-
end
|
245
121
|
end
|
122
|
+
end
|
246
123
|
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
end
|
124
|
+
class SucessDatabase < described_class
|
125
|
+
def command
|
126
|
+
'echo boo'
|
251
127
|
end
|
128
|
+
end
|
252
129
|
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
end
|
130
|
+
class FailingDatabase < described_class
|
131
|
+
def command
|
132
|
+
'exit 127'
|
257
133
|
end
|
258
134
|
end
|
259
135
|
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Stratocumulus::MySQL do
|
5
|
+
subject do
|
6
|
+
described_class.new(config)
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:config) do
|
10
|
+
{ 'name' => 'stratocumulus_test' }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#dependencies' do
|
14
|
+
specify do
|
15
|
+
expect(subject.dependencies).to eq %w(gzip mysqldump)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#command' do
|
20
|
+
context 'default' do
|
21
|
+
it 'generates the dump command with sensible defaults' do
|
22
|
+
expect(subject.command).to eq(
|
23
|
+
'mysqldump --single-transaction -uroot stratocumulus_test'
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'with the password set' do
|
29
|
+
let(:config) do
|
30
|
+
{
|
31
|
+
'name' => 'stratocumulus_test',
|
32
|
+
'password' => 'seecrit'
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'generates the dump command with a default host' do
|
37
|
+
expect(subject.command).to eq(
|
38
|
+
'mysqldump --single-transaction -uroot -pseecrit stratocumulus_test'
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'with the port set' do
|
44
|
+
let(:config) do
|
45
|
+
{
|
46
|
+
'name' => 'stratocumulus_test',
|
47
|
+
'port' => 13_306
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'generates the dump command with a default host' do
|
52
|
+
expect(subject.command).to eq(
|
53
|
+
'mysqldump --single-transaction -uroot -hlocalhost -P13306 stratocumulus_test' # rubocop:disable Style/LineLength
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'with the host set' do
|
59
|
+
let(:config) do
|
60
|
+
{
|
61
|
+
'name' => 'stratocumulus_test',
|
62
|
+
'host' => 'db.example.com'
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'generates the dump command with a default port' do
|
67
|
+
expect(subject.command).to eq(
|
68
|
+
'mysqldump --single-transaction -uroot -hdb.example.com -P3306 stratocumulus_test' # rubocop:disable Style/LineLength
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'with the port and host set' do
|
74
|
+
let(:config) do
|
75
|
+
{
|
76
|
+
'name' => 'stratocumulus_test',
|
77
|
+
'port' => 33_306,
|
78
|
+
'host' => 'db.example.com'
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'generates the dump command with the port and host' do
|
83
|
+
expect(subject.command).to eq(
|
84
|
+
'mysqldump --single-transaction -uroot -hdb.example.com -P33306 stratocumulus_test' # rubocop:disable Style/LineLength
|
85
|
+
)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'with the username set' do
|
90
|
+
let(:config) do
|
91
|
+
{
|
92
|
+
'name' => 'stratocumulus_test',
|
93
|
+
'username' => 'susan'
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'generates the dump command with the username' do
|
98
|
+
expect(subject.command).to eq(
|
99
|
+
'mysqldump --single-transaction -ususan stratocumulus_test'
|
100
|
+
)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Stratocumulus::PostgreSQL do
|
5
|
+
subject do
|
6
|
+
described_class.new(config)
|
7
|
+
end
|
8
|
+
|
9
|
+
let(:config) do
|
10
|
+
{ 'name' => 'stratocumulus_test' }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe '#dependencies' do
|
14
|
+
specify do
|
15
|
+
expect(subject.dependencies).to eq %w(gzip pg_dump)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#command' do
|
20
|
+
context 'default' do
|
21
|
+
it 'generates the dump command with sensible defaults' do
|
22
|
+
expect(subject.command).to eq(
|
23
|
+
'pg_dump -Upostgres stratocumulus_test'
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context 'with the password set' do
|
29
|
+
let(:config) do
|
30
|
+
{
|
31
|
+
'name' => 'stratocumulus_test',
|
32
|
+
'password' => 'sekret'
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'sets the password env var' do
|
37
|
+
expect(subject.command).to eq(
|
38
|
+
'PGPASSWORD="sekret" pg_dump -Upostgres stratocumulus_test'
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'with the port set' do
|
44
|
+
let(:config) do
|
45
|
+
{
|
46
|
+
'name' => 'stratocumulus_test',
|
47
|
+
'port' => 15_432
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'generates the dump command with a default host' do
|
52
|
+
expect(subject.command).to eq(
|
53
|
+
'pg_dump -Upostgres -hlocalhost -p15432 stratocumulus_test'
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'with the host set' do
|
59
|
+
let(:config) do
|
60
|
+
{
|
61
|
+
'name' => 'stratocumulus_test',
|
62
|
+
'host' => 'db.example.com'
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'generates the dump command with a default port' do
|
67
|
+
expect(subject.command).to eq(
|
68
|
+
'pg_dump -Upostgres -hdb.example.com -p5432 stratocumulus_test'
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context 'with the port and host set' do
|
74
|
+
let(:config) do
|
75
|
+
{
|
76
|
+
'name' => 'stratocumulus_test',
|
77
|
+
'port' => 15_432,
|
78
|
+
'host' => 'db.example.com'
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'generates the dump command with port and host set' do
|
83
|
+
expect(subject.command).to eq(
|
84
|
+
'pg_dump -Upostgres -hdb.example.com -p15432 stratocumulus_test'
|
85
|
+
)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context 'with the username set' do
|
90
|
+
let(:config) do
|
91
|
+
{
|
92
|
+
'name' => 'stratocumulus_test',
|
93
|
+
'username' => 'susan'
|
94
|
+
}
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'generates the dump command with the username' do
|
98
|
+
expect(subject.command).to eq(
|
99
|
+
'pg_dump -Ususan stratocumulus_test'
|
100
|
+
)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
data/spec/unit/storage_spec.rb
CHANGED
@@ -30,12 +30,18 @@ describe Stratocumulus::Storage do
|
|
30
30
|
)
|
31
31
|
end
|
32
32
|
|
33
|
+
let(:stderr) do
|
34
|
+
capture_stderr do
|
35
|
+
subject.upload(database)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
33
39
|
before do
|
34
40
|
allow(Fog::Storage).to receive(:new).and_return(connection)
|
35
41
|
end
|
36
42
|
|
37
43
|
after do
|
38
|
-
|
44
|
+
stderr
|
39
45
|
end
|
40
46
|
|
41
47
|
it 'uploads the dump to s3' do
|
@@ -122,6 +128,12 @@ describe Stratocumulus::Storage do
|
|
122
128
|
it 'does not create a expiry rule' do
|
123
129
|
expect(service).to_not receive(:put_bucket_lifecycle)
|
124
130
|
end
|
131
|
+
|
132
|
+
it 'logs the error to stderr' do
|
133
|
+
expect(stderr).to include(
|
134
|
+
'ERROR -- : there was an error generating foo.sql.gz'
|
135
|
+
)
|
136
|
+
end
|
125
137
|
end
|
126
138
|
|
127
139
|
context 'rules allready set on the bucket' do
|
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.5
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-06-
|
12
|
+
date: 2014-06-24 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: fog
|
@@ -162,17 +162,23 @@ files:
|
|
162
162
|
- lib/stratocumulus/database.rb
|
163
163
|
- lib/stratocumulus/database/mysql.rb
|
164
164
|
- lib/stratocumulus/database/pipe_io.rb
|
165
|
+
- lib/stratocumulus/database/postgresql.rb
|
166
|
+
- lib/stratocumulus/database/rethinkdb.rb
|
165
167
|
- lib/stratocumulus/retention.rb
|
166
168
|
- lib/stratocumulus/runner.rb
|
167
169
|
- lib/stratocumulus/storage.rb
|
168
170
|
- lib/stratocumulus/version.rb
|
169
171
|
- spec/intergration/database_spec.rb
|
170
172
|
- spec/spec_helper.rb
|
171
|
-
- spec/support/
|
173
|
+
- spec/support/mysql.sql
|
174
|
+
- spec/support/psql.sql
|
175
|
+
- spec/support/rethinkdb.tar.gz
|
172
176
|
- spec/support/test_config_file.yml
|
173
177
|
- spec/unit/cli_spec.rb
|
174
178
|
- spec/unit/database_spec.rb
|
179
|
+
- spec/unit/mysql_spec.rb
|
175
180
|
- spec/unit/pipe_io_spec.rb
|
181
|
+
- spec/unit/postgresql_spec.rb
|
176
182
|
- spec/unit/retention_spec.rb
|
177
183
|
- spec/unit/runner_spec.rb
|
178
184
|
- spec/unit/storage_spec.rb
|
@@ -192,7 +198,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
192
198
|
version: '0'
|
193
199
|
segments:
|
194
200
|
- 0
|
195
|
-
hash:
|
201
|
+
hash: 646644050683609050
|
196
202
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
197
203
|
none: false
|
198
204
|
requirements:
|
@@ -201,7 +207,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
201
207
|
version: '0'
|
202
208
|
segments:
|
203
209
|
- 0
|
204
|
-
hash:
|
210
|
+
hash: 646644050683609050
|
205
211
|
requirements: []
|
206
212
|
rubyforge_project:
|
207
213
|
rubygems_version: 1.8.25
|
@@ -211,11 +217,15 @@ summary: Backup Databases to Cloud Storage
|
|
211
217
|
test_files:
|
212
218
|
- spec/intergration/database_spec.rb
|
213
219
|
- spec/spec_helper.rb
|
214
|
-
- spec/support/
|
220
|
+
- spec/support/mysql.sql
|
221
|
+
- spec/support/psql.sql
|
222
|
+
- spec/support/rethinkdb.tar.gz
|
215
223
|
- spec/support/test_config_file.yml
|
216
224
|
- spec/unit/cli_spec.rb
|
217
225
|
- spec/unit/database_spec.rb
|
226
|
+
- spec/unit/mysql_spec.rb
|
218
227
|
- spec/unit/pipe_io_spec.rb
|
228
|
+
- spec/unit/postgresql_spec.rb
|
219
229
|
- spec/unit/retention_spec.rb
|
220
230
|
- spec/unit/runner_spec.rb
|
221
231
|
- spec/unit/storage_spec.rb
|