outback 0.0.2 → 0.0.3

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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.2
1
+ 0.0.3
@@ -1,5 +1,30 @@
1
1
  module Outback
2
2
  class Archive
3
-
3
+ NAME_PATTERN = /([A-Za-z0-9.\-]+)_(\d{14})_(\w+)/
4
+
5
+ attr_reader :filename, :backup_name, :timestamp, :source_name, :parent
6
+
7
+ def initialize(filename, parent)
8
+ @filename, @parent = Pathname.new(filename), parent
9
+ unless match_data = @filename.basename.to_s.match(NAME_PATTERN)
10
+ raise ArgumentError, 'invalid name'
11
+ end
12
+ @backup_name, @timestamp, @source_name = match_data.captures[0..2]
13
+ end
14
+
15
+ def size
16
+ filename.size
17
+ end
18
+
19
+ def open
20
+ filename.open
21
+ end
22
+
23
+ def outdated?
24
+ if timestamp && parent && parent.ttl
25
+ Time.now - Time.parse(timestamp) > parent.ttl
26
+ end
27
+ end
28
+
4
29
  end
5
30
  end
@@ -4,6 +4,7 @@ module Outback
4
4
  delegate :sources, :targets, :to => :configuration
5
5
 
6
6
  def initialize(configuration)
7
+ raise ArgumentError, "configuration required" unless configuration.is_a?(Outback::Configuration)
7
8
  @configuration = configuration
8
9
  @name = configuration.name
9
10
  @timestamp = Time.now.to_formatted_s(:number)
@@ -11,15 +12,14 @@ module Outback
11
12
 
12
13
  def run!
13
14
  @archives = []
14
- puts configuration.inspect
15
15
  begin
16
16
  @tmpdir = Dir.mktmpdir([name, timestamp])
17
17
  @archives = create_archives
18
- upload_archives
18
+ store_archives
19
19
  ensure
20
20
  FileUtils.remove_entry_secure(tmpdir)
21
21
  end
22
- purge_repository
22
+ purge_targets
23
23
  end
24
24
 
25
25
  private
@@ -31,14 +31,12 @@ module Outback
31
31
  archives.flatten.compact
32
32
  end
33
33
 
34
- def purge_repository
35
-
34
+ def store_archives
35
+ targets.each { |target| target.put(archives) }
36
36
  end
37
-
38
- def upload_archives
39
- targets.each do |target|
40
- target.put(archives, timestamp)
41
- end
37
+
38
+ def purge_targets
39
+ targets.each { |target| target.purge! }
42
40
  end
43
41
 
44
42
  end
data/lib/outback/cli.rb CHANGED
@@ -4,47 +4,60 @@ module Outback
4
4
  module CLI
5
5
  DEFAULT_CONFIGURATION_FILE = '/etc/outback/outback.conf'
6
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
7
+ class << self
8
+ def invoke
9
+ options = {}
10
+ option_parser = OptionParser.new do |p|
11
+ p.banner = "Usage: outback [configfile]"
12
+ p.on('-v', '--verbose [VERBOSE]', 'be talky') do
13
+ Outback.verbose = true
14
+ end
15
+ p.on('-s', '--silent [SILENT]', 'be silent') do
16
+ Outback.silent = true
17
+ end
18
+ p.on_tail("-h", "--help", "Show this message") do
19
+ puts p
20
+ exit
21
+ end
22
+ p.on_tail("--version", "Show version") do
23
+ Outback.info "Outback #{Outback::VERSION}"
24
+ exit
25
+ end
15
26
  end
16
- p.on_tail("--version", "Show version") do
17
- puts "Outback #{Outback::VERSION}"
18
- exit
27
+ option_parser.parse!
28
+ options[:configuration] = ARGV.first unless ARGV.first.blank?
29
+ Outback::Configuration.reset
30
+ config_file = options[:configuration] or begin
31
+ unless File.exists?(DEFAULT_CONFIGURATION_FILE)
32
+ Outback.error 'no configuration found'
33
+ exit(1)
34
+ end
35
+ DEFAULT_CONFIGURATION_FILE
19
36
  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'
37
+ begin
38
+ load config_file
39
+ rescue ConfigurationError => conf_error
40
+ Outback.error "Configuration Error! #{conf_error}"
41
+ exit(1)
42
+ rescue Exception => e
43
+ Outback.error "Error loading config file: #{e}"
27
44
  exit(1)
28
45
  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!
46
+ if Outback::Configuration.loaded.empty?
47
+ Outback.info 'no configuration could be loaded'
48
+ exit(1)
49
+ end
50
+ invalid_configs = Outback::Configuration.loaded.reject(&:valid?)
51
+ invalid_configs.each do |configuration|
52
+ Outback.error "configuration #{configuration.name}: #{configuration.errors}"
53
+ end
54
+ exit(1) unless invalid_configs.empty?
55
+ Outback::Configuration.loaded.each do |configuration|
56
+ Outback::Backup.new(configuration).run!
57
+ end
47
58
  end
