outback 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/VERSION +1 -0
- data/bin/outback +6 -0
- data/lib/outback/archive.rb +5 -0
- data/lib/outback/backup.rb +45 -0
- data/lib/outback/cli.rb +50 -0
- data/lib/outback/configuration.rb +54 -0
- data/lib/outback/configuration_error.rb +10 -0
- data/lib/outback/directory_source.rb +32 -0
- data/lib/outback/directory_target.rb +37 -0
- data/lib/outback/local_archive.rb +6 -0
- data/lib/outback/mysql_source.rb +49 -0
- data/lib/outback/remote_archive.rb +6 -0
- data/lib/outback/s3_archive.rb +6 -0
- data/lib/outback/s3_target.rb +7 -0
- data/lib/outback/source.rb +10 -0
- data/lib/outback/support/attr_setter.rb +24 -0
- data/lib/outback/support/configurable.rb +33 -0
- data/lib/outback/support/mysql_ext.rb +23 -0
- data/lib/outback/support/returning.rb +11 -0
- data/lib/outback/target.rb +6 -0
- data/lib/outback/temp_archive.rb +9 -0
- data/lib/outback/vendor/metaclass.rb +15 -0
- data/lib/outback/vendor/methodphitamine.rb +27 -0
- data/lib/outback/vendor/mysql.rb +1214 -0
- data/lib/outback.rb +37 -0
- metadata +122 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 onrooby GmbH
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/bin/outback
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Outback
|
2
|
+
class Backup
|
3
|
+
attr_reader :configuration, :name, :timestamp, :archives, :tmpdir
|
4
|
+
delegate :sources, :targets, :to => :configuration
|
5
|
+
|
6
|
+
def initialize(configuration)
|
7
|
+
@configuration = configuration
|
8
|
+
@name = configuration.name
|
9
|
+
@timestamp = Time.now.to_formatted_s(:number)
|
10
|
+
end
|
11
|
+
|
12
|
+
def run!
|
13
|
+
@archives = []
|
14
|
+
puts configuration.inspect
|
15
|
+
begin
|
16
|
+
@tmpdir = Dir.mktmpdir([name, timestamp])
|
17
|
+
@archives = create_archives
|
18
|
+
upload_archives
|
19
|
+
ensure
|
20
|
+
FileUtils.remove_entry_secure(tmpdir)
|
21
|
+
end
|
22
|
+
purge_repository
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def create_archives
|
28
|
+
archives = sources.collect do |source|
|
29
|
+
source.create_archives(name, timestamp, tmpdir)
|
30
|
+
end
|
31
|
+
archives.flatten.compact
|
32
|
+
end
|
33
|
+
|
34
|
+
def purge_repository
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
def upload_archives
|
39
|
+
targets.each do |target|
|
40
|
+
target.put(archives, timestamp)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
data/lib/outback/cli.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module Outback
|
4
|
+
module CLI
|
5
|
+
DEFAULT_CONFIGURATION_FILE = '/etc/outback/outback.conf'
|
6
|
+
|
7
|
+
def self.invoke
|
8
|
+
options = {}
|
9
|
+
option_parser = OptionParser.new do |p|
|
10
|
+
p.banner = "Usage: outback [configfile]"
|
11
|
+
# p.on -v --verbose be talky
|
12
|
+
p.on_tail("-h", "--help", "Show this message") do
|
13
|
+
puts p
|
14
|
+
exit
|
15
|
+
end
|
16
|
+
p.on_tail("--version", "Show version") do
|
17
|
+
puts "Outback #{Outback::VERSION}"
|
18
|
+
exit
|
19
|
+
end
|
20
|
+
end
|
21
|
+
option_parser.parse!
|
22
|
+
options[:configuration] = ARGV.first unless ARGV.first.blank?
|
23
|
+
Outback::Configuration.reset
|
24
|
+
config_file = options[:configuration] or begin
|
25
|
+
unless File.exists?(DEFAULT_CONFIGURATION_FILE)
|
26
|
+
puts 'no configuration found'
|
27
|
+
exit(1)
|
28
|
+
end
|
29
|
+
DEFAULT_CONFIGURATION_FILE
|
30
|
+
end
|
31
|
+
begin
|
32
|
+
load config_file
|
33
|
+
rescue ConfigurationError => conf_error
|
34
|
+
puts "Configuration Error! #{conf_error}"
|
35
|
+
exit(1)
|
36
|
+
rescue Exception => e
|
37
|
+
puts 'Error loading config file'
|
38
|
+
puts e
|
39
|
+
exit(1)
|
40
|
+
end
|
41
|
+
if Outback::Configuration.loaded.empty?
|
42
|
+
puts 'no configuration could be loaded'
|
43
|
+
exit(1)
|
44
|
+
end
|
45
|
+
Outback::Configuration.loaded.each do |configuration|
|
46
|
+
Outback::Backup.new(configuration).run!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Outback
|
2
|
+
class Configuration
|
3
|
+
@loaded = []
|
4
|
+
|
5
|
+
class << self
|
6
|
+
def add(configuration)
|
7
|
+
raise "duplicate configuration #{configuration.name}" if loaded.any?(&its.name == configuration.name)
|
8
|
+
loaded << configuration
|
9
|
+
end
|
10
|
+
|
11
|
+
def loaded
|
12
|
+
@loaded
|
13
|
+
end
|
14
|
+
|
15
|
+
def reset
|
16
|
+
@loaded = []
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :name, :sources, :targets, :errors
|
21
|
+
|
22
|
+
def initialize(name, &block)
|
23
|
+
raise(ConfigurationError.new(:name)) if name.blank?
|
24
|
+
@name = name.to_s
|
25
|
+
@sources, @targets, @errors = [], [], []
|
26
|
+
if block_given?
|
27
|
+
if block.arity == 1 then yield(self) else instance_eval(&block) end
|
28
|
+
end
|
29
|
+
raise unless valid?
|
30
|
+
self.class.add(self)
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
|
35
|
+
def source(type, *args, &block)
|
36
|
+
"Outback::#{type.to_s.classify}Source".constantize.configure(*args, &block).tap { |instance| sources << instance }
|
37
|
+
end
|
38
|
+
|
39
|
+
def target(type, *args, &block)
|
40
|
+
"Outback::#{type.to_s.classify}Target".constantize.configure(*args, &block).tap { |instance| targets << instance }
|
41
|
+
end
|
42
|
+
|
43
|
+
def valid?
|
44
|
+
errors.clear
|
45
|
+
return error(:targets) if targets.empty?
|
46
|
+
true
|
47
|
+
end
|
48
|
+
|
49
|
+
def error(attribute, description = nil)
|
50
|
+
errors << ConfigurationError.new(attribute, description)
|
51
|
+
false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Outback
|
2
|
+
class DirectorySource < Source
|
3
|
+
attr_reader :path
|
4
|
+
|
5
|
+
def initialize(path)
|
6
|
+
@path = path
|
7
|
+
end
|
8
|
+
|
9
|
+
def source_name
|
10
|
+
path.gsub(/[^A-Za-z0-9\-_.]/, '_').gsub(/(\A_|_\z)/, '')
|
11
|
+
end
|
12
|
+
|
13
|
+
def excludes
|
14
|
+
@excludes ||= []
|
15
|
+
end
|
16
|
+
|
17
|
+
def exclude(path)
|
18
|
+
excludes << path
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_archives(backup_name, timestamp, tmpdir)
|
22
|
+
source_dir = Pathname.new(path).realpath
|
23
|
+
archive_name = Pathname.new(tmpdir).join("#{backup_name}_#{timestamp}_#{source_name}.tar.gz")
|
24
|
+
exclude_list = Pathname.new(tmpdir).join('exclude_list.txt')
|
25
|
+
File.open(exclude_list, 'w') { |f| f << excludes.join("\n") }
|
26
|
+
commandline = "tar --create --file #{archive_name} --preserve-permissions --gzip --verbose --exclude-from #{exclude_list} #{source_dir}"
|
27
|
+
puts "EXECUTING #{commandline}"
|
28
|
+
puts `#{commandline}`
|
29
|
+
[TempArchive.new(archive_name)]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Outback
|
2
|
+
class DirectoryTarget < Target
|
3
|
+
attr_reader :path
|
4
|
+
attr_setter :user, :group, :directory_permissions, :archive_permissions, :ttl, :move
|
5
|
+
|
6
|
+
def initialize(path)
|
7
|
+
@path = path
|
8
|
+
end
|
9
|
+
|
10
|
+
def valid?
|
11
|
+
(user and group) or (not user and not group)
|
12
|
+
end
|
13
|
+
|
14
|
+
def put(archives, timestamp)
|
15
|
+
backup_folder = Pathname.new(path).join(timestamp[0..7]) # yyyymmdd
|
16
|
+
Dir.mkdir(backup_folder) unless backup_folder.directory?
|
17
|
+
FileUtils.chmod directory_permissions || 0700, backup_folder
|
18
|
+
archives.each do |archive|
|
19
|
+
basename = Pathname.new(archive.filename).basename
|
20
|
+
if move
|
21
|
+
puts "moving #{archive.filename} to #{backup_folder}"
|
22
|
+
FileUtils.mv archive.filename, backup_folder
|
23
|
+
else
|
24
|
+
puts "copying #{archive.filename} to #{backup_folder}"
|
25
|
+
FileUtils.cp_r
|
26
|
+
end
|
27
|
+
archived_file = backup_folder.join(basename)
|
28
|
+
puts "chmodding"
|
29
|
+
FileUtils.chmod archive_permissions || 0600, archived_file
|
30
|
+
if user && group
|
31
|
+
puts "chowning"
|
32
|
+
FileUtils.chown user, group, archived_file
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Outback
|
2
|
+
class MysqlSource < Source
|
3
|
+
attr_setter :user, :password, :host, :port, :socket
|
4
|
+
|
5
|
+
def databases
|
6
|
+
@databases ||= []
|
7
|
+
end
|
8
|
+
|
9
|
+
def excludes
|
10
|
+
@excludes ||= []
|
11
|
+
end
|
12
|
+
|
13
|
+
def database(name)
|
14
|
+
databases << name
|
15
|
+
end
|
16
|
+
|
17
|
+
def exclude(name)
|
18
|
+
excludes << name.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def valid?
|
22
|
+
user && password
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_archives(backup_name, timestamp, tmpdir)
|
26
|
+
mysql_host = host || 'localhost'
|
27
|
+
mysql_port = (port || 3306) unless socket
|
28
|
+
if databases.empty?
|
29
|
+
# (host=nil, user=nil, passwd=nil, db=nil, port=nil, socket=nil, flag=nil)
|
30
|
+
mysql = Mysql.connect(mysql_host, user, password, nil, mysql_port, socket)
|
31
|
+
@databases = mysql.databases - excludes
|
32
|
+
mysql.close
|
33
|
+
end
|
34
|
+
|
35
|
+
archives = databases.collect do |database|
|
36
|
+
archive_name = Pathname.new(tmpdir).join("#{backup_name}_#{timestamp}_#{database}.sql.gz")
|
37
|
+
mysql_conf_file = Pathname.new(tmpdir).join('outback_my.cnf')
|
38
|
+
File.open(mysql_conf_file, 'w') { |f| f << "[client]\npassword=#{password}\n" }
|
39
|
+
FileUtils.chmod 0600, mysql_conf_file
|
40
|
+
|
41
|
+
# base_command="$mysqldump --defaults-extra-file=$mysql_conffile $opt -u$BM_MYSQL_ADMINLOGIN -h$BM_MYSQL_HOST -P$BM_MYSQL_PORT"
|
42
|
+
commandline = "mysqldump --defaults-extra-file=#{mysql_conf_file} --user=#{user} --host=#{mysql_host} --port=#{mysql_port} #{database} | gzip > #{archive_name}"
|
43
|
+
puts commandline
|
44
|
+
puts `#{commandline}`
|
45
|
+
TempArchive.new(archive_name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Outback
|
2
|
+
|
3
|
+
class AttrSetter
|
4
|
+
def initialize(configurable)
|
5
|
+
@configurable = configurable
|
6
|
+
configurable.class.attributes.each do |name|
|
7
|
+
meta_def(name) do |value|
|
8
|
+
write_attribute(name, value)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(name, *args, &block)
|
14
|
+
@configurable.send(name, *args, &block)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def write_attribute(name, value)
|
20
|
+
@configurable.instance_variable_set "@#{name}", value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Outback
|
2
|
+
module Configurable
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def configure(*args, &block)
|
10
|
+
returning new(*args) do |instance|
|
11
|
+
if block_given?
|
12
|
+
if block.arity == 1 then yield(instance.attr_setter) else instance.attr_setter.instance_eval(&block) end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def attr_setter(*names)
|
18
|
+
attributes.concat(names).uniq!
|
19
|
+
names.each { |name| attr_reader name }
|
20
|
+
end
|
21
|
+
|
22
|
+
def attributes
|
23
|
+
read_inheritable_attribute(:attributes) || write_inheritable_attribute(:attributes, [])
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
def attr_setter
|
29
|
+
@attr_setter ||= AttrSetter.new(self)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Outback
|
2
|
+
module MysqlExt
|
3
|
+
module Mysql
|
4
|
+
def databases
|
5
|
+
result = query('SHOW DATABASES')
|
6
|
+
databases = result.fetch_all_rows.flatten
|
7
|
+
result.free
|
8
|
+
databases
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Result
|
13
|
+
def fetch_all_rows
|
14
|
+
returning [] do |rows|
|
15
|
+
each { |row| rows << row }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
Mysql.send :include, Outback::MysqlExt::Mysql
|
23
|
+
Mysql::Result.send :include, Outback::MysqlExt::Result
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Object
|
2
|
+
# The hidden singleton lurks behind everyone
|
3
|
+
def metaclass; class << self; self; end; end
|
4
|
+
def meta_eval &blk; metaclass.instance_eval &blk; end
|
5
|
+
|
6
|
+
# Adds methods to a metaclass
|
7
|
+
def meta_def name, &blk
|
8
|
+
meta_eval { define_method name, &blk }
|
9
|
+
end
|
10
|
+
|
11
|
+
# Defines an instance method within a class
|
12
|
+
def class_def name, &blk
|
13
|
+
class_eval { define_method name, &blk }
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Kernel
|
2
|
+
protected
|
3
|
+
def it() It.new end
|
4
|
+
alias its it
|
5
|
+
end
|
6
|
+
|
7
|
+
class It
|
8
|
+
|
9
|
+
undef_method(*(instance_methods - %w*__id__ __send__*))
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@methods = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def method_missing(*args, &block)
|
16
|
+
@methods << [args, block] unless args == [:respond_to?, :to_proc]
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_proc
|
21
|
+
lambda do |obj|
|
22
|
+
@methods.inject(obj) do |current,(args,block)|
|
23
|
+
current.send(*args, &block)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|