outback 0.0.14 → 1.0.2

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.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +6 -0
  3. data/LICENSE +21 -0
  4. data/README.md +29 -3
  5. data/lib/outback.rb +16 -11
  6. data/lib/outback/archive.rb +6 -17
  7. data/lib/outback/backup.rb +24 -19
  8. data/lib/outback/cli.rb +6 -2
  9. data/lib/outback/configuration.rb +15 -10
  10. data/lib/outback/directory_source.rb +8 -7
  11. data/lib/outback/directory_target.rb +18 -11
  12. data/lib/outback/encryption_processor.rb +34 -0
  13. data/lib/outback/errors.rb +7 -0
  14. data/lib/outback/logging.rb +7 -0
  15. data/lib/outback/mysql_source.rb +9 -9
  16. data/lib/outback/processor.rb +17 -0
  17. data/lib/outback/s3_target.rb +18 -9
  18. data/lib/outback/sftp_target.rb +70 -0
  19. data/lib/outback/source.rb +9 -2
  20. data/lib/outback/source_archive.rb +17 -0
  21. data/lib/outback/support/attr_setter.rb +1 -1
  22. data/lib/outback/support/configurable.rb +5 -3
  23. data/lib/outback/target.rb +51 -14
  24. data/lib/outback/target_archive.rb +30 -0
  25. data/lib/outback/version.rb +3 -0
  26. data/lib/vendor/enumerable_ext.rb +9 -0
  27. data/lib/{outback/vendor → vendor}/metaclass.rb +1 -1
  28. data/lib/{outback/vendor → vendor}/methodphitamine.rb +1 -1
  29. data/lib/vendor/mysql.rb +1093 -0
  30. data/lib/vendor/mysql/charset.rb +325 -0
  31. data/lib/vendor/mysql/constants.rb +165 -0
  32. data/lib/vendor/mysql/error.rb +989 -0
  33. data/lib/vendor/mysql/packet.rb +78 -0
  34. data/lib/vendor/mysql/protocol.rb +770 -0
  35. data/lib/vendor/numeric_ext.rb +49 -0
  36. data/lib/vendor/string_ext.rb +19 -0
  37. metadata +53 -39
  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/mysql.rb +0 -1214
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f2a87a0171b3d8506d69d5d8b0611916a7bccd8a
4
+ data.tar.gz: d86ec7bddb738e5b9574073e06aae4ffa65b25c8
5
+ SHA512:
6
+ metadata.gz: f252a3dab0597a9578c62c94ae4a9b739068c7090d357861ec534db8ae83f2327b6b5eead6cdc7e45e14f3fd502397cb6747d1ac7f900aba4692087d8004921d
7
+ data.tar.gz: 3e5a5ef024af75c835dbb4fae15f6b9e0d193c2e4865223beac15a33f83bef2b1c5345ff8a2795fc6b81634594d447ec294aedb04ae7a2b05dd2dd835140dd47
data/CHANGELOG CHANGED
@@ -1,3 +1,9 @@
1
+ *1.0.2 (September 20, 2016)*
2
+
3
+ * Add SCP target
4
+ * Add encryption with OpenSSL
5
+ * Drop activesupport dependency
6
+
1
7
  *0.0.12 (August 16, 2012)*
2
8
 
3
9
  * 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.
@@ -3,35 +3,40 @@ require 'fileutils'
3
3
  require 'tempfile'
4
4
  require 'tmpdir'
5
5
 
6
- require 'active_support/core_ext'
7
-
8
6
  require 's3'
9
7
 
10
- require 'outback/vendor/mysql'
11
- require 'outback/vendor/metaclass'
12
- require 'outback/vendor/methodphitamine'
8
+ require_relative 'vendor/string_ext'
9
+ require_relative 'vendor/numeric_ext'
10
+ require_relative 'vendor/enumerable_ext'
11
+ require_relative 'vendor/mysql'
12
+ require_relative 'vendor/metaclass'
13
+ require_relative 'vendor/methodphitamine'
13
14
 
15
+ require 'outback/version'
14
16
  require 'outback/support/returning'
15
17
  require 'outback/support/attr_setter'
16
18
  require 'outback/support/configurable'
17
19
  require 'outback/support/mysql_ext'
18
20
  require 'outback/support/pathname_ext'
21
+ require 'outback/errors'
22
+ require 'outback/logging'
19
23
  require 'outback/configuration'
20
- require 'outback/configuration_error'
24
+ require 'outback/archive'
21
25
  require 'outback/source'
26
+ require 'outback/source_archive'
22
27
  require 'outback/directory_source'
23
28
  require 'outback/mysql_source'
24
- require 'outback/archive'
25
- require 'outback/temp_archive'
29
+ require 'outback/processor'
30
+ require 'outback/encryption_processor'
26
31
  require 'outback/target'
32
+ require 'outback/target_archive'
27
33
  require 'outback/directory_target'
28
- require 'outback/directory_archive'
29
34
  require 'outback/s3_target'
30
- require 'outback/s3_archive'
35
+ require 'outback/sftp_target'
31
36
  require 'outback/backup'
32
37
 
33
38
  module Outback
34
- VERSION = Pathname.new(__FILE__).dirname.join('..', 'VERSION').read.strip
39
+ TIME_FORMAT = '%Y%m%d%H%M%S'.freeze
35
40
 
36
41
  class << self
37
42
  %w(verbose silent).each do |method|
@@ -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,49 @@
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
35
+ end
36
+
37
+ def process_archives(archives)
38
+ processors.inject(archives) { |archives, processor| processor.process!(archives) }.flatten.compact
34
39
  end
35
40
 
36
- def store_archives
41
+ def store_archives(archives)
37
42
  targets.each { |target| target.put(archives) }
38
43
  end
39
44
 
40
45
  def purge_targets
41
- targets.each { |target| target.purge!(name) }
46
+ targets.each { |target| target.purge! }
42
47
  end
43
48
 
44
49
  end
@@ -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