59
+
48
60
  end
61
+
49
62
  end
50
63
  end
@@ -4,7 +4,7 @@ module Outback
4
4
 
5
5
  class << self
6
6
  def add(configuration)
7
- raise "duplicate configuration #{configuration.name}" if loaded.any?(&its.name == configuration.name)
7
+ raise ConfigurationError.new("duplicate configuration #{configuration.name}") if loaded.any?(&its.name == configuration.name)
8
8
  loaded << configuration
9
9
  end
10
10
 
@@ -12,6 +12,10 @@ module Outback
12
12
  @loaded
13
13
  end
14
14
 
15
+ def [](name)
16
+ loaded.detect { |configuration| configuration.name == name.to_s }
17
+ end
18
+
15
19
  def reset
16
20
  @loaded = []
17
21
  end
@@ -20,13 +24,13 @@ module Outback
20
24
  attr_reader :name, :sources, :targets, :errors
21
25
 
22
26
  def initialize(name, &block)
23
- raise(ConfigurationError.new(:name)) if name.blank?
27
+ raise(ConfigurationError.new("configuration name can't be blank")) if name.blank?
24
28
  @name = name.to_s
29
+ raise(ConfigurationError.new('configuration name may not contain underscores')) if @name.include?('_')
25
30
  @sources, @targets, @errors = [], [], []
26
31
  if block_given?
27
32
  if block.arity == 1 then yield(self) else instance_eval(&block) end
28
33
  end
29
- raise unless valid?
30
34
  self.class.add(self)
31
35
  end
32
36
 
@@ -42,12 +46,15 @@ module Outback
42
46
 
43
47
  def valid?
44
48
  errors.clear
45
- return error(:targets) if targets.empty?
49
+ return error('no targets specified') if targets.empty?
50
+ moving_targets = targets.select { |t| t.is_a?(DirectoryTarget) && t.move }
51
+ return error('cannot define more than one moving target') if moving_targets.size > 1
52
+ return error('moving target must be defined last') if moving_targets.first && moving_targets.first != targets.last
46
53
  true
47
54
  end
48
55
 
49
- def error(attribute, description = nil)
50
- errors << ConfigurationError.new(attribute, description)
56
+ def error(message)
57
+ errors << ConfigurationError.new(message)
51
58
  false
52
59
  end
53
60
  end
@@ -1,10 +1,4 @@
1
1
  module Outback
2
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
3
  end
10
4
  end
@@ -0,0 +1,8 @@
1
+ module Outback
2
+ class DirectoryArchive < Archive
3
+ def purge!
4
+ Outback.debug "purging DirectoryArchive: #{filename}"
5
+ filename.unlink
6
+ end
7
+ end
8
+ end
@@ -14,8 +14,8 @@ module Outback
14
14
  @excludes ||= []
15
15
  end
16
16
 
17
- def exclude(path)
18
- excludes << path
17
+ def exclude(*paths)
18
+ excludes.concat(paths.map(&:to_s)).uniq!
19
19
  end
20
20
 
21
21
  def create_archives(backup_name, timestamp, tmpdir)
@@ -24,9 +24,10 @@ module Outback
24
24
  exclude_list = Pathname.new(tmpdir).join('exclude_list.txt')
25
25
  File.open(exclude_list, 'w') { |f| f << excludes.join("\n") }
26
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)]
27
+ Outback.debug "executing command: #{commandline}"
28
+ result = `#{commandline}`
29
+ Outback.debug result
30
+ [TempArchive.new(archive_name, self)]
30
31
  end
31
32
  end
32
33
  end
@@ -4,34 +4,38 @@ module Outback
4
4
  attr_setter :user, :group, :directory_permissions, :archive_permissions, :ttl, :move
5
5
 
6
6
  def initialize(path)
7
- @path = path
7
+ @path = Pathname.new(path)
8
8
  end
9
9
 
10
10
  def valid?
11
11
  (user and group) or (not user and not group)
12
12
  end
13
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
14
+ def put(archives)
15
+ Dir.mkdir(path) unless path.directory?
16
+ FileUtils.chmod directory_permissions || 0700, path
18
17
  archives.each do |archive|
19
18
  basename = Pathname.new(archive.filename).basename
20
19
  if move
21
- puts "moving #{archive.filename} to #{backup_folder}"
22
- FileUtils.mv archive.filename, backup_folder
20
+ Outback.debug "moving #{archive.filename} to #{path}"
21
+ FileUtils.mv archive.filename, path
23
22
  else
24
- puts "copying #{archive.filename} to #{backup_folder}"
25
- FileUtils.cp_r
23
+ Outback.debug "copying #{archive.filename} to #{path}"
24
+ FileUtils.cp_r archive.filename, path
26
25
  end
