backup 3.6.0 → 3.7.0

Sign up to get free protection for your applications and to get access to all the features.
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