outback 0.0.14 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG +11 -0
- data/LICENSE +21 -0
- data/README.md +29 -3
- data/lib/outback/archive.rb +6 -17
- data/lib/outback/backup.rb +38 -20
- data/lib/outback/cli.rb +6 -2
- data/lib/outback/configuration.rb +15 -10
- data/lib/outback/directory_source.rb +8 -7
- data/lib/outback/directory_target.rb +18 -11
- data/lib/outback/encryption_processor.rb +34 -0
- data/lib/outback/errors.rb +7 -0
- data/lib/outback/logging.rb +7 -0
- data/lib/outback/mysql_source.rb +9 -9
- data/lib/outback/processor.rb +17 -0
- data/lib/outback/s3_target.rb +18 -9
- data/lib/outback/sftp_target.rb +70 -0
- data/lib/outback/source.rb +9 -2
- data/lib/outback/source_archive.rb +17 -0
- data/lib/outback/support/attr_setter.rb +1 -1
- data/lib/outback/support/configurable.rb +5 -3
- data/lib/outback/target.rb +52 -14
- data/lib/outback/target_archive.rb +30 -0
- data/lib/outback/version.rb +3 -0
- data/lib/outback.rb +16 -11
- data/lib/vendor/enumerable_ext.rb +9 -0
- data/lib/{outback/vendor → vendor}/metaclass.rb +1 -1
- data/lib/vendor/methodphitamine.rb +28 -0
- data/lib/vendor/mysql/charset.rb +325 -0
- data/lib/vendor/mysql/constants.rb +165 -0
- data/lib/vendor/mysql/error.rb +989 -0
- data/lib/vendor/mysql/packet.rb +78 -0
- data/lib/vendor/mysql/protocol.rb +770 -0
- data/lib/vendor/mysql.rb +1093 -0
- data/lib/vendor/numeric_ext.rb +49 -0
- data/lib/vendor/string_ext.rb +19 -0
- metadata +84 -43
- data/MIT-LICENSE +0 -20
- data/VERSION +0 -1
- data/lib/outback/configuration_error.rb +0 -4
- data/lib/outback/directory_archive.rb +0 -8
- data/lib/outback/local_archive.rb +0 -6
- data/lib/outback/s3_archive.rb +0 -18
- data/lib/outback/temp_archive.rb +0 -5
- data/lib/outback/vendor/methodphitamine.rb +0 -25
- data/lib/outback/vendor/mysql.rb +0 -1214
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4503e45304f0be857fab9999cddf991b4a3823668951f0ee476eb94c80abd420
|
4
|
+
data.tar.gz: 49451b535df2adcf44c66089101de73cd38fd9d4494cda5503db130bca36674e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: b88bc0e63f3145bf2d84e032c725e01de590f3d8560de0cb4d60b064895c81aa63827614f729b95d86c89282b106dbcb76afe6f7a9281fbaaef6449ecb937a37
|
7
|
+
data.tar.gz: 1dd915c8b1bf994bd7f78de43f834f4b3b1424da88636c788de8708fb30ca8b9ad05827dbd236e7070013e00484cd61d18ef0301186f870ffa0529f991ed9fed
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
*1.1.0 (July 8, 2024)*
|
2
|
+
|
3
|
+
* Support Ruby 3
|
4
|
+
* Do not fail on store or purge
|
5
|
+
|
6
|
+
*1.0.2 (September 20, 2016)*
|
7
|
+
|
8
|
+
* Add SCP target
|
9
|
+
* Add encryption with OpenSSL
|
10
|
+
* Drop activesupport dependency
|
11
|
+
|
1
12
|
*0.0.12 (August 16, 2012)*
|
2
13
|
|
3
14
|
* Fix warning caused by methodphitamine.rb
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2016 onrooby
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
@@ -28,7 +28,7 @@ instantiated during a single run.
|
|
28
28
|
|
29
29
|
```` ruby
|
30
30
|
Outback::Configuration.new 'name' do
|
31
|
-
source :directory, '/
|
31
|
+
source :directory, '/var/www' do
|
32
32
|
exclude '/var/www/foo'
|
33
33
|
exclude '/var/www/icons/*.png'
|
34
34
|
end
|
@@ -45,6 +45,13 @@ Outback::Configuration.new 'name' do
|
|
45
45
|
# database 'specific_database'
|
46
46
|
end
|
47
47
|
|
48
|
+
processor :encryption do
|
49
|
+
password 'very secure encryption password'
|
50
|
+
|
51
|
+
# The default cipher is aes-256-cbc
|
52
|
+
#cipher 'openssl supported cipher'
|
53
|
+
end
|
54
|
+
|
48
55
|
# Amazon S3 storage
|
49
56
|
target :s3 do
|
50
57
|
access_key 'S3 access key'
|
@@ -56,6 +63,19 @@ Outback::Configuration.new 'name' do
|
|
56
63
|
# Just omit the definition to keep archives forever.
|
57
64
|
ttl 1.month
|
58
65
|
end
|
66
|
+
|
67
|
+
# SFTP storage
|
68
|
+
target :sftp, 'example.com' do
|
69
|
+
user 'backupuser'
|
70
|
+
# Usually, SSH keys will be used to connect to the server.
|
71
|
+
# Alternatively, a password can be given here.
|
72
|
+
#password 'your password'
|
73
|
+
|
74
|
+
# Default port is 22
|
75
|
+
# port 1234
|
76
|
+
ttl 1.day
|
77
|
+
path '/backups'
|
78
|
+
end
|
59
79
|
|
60
80
|
# Store on a local filesystem path
|
61
81
|
target :directory, '/media/backups/daily' do
|
@@ -79,7 +99,7 @@ Default configurations and commandline options
|
|
79
99
|
If you place your backup configurations in the file `/etc/outback.conf` they
|
80
100
|
will be read automatically when the outback executable is invoked. Make
|
81
101
|
sure to have correct permissions on the configuration files, as they might
|
82
|
-
include database passwords.
|
102
|
+
include database and encryption passwords.
|
83
103
|
|
84
104
|
Alternatively, you can pass in the configuration file to read as a
|
85
105
|
commandline argument. The default configuration file in /etc will then be
|
@@ -131,4 +151,10 @@ Other commandline options are:
|
|
131
151
|
* `-t` `--test` test configuration, then exit
|
132
152
|
* `-l` `--list` list available configurations, then exit
|
133
153
|
* `-h` `--help` display help
|
134
|
-
* `--version` display version
|
154
|
+
* `--version` display version
|
155
|
+
|
156
|
+
Known Issues
|
157
|
+
------------
|
158
|
+
|
159
|
+
The s3 gem currently doesn't support V4 signatures, which are required for
|
160
|
+
s3 regions introduced after January 2014.
|
data/lib/outback/archive.rb
CHANGED
@@ -1,30 +1,19 @@
|
|
1
1
|
module Outback
|
2
2
|
class Archive
|
3
|
-
NAME_PATTERN =
|
3
|
+
NAME_PATTERN = /\A([A-Za-z0-9.\-]+)_(\d{14})_(\w+)/
|
4
4
|
|
5
|
-
attr_reader :filename, :backup_name, :timestamp, :source_name, :
|
5
|
+
attr_reader :filename, :backup_name, :timestamp, :source_name, :size
|
6
6
|
|
7
|
-
def initialize(filename
|
8
|
-
@filename
|
7
|
+
def initialize(filename)
|
8
|
+
@filename = Pathname.new(filename)
|
9
9
|
unless match_data = @filename.basename.to_s.match(NAME_PATTERN)
|
10
10
|
raise ArgumentError, 'invalid name'
|
11
11
|
end
|
12
12
|
@backup_name, @timestamp, @source_name = match_data.captures[0..2]
|
13
13
|
end
|
14
14
|
|
15
|
-
def
|
16
|
-
filename
|
15
|
+
def to_s
|
16
|
+
"#{filename}"
|
17
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
|
-
|
29
18
|
end
|
30
19
|
end
|
data/lib/outback/backup.rb
CHANGED
@@ -1,44 +1,62 @@
|
|
1
1
|
module Outback
|
2
2
|
class Backup
|
3
|
-
|
4
|
-
delegate :sources, :targets, :to => :configuration
|
3
|
+
include Logging
|
5
4
|
|
5
|
+
attr_reader :configuration, :name, :timestamp, :tmpdir
|
6
|
+
|
6
7
|
def initialize(configuration)
|
7
8
|
raise ArgumentError, "configuration required" unless configuration.is_a?(Outback::Configuration)
|
8
9
|
@configuration = configuration
|
9
10
|
@name = configuration.name
|
10
|
-
@timestamp = Time.now.
|
11
|
+
@timestamp = Time.now.strftime(Outback::TIME_FORMAT)
|
11
12
|
end
|
12
13
|
|
13
14
|
def run!
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
ensure
|
22
|
-
FileUtils.remove_entry_secure(tmpdir)
|
23
|
-
end
|
15
|
+
logger.info "Using working directory #{configuration.tmpdir}" if configuration.tmpdir
|
16
|
+
@tmpdir = Dir.mktmpdir([name, timestamp], configuration.tmpdir)
|
17
|
+
archives = create_archives
|
18
|
+
logger.info "Created #{archives.size} archives"
|
19
|
+
archives = process_archives(archives)
|
20
|
+
logger.info "Processed #{archives.size} archives"
|
21
|
+
store_archives(archives)
|
24
22
|
purge_targets
|
23
|
+
ensure
|
24
|
+
FileUtils.remove_entry_secure(tmpdir)
|
25
|
+
end
|
26
|
+
|
27
|
+
[:sources, :processors, :targets].each do |collection|
|
28
|
+
define_method(collection) { configuration.public_send(collection) }
|
25
29
|
end
|
26
30
|
|
27
31
|
private
|
28
32
|
|
29
33
|
def create_archives
|
30
|
-
|
31
|
-
source.create_archives(name, timestamp, tmpdir)
|
32
|
-
end
|
33
|
-
archives.flatten.compact
|
34
|
+
sources.map { |source| source.create_archives(timestamp, tmpdir) }.flatten.compact
|
34
35
|
end
|
35
36
|
|
36
|
-
def
|
37
|
-
|
37
|
+
def process_archives(archives)
|
38
|
+
processors.inject(archives) { |archives, processor| processor.process!(archives) }.flatten.compact
|
39
|
+
end
|
40
|
+
|
41
|
+
def store_archives(archives)
|
42
|
+
targets.each do |target|
|
43
|
+
begin
|
44
|
+
target.put(archives)
|
45
|
+
rescue => e
|
46
|
+
logger.info "Error while storing to #{target}: #{e.inspect}"
|
47
|
+
0
|
48
|
+
end
|
49
|
+
end
|
38
50
|
end
|
39
51
|
|
40
52
|
def purge_targets
|
41
|
-
targets.each
|
53
|
+
targets.each do |target|
|
54
|
+
begin
|
55
|
+
target.purge!
|
56
|
+
rescue => e
|
57
|
+
logger.info "Error while purging #{target}: #{e.inspect}"
|
58
|
+
end
|
59
|
+
end
|
42
60
|
end
|
43
61
|
|
44
62
|
end
|
data/lib/outback/cli.rb
CHANGED
@@ -35,7 +35,11 @@ module Outback
|
|
35
35
|
end
|
36
36
|
option_parser.parse!
|
37
37
|
Outback::Configuration.reset
|
38
|
-
config_file = ARGV.first
|
38
|
+
config_file = if ARGV.first
|
39
|
+
ARGV.first.start_with?('/') ? ARGV.first : File.join(Dir.pwd, ARGV.first)
|
40
|
+
else
|
41
|
+
DEFAULT_CONFIGURATION_FILE
|
42
|
+
end
|
39
43
|
begin
|
40
44
|
load config_file
|
41
45
|
rescue ConfigurationError => conf_error
|
@@ -83,4 +87,4 @@ module Outback
|
|
83
87
|
end
|
84
88
|
|
85
89
|
end
|
86
|
-
end
|
90
|
+
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
module Outback
|
2
2
|
class Configuration
|
3
|
+
|
3
4
|
@loaded = []
|
4
5
|
|
5
6
|
class << self
|
6
7
|
def add(configuration)
|
7
|
-
raise ConfigurationError
|
8
|
+
raise ConfigurationError, "duplicate configuration #{configuration.name}" if loaded.any?(&its.name == configuration.name)
|
8
9
|
loaded << configuration
|
9
10
|
end
|
10
11
|
|
@@ -21,13 +22,13 @@ module Outback
|
|
21
22
|
end
|
22
23
|
end
|
23
24
|
|
24
|
-
attr_reader :name, :sources, :targets, :errors
|
25
|
+
attr_reader :name, :sources, :targets, :processors, :errors
|
25
26
|
|
26
27
|
def initialize(name, &block)
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
@sources, @targets, @errors = [], [], []
|
28
|
+
name = name.to_s
|
29
|
+
raise ConfigurationError, "Illegal configuration name #{name.inspect}" unless name.match(/\A[a-z][a-z0-9_\-.]+\z/)
|
30
|
+
@name = name
|
31
|
+
@sources, @processors, @targets, @errors = [], [], [], []
|
31
32
|
if block_given?
|
32
33
|
if block.arity == 1 then yield(self) else instance_eval(&block) end
|
33
34
|
end
|
@@ -48,21 +49,25 @@ module Outback
|
|
48
49
|
@tmpdir
|
49
50
|
elsif args.size == 1
|
50
51
|
dir = Pathname.new(args.first).realpath
|
51
|
-
raise
|
52
|
+
raise ConfigurationError, "tmpdir #{dir} is not a directory" unless dir.directory?
|
52
53
|
@tmpdir = dir
|
53
54
|
else
|
54
|
-
raise ConfigurationError
|
55
|
+
raise ConfigurationError, "tmpdir: wrong number of arguments(#{args.size} for 1)"
|
55
56
|
end
|
56
57
|
end
|
57
58
|
|
58
59
|
protected
|
59
60
|
|
60
61
|
def source(type, *args, &block)
|
61
|
-
"Outback::#{type.to_s.classify}Source".constantize.configure(*args, &block).tap { |instance| sources << instance }
|
62
|
+
"Outback::#{type.to_s.classify}Source".constantize.configure(name, *args, &block).tap { |instance| sources << instance }
|
63
|
+
end
|
64
|
+
|
65
|
+
def processor(type, *args, &block)
|
66
|
+
"Outback::#{type.to_s.classify}Processor".constantize.configure(name, *args, &block).tap { |instance| processors << instance }
|
62
67
|
end
|
63
68
|
|
64
69
|
def target(type, *args, &block)
|
65
|
-
"Outback::#{type.to_s.classify}Target".constantize.configure(*args, &block).tap { |instance| targets << instance }
|
70
|
+
"Outback::#{type.to_s.classify}Target".constantize.configure(name, *args, &block).tap { |instance| targets << instance }
|
66
71
|
end
|
67
72
|
|
68
73
|
def error(message)
|
@@ -2,7 +2,8 @@ module Outback
|
|
2
2
|
class DirectorySource < Source
|
3
3
|
attr_reader :path
|
4
4
|
|
5
|
-
def initialize(path)
|
5
|
+
def initialize(backup_name, path)
|
6
|
+
super(backup_name)
|
6
7
|
@path = path
|
7
8
|
end
|
8
9
|
|
@@ -18,18 +19,18 @@ module Outback
|
|
18
19
|
excludes.concat(paths.map(&:to_s)).uniq!
|
19
20
|
end
|
20
21
|
|
21
|
-
def create_archives(
|
22
|
+
def create_archives(timestamp, tmpdir)
|
22
23
|
source_dir = Pathname.new(path).realpath
|
23
24
|
archive_name = Pathname.new(tmpdir).join("#{backup_name}_#{timestamp}_#{source_name}.tar.gz")
|
24
25
|
exclude_list = Pathname.new(tmpdir).join('exclude_list.txt')
|
25
26
|
File.open(exclude_list, 'w') { |f| f << excludes.map { |e| e.to_s.sub(/\A\//, '') }.join("\n") }
|
26
27
|
verbose_switch = Outback.verbose? ? 'v' : ''
|
27
28
|
commandline = "tar -cz#{verbose_switch}pf #{archive_name} --exclude-from=#{exclude_list} --directory=/ #{source_dir.to_s.sub(/\A\//, '')}"
|
28
|
-
|
29
|
+
logger.debug "executing command: #{commandline}"
|
29
30
|
result = `#{commandline}`
|
30
|
-
|
31
|
-
|
32
|
-
[
|
31
|
+
logger.debug "result: #{result}"
|
32
|
+
logger.info "Archived directory #{path}"
|
33
|
+
[SourceArchive.new(archive_name)]
|
33
34
|
end
|
34
35
|
end
|
35
|
-
end
|
36
|
+
end
|
@@ -3,7 +3,8 @@ module Outback
|
|
3
3
|
attr_reader :path
|
4
4
|
attr_setter :user, :group, :directory_permissions, :archive_permissions, :ttl, :move
|
5
5
|
|
6
|
-
def initialize(path)
|
6
|
+
def initialize(backup_name, path)
|
7
|
+
super(backup_name)
|
7
8
|
@path = Pathname.new(path)
|
8
9
|
end
|
9
10
|
|
@@ -11,39 +12,45 @@ module Outback
|
|
11
12
|
(user and group) or (not user and not group)
|
12
13
|
end
|
13
14
|
|
14
|
-
def
|
15
|
+
def to_s
|
15
16
|
"directory:#{path}"
|
16
17
|
end
|
17
18
|
|
18
19
|
def put(archives)
|
19
|
-
|
20
|
+
FileUtils.mkdir_p(path) unless path.directory?
|
20
21
|
FileUtils.chmod directory_permissions || 0700, path
|
21
22
|
size = 0
|
22
23
|
archives.each do |archive|
|
23
24
|
basename = Pathname.new(archive.filename).basename
|
24
25
|
if move
|
25
|
-
|
26
|
+
logger.debug "moving #{archive.filename} to #{path}"
|
26
27
|
FileUtils.mv archive.filename, path
|
27
28
|
else
|
28
|
-
|
29
|
+
logger.debug "copying #{archive.filename} to #{path}"
|
29
30
|
FileUtils.cp_r archive.filename, path
|
30
31
|
end
|
31
32
|
archived_file = path.join(basename)
|
32
|
-
|
33
|
+
logger.debug "setting permissions for #{archived_file}"
|
33
34
|
FileUtils.chmod archive_permissions || 0600, archived_file
|
34
35
|
if user && group
|
35
|
-
|
36
|
+
logger.debug "setting owner #{user}, group #{group} for #{archived_file}"
|
36
37
|
FileUtils.chown user, group, archived_file
|
37
38
|
end
|
38
39
|
size += archived_file.size
|
39
40
|
end
|
40
|
-
|
41
|
+
logger.info "#{move ? 'Moved' : 'Copied'} #{archives.size} archives (#{size} bytes) to #{self}"
|
41
42
|
archives.size
|
42
43
|
end
|
44
|
+
|
45
|
+
private
|
43
46
|
|
44
|
-
def
|
45
|
-
path.files(Archive::NAME_PATTERN).map { |f|
|
47
|
+
def list_all_archives
|
48
|
+
path.files(Archive::NAME_PATTERN).map { |f| build_archive(f.to_s, f.size) }
|
49
|
+
end
|
50
|
+
|
51
|
+
def unlink_archive!(archive)
|
52
|
+
archive.filename.unlink
|
46
53
|
end
|
47
54
|
|
48
55
|
end
|
49
|
-
end
|
56
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
module Outback
|
5
|
+
class EncryptionProcessor < Processor
|
6
|
+
|
7
|
+
attr_setter :password, :cipher
|
8
|
+
|
9
|
+
def cipher
|
10
|
+
@cipher ||= 'aes-256-cbc'
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
"encryption:#{cipher}"
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def process_archive!(archive)
|
20
|
+
result = nil
|
21
|
+
outfile = Pathname.new("#{archive.filename}.enc")
|
22
|
+
logger.debug "Encrypting #{archive} with #{self}"
|
23
|
+
Open3.popen3("openssl enc -#{cipher} -pass stdin -in #{archive.filename} -out #{outfile}") do |stdin, stdout, stderr, wait_thr|
|
24
|
+
stdin << password
|
25
|
+
stdin.close
|
26
|
+
result = wait_thr.value
|
27
|
+
end
|
28
|
+
raise ProcessingError, "error processing archive #{archive} in #{self}" unless result.success?
|
29
|
+
raise ProcessingError, "outfile #{outfile} not found" unless outfile.file?
|
30
|
+
archive.unlink
|
31
|
+
SourceArchive.new(outfile)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/lib/outback/mysql_source.rb
CHANGED
@@ -7,7 +7,7 @@ module Outback
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def excludes
|
10
|
-
@excludes ||= []
|
10
|
+
@excludes ||= ['mysql', 'performance_schema', 'information_schema']
|
11
11
|
end
|
12
12
|
|
13
13
|
def database(*names)
|
@@ -22,13 +22,13 @@ module Outback
|
|
22
22
|
user && password
|
23
23
|
end
|
24
24
|
|
25
|
-
def create_archives(
|
25
|
+
def create_archives(timestamp, tmpdir)
|
26
26
|
mysql_host = host || 'localhost'
|
27
27
|
mysql_port = (port || 3306) unless socket
|
28
28
|
if databases.empty?
|
29
|
-
#
|
29
|
+
# (host=nil, user=nil, passwd=nil, db=nil, port=nil, socket=nil, flag=nil)
|
30
30
|
mysql = Mysql.connect(mysql_host, user, password, nil, mysql_port, socket)
|
31
|
-
@databases = mysql.
|
31
|
+
@databases = mysql.list_dbs - excludes
|
32
32
|
mysql.close
|
33
33
|
end
|
34
34
|
|
@@ -37,13 +37,13 @@ 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
|
-
|
40
|
+
logger.debug "MysqlSource: dumping database '#{database}'"
|
41
41
|
commandline = "mysqldump --defaults-extra-file=#{mysql_conf_file} --opt --user=#{user} --host=#{mysql_host} --port=#{mysql_port} #{database} | gzip > #{archive_name}"
|
42
42
|
result = `#{commandline}`.strip
|
43
|
-
|
44
|
-
|
45
|
-
|
43
|
+
logger.debug(result) unless result.blank?
|
44
|
+
logger.info "Archived database #{database}"
|
45
|
+
SourceArchive.new(archive_name).tap { |archive| logger.debug "dumped #{archive.filename.basename} with #{archive.size} bytes" }
|
46
46
|
end
|
47
47
|
end
|
48
48
|
end
|
49
|
-
end
|
49
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Outback
|
2
|
+
class Processor
|
3
|
+
include Configurable
|
4
|
+
include Logging
|
5
|
+
|
6
|
+
attr_reader :backup_name
|
7
|
+
|
8
|
+
def initialize(backup_name)
|
9
|
+
@backup_name = backup_name
|
10
|
+
end
|
11
|
+
|
12
|
+
def process!(archives)
|
13
|
+
archives.map { |archive| process_archive!(archive) }.flatten.compact
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
data/lib/outback/s3_target.rb
CHANGED
@@ -2,7 +2,7 @@ module Outback
|
|
2
2
|
class S3Target < Target
|
3
3
|
attr_setter :bucket_name, :access_key, :secret_key, :ttl, :prefix
|
4
4
|
|
5
|
-
def
|
5
|
+
def to_s
|
6
6
|
"s3:#{bucket_name}/#{prefix}"
|
7
7
|
end
|
8
8
|
|
@@ -12,7 +12,7 @@ module Outback
|
|
12
12
|
|
13
13
|
def service(force_reconnect = false)
|
14
14
|
@service = nil if force_reconnect
|
15
|
-
@service ||= S3::Service.new(:
|
15
|
+
@service ||= S3::Service.new(access_key_id: access_key, secret_access_key: secret_key, use_ssl: true)
|
16
16
|
end
|
17
17
|
|
18
18
|
def bucket
|
@@ -23,7 +23,7 @@ module Outback
|
|
23
23
|
size = count = 0
|
24
24
|
archives.each do |archive|
|
25
25
|
object_name = [prefix.to_s, archive.filename.basename.to_s].join('/')
|
26
|
-
|
26
|
+
logger.debug "S3Target: storing #{archive.filename} in s3://#{bucket_name}/#{object_name}"
|
27
27
|
object = bucket.objects.build(object_name)
|
28
28
|
object.content = archive.open
|
29
29
|
object.acl = :private
|
@@ -32,17 +32,26 @@ module Outback
|
|
32
32
|
size += archive.size
|
33
33
|
count += 1
|
34
34
|
else
|
35
|
-
|
35
|
+
logger.error "S3 archive upload failed: #{object_name}"
|
36
36
|
end
|
37
37
|
end
|
38
|
-
|
38
|
+
logger.info "Uploaded #{count} archives (#{size} bytes) to #{self}"
|
39
39
|
count
|
40
40
|
end
|
41
41
|
|
42
|
-
|
42
|
+
private
|
43
|
+
|
44
|
+
def list_all_archives
|
43
45
|
entries = bucket.objects.select { |e| e.key.start_with?(prefix.to_s) && e.key[prefix.to_s.size..-1].match(Archive::NAME_PATTERN) }
|
44
|
-
entries.map { |e|
|
46
|
+
entries.map { |e| build_archive(e.key, e.size) }
|
45
47
|
end
|
48
|
+
|
49
|
+
def unlink_archive!(archive)
|
50
|
+
logger.debug "purging S3Archive: #{archive.filename}"
|
51
|
+
object = bucket.objects.find(archive.filename.to_s)
|
52
|
+
return true if object && object.destroy
|
53
|
+
raise BackupError, "could not find object #{archive.filename} for purging"
|
54
|
+
end
|
55
|
+
|
46
56
|
end
|
47
|
-
|
48
|
-
end
|
57
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'net/sftp'
|
2
|
+
|
3
|
+
module Outback
|
4
|
+
class SftpTarget < Target
|
5
|
+
|
6
|
+
attr_reader :host
|
7
|
+
attr_setter :user, :password, :port, :path, :ttl, :ssh_opts
|
8
|
+
|
9
|
+
def initialize(backup_name, host)
|
10
|
+
super(backup_name)
|
11
|
+
@host = host
|
12
|
+
end
|
13
|
+
|
14
|
+
def valid?
|
15
|
+
host.present?
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
"sftp:#{user}@#{host}#{':' + port.to_s if port}:#{path}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def put(archives)
|
23
|
+
size = count = 0
|
24
|
+
connect do
|
25
|
+
archives.each do |archive|
|
26
|
+
basename = archive.filename.basename.to_s
|
27
|
+
upload_filename = path ? File.join(path, basename) : basename
|
28
|
+
logger.debug "SftpTarget: storing #{archive.filename} in sftp://#{user}@#{host}#{':' + port.to_s if port}:#{upload_filename}"
|
29
|
+
connection.upload!(archive.filename.to_s, upload_filename)
|
30
|
+
size += archive.size
|
31
|
+
count += 1
|
32
|
+
end
|
33
|
+
end
|
34
|
+
logger.info "Uploaded #{count} archives (#{size} bytes) to #{self}"
|
35
|
+
count
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def connect
|
41
|
+
result = nil
|
42
|
+
Net::SFTP.start(host, user, ssh_options) do |sftp|
|
43
|
+
@connection = sftp
|
44
|
+
result = yield
|
45
|
+
end
|
46
|
+
result
|
47
|
+
ensure
|
48
|
+
@connection = nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def ssh_options
|
52
|
+
options = ssh_opts || {}
|
53
|
+
if password
|
54
|
+
options[:password] = password
|
55
|
+
options[:auth_methods] ||= %w[password]
|
56
|
+
end
|
57
|
+
options[:port] = port if port
|
58
|
+
options
|
59
|
+
end
|
60
|
+
|
61
|
+
def list_all_archives
|
62
|
+
connection.dir.entries(path).select(&:file?).map { |entry| build_archive(File.join(path, entry.name), entry.attributes.size.to_i) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def unlink_archive!(archive)
|
66
|
+
connection.remove!(archive.filename)
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|