outback 0.0.14 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +11 -0
  3. data/LICENSE +21 -0
  4. data/README.md +29 -3
  5. data/lib/outback/archive.rb +6 -17
  6. data/lib/outback/backup.rb +38 -20
  7. data/lib/outback/cli.rb +6 -2
  8. data/lib/outback/configuration.rb +15 -10
  9. data/lib/outback/directory_source.rb +8 -7
  10. data/lib/outback/directory_target.rb +18 -11
  11. data/lib/outback/encryption_processor.rb +34 -0
  12. data/lib/outback/errors.rb +7 -0
  13. data/lib/outback/logging.rb +7 -0
  14. data/lib/outback/mysql_source.rb +9 -9
  15. data/lib/outback/processor.rb +17 -0
  16. data/lib/outback/s3_target.rb +18 -9
  17. data/lib/outback/sftp_target.rb +70 -0
  18. data/lib/outback/source.rb +9 -2
  19. data/lib/outback/source_archive.rb +17 -0
  20. data/lib/outback/support/attr_setter.rb +1 -1
  21. data/lib/outback/support/configurable.rb +5 -3
  22. data/lib/outback/target.rb +52 -14
  23. data/lib/outback/target_archive.rb +30 -0
  24. data/lib/outback/version.rb +3 -0
  25. data/lib/outback.rb +16 -11
  26. data/lib/vendor/enumerable_ext.rb +9 -0
  27. data/lib/{outback/vendor → vendor}/metaclass.rb +1 -1
  28. data/lib/vendor/methodphitamine.rb +28 -0
  29. data/lib/vendor/mysql/charset.rb +325 -0
  30. data/lib/vendor/mysql/constants.rb +165 -0
  31. data/lib/vendor/mysql/error.rb +989 -0
  32. data/lib/vendor/mysql/packet.rb +78 -0
  33. data/lib/vendor/mysql/protocol.rb +770 -0
  34. data/lib/vendor/mysql.rb +1093 -0
  35. data/lib/vendor/numeric_ext.rb +49 -0
  36. data/lib/vendor/string_ext.rb +19 -0
  37. metadata +84 -43
  38. data/MIT-LICENSE +0 -20
  39. data/VERSION +0 -1
  40. data/lib/outback/configuration_error.rb +0 -4
  41. data/lib/outback/directory_archive.rb +0 -8
  42. data/lib/outback/local_archive.rb +0 -6
  43. data/lib/outback/s3_archive.rb +0 -18
  44. data/lib/outback/temp_archive.rb +0 -5
  45. data/lib/outback/vendor/methodphitamine.rb +0 -25
  46. 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, '/ver/www' do
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.
@@ -1,30 +1,19 @@
1
1
  module Outback
2
2
  class Archive
3
- NAME_PATTERN = /([A-Za-z0-9.\-]+)_(\d{14})_(\w+)/
3
+ NAME_PATTERN = /\A([A-Za-z0-9.\-]+)_(\d{14})_(\w+)/
4
4
 
5
- attr_reader :filename, :backup_name, :timestamp, :source_name, :parent
5
+ attr_reader :filename, :backup_name, :timestamp, :source_name, :size
6
6
 
7
- def initialize(filename, parent)
8
- @filename, @parent = Pathname.new(filename), parent
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 size
16
- filename.size
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
@@ -1,44 +1,62 @@
1
1
  module Outback
2
2
  class Backup
3
- attr_reader :configuration, :name, :timestamp, :archives, :tmpdir
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.to_formatted_s(:number)
11
+ @timestamp = Time.now.strftime(Outback::TIME_FORMAT)
11
12
  end
12
13
 
13
14
  def run!
