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,122 @@
1
+ # encoding: utf-8
2
+ require 'uri'
3
+
4
+ module Backup
5
+ module Notifier
6
+ class HttpPost < Base
7
+
8
+ ##
9
+ # URI to post notification to.
10
+ #
11
+ # URI scheme may be `http` or `https`.
12
+ #
13
+ # If Basic Authentication is needed, supply the `user:password` in the URI.
14
+ # e.g. 'https://user:pass@www.example.com/path'
15
+ #
16
+ # Port may also be supplied.
17
+ # e.g. 'http://www.example.com:8080/path'
18
+ attr_accessor :uri
19
+
20
+ ##
21
+ # Hash of additional HTTP headers to send.
22
+ #
23
+ # This notifier sets the following headers:
24
+ # { 'User-Agent' => "Backup/#{ Backup::VERSION }",
25
+ # 'Content-Type' => 'x-www-form-urlencoded' }
26
+ #
27
+ # 'Content-Type' may not be changed.
28
+ # 'User-Agent' may be overridden or omitted by setting it to +nil+.
29
+ # e.g. { 'Authorization' => 'my_auth_info', 'User-Agent' => nil }
30
+ attr_accessor :headers
31
+
32
+ ##
33
+ # Hash of additional POST parameters to send.
34
+ #
35
+ # This notifier will set two parameters:
36
+ # { 'status' => 'success|warning|failure',
37
+ # 'message' => '[Backup::(Success|Warning|Failure)] label (trigger)' }
38
+ #
39
+ # 'status' may not be changed.
40
+ # 'message' may be overridden or omitted by setting a +nil+ value.
41
+ # e.g. { 'auth_token' => 'my_token', 'message' => nil }
42
+ attr_accessor :params
43
+
44
+ ##
45
+ # Successful HTTP Status Code(s) that should be returned.
46
+ #
47
+ # This may be a single code or an Array of acceptable codes.
48
+ # e.g. [200, 201, 204]
49
+ #
50
+ # If any other response code is returned, the request will be retried
51
+ # using `max_retries` and `retry_waitsec`.
52
+ #
53
+ # Default: 200
54
+ attr_accessor :success_codes
55
+
56
+ ##
57
+ # Verify the server's certificate when using SSL.
58
+ #
59
+ # This will default to +true+ for most systems.
60
+ # It may be forced by setting to +true+, or disabled by setting to +false+.
61
+ attr_accessor :ssl_verify_peer
62
+
63
+ ##
64
+ # Path to a +cacert.pem+ file to use for +ssl_verify_peer+.
65
+ #
66
+ # This is provided (via Excon), but may be specified if needed.
67
+ attr_accessor :ssl_ca_file
68
+
69
+ def initialize(model, &block)
70
+ super
71
+ instance_eval(&block) if block_given?
72
+
73
+ @headers ||= {}
74
+ @params ||= {}
75
+ @success_codes ||= 200
76
+ end
77
+
78
+ private
79
+
80
+ ##
81
+ # Notify the user of the backup operation results.
82
+ #
83
+ # `status` indicates one of the following:
84
+ #
85
+ # `:success`
86
+ # : The backup completed successfully.
87
+ # : Notification will be sent if `on_success` is `true`.
88
+ #
89
+ # `:warning`
90
+ # : The backup completed successfully, but warnings were logged.
91
+ # : Notification will be sent if `on_warning` or `on_success` is `true`.
92
+ #
93
+ # `:failure`
94
+ # : The backup operation failed.
95
+ # : Notification will be sent if `on_warning` or `on_success` is `true`.
96
+ #
97
+ def notify!(status)
98
+ tag = case status
99
+ when :success then '[Backup::Success]'
100
+ when :failure then '[Backup::Failure]'
101
+ when :warning then '[Backup::Warning]'
102
+ end
103
+ message = "#{ tag } #{ model.label } (#{ model.trigger })"
104
+
105
+ opts = {
106
+ :headers => { 'User-Agent' => "Backup/#{ VERSION }" }.
107
+ merge(headers).reject {|k,v| v.nil? }.
108
+ merge('Content-Type' => 'application/x-www-form-urlencoded'),
109
+ :body => encode_www_form({ 'message' => message }.
110
+ merge(params).reject {|k,v| v.nil? }.
111
+ merge('status' => status.to_s)),
112
+ :expects => success_codes # raise error if unsuccessful
113
+ }
114
+ opts.merge!(:ssl_verify_peer => ssl_verify_peer) unless ssl_verify_peer.nil?
115
+ opts.merge!(:ssl_ca_file => ssl_ca_file) if ssl_ca_file
116
+
117
+ Excon.post(uri, opts)
118
+ end
119
+
120
+ end
121
+ end
122
+ end
@@ -240,3 +240,41 @@ module Backup
240
240
  end
