backup 3.6.0 → 3.7.0

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 (65) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -0
  3. data/lib/backup.rb +14 -4
  4. data/lib/backup/archive.rb +3 -2
  5. data/lib/backup/cleaner.rb +4 -2
  6. data/lib/backup/cli.rb +7 -5
  7. data/lib/backup/cloud_io/base.rb +41 -0
  8. data/lib/backup/cloud_io/cloud_files.rb +296 -0
  9. data/lib/backup/cloud_io/s3.rb +252 -0
  10. data/lib/backup/compressor/gzip.rb +2 -1
  11. data/lib/backup/config.rb +13 -5
  12. data/lib/backup/configuration.rb +1 -1
  13. data/lib/backup/configuration/helpers.rb +3 -1
  14. data/lib/backup/database/base.rb +3 -1
  15. data/lib/backup/database/mongodb.rb +2 -2
  16. data/lib/backup/database/mysql.rb +2 -2
  17. data/lib/backup/database/postgresql.rb +12 -2
  18. data/lib/backup/database/redis.rb +3 -2
  19. data/lib/backup/encryptor/gpg.rb +8 -10
  20. data/lib/backup/errors.rb +39 -70
  21. data/lib/backup/logger.rb +7 -2
  22. data/lib/backup/logger/fog_adapter.rb +30 -0
  23. data/lib/backup/model.rb +32 -14
  24. data/lib/backup/notifier/base.rb +4 -3
  25. data/lib/backup/notifier/campfire.rb +0 -1
  26. data/lib/backup/notifier/http_post.rb +122 -0
  27. data/lib/backup/notifier/mail.rb +38 -0
  28. data/lib/backup/notifier/nagios.rb +69 -0
  29. data/lib/backup/notifier/prowl.rb +0 -1
  30. data/lib/backup/notifier/pushover.rb +0 -1
  31. data/lib/backup/package.rb +5 -0
  32. data/lib/backup/packager.rb +3 -2
  33. data/lib/backup/pipeline.rb +4 -2
  34. data/lib/backup/storage/base.rb +2 -1
  35. data/lib/backup/storage/cloud_files.rb +151 -0
  36. data/lib/backup/storage/cycler.rb +4 -2
  37. data/lib/backup/storage/dropbox.rb +20 -16
  38. data/lib/backup/storage/ftp.rb +1 -2
  39. data/lib/backup/storage/local.rb +3 -3
  40. data/lib/backup/storage/ninefold.rb +3 -4
  41. data/lib/backup/storage/rsync.rb +1 -2
  42. data/lib/backup/storage/s3.rb +49 -158
  43. data/lib/backup/storage/scp.rb +3 -4
  44. data/lib/backup/storage/sftp.rb +1 -2
  45. data/lib/backup/syncer/base.rb +0 -1
  46. data/lib/backup/syncer/cloud/base.rb +129 -208
  47. data/lib/backup/syncer/cloud/cloud_files.rb +56 -41
  48. data/lib/backup/syncer/cloud/local_file.rb +93 -0
  49. data/lib/backup/syncer/cloud/s3.rb +78 -31
  50. data/lib/backup/syncer/rsync/base.rb +7 -0
  51. data/lib/backup/syncer/rsync/local.rb +0 -5
  52. data/lib/backup/syncer/rsync/push.rb +1 -2
  53. data/lib/backup/utilities.rb +18 -15
  54. data/lib/backup/version.rb +1 -1
  55. data/templates/cli/notifier/http_post +35 -0
  56. data/templates/cli/notifier/nagios +13 -0
  57. data/templates/cli/storage/cloud_files +8 -17
  58. data/templates/cli/storage/s3 +3 -10
  59. data/templates/cli/syncer/cloud_files +3 -31
  60. data/templates/cli/syncer/s3 +3 -27
  61. data/templates/notifier/mail/failure.erb +6 -1
  62. data/templates/notifier/mail/success.erb +6 -1
  63. data/templates/notifier/mail/warning.erb +6 -1
  64. metadata +37 -42
  65. data/lib/backup/storage/cloudfiles.rb +0 -68
