outback 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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