241
241
  end
242
242
  end
243
+
244
+ # Patch mail v2.5.4 Exim delivery method
245
+ # https://github.com/meskyanichi/backup/issues/446
246
+ # https://github.com/mikel/mail/pull/546
247
+ module Mail
248
+ class Exim
249
+ def self.call(path, arguments, destinations, encoded_message)
250
+ popen "#{path} #{arguments}" do |io|
251
+ io.puts encoded_message.to_lf
252
+ io.flush
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ # Patch mail v2.5.4 for ruby-1.8.7
259
+ # https://github.com/mikel/mail/issues/548
260
+ # https://github.com/mikel/mail/commit/c7318a6c03c1ecb3f574ccd2e3f06778687d1d15
261
+ if RUBY_VERSION < '1.9'
262
+ module Mail
263
+ class SMTP
264
+ private
265
+ def ssl_context
266
+ openssl_verify_mode = settings[:openssl_verify_mode]
267
+
268
+ if openssl_verify_mode.kind_of?(String)
269
+ openssl_verify_mode = "OpenSSL::SSL::VERIFY_#{openssl_verify_mode.upcase}".constantize
270
+ end
271
+
272
+ context = Net::SMTP.default_ssl_context
273
+ context.verify_mode = openssl_verify_mode
274
+ context.ca_path = settings[:ca_path] if settings[:ca_path]
275
+ context.ca_file = settings[:ca_file] if settings[:ca_file]
276
+ context
277
+ end
278
+ end
279
+ end
280
+ end
@@ -0,0 +1,69 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ module Notifier
5
+ class Nagios < Base
6
+
7
+ ##
8
+ # Host of Nagios server to notify on backup completion.
9
+ attr_accessor :nagios_host
10
+
11
+ ##
12
+ # Port of Nagios server to notify on backup completion.
13
+ attr_accessor :nagios_port
14
+
15
+ ##
16
+ # Name of the Nagios service for the backup check.
17
+ attr_accessor :service_name
18
+
19
+ ##
20
+ # Host name in Nagios for the backup check.
21
+ attr_accessor :service_host
22
+
23
+ def initialize(model, &block)
24
+ super
25
+ instance_eval(&block) if block_given?
26
+
27
+ @nagios_host ||= Config.hostname
28
+ @nagios_port ||= 5667
29
+ @service_name ||= "Backup #{ model.trigger }"
30
+ @service_host ||= Config.hostname
31
+ end
32
+
33
+ private
34
+
35
+ ##
36
+ # Notify the user of the backup operation results.
37
+ #
38
+ # `status` indicates one of the following:
39
+ #
40
+ # `:success`
41
+ # : The backup completed successfully.
42
+ # : Notification will be sent if `on_success` is `true`.
43
+ #
44
+ # `:warning`
45
+ # : The backup completed successfully, but warnings were logged.
46
+ # : Notification will be sent if `on_warning` or `on_success` is `true`.
47
+ #
48
+ # `:failure`
49
+ # : The backup operation failed.
50
+ # : Notification will be sent if `on_warning` or `on_success` is `true`.
51
+ #
52
+ def notify!(status)
53
+ message = case status
54
+ when :success then 'Completed Successfully'
55
+ when :warning then 'Completed Successfully (with Warnings)'
56
+ when :failure then 'Failed'
57
+ end
58
+ send_message("#{ message } in #{ model.duration }")
59
+ end
60
+
61
+ def send_message(message)
62
+ cmd = "#{ utility(:send_nsca) } -H '#{ nagios_host }' -p '#{ nagios_port }'"
63
+ msg = [service_host, service_name, model.exit_status, message].join("\t")
64
+ run("echo '#{ msg }' | #{ cmd }")
65
+ end
66
+
67
+ end
68
+ end
69
+ end
@@ -1,5 +1,4 @@
1
1
  # encoding: utf-8
