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
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
|
+
|