outback 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/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