2
- require 'excon'
3
2
  require 'uri'
4
3
 
5
4
  module Backup
@@ -1,5 +1,4 @@
1
1
  # encoding: utf-8
2
- require 'excon'
3
2
  require 'uri'
4
3
 
5
4
  module Backup
@@ -19,6 +19,10 @@ module Backup
19
19
  # Set by the Splitter if the final archive was "chunked"
20
20
  attr_accessor :chunk_suffixes
21
21
 
22
+ ##
23
+ # If true, the Cycler will not attempt to remove the package when Cycling.
24
+ attr_accessor :no_cycle
25
+
22
26
  ##
23
27
  # The version of Backup used to create the package
24
28
  attr_reader :version
@@ -27,6 +31,7 @@ module Backup
27
31
  @trigger = model.trigger
28
32
  @extension = 'tar'
29
33
  @chunk_suffixes = Array.new
34
+ @no_cycle = false
30
35
  @version = VERSION
31
36
  end
32
37
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Backup
4
4
  module Packager
5
+ class Error < Backup::Error; end
6
+
5
7
  class << self
6
8
  include Backup::Utilities::Helpers
7
9
 
@@ -19,8 +21,7 @@ module Backup
19
21
  if @pipeline.success?
20
22
  Logger.info "Packaging Complete!"
21
23
  else
22
- raise Errors::Packager::PipelineError,
23
- "Failed to Create Backup Package\n" +
24
+ raise Error, "Failed to Create Backup Package\n" +
24
25
  @pipeline.error_messages
25
26
  end
26
27
  end
@@ -2,6 +2,8 @@
2
2
 
3
3
  module Backup
4
4
  class Pipeline
5
+ class Error < Backup::Error; end
6
+
5
7
  include Backup::Utilities::Helpers
6
8
 
7
9
  attr_reader :stderr, :errors
@@ -63,8 +65,8 @@ module Backup
63
65
  @stderr = stderr.read.strip
64
66
  end
65
67
  Logger.warn(stderr_messages) if success? && stderr_messages
66
- rescue Exception => e
67
- raise Errors::Pipeline::ExecutionError.wrap(e)
68
+ rescue Exception => err
69
+ raise Error.wrap(err, 'Pipeline failed to execute')
68
70
  end
69
71
 
70
72
  def success?
@@ -21,12 +21,13 @@ module Backup
21
21
  # multiple storages of the same type. If multiple storages of the same
22
22
  # type are added to a single backup model, this identifier must be set.
23
23
  # This will be appended to the YAML storage file used for cycling backups.
24
- def initialize(model, storage_id = nil)
24
+ def initialize(model, storage_id = nil, &block)
25
25
  @model = model
26
26
  @package = model.package
27
27
  @storage_id = storage_id.to_s.gsub(/\W/, '_') if storage_id
28
28
 
29
29
  load_defaults!
30
+ instance_eval(&block) if block_given?
30
31
  end
31
32
 
32
33
  def perform!
