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.
Files changed (41) hide show
  1. data/.gitignore +1 -0
  2. data/COPYING +340 -0
  3. data/LICENSE +57 -0
  4. data/README.markdown +70 -0
  5. data/Rakefile +28 -0
  6. data/VERSION +2 -0
  7. data/backzilla.gemspec +85 -0
  8. data/bin/backzilla +40 -0
  9. data/examples/projects.yaml +31 -0
  10. data/examples/stores.yaml +12 -0
  11. data/lib/backzilla.rb +116 -0
  12. data/lib/backzilla/configuration.rb +6 -0
  13. data/lib/backzilla/connection.rb +5 -0
  14. data/lib/backzilla/entity.rb +31 -0
  15. data/lib/backzilla/entity/directory.rb +46 -0
  16. data/lib/backzilla/entity/mongo_db.rb +47 -0
  17. data/lib/backzilla/entity/my_sql.rb +68 -0
  18. data/lib/backzilla/executor.rb +17 -0
  19. data/lib/backzilla/logger_helper.rb +31 -0
  20. data/lib/backzilla/project.rb +41 -0
  21. data/lib/backzilla/store.rb +49 -0
  22. data/lib/backzilla/store/directory.rb +21 -0
  23. data/lib/backzilla/store/ftp.rb +37 -0
  24. data/lib/backzilla/store/ssh.rb +37 -0
  25. data/lib/backzilla/version.rb +4 -0
  26. data/spec/configs/directory/projects.yaml +5 -0
  27. data/spec/configs/directory/stores.yaml +6 -0
  28. data/spec/configs/mongodb/projects.yaml +5 -0
  29. data/spec/configs/mongodb/stores.yaml +11 -0
  30. data/spec/configs/mysql/projects.yaml +7 -0
  31. data/spec/configs/mysql/stores.yaml +11 -0
  32. data/spec/entities/directory_spec.rb +46 -0
  33. data/spec/entities/mongodb_spec.rb +104 -0
  34. data/spec/entities/mysql_spec.rb +126 -0
  35. data/spec/fixtures/directory/a.txt +2 -0
  36. data/spec/fixtures/directory/b.txt +1 -0
  37. data/spec/fixtures/directory/some/nested/stuff/c.txt +1 -0
  38. data/spec/fixtures/mysql/backzilla_test.sql +54 -0
  39. data/spec/spec.opts +1 -0
  40. data/spec/spec_helper.rb +47 -0
  41. 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,4 @@
1
+ module Backzilla::Version
2
+ VERSION = '0.0.1'
3
+ end
4
+
@@ -0,0 +1,5 @@
1
+ test:
2
+ files:
3
+ type: Directory
4
+ path: /tmp/backzilla/directory
5
+
@@ -0,0 +1,6 @@
1
+ gnupg_passphrase: 'some_password'
2
+ stores:
3
+ local:
4
+ type: Directory
5
+ path: spec/backups/directory_backup/local
6
+
@@ -0,0 +1,5 @@
1
+ test:
2
+ databass:
3
+ type: MongoDB
4
+ database: backzilla_test
5
+
@@ -0,0 +1,11 @@
1
+ gnupg_passphrase: 'some_password'
2
+ stores:
3
+ local:
4
+ type: Directory
5
+ path: spec/backups/directory_backup/local
6
+ # my_ssh:
7
+ # type: SSH
8
+ # host: localhost
9
+ # user: sobol
10
+ # path: spec/backups/directory_backup/SSH
11
+
@@ -0,0 +1,7 @@
1
+ test:
2
+ databass:
3
+ type: MySQL
4
+ database: backzilla_test
5
+ user: root
6
+ password: root
7
+
@@ -0,0 +1,11 @@
1
+ gnupg_passphrase: 'some_password'
2
+ stores:
3
+ local:
4
+ type: Directory
5
+ path: spec/backups/directory_backup/local
6
+ # my_ssh:
7
+ # type: SSH
8
+ # host: localhost
9
+ # user: sobol
10
+ # path: spec/backups/directory_backup/SSH
11
+
@@ -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
+