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 +1 -1
- data/lib/outback/archive.rb +26 -1
- data/lib/outback/backup.rb +8 -10
- data/lib/outback/cli.rb +49 -36
- data/lib/outback/configuration.rb +13 -6
- data/lib/outback/configuration_error.rb +0 -6
- data/lib/outback/directory_archive.rb +8 -0
- data/lib/outback/directory_source.rb +6 -5
- data/lib/outback/directory_target.rb +16 -12
- data/lib/outback/mysql_source.rb +9 -10
- data/lib/outback/s3_archive.rb +6 -3
- data/lib/outback/s3_target.rb +26 -1
- data/lib/outback/support/pathname_ext.rb +17 -0
- data/lib/outback/target.rb +8 -0
- data/lib/outback/temp_archive.rb +1 -5
- data/lib/outback.rb +28 -0
- metadata +19 -18
- data/lib/outback/remote_archive.rb +0 -6
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.0.
|
1
|
+
0.0.3
|
data/lib/outback/archive.rb
CHANGED
@@ -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
|
data/lib/outback/backup.rb
CHANGED
@@ -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
|
-
|
18
|
+
store_archives
|
19
19
|
ensure
|
20
20
|
FileUtils.remove_entry_secure(tmpdir)
|
21
21
|
end
|
22
|
-
|
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
|
35
|
-
|
34
|
+
def store_archives
|
35
|
+
targets.each { |target| target.put(archives) }
|
36
36
|
end
|
37
|
-
|
38
|
-
def
|
39
|
-
targets.each
|
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
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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(
|
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(
|
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(
|
50
|
-
errors << ConfigurationError.new(
|
56
|
+
def error(message)
|
57
|
+
errors << ConfigurationError.new(message)
|
51
58
|
false
|
52
59
|
end
|
53
60
|
end
|
@@ -14,8 +14,8 @@ module Outback
|
|
14
14
|
@excludes ||= []
|
15
15
|
end
|
16
16
|
|
17
|
-
def exclude(
|
18
|
-
excludes
|
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
|
-
|
28
|
-
|
29
|
-
|
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
|
15
|
-
|
16
|
-
|
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
|
-
|
22
|
-
FileUtils.mv archive.filename,
|
20
|
+
Outback.debug "moving #{archive.filename} to #{path}"
|
21
|
+
FileUtils.mv archive.filename, path
|
23
22
|
else
|
24
|
-
|
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 =
|
28
|
-
|
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
|
-
|
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
|
data/lib/outback/mysql_source.rb
CHANGED
@@ -10,12 +10,12 @@ module Outback
|
|
10
10
|
@excludes ||= []
|
11
11
|
end
|
12
12
|
|
13
|
-
def database(
|
14
|
-
databases
|
13
|
+
def database(*names)
|
14
|
+
databases.concat(names.map(&:to_s)).uniq!
|
15
15
|
end
|
16
16
|
|
17
|
-
def exclude(
|
18
|
-
excludes
|
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
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
data/lib/outback/s3_archive.rb
CHANGED
data/lib/outback/s3_target.rb
CHANGED
@@ -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
|
data/lib/outback/target.rb
CHANGED
data/lib/outback/temp_archive.rb
CHANGED
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:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
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/
|
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/
|
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/
|
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.
|
118
|
+
rubygems_version: 1.5.3
|
118
119
|
signing_key:
|
119
120
|
specification_version: 3
|
120
121
|
summary: Ruby Backup Tool
|