backzilla 0.0.1
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/.gitignore +1 -0
- data/COPYING +340 -0
- data/LICENSE +57 -0
- data/README.markdown +70 -0
- data/Rakefile +28 -0
- data/VERSION +2 -0
- data/backzilla.gemspec +85 -0
- data/bin/backzilla +40 -0
- data/examples/projects.yaml +31 -0
- data/examples/stores.yaml +12 -0
- data/lib/backzilla.rb +116 -0
- data/lib/backzilla/configuration.rb +6 -0
- data/lib/backzilla/connection.rb +5 -0
- data/lib/backzilla/entity.rb +31 -0
- data/lib/backzilla/entity/directory.rb +46 -0
- data/lib/backzilla/entity/mongo_db.rb +47 -0
- data/lib/backzilla/entity/my_sql.rb +68 -0
- data/lib/backzilla/executor.rb +17 -0
- data/lib/backzilla/logger_helper.rb +31 -0
- data/lib/backzilla/project.rb +41 -0
- data/lib/backzilla/store.rb +49 -0
- data/lib/backzilla/store/directory.rb +21 -0
- data/lib/backzilla/store/ftp.rb +37 -0
- data/lib/backzilla/store/ssh.rb +37 -0
- data/lib/backzilla/version.rb +4 -0
- data/spec/configs/directory/projects.yaml +5 -0
- data/spec/configs/directory/stores.yaml +6 -0
- data/spec/configs/mongodb/projects.yaml +5 -0
- data/spec/configs/mongodb/stores.yaml +11 -0
- data/spec/configs/mysql/projects.yaml +7 -0
- data/spec/configs/mysql/stores.yaml +11 -0
- data/spec/entities/directory_spec.rb +46 -0
- data/spec/entities/mongodb_spec.rb +104 -0
- data/spec/entities/mysql_spec.rb +126 -0
- data/spec/fixtures/directory/a.txt +2 -0
- data/spec/fixtures/directory/b.txt +1 -0
- data/spec/fixtures/directory/some/nested/stuff/c.txt +1 -0
- data/spec/fixtures/mysql/backzilla_test.sql +54 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +47 -0
- metadata +98 -0
@@ -0,0 +1,68 @@
|
|
1
|
+
class Backzilla::Entity::MySQL < Backzilla::Entity
|
2
|
+
include Backzilla::Executor
|
3
|
+
|
4
|
+
def initialize(name, options)
|
5
|
+
super(name)
|
6
|
+
@database = options['database']
|
7
|
+
@mysql_options = {
|
8
|
+
'user' => options['user'],
|
9
|
+
'password' => options['password'],
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def prepare_backup
|
14
|
+
if @database.blank?
|
15
|
+
fatal "Database name is blank"
|
16
|
+
exit -1
|
17
|
+
end
|
18
|
+
path = Pathname.new(BASE_PATH) + project.name + name
|
19
|
+
FileUtils.mkdir_p path
|
20
|
+
filename = path + "#{@database}.sql"
|
21
|
+
|
22
|
+
cmd = "mysqldump #{mysql_options} #{@database} > #{filename}"
|
23
|
+
execute cmd
|
24
|
+
filename
|
25
|
+
end
|
26
|
+
|
27
|
+
def backup
|
28
|
+
prepare_backup
|
29
|
+
backup_msg
|
30
|
+
path = Pathname.new(BASE_PATH) + project.name + name
|
31
|
+
Backzilla.store path, project.name, self.name
|
32
|
+
|
33
|
+
FileUtils.rm_rf path
|
34
|
+
end
|
35
|
+
|
36
|
+
def finalize_restore(options={})
|
37
|
+
path = options[:path] + "*.sql"
|
38
|
+
Dir.glob(path).each do |dir|
|
39
|
+
cmd = "mysql #{mysql_options} #{@database} <"+dir
|
40
|
+
execute cmd
|
41
|
+
end
|
42
|
+
FileUtils.rm_rf options[:path]
|
43
|
+
end
|
44
|
+
|
45
|
+
def restore
|
46
|
+
filename = ""
|
47
|
+
restore_msg
|
48
|
+
path = Pathname.new(BASE_PATH) + project.name + name
|
49
|
+
FileUtils.mkdir_p path
|
50
|
+
filename = path + "#{@database}.sql"
|
51
|
+
|
52
|
+
Backzilla.restore path, project.name, self.name
|
53
|
+
finalize_restore(:path => path)
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def mysql_options
|
59
|
+
@mysql_options.inject([]) do |options, mysql_option|
|
60
|
+
name, value = *mysql_option
|
61
|
+
unless value.blank?
|
62
|
+
options << "--#{name}=#{value}"
|
63
|
+
end
|
64
|
+
options
|
65
|
+
end.join(' ')
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Backzilla::Executor
|
2
|
+
def execute(cmd)
|
3
|
+
debug cmd
|
4
|
+
pid, stdin, stdout, stderr = Open4.popen4(cmd)
|
5
|
+
ignored, status = Process::waitpid2 pid
|
6
|
+
out = stdout.read
|
7
|
+
debug out unless out.blank?
|
8
|
+
err = stderr.read
|
9
|
+
if status.exitstatus != 0
|
10
|
+
fatal err unless err.blank?
|
11
|
+
exit -1
|
12
|
+
else
|
13
|
+
debug err unless err.blank?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Backzilla::LoggerHelper
|
2
|
+
[:fatal, :error].each do |method_name|
|
3
|
+
severity = Logger.const_get(method_name.to_s.upcase)
|
4
|
+
define_method(method_name) do |msg|
|
5
|
+
log severity, msg
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
[:warn, :info].each do |method_name|
|
10
|
+
severity = Logger.const_get(method_name.to_s.upcase)
|
11
|
+
define_method(method_name) do |msg|
|
12
|
+
unless Backzilla.options.quiet
|
13
|
+
log severity, msg
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def debug(msg)
|
19
|
+
if !Backzilla.options.quiet && Backzilla.options.debug
|
20
|
+
log Logger::DEBUG, msg
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def log(severity, msg)
|
27
|
+
msg = msg.pretty_inspect unless String === msg
|
28
|
+
Backzilla.logger.log severity, msg
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,41 @@
|
|
1
|
+
class Backzilla::Project
|
2
|
+
include Backzilla::LoggerHelper
|
3
|
+
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
def initialize(name)
|
7
|
+
@name = name
|
8
|
+
@entities = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def <<(entity)
|
12
|
+
entity.project = self
|
13
|
+
@entities[entity.name] = entity
|
14
|
+
end
|
15
|
+
|
16
|
+
def setup_entities(entities_data)
|
17
|
+
entities_data.each do |entity_name, entity_data|
|
18
|
+
klass = Backzilla::Entity.const_get(entity_data['type'])
|
19
|
+
self << klass.new(entity_name, entity_data)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def backup(spec_parts=[])
|
24
|
+
info "Project #{name}:"
|
25
|
+
if spec_parts.empty?
|
26
|
+
@entities.each { |name, entity| entity.backup }
|
27
|
+
else
|
28
|
+
@entities[spec_parts.shift].backup
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def restore(spec_parts=[])
|
33
|
+
info "Project #{name}:"
|
34
|
+
if spec_parts.empty?
|
35
|
+
@entities.each { |name, entity| entity.restore }
|
36
|
+
else
|
37
|
+
@entities[spec_parts.shift].restore
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
@@ -0,0 +1,49 @@
|
|
1
|
+
class Backzilla::Store
|
2
|
+
autoload :Directory, 'backzilla/store/directory'
|
3
|
+
autoload :FTP, 'backzilla/store/ftp'
|
4
|
+
autoload :SSH, 'backzilla/store/ssh'
|
5
|
+
|
6
|
+
include Backzilla::LoggerHelper
|
7
|
+
include Backzilla::Executor
|
8
|
+
|
9
|
+
attr_reader :name
|
10
|
+
|
11
|
+
def initialize(name)
|
12
|
+
@name = name
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.gnugpg_passphrase=(value)
|
16
|
+
@@gnugpg_passphrase = value
|
17
|
+
end
|
18
|
+
|
19
|
+
def put(source_path, project_name, entity_name)
|
20
|
+
cmd =<<-CMD
|
21
|
+
PASSPHRASE='#{@@gnugpg_passphrase}' #{env_options} \\
|
22
|
+
duplicity #{source_path} #{protocol}://#{uri}/#{project_name}/#{entity_name}
|
23
|
+
CMD
|
24
|
+
execute cmd
|
25
|
+
end
|
26
|
+
|
27
|
+
def get(source_path, project_name, entity_name)
|
28
|
+
cmd =<<-CMD
|
29
|
+
PASSPHRASE='#{@@gnugpg_passphrase}' #{env_options} \\
|
30
|
+
duplicity restore #{protocol}://#{uri}/#{project_name}/#{entity_name} #{source_path}
|
31
|
+
CMD
|
32
|
+
execute cmd
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def env_options
|
38
|
+
env_options = environment_options.inject('') do |m, option|
|
39
|
+
name, value = *option
|
40
|
+
next if value.blank?
|
41
|
+
m << "#{name.to_s.upcase}='#{value}' "
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def environment_options
|
46
|
+
{}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Backzilla::Store::Directory < Backzilla::Store
|
2
|
+
def initialize(name, options)
|
3
|
+
super(name)
|
4
|
+
@path = options['path']
|
5
|
+
end
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def protocol
|
10
|
+
'file'
|
11
|
+
end
|
12
|
+
|
13
|
+
def uri
|
14
|
+
if @path.blank?
|
15
|
+
fatal "Missing path option"
|
16
|
+
exit -1
|
17
|
+
end
|
18
|
+
@path
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# NcFTP required
|
2
|
+
class Backzilla::Store::FTP < Backzilla::Store
|
3
|
+
def initialize(name, options)
|
4
|
+
super(name)
|
5
|
+
@path = options['path']
|
6
|
+
@host = options['host']
|
7
|
+
@user = options['user']
|
8
|
+
@password = options['password']
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def protocol
|
14
|
+
'ftp'
|
15
|
+
end
|
16
|
+
|
17
|
+
def uri
|
18
|
+
if @path.blank?
|
19
|
+
fatal "Missing path option"
|
20
|
+
exit -1
|
21
|
+
end
|
22
|
+
if @host.blank?
|
23
|
+
fatal "Missing host option"
|
24
|
+
exit -1
|
25
|
+
end
|
26
|
+
|
27
|
+
uri = ''
|
28
|
+
uri << "#{@user}@" unless @user.blank?
|
29
|
+
uri << @host
|
30
|
+
uri << @path
|
31
|
+
end
|
32
|
+
|
33
|
+
def environment_options
|
34
|
+
{:FTP_PASSWORD => @password}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
|
3
|
+
class Backzilla::Store::SSH < Backzilla::Store
|
4
|
+
def initialize(name, options)
|
5
|
+
super(name)
|
6
|
+
@path = options['path']
|
7
|
+
@host = options['host']
|
8
|
+
@user = options['user']
|
9
|
+
end
|
10
|
+
|
11
|
+
def put(source_path, project_name, entity_name)
|
12
|
+
Net::SSH.start(@host, @user ) do |ssh|
|
13
|
+
ssh.exec "mkdir -p " + @path.to_s + "/#{project_name}/#{entity_name}"
|
14
|
+
end
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def protocol
|
21
|
+
'ssh'
|
22
|
+
end
|
23
|
+
|
24
|
+
def uri
|
25
|
+
if @path.blank?
|
26
|
+
fatal "Missing path option"
|
27
|
+
exit -1
|
28
|
+
end
|
29
|
+
|
30
|
+
uri = ''
|
31
|
+
uri << "#{@user}@" unless @user.blank?
|
32
|
+
uri << @host
|
33
|
+
uri << "/"
|
34
|
+
uri << @path
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
def remove_files
|
4
|
+
FileUtils.rm_rf "/tmp/backzilla/"
|
5
|
+
FileUtils.rm_rf "spec/backups/"
|
6
|
+
FileUtils.rm "spec/fixtures/file.md5"
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "Backzilla", "Directory" do
|
10
|
+
before :each do
|
11
|
+
prefix_configs "directory"
|
12
|
+
directory_path
|
13
|
+
setup_directory
|
14
|
+
end
|
15
|
+
|
16
|
+
after(:all) do
|
17
|
+
remove_files
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should prepare files to be backuped up" do
|
21
|
+
if !File.exist? "spec/fixtures/file.md5"
|
22
|
+
FileUtils.touch "spec/fixtures/file.md5"
|
23
|
+
else
|
24
|
+
FileUtils.rm "spec/fixtures/file.md5"
|
25
|
+
FileUtils.touch "spec/fixtures/file.md5"
|
26
|
+
end
|
27
|
+
cmd =<<-CMD
|
28
|
+
find #{directory_path} -type f -print0 | xargs -0 md5sum >> spec/fixtures/file.md5
|
29
|
+
CMD
|
30
|
+
`#{cmd}`
|
31
|
+
run_backzilla(:option => "-b", :project_name => "test")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should restore files" do
|
35
|
+
run_backzilla(:option => "-r", :project_name => "test")
|
36
|
+
cmd =<<-CMD
|
37
|
+
cp spec/fixtures/file.md5 /tmp/backzilla
|
38
|
+
cd #{directory_path}
|
39
|
+
sh -c "LANG=en md5sum -c /tmp/backzilla/file.md5 | grep -v OK"
|
40
|
+
CMD
|
41
|
+
$md5sum_result = `#{cmd}`
|
42
|
+
$md5sum_result.should == ""
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require 'spec/spec_helper'
|
2
|
+
|
3
|
+
PROJECTS_CONFIG = 'spec/configs/mysql/projects.yaml'
|
4
|
+
|
5
|
+
def create_mongodb_database
|
6
|
+
cmd =<<-CMD
|
7
|
+
echo "use backzilla_test
|
8
|
+
db.users.insert({name: 'Paweł', email: 'cokolwiek@amberbit.com', password: 'qweqwe'})
|
9
|
+
db.users.insert({name: 'Łukasz1', email: 'cokolwiek@amberbit.com', password: 'qweqwe'})
|
10
|
+
db.users.insert({name: 'Łukasz2', email: 'cokolwiek@amberbit.com', password: 'qweqwe'})
|
11
|
+
db.users.insert({name: 'Wojtek', email: 'cokolwiek@amberbit.com', password: 'qweqwe'})
|
12
|
+
db.users.insert({name: 'Marcin', email: 'cokolwiek@amberbit.com', password: 'qweqwe'})
|
13
|
+
db.users.insert({name: 'Hubert', email: 'cokolwiek@amberbit.com', password: 'qweqwe'})" |\
|
14
|
+
mongo
|
15
|
+
CMD
|
16
|
+
`#{cmd}`
|
17
|
+
end
|
18
|
+
|
19
|
+
def modify_mongodb_database
|
20
|
+
cmd =<<-CMD
|
21
|
+
echo "use backzilla_test
|
22
|
+
db.users.update({name: \\"Paweł\\"},{\\$set:{name: \\"Wiesław\\"}})
|
23
|
+
db.users.find()" |\
|
24
|
+
mongo
|
25
|
+
CMD
|
26
|
+
`#{cmd}`
|
27
|
+
end
|
28
|
+
|
29
|
+
def drop_mongodb_database
|
30
|
+
cmd =<<-CMD
|
31
|
+
echo "use backzilla_test
|
32
|
+
db.dropDatabase()" |\
|
33
|
+
mongo
|
34
|
+
CMD
|
35
|
+
`#{cmd}`
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "Backzilla", "mongodb", "backup preparation" do
|
39
|
+
before :each do
|
40
|
+
projects_file = File.expand_path PROJECTS_CONFIG
|
41
|
+
data = YAML.load_file projects_file
|
42
|
+
projects = data.inject([]) do |projects, project_data|
|
43
|
+
project_name, project_entities_data = *project_data
|
44
|
+
data[project_name].each do |entity_name, entity_data|
|
45
|
+
@mongodb = Backzilla::Entity::MongoDB.new('test', entity_data)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
create_mongodb_database
|
49
|
+
@mongodb.project = Backzilla::Project.new('test')
|
50
|
+
end
|
51
|
+
|
52
|
+
it "schoule prepare database to be backed up" do
|
53
|
+
path = Pathname.new(@mongodb.prepare_backup)
|
54
|
+
path.should == Pathname.new("/tmp/backzilla/test/test")
|
55
|
+
|
56
|
+
cmd =<<-CMD
|
57
|
+
cd /tmp/backzilla/test/test/backzilla_test/
|
58
|
+
md5sum users.bson
|
59
|
+
CMD
|
60
|
+
$md5sum_before_backup = `#{cmd}`
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "Backzilla", "mongodb", "finalize restore" do
|
65
|
+
before :each do
|
66
|
+
projects_file = File.expand_path PROJECTS_CONFIG
|
67
|
+
data = YAML.load_file projects_file
|
68
|
+
projects = data.inject([]) do |projects, project_data|
|
69
|
+
project_name, project_entities_data = *project_data
|
70
|
+
data[project_name].each do |entity_name, entity_data|
|
71
|
+
@mongodb = Backzilla::Entity::MongoDB.new('test', entity_data)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
@mongodb.project = Backzilla::Project.new('test')
|
75
|
+
end
|
76
|
+
|
77
|
+
after(:all) do
|
78
|
+
drop_mongodb_database
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should restore database from given file" do
|
82
|
+
modify_mongodb_database
|
83
|
+
@mongodb.finalize_restore(:path => '/tmp/backzilla/test/test/')
|
84
|
+
cmd =<<-CMD
|
85
|
+
echo "use backzilla_test
|
86
|
+
db.users.find()" |\
|
87
|
+
mongo
|
88
|
+
CMD
|
89
|
+
tmp = `#{cmd}`
|
90
|
+
tmp.should_not include "Wiesiek"
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should restore the exact same database" do
|
94
|
+
path = Pathname.new(@mongodb.prepare_backup)
|
95
|
+
cmd =<<-CMD
|
96
|
+
cd /tmp/backzilla/test/test/backzilla_test/
|
97
|
+
md5sum users.bson
|
98
|
+
CMD
|
99
|
+
$md5sum_after_backup = `#{cmd}`
|
100
|
+
|
101
|
+
$md5sum_before_backup.should == $md5sum_after_backup
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|