@@ -0,0 +1,151 @@
1
+ # encoding: utf-8
2
+ require 'backup/cloud_io/cloud_files'
3
+
4
+ module Backup
5
+ module Storage
6
+ class CloudFiles < Base
7
+ class Error < Backup::Error; end
8
+
9
+ ##
10
+ # Rackspace CloudFiles Credentials
11
+ attr_accessor :username, :api_key
12
+
13
+ ##
14
+ # Rackspace Auth URL (optional)
15
+ attr_accessor :auth_url
16
+
17
+ ##
18
+ # Rackspace Service Net
19
+ # (LAN-based transfers to avoid charges and improve performance)
20
+ attr_accessor :servicenet
21
+
22
+ ##
23
+ # Rackspace Region (optional)
24
+ attr_accessor :region
25
+
26
+ ##
27
+ # Rackspace Container Name
28
+ attr_accessor :container
29
+
30
+ ##
31
+ # Rackspace Container Name for SLO Segments
32
+ # Required if #segment_size is set. Must be different from #container.
33
+ attr_accessor :segments_container
34
+
35
+ ##
36
+ # SLO Segment size, specified in MiB.
37
+ #
38
+ # Each package file larger than +segment_size+
39
+ # will be uploaded as a Static Large Objects (SLO).
40
+ #
41
+ # Defaults to 0 for backward compatibility (pre v.3.7.0),
42
+ # since #segments_container would be required.
43
+ #
44
+ # Minimum: 1 (0 disables SLO support)
45
+ # Maximum: 5120 (5 GiB)
46
+ attr_accessor :segment_size
47
+
48
+ ##
49
+ # If set, all backup package files (including SLO segments) will be
50
+ # scheduled for automatic removal by the server.
51
+ #
52
+ # The `keep` option should not be used if this is set,
53
+ # unless you're transitioning from the `keep` option.
54
+ attr_accessor :days_to_keep
55
+
56
+ ##
57
+ # Number of times to retry failed operations.
58
+ #
59
+ # Default: 10
60
+ attr_accessor :max_retries
61
+
62
+ ##
63
+ # Time in seconds to pause before each retry.
64
+ #
65
+ # Default: 30
66
+ attr_accessor :retry_waitsec
67
+
68
+ def initialize(model, storage_id = nil)
69
+ super
70
+
71
+ @servicenet ||= false
72
+ @segment_size ||= 0
73
+ @max_retries ||= 10
74
+ @retry_waitsec ||= 30
75
+
76
+ @path ||= 'backups'
77
+ path.sub!(/^\//, '')
78
+
79
+ check_configuration
80
+ end
81
+
82
+ private
83
+
84
+ def cloud_io
85
+ @cloud_io ||= CloudIO::CloudFiles.new(
86
+ :username => username,
87
+ :api_key => api_key,
88
+ :auth_url => auth_url,
89
+ :region => region,
90
+ :servicenet => servicenet,
91
+ :container => container,
92
+ :segments_container => segments_container,
93
+ :segment_size => segment_size,
94
+ :days_to_keep => days_to_keep,
95
+ :max_retries => max_retries,
96
+ :retry_waitsec => retry_waitsec
97
+ )
98
+ end
99
+
100
+ def transfer!
101
+ package.filenames.each do |filename|
102
+ src = File.join(Config.tmp_path, filename)
103
+ dest = File.join(remote_path, filename)
104
+ Logger.info "Storing '#{ container }/#{ dest }'..."
105
+ cloud_io.upload(src, dest)
106
+ end
107
+
108
+ package.no_cycle = true if days_to_keep
109
+ end
110
+
111
+ # Called by the Cycler.
112
+ # Any error raised will be logged as a warning.
113
+ def remove!(package)
114
+ Logger.info "Removing backup package dated #{ package.time }..."
115
+
116
+ remote_path = remote_path_for(package)
117
+ objects = cloud_io.objects(remote_path)
118
+
119
+ raise Error, "Package at '#{ remote_path }' not found" if objects.empty?
120
+
121
+ slo_objects, objects = objects.partition(&:slo?)
122
+ cloud_io.delete_slo(slo_objects)
123
+ cloud_io.delete(objects)
124
+ end
125
+
126
+ def check_configuration
127
+ required = %w{ username api_key container }
128
+ raise Error, <<-EOS if required.map {|name| send(name) }.any?(&:nil?)
129
+ Configuration Error
130
+ #{ required.map {|name| "##{ name }"}.join(', ') } are all required
131
+ EOS
132
+
133
+ raise Error, <<-EOS if segment_size > 0 && segments_container.to_s.empty?
134
+ Configuration Error
135
+ #segments_container is required if #segment_size is > 0
136
+ EOS
137
+
138
+ raise Error, <<-EOS if container == segments_container
139
+ Configuration Error
140
+ #container and #segments_container must not be the same container.
141
+ EOS
142
+
143
+ raise Error, <<-EOS if segment_size > 5120
144
+ Configuration Error
145
+ #segment_size is too large (max 5120)
146
+ EOS
147
+ end
148
+
149
+ end
150
+ end
151
+ end