@@ -0,0 +1,93 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Syncer
5
+ module Cloud
6
+ class LocalFile
7
+ attr_reader :path, :md5
8
+
9
+ class << self
10
+ include Utilities::Helpers
11
+
12
+ # Returns a Hash of LocalFile objects for each file within +dir+.
13
+ # Hash keys are the file's path relative to +dir+.
14
+ def find(dir)
15
+ dir = File.expand_path(dir)
16
+ hash = {}
17
+ find_md5(dir).each do |path, md5|
18
+ file = new(path, md5)
19
+ hash[path.sub(dir + '/', '')] = file if file
20
+ end
21
+ hash
22
+ end
23
+
24
+ # Return a new LocalFile object if it's valid.
25
+ # Otherwise, log a warning and return nil.
26
+ def new(*args)
27
+ file = super
28
+ if file.invalid?
29
+ Logger.warn("\s\s[skipping] #{ file.path }\n" +
30
+ "\s\sPath Contains Invalid UTF-8 byte sequences")
31
+ file = nil
32
+ end
33
+ file
34
+ end
35
+
36
+ private
37
+
38
+ # Returns an Array of file paths and their md5 hashes.
39
+ #
40
+ # Lines output from `cmd` are formatted like:
41
+ # MD5(/dir/subdir/file)= 7eaabd1f53024270347800d0fdb34357
42
+ # However, if +dir+ is empty, the following is returned:
43
+ # (stdin)= d41d8cd98f00b204e9800998ecf8427e
44
+ # Which extracts as: ['in', 'd41d8cd98f00b204e9800998ecf8427e']
45
+ # I'm not sure I can rely on the fact this doesn't begin with 'MD5',
46
+ # so I'll reject entries with a path that doesn't start with +dir+.
47
+ #
48
+ # String#slice avoids `invalid byte sequence in UTF-8` errors
49
+ # that String#split would raise.
50
+ #
51
+ # Utilities#run is not used here because this would produce too much
52
+ # log output, and Pipeline does not support capturing output.
53
+ def find_md5(dir)
54
+ cmd = "#{ utility(:find) } -L '#{ dir }' -type f -print0 | " +
55
+ "#{ utility(:xargs) } -0 #{ utility(:openssl) } md5 2> /dev/null"
56
+ %x[#{ cmd }].lines.map do |line|
57
+ line.chomp!
58
+ entry = [line.slice(4..-36), line.slice(-32..-1)]
59
+ entry[0].to_s.start_with?(dir) ? entry : nil
60
+ end.compact
61
+ end
62
+ end
63
+
64
+ # If +path+ contains invalid UTF-8, it will be sanitized
65
+ # and the LocalFile object will be flagged as invalid.
66
+ # This is done so @file.path may be logged.
67
+ def initialize(path, md5)
68
+ @path = sanitize(path)
69
+ @md5 = md5
70
+ end
71
+
72
+ def invalid?
73
+ !!@invalid
74
+ end
75
+
76
+ private
77
+
78
+ def sanitize(str)
79
+ str.each_char.map do |char|
80
+ begin
81
+ char.unpack('U')
82
+ char
83
+ rescue
84
+ @invalid = true
85
+ "\xEF\xBF\xBD" # => "\uFFFD"
86
+ end
87
+ end.join
88
+ end
89
+
90
+ end
91
+ end
92
+ end
93
+ end
@@ -1,67 +1,114 @@
1
1
  # encoding: utf-8
2
+ require 'backup/cloud_io/s3'
2
3
 
3
4
  module Backup
4
5
  module Syncer
5
6
  module Cloud
6
7
  class S3 < Base
8
+ class Error < Backup::Error; end
7
9
 
8
10
  ##
9
11
  # Amazon Simple Storage Service (S3) Credentials
10
12
  attr_accessor :access_key_id, :secret_access_key
11
13
 
12
14
  ##
13
- # The S3 bucket to store files to
15
+ # Amazon S3 bucket name
14
16
  attr_accessor :bucket
15
17
 
16
18
  ##
17
- # The AWS region of the specified S3 bucket
19
+ # Region of the specified S3 bucket
18
20
  attr_accessor :region
19
21
 
20
22
  ##
21
- # Instantiates a new Cloud::S3 Syncer.
23
+ # Encryption algorithm to use for Amazon Server-Side Encryption
22
24
  #
23
- # Pre-configured defaults specified in
24
- # Configuration::Syncer::Cloud::S3
25
- # are set via a super() call to Cloud::Base,
26
- # which in turn will invoke Syncer::Base.
25
+ # Supported values:
27
26
  #
28
- # Once pre-configured defaults and Cloud specific defaults are set,
29
- # the block from the user's configuration file is evaluated.
30
- def initialize(syncer_id = nil, &block)
27
+ # - :aes256
28
+ #
29
+ # Default: nil
30
+ attr_accessor :encryption
31
+
32
+ ##
33
+ # Storage class to use for the S3 objects uploaded
34
+ #
35
+ # Supported values:
36
+ #
37
+ # - :standard (default)
38
+ # - :reduced_redundancy
39
+ #
40
+ # Default: :standard
41
+ attr_accessor :storage_class
42
+
43
+ def initialize(syncer_id = nil)
31
44
  super
32
45
 
33
- instance_eval(&block) if block_given?
34
- @path = path.sub(/^\//, '')
46
+ @storage_class ||= :standard
47
+
48
+ check_configuration
35
49
  end
36
50
 
37
51
  private
38
52
 
39
- ##
40
- # Established and creates a new Fog storage object for S3.
41
- def connection
42
- @connection ||= Fog::Storage.new(
43
- :provider => provider,
44
- :aws_access_key_id => access_key_id,
45
- :aws_secret_access_key => secret_access_key,
46
- :region => region
53
+ def cloud_io
54
+ @cloud_io ||= CloudIO::S3.new(
55
+ :access_key_id => access_key_id,
56
+ :secret_access_key => secret_access_key,
57
+ :bucket => bucket,
58
+ :region => region,
59
+ :encryption => encryption,
60
+ :storage_class => storage_class,
61
+ :max_retries => max_retries,
62
+ :retry_waitsec => retry_waitsec,
63
+ # Syncer can not use multipart upload.
64
+ :chunk_size => 0
47
65
  )
48
66
  end
49
67
 
50
- ##
51
- # Creates a new @repository_object (bucket).
52
- # Fetches it from S3 if it already exists,
53
- # otherwise it will create it first and fetch use that instead.
54
- def repository_object
55
- @repository_object ||= connection.directories.get(bucket) ||
56
- connection.directories.create(:key => bucket, :location => region)
68
+ def get_remote_files(remote_base)
69
+ hash = {}
70
+ cloud_io.objects(remote_base).each do |object|
71
+ relative_path = object.key.sub(remote_base + '/', '')
72
+ hash[relative_path] = object.etag
73
+ end
74
+ hash
57
75
  end
58
76
 
59
- ##
60
- # This is the provider that Fog uses for the Cloud Files
61
- def provider
62
- "AWS"
77
+ def check_configuration
78
+ required = %w{ access_key_id secret_access_key bucket }
79
+ raise Error, <<-EOS if required.map {|name| send(name) }.any?(&:nil?)
80
+ Configuration Error
81
+ #{ required.map {|name| "##{ name }"}.join(', ') } are all required
82
+ EOS
83
+
84
+ raise Error, <<-EOS if encryption && encryption.to_s.upcase != 'AES256'
85
+ Configuration Error
86
+ #encryption must be :aes256 or nil
87
+ EOS
88
+
89
+ classes = ['STANDARD', 'REDUCED_REDUNDANCY']
90
+ raise Error, <<-EOS unless classes.include?(storage_class.to_s.upcase)
91
+ Configuration Error
92
+ #storage_class must be :standard or :reduced_redundancy
93
+ EOS
63
94
  end
64
95
 
96
+ attr_deprecate :concurrency_type, :version => '3.7.0',
97
+ :message => 'Use #thread_count instead.',
98
+ :action => lambda {|klass, val|
99
+ if val == :threads
100
+ klass.thread_count = 2 unless klass.thread_count
101
+ else
102
+ klass.thread_count = 0
103
+ end
104
+ }
105
+
106
+ attr_deprecate :concurrency_level, :version => '3.7.0',
107
+ :message => 'Use #thread_count instead.',
108
+ :action => lambda {|klass, val|
109
+ klass.thread_count = val unless klass.thread_count == 0
110
+ }
111
+
65
112
  end # Class S3 < Base
66
113
  end # module Cloud
67
114
  end
@@ -9,6 +9,13 @@ module Backup
9
9
  # Additional String or Array of options for the rsync cli
10
10
  attr_accessor :additional_rsync_options
11
11
 
12
+ def initialize(syncer_id = nil, &block)
13
+ super
14
+ instance_eval(&block) if block_given?
15
+
16
+ @path ||= '~/backups'
17
+ end
18
+
12
19
  private
13
20
 
14
21
  ##
@@ -5,11 +5,6 @@ module Backup
5
5
  module RSync
6
6
  class Local < Base
7
7
 
8
- def initialize(syncer_id = nil, &block)
9
- super
10
- instance_eval(&block) if block_given?
11
- end
12
-
13
8
  def perform!
14
9
  log!(:started)
15
10
 
@@ -101,9 +101,8 @@ module Backup
101
101
  # Flag for compressing (only compresses for the transfer)
102
102
  attr_accessor :compress
103
103
 
104
- def initialize(syncer_id = nil, &block)
104
+ def initialize(syncer_id = nil)
105
105
  super
106
- instance_eval(&block) if block_given?
107
106
 
108
107
  @mode ||= :ssh
109
108
  @port ||= mode == :rsync_daemon ? 873 : 22
@@ -2,14 +2,17 @@
2
2
 
3
3
  module Backup
4
4
  module Utilities
5
+ class Error < Backup::Error; end
6
+
5
7
  UTILITY = {}
6
8
  NAMES = %w{
7
- tar cat split find xargs sudo chown
9
+ tar cat split find xargs sudo chown hostname
8
10
  gzip bzip2 lzma pbzip2
9
11
  mongo mongodump mysqldump pg_dump pg_dumpall redis-cli riak-admin
10
12
  gpg openssl
11
13
  rsync ssh
12
14
  sendmail exim
15
+ send_nsca
13
16
  }
14
17
 
15
18
  module DSL
@@ -23,7 +26,7 @@ module Backup
23
26
  define_method name.gsub('-', '_'), lambda {|val|
24
27
  path = File.expand_path(val)
25
28
  unless File.executable?(path)
26
- raise Errors::Utilities::NotFoundError, <<-EOS
29
+ raise Utilities::Error, <<-EOS
27
30
  The path given for '#{ name }' was not found or not executable.
28
31
  Path was: #{ path }
29
32
  EOS
@@ -58,14 +61,15 @@ module Backup
58
61
  #
59
62
  # Backup::Utilities.configure do
60
63
  # # General Utilites
61
- # tar '/path/to/tar'
64
+ # tar '/path/to/tar'
62
65
  # tar_dist :gnu # or :bsd
63
- # cat '/path/to/cat'
64
- # split '/path/to/split'
65
- # find '/path/to/find'
66
- # xargs '/path/to/xargs'
67
- # sudo '/path/to/sudo'
68
- # chown '/path/to/chown'
66
+ # cat '/path/to/cat'
67
+ # split '/path/to/split'
68
+ # find '/path/to/find'
69
+ # xargs '/path/to/xargs'
70
+ # sudo '/path/to/sudo'
71
+ # chown '/path/to/chown'
72
+ # hostname '/path/to/hostname'
69
73
  #
70
74
  # # Compressors
71
75
  # gzip '/path/to/gzip'
@@ -93,6 +97,7 @@ module Backup
93
97
  # # Notifiers
94
98
  # sendmail '/path/to/sendmail'
95
99
  # exim '/path/to/exim'
100
+ # send_nsca '/path/to/send_nsca'
96
101
  # end
97
102
  #
98
103
  # These paths may be set using absolute paths, or relative to the
@@ -129,11 +134,10 @@ module Backup
129
134
  # Raises an error if utility can not be found in the system's $PATH
130
135
  def utility(name)
131
136
  name = name.to_s.strip
132
- raise Errors::Utilities::NotFoundError,
133
- 'Utility Name Empty' if name.empty?
137
+ raise Error, 'Utility Name Empty' if name.empty?
134
138
 
135
139
  UTILITY[name] ||= %x[which '#{ name }' 2>/dev/null].chomp
136
- raise(Errors::Utilities::NotFoundError, <<-EOS) if UTILITY[name].empty?
140
+ raise Error, <<-EOS if UTILITY[name].empty?
137
141
  Could not locate '#{ name }'.
138
142
  Make sure the specified utility is installed
139
143
  and available in your system's $PATH, or specify it's location
@@ -190,8 +194,7 @@ module Backup
190
194
  out, err = stdout.read.strip, stderr.read.strip
191
195
  end
192
196
  rescue Exception => e
193
- raise Errors::Utilities::SystemCallError.wrap(
194
- e, "Failed to execute '#{ name }'")
197
+ raise Error.wrap(e, "Failed to execute '#{ name }'")
195
198
  ensure
196
199
  GC.enable if RUBY_VERSION < '1.9'
197
200
  end
@@ -211,7 +214,7 @@ module Backup
211
214
 
212
215
  return out
213
216
  else
214
- raise Errors::Utilities::SystemCallError, <<-EOS
217
+ raise Error, <<-EOS
215
218
  '#{ name }' failed with exit status: #{ ps.exitstatus }
216
219
  STDOUT Messages: #{ out.empty? ? 'None' : "\n#{ out }" }
217
220
  STDERR Messages: #{ err.empty? ? 'None' : "\n#{ err }" }
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Backup
4
- VERSION = '3.6.0'
4
+ VERSION = '3.7.0'
5
5
  end
@@ -0,0 +1,35 @@
1
+ ##
2
+ # HttpPost [Notifier]
3
+ #
4
+ # For details, see:
5
+ # https://github.com/meskyanichi/backup/wiki/Notifiers
6
+ #
7
+ notify_by HttpPost do |post|
8
+ post.on_success = true
9
+ post.on_warning = true
10
+ post.on_failure = true
11
+
12
+ # URI to post the notification to.
13
+ # Port may be specified if needed.
14
+ # If Basic Authentication is required, supply user:pass.
15
+ post.uri = 'https://user:pass@your.domain.com:8443/path'
16
+
17
+ ##
18
+ # Optional
19
+ #
20
+ # Additional headers to send.
21
+ # post.headers = { 'Authentication' => 'my_auth_info' }
22
+ #
23
+ # Additional form params to post.
24
+ # post.params = { 'auth_token' => 'my_token' }
25
+ #
26
+ # Successful response codes. Default: 200
27
+ # post.success_codes = [200, 201, 204]
28
+ #
29
+ # Defaults to true on most systems.
30
+ # Force with +true+, disable with +false+
31
+ # post.ssl_verify_peer = false
32
+ #
33
+ # Supplied by default. Override with a custom 'cacert.pem' file.
34
+ # post.ssl_ca_file = '/my/cacert.pem'
35
+ end
@@ -0,0 +1,13 @@
1
+ ##
2
+ # Nagios [Notifier]
3
+ #
4
+ notify_by Nagios do |nagios|
5
+ nagios.on_success = true
6
+ nagios.on_warning = true
7
+ nagios.on_failure = true
8
+
9
+ nagios.nagios_host = 'nagioshost'
10
+ nagios.nagios_port = 5667
11
+ nagios.service_name = 'My Backup'
12
+ nagios.service_host = 'backuphost'
13
+ end
@@ -1,22 +1,13 @@
1
1
  ##
2
2
  # Rackspace Cloud Files [Storage]
3
3
  #
4
- # Available Auth URLs:
5
- #
6
- # - https://auth.api.rackspacecloud.com (US - Default)
7
- # - https://lon.auth.api.rackspacecloud.com (UK)
8
- #
9
- # Servicenet:
10
- #
11
- # Set this to 'true' if Backup runs on a Rackspace server. It will avoid
12
- # transfer charges and it's more performant.
13
- #
4
+ # See the documentation on the Wiki for details.
5
+ # https://github.com/meskyanichi/backup/wiki/Storages
14
6
  store_with CloudFiles do |cf|
15
- cf.api_key = "my_api_key"
16
- cf.username = "my_username"
17
- cf.container = "my_container"
18
- cf.path = "/path/to/my/backups"
19
- cf.keep = 5
20
- cf.auth_url = "lon.auth.api.rackspacecloud.com"
21
- cf.servicenet = false
7
+ cf.api_key = 'my_api_key'
8
+ cf.username = 'my_username'
9
+ cf.container = 'my_container'
10
+ cf.segments_container = 'my_segments_container' # must be different than `container`
11
+ cf.segment_size = 5 # MiB
12
+ cf.path = 'path/to/backups' # path within the container
22
13
  end