14
- @archives = []
15
- begin
16
- Outback.info "Using working directory #{configuration.tmpdir}" if configuration.tmpdir
17
- @tmpdir = Dir.mktmpdir([name, timestamp], configuration.tmpdir)
18
- @archives = create_archives
19
- Outback.info "Created #{@archives.size} archives"
20
- store_archives
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
- archives = sources.collect do |source|
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 store_archives
37
- targets.each { |target| target.put(archives) }
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 { |target| target.purge!(name) }
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 || DEFAULT_CONFIGURATION_FILE
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.new("duplicate configuration #{configuration.name}") if loaded.any?(&its.name == configuration.name)
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
- raise(ConfigurationError.new("configuration name can't be blank")) if name.blank?
28
- @name = name.to_s
29
- raise(ConfigurationError.new('configuration name may not contain underscores')) if @name.include?('_')
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(ConfigurationError.new("tmpdir #{dir} is no directory")) unless dir.directory?
52
+ raise ConfigurationError, "tmpdir #{dir} is not a directory" unless dir.directory?
52
53
  @tmpdir = dir
53
54
  else
54
- raise ConfigurationError.new("tmpdir: wrong number of arguments(#{args.size} for 1)")
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(backup_name, timestamp, tmpdir)
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
- Outback.debug "executing command: #{commandline}"
29
+ logger.debug "executing command: #{commandline}"
29
30
  result = `#{commandline}`
30
- Outback.debug "result: #{result}"
31
- Outback.info "Archived directory #{path}"
32
- [TempArchive.new(archive_name, self)]
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 display_name
15
+ def to_s
15
16
  "directory:#{path}"
16
17
  end
17
18
 
18
19
  def put(archives)
19
- Dir.mkdir(path) unless path.directory?
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
- Outback.debug "moving #{archive.filename} to #{path}"
26
+ logger.debug "moving #{archive.filename} to #{path}"
26
27
  FileUtils.mv archive.filename, path
27
28
  else
28
- Outback.debug "copying #{archive.filename} to #{path}"
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
- Outback.debug "setting permissions for #{archived_file}"
33
+ logger.debug "setting permissions for #{archived_file}"
33
34
  FileUtils.chmod archive_permissions || 0600, archived_file
34
35
  if user && group
35
- Outback.debug "setting owner #{user}, group #{group} for #{archived_file}"
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
- Outback.info "#{move ? 'Moved' : 'Copied'} #{archives.size} archives (#{size} bytes) to #{display_name}"
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 list_archives(name)
45
- path.files(Archive::NAME_PATTERN).map { |f| DirectoryArchive.new(f, self) }.select(&its.backup_name == name)
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
@@ -0,0 +1,7 @@
1
+ module Outback
2
+ class Error < StandardError; end
3
+
4
+ class ConfigurationError < Error; end
5
+ class BackupError < Error; end
6
+ class ProcessingError < Error; end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Outback
2
+ module Logging
3
+ def logger
4
+ Outback
5
+ end
6
+ end
7
+ end
@@ -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(backup_name, timestamp, tmpdir)
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
- # (host=nil, user=nil, passwd=nil, db=nil, port=nil, socket=nil, flag=nil)
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.databases - excludes
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
- Outback.debug "MysqlSource: dumping database '#{database}'"
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
- Outback.debug(result) unless result.blank?
44
- Outback.info "Archived database #{database}"
45
- TempArchive.new(archive_name, self).tap { |archive| Outback.debug "dumped #{archive.filename.basename} with #{archive.size} bytes" }
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
@@ -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 display_name
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(:access_key_id => access_key, :secret_access_key => secret_key)
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
- Outback.debug "S3Target: storing #{archive.filename} in s3://#{bucket_name}/#{object_name}"
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
- Outback.error "S3 archive upload failed: #{object_name}"
35
+ logger.error "S3 archive upload failed: #{object_name}"
36
36
  end
37
37
  end
38
- Outback.info "Uploaded #{count} archives (#{size} bytes) to #{display_name}"
38
+ logger.info "Uploaded #{count} archives (#{size} bytes) to #{self}"
39
39
  count
40
40
  end
41
41
 
42
- def list_archives(name)
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| S3Archive.new(e.key, self) }.select(&its.backup_name == name)
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