27
- archived_file = backup_folder.join(basename)
28
- puts "chmodding"
26
+ archived_file = path.join(basename)
27
+ Outback.debug "setting permissions for #{archived_file}"
29
28
  FileUtils.chmod archive_permissions || 0600, archived_file
30
29
  if user && group
31
- puts "chowning"
30
+ Outback.debug "setting owner #{user}, group #{group} for #{archived_file}"
32
31
  FileUtils.chown user, group, archived_file
33
32
  end
34
33
  end
35
34
  end
35
+
36
+ def list_archives
37
+ path.files(Archive::NAME_PATTERN).map { |f| DirectoryArchive.new(f, self) }
38
+ end
39
+
36
40
  end
37
41
  end
@@ -10,12 +10,12 @@ module Outback
10
10
  @excludes ||= []
11
11
  end
12
12
 
13
- def database(name)
14
- databases << name
13
+ def database(*names)
14
+ databases.concat(names.map(&:to_s)).uniq!
15
15
  end
16
16
 
17
- def exclude(name)
18
- excludes << name.to_s
17
+ def exclude(*names)
18
+ excludes.concat(names.map(&:to_s)).uniq!
19
19
  end
20
20
 
21
21
  def valid?
@@ -37,12 +37,11 @@ module Outback
37
37
  mysql_conf_file = Pathname.new(tmpdir).join('outback_my.cnf')
38
38
  File.open(mysql_conf_file, 'w') { |f| f << "[client]\npassword=#{password}\n" }
39
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)
40
+ Outback.debug "MysqlSource: dumping database '#{database}'"
41
+ commandline = "mysqldump --defaults-extra-file=#{mysql_conf_file} --opt --user=#{user} --host=#{mysql_host} --port=#{mysql_port} #{database} | gzip > #{archive_name}"
42
+ result = `#{commandline}`.strip
43
+ Outback.debug(result) unless result.blank?
44
+ TempArchive.new(archive_name, self).tap { |archive| Outback.debug "dumped #{archive.filename.basename} with #{archive.size} bytes" }
46
45
  end
47
46
  end
48
47
  end
@@ -1,6 +1,9 @@
1
1
  module Outback
2
- class S3Archive < RemoteArchive
3
-
4
-
2
+ class S3Archive < Archive
3
+ def purge!
4
+ Outback.debug "purging S3Archive: #{filename}"
5
+ parent.connect
6
+ AWS::S3::S3Object.delete filename, parent.bucket
7
+ end
5
8
  end
6
9
  end
@@ -1,7 +1,32 @@
1
1
  module Outback
2
2
  class S3Target < Target
3
3
  attr_setter :bucket, :access_key, :secret_key, :ttl, :prefix
4
-
4
+
5
+ def valid?
6
+ bucket && access_key && secret_key
7
+ end
8
+
9
+ def connect(force = true)
10
+ @connection = nil if force
11
+ @connection ||= AWS::S3::Base.establish_connection!(:access_key_id => access_key, :secret_access_key => secret_key)
12
+ end
13
+
14
+ def put(archives)
15
+ connect
16
+ archives.each do |archive|
17
+ object_name = [prefix.to_s, archive.filename.basename.to_s].join('/')
18
+ Outback.debug "S3Target: storing #{archive.filename} in s3://#{bucket}/#{object_name}"
19
+ AWS::S3::S3Object.store object_name, archive.open, bucket
20
+ object_exists = AWS::S3::S3Object.exists?(object_name, bucket)
21
+ Outback.debug "Checking if object exists: #{object_exists}"
22
+ end
23
+ end
24
+
25
+ def list_archives
26
+ connect
27
+ entries = AWS::S3::Bucket.objects(bucket).select { |e| e.key.start_with?(prefix.to_s) && e.key[prefix.to_s.size..-1].match(Archive::NAME_PATTERN) }
28
+ entries.map { |e| S3Archive.new(e.key, self) }
29
+ end
5
30
  end
6
31
 
7
32
  end
@@ -0,0 +1,17 @@
1
+ module Outback
2
+ module PathnameExt
3
+ def files(regexp = nil)
4
+ returning Dir[join('**')].map { |f| Pathname.new(f) }.select(&:file?) do |entries|
5
+ entries.delete_if { |f| not f.basename.to_s.match(regexp) } if regexp
6
+ end
7
+ end
8
+
9
+ def directories(regexp = nil)
10
+ returning Dir[join('**')].map { |f| Pathname.new(f) }.select(&:directory?) do |entries|
11
+ entries.delete_if { |f| not f.basename.to_s.match(regexp) } if regexp
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ Pathname.send :include, Outback::PathnameExt
@@ -1,6 +1,14 @@
1
1
  module Outback
