backzilla 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/rdoctask'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
require 'rubygems'
|
5
|
+
desc 'Default: run Specs.'
|
6
|
+
task :default => :spec
|
7
|
+
|
8
|
+
|
9
|
+
desc 'Run specs'
|
10
|
+
Spec::Rake::SpecTask.new(:spec) do |t|
|
11
|
+
t.libs << 'lib'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
begin
|
16
|
+
require 'jeweler'
|
17
|
+
Jeweler::Tasks.new do |gemspec|
|
18
|
+
gemspec.name = "backzilla"
|
19
|
+
gemspec.summary = "Multi-purpose backup tool"
|
20
|
+
gemspec.description = "Backzilla can backup multiple entities to multiple destinations."
|
21
|
+
gemspec.email = "pawel.sobolewski@amberbit.com"
|
22
|
+
gemspec.homepage = "http://amberbit.com/"
|
23
|
+
gemspec.authors = ["Wojtek Piekutowski, Paweł Sobolewski"]
|
24
|
+
end
|
25
|
+
rescue LoadError
|
26
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
27
|
+
end
|
28
|
+
|
data/VERSION
ADDED
data/backzilla.gemspec
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{backzilla}
|
8
|
+
s.version = "0.0.1"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Wojtek Piekutowski, Pawe\305\202 Sobolewski"]
|
12
|
+
s.date = %q{2010-07-30}
|
13
|
+
s.default_executable = %q{backzilla}
|
14
|
+
s.description = %q{Backzilla can backup multiple entities to multiple destinations.}
|
15
|
+
s.email = %q{pawel.sobolewski@amberbit.com}
|
16
|
+
s.executables = ["backzilla"]
|
17
|
+
s.extra_rdoc_files = [
|
18
|
+
"LICENSE",
|
19
|
+
"README.markdown"
|
20
|
+
]
|
21
|
+
s.files = [
|
22
|
+
".gitignore",
|
23
|
+
"COPYING",
|
24
|
+
"LICENSE",
|
25
|
+
"README.markdown",
|
26
|
+
"Rakefile",
|
27
|
+
"VERSION",
|
28
|
+
"backzilla.gemspec",
|
29
|
+
"bin/backzilla",
|
30
|
+
"examples/projects.yaml",
|
31
|
+
"examples/stores.yaml",
|
32
|
+
"lib/backzilla.rb",
|
33
|
+
"lib/backzilla/configuration.rb",
|
34
|
+
"lib/backzilla/connection.rb",
|
35
|
+
"lib/backzilla/entity.rb",
|
36
|
+
"lib/backzilla/entity/directory.rb",
|
37
|
+
"lib/backzilla/entity/mongo_db.rb",
|
38
|
+
"lib/backzilla/entity/my_sql.rb",
|
39
|
+
"lib/backzilla/executor.rb",
|
40
|
+
"lib/backzilla/logger_helper.rb",
|
41
|
+
"lib/backzilla/project.rb",
|
42
|
+
"lib/backzilla/store.rb",
|
43
|
+
"lib/backzilla/store/directory.rb",
|
44
|
+
"lib/backzilla/store/ftp.rb",
|
45
|
+
"lib/backzilla/store/ssh.rb",
|
46
|
+
"lib/backzilla/version.rb",
|
47
|
+
"spec/configs/directory/projects.yaml",
|
48
|
+
"spec/configs/directory/stores.yaml",
|
49
|
+
"spec/configs/mongodb/projects.yaml",
|
50
|
+
"spec/configs/mongodb/stores.yaml",
|
51
|
+
"spec/configs/mysql/projects.yaml",
|
52
|
+
"spec/configs/mysql/stores.yaml",
|
53
|
+
"spec/entities/directory_spec.rb",
|
54
|
+
"spec/entities/mongodb_spec.rb",
|
55
|
+
"spec/entities/mysql_spec.rb",
|
56
|
+
"spec/fixtures/directory/a.txt",
|
57
|
+
"spec/fixtures/directory/b.txt",
|
58
|
+
"spec/fixtures/directory/some/nested/stuff/c.txt",
|
59
|
+
"spec/fixtures/mysql/backzilla_test.sql",
|
60
|
+
"spec/spec.opts",
|
61
|
+
"spec/spec_helper.rb"
|
62
|
+
]
|
63
|
+
s.homepage = %q{http://amberbit.com/}
|
64
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
65
|
+
s.require_paths = ["lib"]
|
66
|
+
s.rubygems_version = %q{1.3.5}
|
67
|
+
s.summary = %q{Multi-purpose backup tool}
|
68
|
+
s.test_files = [
|
69
|
+
"spec/spec_helper.rb",
|
70
|
+
"spec/entities/directory_spec.rb",
|
71
|
+
"spec/entities/mysql_spec.rb",
|
72
|
+
"spec/entities/mongodb_spec.rb"
|
73
|
+
]
|
74
|
+
|
75
|
+
if s.respond_to? :specification_version then
|
76
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
77
|
+
s.specification_version = 3
|
78
|
+
|
79
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
80
|
+
else
|
81
|
+
end
|
82
|
+
else
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
data/bin/backzilla
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/ruby
|
2
|
+
|
3
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
|
4
|
+
|
5
|
+
require 'backzilla'
|
6
|
+
require 'optparse'
|
7
|
+
|
8
|
+
options = {}
|
9
|
+
options[:quiet] = false
|
10
|
+
options[:debug] = false
|
11
|
+
|
12
|
+
OptionParser.new do |cmd_options|
|
13
|
+
cmd_options.banner = "Usage: backzilla.rb [OPTIONS]"
|
14
|
+
|
15
|
+
cmd_options.on("-h", "--help", "Show this message") do
|
16
|
+
puts cmd_options
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
|
20
|
+
cmd_options.on("-b", "--backup PROJECT_SPEC", "Backups PROJECT_SPEC") do |spec|
|
21
|
+
options[:backup] = true
|
22
|
+
options[:spec] = spec
|
23
|
+
end
|
24
|
+
|
25
|
+
cmd_options.on("-r", "--restore PROJECT_SPEC", "Restores PROJECT_SPEC") do |spec|
|
26
|
+
options[:restore] = true
|
27
|
+
options[:spec] = spec
|
28
|
+
end
|
29
|
+
|
30
|
+
cmd_options.on("-q", "--quite", "Supress log messages with level lower than ERROR") do |spec|
|
31
|
+
options[:quiet] = true
|
32
|
+
end
|
33
|
+
|
34
|
+
cmd_options.on("-d", "--debug", "Run in debug mode") do |spec|
|
35
|
+
options[:debug] = true
|
36
|
+
end
|
37
|
+
end.parse!
|
38
|
+
|
39
|
+
Backzilla.run(options)
|
40
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
pkczb:
|
2
|
+
database:
|
3
|
+
type: MySQL
|
4
|
+
database: pkczb_production
|
5
|
+
user: backup
|
6
|
+
password: q
|
7
|
+
files:
|
8
|
+
type: Directory
|
9
|
+
path: /home/pkczb-www/app
|
10
|
+
|
11
|
+
amberbit.com:
|
12
|
+
database:
|
13
|
+
type: MySQL
|
14
|
+
database: amberbit
|
15
|
+
uploads:
|
16
|
+
type: Directory
|
17
|
+
path: /home/amberbit/uploads
|
18
|
+
|
19
|
+
test:
|
20
|
+
database:
|
21
|
+
type: MySQL
|
22
|
+
database: br_development
|
23
|
+
user: backup
|
24
|
+
password: q
|
25
|
+
host:
|
26
|
+
port:
|
27
|
+
socket:
|
28
|
+
files:
|
29
|
+
type: Directory
|
30
|
+
path: /tmp/test_project
|
31
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
gnupg_passphrase: '`a\bv/]|xDcsfEq,$X|y4:U=5y"{cqRE@RE<w0Mn3A?8DMNBVmF"ZxH]&w1_D4u'
|
2
|
+
stores:
|
3
|
+
local:
|
4
|
+
type: Directory
|
5
|
+
path: /tmp/backups/server1
|
6
|
+
# my_ftp:
|
7
|
+
# type: FTP
|
8
|
+
# host: ubuntu-server
|
9
|
+
# user: backuper
|
10
|
+
# password: q
|
11
|
+
# path: /backup/production.amberbit.com
|
12
|
+
|
data/lib/backzilla.rb
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'pp'
|
3
|
+
require 'ostruct'
|
4
|
+
require 'logger'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'pathname'
|
7
|
+
|
8
|
+
require 'rubygems'
|
9
|
+
require 'open4'
|
10
|
+
|
11
|
+
class Object
|
12
|
+
def blank?
|
13
|
+
respond_to?(:empty?) ? empty? : !self
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module Backzilla
|
18
|
+
autoload :LoggerHelper, 'backzilla/logger_helper'
|
19
|
+
autoload :Project, 'backzilla/project'
|
20
|
+
autoload :Entity, 'backzilla/entity'
|
21
|
+
autoload :Store, 'backzilla/store'
|
22
|
+
autoload :Executor, 'backzilla/executor'
|
23
|
+
autoload :Version, 'backzilla/version'
|
24
|
+
autoload :Configuration, 'backzilla/configuration'
|
25
|
+
|
26
|
+
include Backzilla::Version
|
27
|
+
include Backzilla::Executor
|
28
|
+
extend Backzilla::LoggerHelper
|
29
|
+
|
30
|
+
STORES_CONFIG = ENV["BACKZILLA_STORES_CONFIG"] || "~/.backzilla/stores.yaml"
|
31
|
+
PROJECTS_CONFIG = ENV["BACKZILLA_PROJECTS_CONFIG"] || "~/.backzilla/projects.yaml"
|
32
|
+
|
33
|
+
def self.store(path, project_name, entity_name)
|
34
|
+
info "Storing #{path}..."
|
35
|
+
|
36
|
+
stores_file = File.expand_path STORES_CONFIG
|
37
|
+
data = YAML.load_file stores_file
|
38
|
+
Store.gnugpg_passphrase = data['gnupg_passphrase']
|
39
|
+
stores = data['stores'].map do |store_name, store_options|
|
40
|
+
klass = Backzilla::Store.const_get(store_options['type'])
|
41
|
+
klass.new(store_name, store_options)
|
42
|
+
end
|
43
|
+
|
44
|
+
stores.each { |s| s.put path, project_name, entity_name }
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.restore(path, project_name, entity_name)
|
48
|
+
info "Restoring #{path}..."
|
49
|
+
|
50
|
+
restores_file = File.expand_path STORES_CONFIG
|
51
|
+
data = YAML.load_file restores_file
|
52
|
+
Store.gnugpg_passphrase = data['gnupg_passphrase']
|
53
|
+
stores = data['stores'].map do |store_name, store_options|
|
54
|
+
klass = Backzilla::Store.const_get(store_options['type'])
|
55
|
+
klass.new(store_name, store_options)
|
56
|
+
end
|
57
|
+
|
58
|
+
stores.each { |s| s.get path, project_name, entity_name }
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.logger
|
62
|
+
return @logger if @logger
|
63
|
+
@logger = Logger.new(STDOUT)
|
64
|
+
@logger.level = Logger::DEBUG
|
65
|
+
@logger.progname = 'Backzilla'
|
66
|
+
@logger
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.options
|
70
|
+
Backzilla::Configuration.instance
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.run(options)
|
74
|
+
config = Backzilla::Configuration.instance
|
75
|
+
options.each do |key, value|
|
76
|
+
config.send "#{key}=", value
|
77
|
+
end
|
78
|
+
|
79
|
+
if config.backup && config.restore
|
80
|
+
fatal "Use -r or -b separately"
|
81
|
+
exit -1
|
82
|
+
elsif !config.backup && !config.restore
|
83
|
+
fatal "-r or -b required"
|
84
|
+
exit -1
|
85
|
+
end
|
86
|
+
|
87
|
+
projects_file = File.expand_path PROJECTS_CONFIG
|
88
|
+
data = YAML.load_file projects_file
|
89
|
+
if config.spec == 'all'
|
90
|
+
projects = data.inject([]) do |projects, project_data|
|
91
|
+
project_name, project_entities_data = *project_data
|
92
|
+
project = Project.new(project_name)
|
93
|
+
project.setup_entities data[project_name]
|
94
|
+
projects << project
|
95
|
+
end
|
96
|
+
projects.each { |p| config.restore ? p.restore : p.backup }
|
97
|
+
else
|
98
|
+
spec_parts = config.spec.split(':')
|
99
|
+
project_name = spec_parts.shift
|
100
|
+
unless data[project_name]
|
101
|
+
fatal "No such project '#{project_name}'"
|
102
|
+
exit -1
|
103
|
+
end
|
104
|
+
|
105
|
+
project = Project.new(project_name)
|
106
|
+
project.setup_entities data[project_name]
|
107
|
+
|
108
|
+
if config.backup
|
109
|
+
project.backup spec_parts
|
110
|
+
elsif config.restore
|
111
|
+
project.restore spec_parts
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Backzilla::Entity
|
2
|
+
autoload :Directory, 'backzilla/entity/directory'
|
3
|
+
autoload :MongoDB, 'backzilla/entity/mongo_db'
|
4
|
+
autoload :MySQL, 'backzilla/entity/my_sql'
|
5
|
+
|
6
|
+
include Backzilla::LoggerHelper
|
7
|
+
|
8
|
+
BASE_PATH = '/tmp/backzilla'
|
9
|
+
|
10
|
+
attr_reader :name
|
11
|
+
attr_accessor :project
|
12
|
+
|
13
|
+
def initialize(name)
|
14
|
+
@name = name
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def self.demodulize
|
20
|
+
to_s.split('::').last
|
21
|
+
end
|
22
|
+
|
23
|
+
def backup_msg(options={})
|
24
|
+
info "Entity name: [#{name}]. Type of backing up files: [#{self.class.demodulize}]"
|
25
|
+
end
|
26
|
+
|
27
|
+
def restore_msg(options={})
|
28
|
+
info "Entity name: [#{name}]. Type of restoring files: [#{self.class.demodulize}]"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Backzilla::Entity::Directory < Backzilla::Entity
|
2
|
+
def initialize(name, options)
|
3
|
+
super(name)
|
4
|
+
@path = options['path']
|
5
|
+
end
|
6
|
+
|
7
|
+
def prepare_backup
|
8
|
+
backup_msg
|
9
|
+
validate_path
|
10
|
+
@path
|
11
|
+
end
|
12
|
+
|
13
|
+
def backup
|
14
|
+
prepare_backup
|
15
|
+
Backzilla.store @path, project.name, self.name
|
16
|
+
@path
|
17
|
+
end
|
18
|
+
|
19
|
+
def finalize_restore
|
20
|
+
restore_msg
|
21
|
+
validate_path
|
22
|
+
@path
|
23
|
+
end
|
24
|
+
|
25
|
+
def restore
|
26
|
+
finalize_restore
|
27
|
+
FileUtils.rm_rf @path
|
28
|
+
FileUtils.mkdir @path
|
29
|
+
Backzilla.restore @path, project.name, self.name
|
30
|
+
@path
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def validate_path
|
36
|
+
if @path.blank?
|
37
|
+
fatal 'path option missing'
|
38
|
+
exit -1
|
39
|
+
end
|
40
|
+
unless File.exist?(@path)
|
41
|
+
fatal "path doesn't exist"
|
42
|
+
exit -1
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
class Backzilla::Entity::MongoDB < Backzilla::Entity
|
2
|
+
include Backzilla::Executor
|
3
|
+
|
4
|
+
def initialize(name, options)
|
5
|
+
super(name)
|
6
|
+
@database = options['database']
|
7
|
+
end
|
8
|
+
|
9
|
+
def prepare_backup
|
10
|
+
if @database.blank?
|
11
|
+
fatal "Database name is blank"
|
12
|
+
exit -1
|
13
|
+
end
|
14
|
+
path = Pathname.new(BASE_PATH) + project.name + name
|
15
|
+
FileUtils.mkdir_p path
|
16
|
+
cmd = "mongodump -d #{@database} -o #{path}"
|
17
|
+
execute cmd
|
18
|
+
path
|
19
|
+
end
|
20
|
+
|
21
|
+
def backup
|
22
|
+
prepare_backup
|
23
|
+
backup_msg
|
24
|
+
path = Pathname.new(BASE_PATH) + project.name + name
|
25
|
+
Backzilla.store path, project.name, self.name
|
26
|
+
|
27
|
+
FileUtils.rm_rf path
|
28
|
+
end
|
29
|
+
|
30
|
+
def finalize_restore(options={})
|
31
|
+
path = options[:path]
|
32
|
+
cmd = "mongorestore --drop -d #{@database} #{path}/#{@database}"
|
33
|
+
execute cmd
|
34
|
+
|
35
|
+
FileUtils.rm_rf path
|
36
|
+
end
|
37
|
+
|
38
|
+
def restore
|
39
|
+
restore_msg
|
40
|
+
path = Pathname.new(BASE_PATH) + project.name + name
|
41
|
+
FileUtils.mkdir_p path
|
42
|
+
|
43
|
+
Backzilla.restore path, project.name, self.name
|
44
|
+
finalize_restore(:path => path)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|