outback 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'outback'
4
+ require 'outback/cli'
5
+
6
+ Outback::CLI.invoke
@@ -0,0 +1,5 @@
1
+ module Outback
2
+ class Archive
3
+
4
+ end
5
+ end
@@ -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
@@ -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,10 @@
1
+ module Outback
2
+ class ConfigurationError < Exception
3
+ attr_reader :attribute, :description
4
+
5
+ def initialize(attribute, description)
6
+ self.attribute = attribute
7
+ self.description = description
8
+ end
9
+ end
10
+ 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,6 @@
1
+ module Outback
2
+ class LocalArchive < Archive
3
+
4
+
5
+ end
6
+ 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,6 @@
1
+ module Outback
2
+ class RemoteArchive < Archive
3
+
4
+
5
+ end
6
+ end
@@ -0,0 +1,6 @@
1
+ module Outback
2
+ class S3Archive < RemoteArchive
3
+
4
+
5
+ end
6
+ end
@@ -0,0 +1,7 @@
1
+ module Outback
2
+ class S3Target < Target
3
+ attr_setter :bucket, :access_key, :secret_key, :ttl, :prefix
4
+
5
+ end
6
+
7
+ end
@@ -0,0 +1,10 @@
1
+ module Outback
2
+ class Source
3
+ include Configurable
4
+
5
+ def create_archive(backup_name, timestamp, tmpdir)
6
+ # implement in subclasses
7
+ end
8
+
9
+ end
10
+ 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,11 @@
1
+ module Outback
2
+ # module Support
3
+ module Returning
4
+ def returning(value, &block)
5
+ value.tap(&block)
6
+ end
7
+ end
8
+ # end
9
+ end
10
+
11
+ Object.send(:include, Outback::Returning)
@@ -0,0 +1,6 @@
1
+ module Outback
2
+ class Target
3
+ include Configurable
4
+
5
+ end
6
+ end
@@ -0,0 +1,9 @@
1
+ module Outback
2
+ class TempArchive < Archive
3
+ attr_reader :filename
4
+
5
+ def initialize(filename)
6
+ @filename = filename
7
+ end
8
+ end
9
+ end
@@ -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