2
2
  class Target
3
3
  include Configurable
4
+
5
+ def outdated_archives
6
+ list_archives.select(&:outdated?)
7
+ end
4
8
 
9
+ def purge!
10
+ outdated_archives.each &:purge!
11
+ end
12
+
5
13
  end
6
14
  end
@@ -1,9 +1,5 @@
1
1
  module Outback
2
2
  class TempArchive < Archive
3
- attr_reader :filename
4
-
5
- def initialize(filename)
6
- @filename = filename
7
- end
3
+
8
4
  end
9
5
  end
data/lib/outback.rb CHANGED
@@ -20,6 +20,7 @@ require 'outback/support/returning'
20
20
  require 'outback/support/attr_setter'
21
21
  require 'outback/support/configurable'
22
22
  require 'outback/support/mysql_ext'
23
+ require 'outback/support/pathname_ext'
23
24
  require 'outback/configuration'
24
25
  require 'outback/configuration_error'
25
26
  require 'outback/source'
@@ -29,9 +30,36 @@ require 'outback/archive'
29
30
  require 'outback/temp_archive'
30
31
  require 'outback/target'
31
32
  require 'outback/directory_target'
33
+ require 'outback/directory_archive'
32
34
  require 'outback/s3_target'
35
+ require 'outback/s3_archive'
33
36
  require 'outback/backup'
34
37
 
35
38
  module Outback
36
39
  VERSION = Pathname.new(__FILE__).dirname.join('..', 'VERSION').read.strip
40
+
41
+ class << self
42
+ %w(verbose silent).each do |method|
43
+ attr_accessor method
44
+ alias_method "#{method}?", method
45
+ end
46
+
47
+ def info(message)
48
+ return if silent?
49
+ puts message
50
+ true
51
+ end
52
+
53
+ def debug(message)
54
+ return unless verbose?
55
+ return if silent?
56
+ puts message
57
+ true
58
+ end
59
+
60
+ def error(message, options = nil)
61
+ return if silent?
62
+ puts "Outback error: #{message}"
63
+ end
64
+ end
37
65
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: outback
3
3
  version: !ruby/object:Gem::Version
4
- hash: 27
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 2
10
- version: 0.0.2
9
+ - 3
10
+ version: 0.0.3
11
11
  platform: ruby
12
12
  authors:
13
13
  - Matthias Grosser
@@ -58,28 +58,29 @@ extensions: []
58
58
  extra_rdoc_files: []
59
59
 
60
60
  files:
61
- - lib/outback/target.rb
62
- - lib/outback/source.rb
63
- - lib/outback/mysql_source.rb
64
- - lib/outback/s3_target.rb
61
+ - lib/outback/archive.rb
65
62
  - lib/outback/backup.rb
66
63
  - lib/outback/cli.rb
64
+ - lib/outback/configuration.rb
65
+ - lib/outback/configuration_error.rb
66
+ - lib/outback/directory_archive.rb
67
+ - lib/outback/directory_source.rb
67
68
  - lib/outback/directory_target.rb
69
+ - lib/outback/local_archive.rb
70
+ - lib/outback/mysql_source.rb
71
+ - lib/outback/s3_archive.rb
72
+ - lib/outback/s3_target.rb
73
+ - lib/outback/source.rb
68
74
  - lib/outback/support/attr_setter.rb
69
- - lib/outback/support/returning.rb
70
- - lib/outback/support/mysql_ext.rb
71
75
  - lib/outback/support/configurable.rb
72
- - lib/outback/configuration_error.rb
76
+ - lib/outback/support/mysql_ext.rb
77
+ - lib/outback/support/pathname_ext.rb
78
+ - lib/outback/support/returning.rb
79
+ - lib/outback/target.rb
73
80
  - lib/outback/temp_archive.rb
74
- - lib/outback/s3_archive.rb
75
- - lib/outback/local_archive.rb
76
- - lib/outback/archive.rb
77
- - lib/outback/configuration.rb
78
- - lib/outback/directory_source.rb
79
81
  - lib/outback/vendor/metaclass.rb
80
- - lib/outback/vendor/mysql.rb
81
82
  - lib/outback/vendor/methodphitamine.rb
82
- - lib/outback/remote_archive.rb
83
+ - lib/outback/vendor/mysql.rb
83
84
  - lib/outback.rb
84
85
  - bin/outback
85
86
  - MIT-LICENSE
@@ -114,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
114
115
  requirements: []
115
116
 
116
117
  rubyforge_project:
117
- rubygems_version: 1.6.2
118
+ rubygems_version: 1.5.3
118
119
  signing_key:
119
120
  specification_version: 3
120
121
  summary: Ruby Backup Tool
@@ -1,6 +0,0 @@
1
- module Outback
2
- class RemoteArchive < Archive
3
-
4
-
5
- end
6
- end