bibliotech 0.1.0
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.
- checksums.yaml +7 -0
- data/bin/bibliotech +5 -0
- data/doc/example_config_file.yml +58 -0
- data/doc/todo.txt +19 -0
- data/lib/bibliotech/application.rb +95 -0
- data/lib/bibliotech/backups/file_record.rb +16 -0
- data/lib/bibliotech/backups/prune_list.rb +58 -0
- data/lib/bibliotech/backups/pruner.rb +71 -0
- data/lib/bibliotech/backups/scheduler.rb +49 -0
- data/lib/bibliotech/builders/database.rb +25 -0
- data/lib/bibliotech/builders/file.rb +75 -0
- data/lib/bibliotech/builders/gzip.rb +51 -0
- data/lib/bibliotech/builders/mysql.rb +35 -0
- data/lib/bibliotech/builders/postgres.rb +37 -0
- data/lib/bibliotech/builders.rb +43 -0
- data/lib/bibliotech/cli.rb +24 -0
- data/lib/bibliotech/command_generator.rb +86 -0
- data/lib/bibliotech/command_runner.rb +36 -0
- data/lib/bibliotech/compression/bzip2.rb +6 -0
- data/lib/bibliotech/compression/gzip.rb +6 -0
- data/lib/bibliotech/compression/sevenzip.rb +5 -0
- data/lib/bibliotech/compression.rb +35 -0
- data/lib/bibliotech/config.rb +269 -0
- data/lib/bibliotech/rake_lib.rb +82 -0
- data/lib/bibliotech.rb +7 -0
- data/spec/bibliotech/backup_pruner_spec.rb +58 -0
- data/spec/bibliotech/backup_scheduler_spec.rb +108 -0
- data/spec/bibliotech/command_generator/mysql_spec.rb +170 -0
- data/spec/bibliotech/command_generator/postgres_spec.rb +180 -0
- data/spec/bibliotech/command_generator_spec.rb +99 -0
- data/spec/bibliotech/command_runner_spec.rb +50 -0
- data/spec/bibliotech/compression/bunzip2_spec.rb +9 -0
- data/spec/bibliotech/compression/bzip2_spec.rb +9 -0
- data/spec/bibliotech/compression/gzip_spec.rb +9 -0
- data/spec/bibliotech/compression/sevenzip_spec.rb +9 -0
- data/spec/bibliotech/compression_spec.rb +28 -0
- data/spec/bibliotech/config_spec.rb +151 -0
- data/spec/gem_test_suite.rb +0 -0
- data/spec/spec_helper.rb +2 -0
- metadata +150 -0
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'pry'
|
3
|
+
|
4
|
+
module BiblioTech
|
5
|
+
describe CommandGenerator, "for mysql" do
|
6
|
+
let :config do
|
7
|
+
Config.new(nil).tap do |config|
|
8
|
+
config.hash = config_hash
|
9
|
+
end
|
10
|
+
end
|
11
|
+
let :generator do
|
12
|
+
CommandGenerator.new(config)
|
13
|
+
end
|
14
|
+
let( :db_name ) { "db_name" }
|
15
|
+
let( :username ) { "user_name" }
|
16
|
+
let( :password ) { "password123" }
|
17
|
+
let( :host ) { "127.0.0.1" }
|
18
|
+
let( :filename ) { "export.sql" }
|
19
|
+
let( :path ) { "/some/path" }
|
20
|
+
|
21
|
+
let :base_config_hash do
|
22
|
+
{ "database_config" => {
|
23
|
+
"adapter" => :mysql,
|
24
|
+
"database" => db_name,
|
25
|
+
"username" => username
|
26
|
+
}}
|
27
|
+
end
|
28
|
+
let(:config_hash){ base_config_hash }
|
29
|
+
|
30
|
+
let(:options){ {} }
|
31
|
+
|
32
|
+
def first_cmd
|
33
|
+
command.commands[0]
|
34
|
+
end
|
35
|
+
|
36
|
+
def second_cmd
|
37
|
+
command.commands[1]
|
38
|
+
end
|
39
|
+
|
40
|
+
describe :export do
|
41
|
+
subject :command do
|
42
|
+
generator.export(options)
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'with username and database' do
|
46
|
+
context 'and password' do
|
47
|
+
let :config_hash do
|
48
|
+
#binding.pry
|
49
|
+
base_config_hash.tap do |hash|
|
50
|
+
hash["database_config"]["password"] = password
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it { is_expected.to be_a(Caliph::CommandLine) }
|
55
|
+
it { expect(command.executable).to eq('mysqldump') }
|
56
|
+
it { expect(command.options).to eq(["-u #{username}", "--password='#{password}'", "#{db_name}"]) }
|
57
|
+
end
|
58
|
+
|
59
|
+
context 'and hostname' do
|
60
|
+
let :config_hash do
|
61
|
+
base_config_hash.tap do |hash|
|
62
|
+
hash["database_config"]["host"] = host
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it { expect(command.options).to eq(["-h #{host}", "-u #{username}", "#{db_name}"]) }
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'plus filename and path' do
|
70
|
+
let :options do
|
71
|
+
{:backups => { :filename => filename, :dir => path}}
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'and compressor' do
|
75
|
+
let :options do
|
76
|
+
{ :backups => {
|
77
|
+
:filename => filename,
|
78
|
+
:dir => path,
|
79
|
+
:compress => :gzip
|
80
|
+
}}
|
81
|
+
end
|
82
|
+
|
83
|
+
it { expect(command).to be_a(Caliph::PipelineChain) }
|
84
|
+
it { expect(second_cmd.redirections).to eq([ "1>#{path}/#{filename}.gz" ]) }
|
85
|
+
|
86
|
+
context "first command" do
|
87
|
+
it { expect(first_cmd.executable).to eq('mysqldump') }
|
88
|
+
it { expect(first_cmd.options).to eq(["-u #{username}", "#{db_name}"]) }
|
89
|
+
end
|
90
|
+
context "second command" do
|
91
|
+
it { expect(second_cmd.executable).to eq('gzip') }
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
context 'with the whole shebang' do
|
97
|
+
let :options do
|
98
|
+
{ :backups => { :filename => filename, :dir => path, :compress => :gzip} }
|
99
|
+
end
|
100
|
+
let :config_hash do
|
101
|
+
base_config_hash.tap do |hash|
|
102
|
+
hash["database_config"].merge!( "host" => host, "password" => password)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
it { expect(second_cmd.redirections).to eq(["1>#{path}/#{filename}.gz"]) }
|
107
|
+
|
108
|
+
context "first command" do
|
109
|
+
it { expect(first_cmd.executable).to eq("mysqldump") }
|
110
|
+
it { expect(first_cmd.options).to eq(["-h #{host}", "-u #{username}", "--password='#{password}'", "#{db_name}"]) }
|
111
|
+
end
|
112
|
+
|
113
|
+
context "second command" do
|
114
|
+
it { expect(second_cmd.executable).to eq('gzip') }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
|
121
|
+
describe :import do
|
122
|
+
let :command do
|
123
|
+
generator.import(options)
|
124
|
+
end
|
125
|
+
|
126
|
+
subject do
|
127
|
+
command
|
128
|
+
end
|
129
|
+
|
130
|
+
context 'with username, database, file, and path' do
|
131
|
+
let :options do
|
132
|
+
{ :backups => { :filename => filename, :dir => path }}
|
133
|
+
end
|
134
|
+
|
135
|
+
it { expect(command).to be_a(Caliph::CommandLine) }
|
136
|
+
|
137
|
+
it { expect(command.redirections).to eq(["0<#{path}/#{filename}"]) }
|
138
|
+
it { expect(command.executable).to eq('mysql')}
|
139
|
+
it { expect(command.options).to eq(["-u #{username}", db_name ]) }
|
140
|
+
|
141
|
+
context "plus password" do
|
142
|
+
let :config_hash do
|
143
|
+
base_config_hash.tap do |hash|
|
144
|
+
hash["database_config"]["password"] = password
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
it { expect(command.options).to eq(["-u #{username}","--password='#{password}'", "#{db_name}"]) }
|
149
|
+
|
150
|
+
context 'and compressor' do
|
151
|
+
let :options do
|
152
|
+
{ :backups => {
|
153
|
+
:filename => filename + '.gz',
|
154
|
+
:dir => path,
|
155
|
+
:compress => :gzip
|
156
|
+
} }
|
157
|
+
end
|
158
|
+
|
159
|
+
it { expect(command).to be_a(Caliph::PipelineChain) }
|
160
|
+
it { expect(first_cmd.executable).to eq('gunzip') }
|
161
|
+
it { expect(first_cmd.options).to eq(["#{path}/#{filename}.gz"]) }
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module BiblioTech
|
4
|
+
describe CommandGenerator do
|
5
|
+
let :generator do
|
6
|
+
CommandGenerator.new(config)
|
7
|
+
end
|
8
|
+
|
9
|
+
let (:db_name){ "db_name" }
|
10
|
+
let (:username){ "user_name" }
|
11
|
+
let (:password){ "password123" }
|
12
|
+
let (:host){ "127.0.0.1" }
|
13
|
+
let (:filename){ "export.pg" }
|
14
|
+
let (:path){ "/some/path" }
|
15
|
+
|
16
|
+
|
17
|
+
let (:base_options){{}}
|
18
|
+
|
19
|
+
let :base_config_hash do
|
20
|
+
{ "database_config" =>
|
21
|
+
{
|
22
|
+
"adapter" => :postgres,
|
23
|
+
"database" => db_name,
|
24
|
+
"username" => username
|
25
|
+
}
|
26
|
+
}
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
let(:config_hash){ base_config_hash }
|
31
|
+
let(:options){ base_options }
|
32
|
+
|
33
|
+
let :config do
|
34
|
+
Config.new(nil).tap do |config|
|
35
|
+
config.hash = config_hash
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def first_cmd
|
40
|
+
command.commands[0]
|
41
|
+
end
|
42
|
+
|
43
|
+
def second_cmd
|
44
|
+
command.commands[1]
|
45
|
+
end
|
46
|
+
|
47
|
+
describe :export do
|
48
|
+
let :command do
|
49
|
+
generator.export(options)
|
50
|
+
end
|
51
|
+
|
52
|
+
subject do
|
53
|
+
command
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'with username and database' do
|
57
|
+
let :config_hash do
|
58
|
+
base_config_hash
|
59
|
+
end
|
60
|
+
|
61
|
+
context 'and password' do
|
62
|
+
let :config_hash do
|
63
|
+
base_config_hash.tap do |hash|
|
64
|
+
hash["database_config"]["password"] = password
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
it { is_expected.to be_a(Caliph::CommandLine) }
|
69
|
+
it { expect(command.executable).to eq('pg_dump') }
|
70
|
+
it { expect(command.options).to eq(["-Fc", "-U #{username}", "#{db_name}"]) }
|
71
|
+
it { expect(command.env['PGPASSWORD']).to eq(password) }
|
72
|
+
end
|
73
|
+
|
74
|
+
context 'and hostname' do
|
75
|
+
let :config_hash do
|
76
|
+
base_config_hash.tap do |hash|
|
77
|
+
hash["database_config"]["host"] = host
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
it { expect(command.options).to eq(["-Fc", "-h #{host}", "-U #{username}", "#{db_name}"]) }
|
82
|
+
end
|
83
|
+
|
84
|
+
context 'plus filename and path and compressor' do
|
85
|
+
let :options do
|
86
|
+
base_options.merge( :backups => {
|
87
|
+
:dir => path,
|
88
|
+
:filename => filename,
|
89
|
+
:compress => :gzip
|
90
|
+
})
|
91
|
+
end
|
92
|
+
|
93
|
+
it { expect(command).to be_a(Caliph::PipelineChain) }
|
94
|
+
it { expect(command.commands[1].redirections).to eq([ "1>#{path}/#{filename}.gz" ]) }
|
95
|
+
|
96
|
+
context "first command" do
|
97
|
+
it { expect(first_cmd.executable).to eq('pg_dump') }
|
98
|
+
it { expect(first_cmd.options).to eq(["-Fc", "-U #{username}", "#{db_name}"]) }
|
99
|
+
end
|
100
|
+
context "second command" do
|
101
|
+
it { expect(second_cmd.executable).to eq('gzip') }
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'with the whole shebang' do
|
106
|
+
let :options do
|
107
|
+
base_options.merge( :backups => {
|
108
|
+
:filename => filename,
|
109
|
+
:dir => path,
|
110
|
+
:compress => :gzip
|
111
|
+
})
|
112
|
+
end
|
113
|
+
let :config_hash do
|
114
|
+
base_config_hash.tap do |hash|
|
115
|
+
hash["database_config"] = hash["database_config"].merge({ "host" => host, "password" => password })
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
it { expect(second_cmd.redirections).to eq(["1>#{path}/#{filename}.gz"]) }
|
120
|
+
|
121
|
+
context "first command" do
|
122
|
+
it { expect(first_cmd.executable).to eq("pg_dump") }
|
123
|
+
it { expect(first_cmd.options).to eq(["-Fc", "-h #{host}", "-U #{username}", "#{db_name}"]) }
|
124
|
+
it { expect(first_cmd.env['PGPASSWORD']).to eq(password) }
|
125
|
+
end
|
126
|
+
|
127
|
+
context "second command" do
|
128
|
+
it { expect(second_cmd.executable).to eq('gzip') }
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
|
135
|
+
describe :import do
|
136
|
+
let :command do
|
137
|
+
generator.import(options)
|
138
|
+
end
|
139
|
+
|
140
|
+
subject do
|
141
|
+
command
|
142
|
+
end
|
143
|
+
|
144
|
+
context 'with username, database, file, and path' do
|
145
|
+
let :options do
|
146
|
+
base_options.merge(:backups => { :filename => filename, :dir => path })
|
147
|
+
end
|
148
|
+
|
149
|
+
it { expect(command.redirections).to eq(["0<#{path}/#{filename}"]) }
|
150
|
+
it { expect(command.executable).to eq('pg_restore')}
|
151
|
+
it { expect(command.options).to eq(["-U #{username}", "-d #{db_name}" ]) }
|
152
|
+
|
153
|
+
context "plus password" do
|
154
|
+
let :config_hash do
|
155
|
+
base_config_hash.tap do |hash|
|
156
|
+
hash["database_config"]["password"] = password
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
it { expect(command.options).to eq(["-U #{username}", "-d #{db_name}"]) }
|
161
|
+
it { expect(command.env['PGPASSWORD']).to eq(password) }
|
162
|
+
|
163
|
+
context 'and compressor' do
|
164
|
+
let :options do
|
165
|
+
base_options.merge(:backups => {
|
166
|
+
:filename => filename + '.gz',
|
167
|
+
:dir => path,
|
168
|
+
:compressor => :gzip
|
169
|
+
})
|
170
|
+
end
|
171
|
+
|
172
|
+
it { expect(command).to be_a(Caliph::PipelineChain) }
|
173
|
+
it { expect(first_cmd.executable).to eq('gunzip') }
|
174
|
+
it { expect(first_cmd.options).to eq(["#{path}/#{filename}.gz"]) }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'bibliotech/application'
|
3
|
+
|
4
|
+
module BiblioTech
|
5
|
+
describe CommandGenerator do
|
6
|
+
describe "class methods", :pending => true do
|
7
|
+
describe "adapter lookup" do
|
8
|
+
class CommandOne < CommandGenerator; end;
|
9
|
+
class CommandTwo < CommandGenerator; end;
|
10
|
+
let :class_1 do CommandOne end
|
11
|
+
let :class_2 do CommandTwo end
|
12
|
+
let :config do { :adapter => :type_1 } end
|
13
|
+
let :return_adapter do double(CommandGenerator) end
|
14
|
+
|
15
|
+
before do
|
16
|
+
CommandGenerator.class_eval do
|
17
|
+
@adapter_registry = nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "with one class registered" do
|
22
|
+
before do
|
23
|
+
CommandGenerator.register(:type_1, CommandOne)
|
24
|
+
end
|
25
|
+
it "should list single supported adapter" do
|
26
|
+
expect(CommandGenerator.supported_adapters()).to eq([:type_1])
|
27
|
+
end
|
28
|
+
it "should return the correct adapter, instantiated with the config" do
|
29
|
+
expect(CommandOne).to receive(:new).and_return(return_adapter)
|
30
|
+
expect(CommandGenerator.for(config)).to eq(return_adapter)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "with two classes registered" do
|
35
|
+
before do
|
36
|
+
CommandGenerator.register(:type_1, CommandOne)
|
37
|
+
CommandGenerator.register(:type_2, CommandTwo)
|
38
|
+
end
|
39
|
+
it "should return a single registered class" do
|
40
|
+
expect(CommandGenerator.supported_adapters()).to include(:type_1, :type_2)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
context "with one class registered twice" do
|
44
|
+
before do
|
45
|
+
CommandGenerator.register(:type_1, CommandOne)
|
46
|
+
CommandGenerator.register(:type_1a, CommandOne)
|
47
|
+
end
|
48
|
+
it "should list single supported adapter" do
|
49
|
+
expect(CommandGenerator.supported_adapters()).to eq([:type_1, :type_1a])
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe "skeleton methods" do
|
56
|
+
let :generator do
|
57
|
+
CommandGenerator.new(config)
|
58
|
+
end
|
59
|
+
|
60
|
+
let :app do
|
61
|
+
Application.new
|
62
|
+
end
|
63
|
+
|
64
|
+
let :config do
|
65
|
+
app.config
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should produce a remote_cli command" do
|
69
|
+
expect(generator.remote_cli("staging", "latest").command).to match(/\Assh.*-- '.*bibliotech latest'\z/)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should produce a fetch command" do
|
73
|
+
expect(generator.fetch("staging", "latest.sql.gz").command).to match(/\Ascp.*@.*latest\.sql\.gz.*latest\.sql\.gz\z/)
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should produce a push command" do
|
77
|
+
expect(generator.push("staging", "latest.sql.gz").command).to match(/\Ascp.*latest\.sql\.gz.*@.*latest\.sql\.gz\z/)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should raise_error an error when calling wipe" do
|
81
|
+
expect do
|
82
|
+
generator.wipe()
|
83
|
+
end.to raise_error(NotImplementedError)
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should raise_error an error when calling delete" do
|
87
|
+
expect do
|
88
|
+
generator.delete()
|
89
|
+
end.to raise_error(NotImplementedError)
|
90
|
+
end
|
91
|
+
|
92
|
+
it "should raise_error an error when calling create" do
|
93
|
+
expect do
|
94
|
+
generator.create()
|
95
|
+
end.to raise_error(NotImplementedError)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'bibliotech/command_runner'
|
3
|
+
|
4
|
+
|
5
|
+
module BiblioTech
|
6
|
+
describe CommandRunner do
|
7
|
+
let :config do
|
8
|
+
double(Config,
|
9
|
+
:db_config => {},
|
10
|
+
:environment => :development
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
let :generator do
|
15
|
+
double(CommandGenerator,
|
16
|
+
:export => 'export command'
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
let :command do "some cli command" end
|
21
|
+
|
22
|
+
let :shell do
|
23
|
+
double("Caliph::Shell")
|
24
|
+
end
|
25
|
+
|
26
|
+
let :runner do
|
27
|
+
CommandRunner.new(config).tap do |runner|
|
28
|
+
runner.shell = shell
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
before do
|
33
|
+
allow(CommandGenerator).to receive(:for).and_return(generator)
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "single commands" do
|
37
|
+
it 'should do export' do
|
38
|
+
expect(generator).to receive(:export).and_return(command)
|
39
|
+
expect(shell).to receive(:run).with(command)
|
40
|
+
runner.export('path/to/file')
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'should do import' do
|
44
|
+
expect(generator).to receive(:import).and_return(command)
|
45
|
+
expect(shell).to receive(:run).with(command)
|
46
|
+
runner.import('path/to/file')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module BiblioTech
|
4
|
+
describe Compression do
|
5
|
+
|
6
|
+
let :generator do
|
7
|
+
double(CommandGenerator)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe 'for' do
|
11
|
+
it do
|
12
|
+
expect(Compression.for("my_archive.sql.gz", generator)).to be_a(Compression::Gzip)
|
13
|
+
end
|
14
|
+
|
15
|
+
it do
|
16
|
+
expect(Compression.for("my_archive.sql.bz2", generator)).to be_a(Compression::Bzip2)
|
17
|
+
end
|
18
|
+
|
19
|
+
it do
|
20
|
+
expect(Compression.for("my_archive.sql.7z", generator)).to be_a(Compression::SevenZip)
|
21
|
+
end
|
22
|
+
|
23
|
+
it do
|
24
|
+
expect(Compression.for("my_archive.sql", generator)).to equal(generator)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|