backzilla 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
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
@@ -0,0 +1,2 @@
1
+ 0.0.1
2
+
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,6 @@
1
+ require 'singleton'
2
+
3
+ class Backzilla::Configuration < OpenStruct
4
+ include Singleton
5
+ end
6
+
@@ -0,0 +1,5 @@
1
+ class Backzilla::Connection
2
+
3
+
4
+ end
5
+
@@ -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
+