backup 3.5.1 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -3,6 +3,7 @@
3
3
  module Backup
4
4
  module Notifier
5
5
  class Base
6
+ include Backup::Utilities::Helpers
6
7
  include Backup::Configuration::Helpers
7
8
 
8
9
  ##
@@ -24,7 +25,17 @@ module Backup
24
25
  alias :notify_on_failure? :on_failure
25
26
 
26
27
  ##
27
- # Called with super(model) from subclasses
28
+ # Number of times to retry failed attempts to send notification.
29
+ # Default: 10
30
+ attr_accessor :max_retries
31
+
32
+ ##
33
+ # Time in seconds to pause before each retry.
34
+ # Default: 30
35
+ attr_accessor :retry_waitsec
36
+
37
+ attr_reader :model
38
+
28
39
  def initialize(model)
29
40
  @model = model
30
41
  load_defaults!
@@ -32,48 +43,68 @@ module Backup
32
43
  @on_success = true if on_success.nil?
33
44
  @on_warning = true if on_warning.nil?
34
45
  @on_failure = true if on_failure.nil?
46
+ @max_retries ||= 10
47
+ @retry_waitsec ||= 30
35
48
  end
36
49
 
37
- ##
38
- # Performs the notification
39
- # Takes a flag to indicate that a failure has occured.
40
- # (this is only set from Model#perform! in the event of an error)
41
- # If this is the case it will set the 'action' to :failure.
42
- # Otherwise, it will set the 'action' to either :success or :warning,
43
- # depending on whether or not any warnings were sent to the Logger.
44
- # It will then invoke the notify! method with the 'action',
45
- # but only if the proper on_success, on_warning or on_failure flag is true.
46
- def perform!(failure = false)
47
- @template = Backup::Template.new({:model => @model})
48
-
49
- action = false
50
- if failure
51
- action = :failure if notify_on_failure?
52
- else
53
- if notify_on_success? || (notify_on_warning? && Logger.has_warnings?)
54
- action = Logger.has_warnings? ? :warning : :success
55
- end
56
- end
50
+ # This method is called from an ensure block in Model#perform! and must
51
+ # not raise any exceptions. However, each Notifier's #notify! method
52
+ # should raise an exception if the request fails so it may be retried.
53
+ def perform!
54
+ status = case model.exit_status
55
+ when 0
56
+ :success if notify_on_success?
57
+ when 1
58
+ :warning if notify_on_success? || notify_on_warning?
59
+ else
60
+ :failure if notify_on_failure?
61
+ end
57
62
 
58
- if action
59
- log!
60
- notify!(action)
63
+ if status
64
+ Logger.info "Sending notification using #{ notifier_name }..."
65
+ with_retries { notify!(status) }
61
66
  end
67
+
68
+ rescue Exception => err
69
+ Logger.error Errors::NotifierError.wrap(err, "#{ notifier_name } Failed!")
62
70
  end
63
71
 
64
72
  private
65
73
 
74
+ def with_retries
75
+ retries = 0
76
+ begin
77
+ yield
78
+ rescue StandardError, Timeout::Error => err
79
+ retries += 1
80
+ raise if retries > max_retries
81
+
82
+ Logger.info Errors::NotifierError.
83
+ wrap(err, "Retry ##{ retries } of #{ max_retries }.")
84
+ sleep(retry_waitsec)
85
+ retry
86
+ end
87
+ end
88
+
66
89
  ##
67
90
  # Return the notifier name, with Backup namespace removed
68
91
  def notifier_name
69
92
  self.class.to_s.sub('Backup::', '')
70
93
  end
71
94
 
72
- ##
73
- # Logs a message to the console and log file to inform
74
- # the client that Backup is notifying about the process
75
- def log!
76
- Logger.info "#{ notifier_name } started notifying about the process."
95
+ # For ruby-1.8.7. Both sorted so specs will match.
96
+ def encode_www_form(enum)
97
+ if RUBY_VERSION < '1.9'
98
+ require 'cgi'
99
+ str = ''
100
+ enum.to_a.map {|k,v| [k.to_s, v] }.sort.each do |k,v|
101
+ str << '&' unless str.empty?
102
+ str << CGI.escape(k) << '=' << CGI.escape(v)
103
+ end
104
+ str
105
+ else
106
+ URI.encode_www_form(enum.sort)
107
+ end
77
108
  end
78
109
 
79
110
  end
@@ -1,5 +1,6 @@
1
1
  # encoding: utf-8
2
- require 'httparty'
2
+ require 'excon'
3
+ require 'json'
3
4
 
4
5
  module Backup
5
6
  module Notifier
@@ -18,8 +19,7 @@ module Backup
18
19
  attr_accessor :room_id
19
20
 
20
21
  def initialize(model, &block)
21
- super(model)
22
-
22
+ super
23
23
  instance_eval(&block) if block_given?
24
24
  end
25
25
 
@@ -27,124 +27,42 @@ module Backup
27
27
 
28
28
  ##
29
29
  # Notify the user of the backup operation results.
30
+ #
30
31
  # `status` indicates one of the following:
31
32
  #
32
33
  # `:success`
33
34
  # : The backup completed successfully.
34
- # : Notification will be sent if `on_success` was set to `true`
35
+ # : Notification will be sent if `on_success` is `true`.
35
36
  #
36
37
  # `:warning`
37
- # : The backup completed successfully, but warnings were logged
38
- # : Notification will be sent, including a copy of the current
39
- # : backup log, if `on_warning` was set to `true`
38
+ # : The backup completed successfully, but warnings were logged.
39
+ # : Notification will be sent if `on_warning` or `on_success` is `true`.
40
40
  #
41
41
  # `:failure`
42
42
  # : The backup operation failed.
43
- # : Notification will be sent, including the Exception which caused
44
- # : the failure, the Exception's backtrace, a copy of the current
45
- # : backup log and other information if `on_failure` was set to `true`
43
+ # : Notification will be sent if `on_warning` or `on_success` is `true`.
46
44
  #
47
45
  def notify!(status)
48
- name = case status
49
- when :success then 'Success'
50
- when :warning then 'Warning'
51
- when :failure then 'Failure'
52
- end
53
- message = "[Backup::%s] #{@model.label} (#{@model.trigger})" % name
46
+ tag = case status
47
+ when :success then '[Backup::Success]'
48
+ when :warning then '[Backup::Warning]'
49
+ when :failure then '[Backup::Failure]'
50
+ end
51
+ message = "#{ tag } #{ model.label } (#{ model.trigger })"
54
52
  send_message(message)
55
53
  end
56
54
 
57
- ##
58
- # Creates a new Campfire::Interface object and passes in the
59
- # campfire clients "room_id", "subdomain" and "api_token". Using this object
60
- # the provided "message" will be sent to the desired Campfire chat room
61
55
  def send_message(message)
62
- room = Interface.room(room_id, subdomain, api_token)
63
- room.message(message)
64
- end
65
-
66
- ##
67
- # The Campfire::Interface acts as the Interface for the Campfire class.
68
- # It uses the HTTParty library and the Campfire::Room class to communicate
69
- # with the Campfire rooms. HTTParty provides the Campfire::Interface with the methods
70
- # necessary to communicate (inside the HTTParty module) such as the class methods:
71
- # * post
72
- # * base_uri
73
- # * basic_auth
74
- class Interface
75
- include HTTParty
76
-
77
- ##
78
- # We communicate using the JSON data format
79
- headers 'Content-Type' => 'application/json'
80
-
81
- ##
82
- # Instantiates a new Campfire::Room object with
83
- # the provided arguments and returns this object
84
- def self.room(room_id, subdomain, api_token)
85
- Room.new(room_id, subdomain, api_token)
86
- end
87
- end
88
-
89
- ##
90
- # The Campfire::Room acts as a model for an actual room on the Campfire service.
91
- # And it uses the Campfire::Interface's (HTTParty) class methods to communicate based
92
- # on the provided parameters (room_id, subdomain and api_token)
93
- class Room
94
-
95
- ##
96
- # Campfire api authentication api_token
97
- attr_accessor :api_token
98
-
99
- ##
100
- # Campfire account's subdomain
101
- attr_accessor :subdomain
102
-
103
- ##
104
- # Campfire account's room id
105
- attr_accessor :room_id
106
-
107
- ##
108
- # Instantiates a new Campfire::Room object and sets all the
109
- # necessary arguments (@room_id, @subdomain, @api_token)
110
- def initialize(room_id, subdomain, api_token)
111
- @room_id = room_id
112
- @subdomain = subdomain
113
- @api_token = api_token
114
- end
115
-
116
- ##
117
- # Wrapper method for the #send_message (private) method
118
- def message(message)
119
- send_message(message)
120
- end
121
-
122
- private
123
-
124
- ##
125
- # Takes a "message" as argument, the "type" defaults to "Textmessage".
126
- # This method builds up a POST request with the necessary params (serialized to JSON format)
127
- # and sends it to the Campfire service in order to submit the message
128
- def send_message(message, type = 'Textmessage')
129
- post 'speak', :body => MultiJson.encode(
130
- { :message => { :body => message, :type => type } }
56
+ uri = "https://#{ subdomain }.campfirenow.com/room/#{ room_id }/speak.json"
57
+ options = {
58
+ :headers => { 'Content-Type' => 'application/json' },
59
+ :body => JSON.dump(
60
+ { :message => { :body => message, :type => 'Textmessage' } }
131
61
  )
132
- end
133
-
134
- ##
135
- # Builds/sets up the Campfire::Interface attributes and submits
136
- # the POST request that was built in the #send_message (private) method
137
- def post(action, options = {})
138
- Interface.base_uri("https://#{subdomain}.campfirenow.com")
139
- Interface.basic_auth(api_token, 'x')
140
- Interface.post(room_url_for(action), options)
141
- end
142
-
143
- ##
144
- # Returns the url for the specified room (in JSON format)
145
- def room_url_for(action)
146
- "/room/#{room_id}/#{action}.json"
147
- end
62
+ }
63
+ options.merge!(:user => api_token, :password => 'x') # Basic Auth
64
+ options.merge!(:expects => 201) # raise error if unsuccessful
65
+ Excon.post(uri, options)
148
66
  end
149
67
 
150
68
  end
@@ -37,55 +37,57 @@ module Backup
37
37
  attr_accessor :failure_color
38
38
 
39
39
  def initialize(model, &block)
40
- super(model)
40
+ super
41
+ instance_eval(&block) if block_given?
41
42
 
42
43
  @notify_users ||= false
43
44
  @rooms_notified ||= []
44
45
  @success_color ||= 'yellow'
45
46
  @warning_color ||= 'yellow'
46
47
  @failure_color ||= 'yellow'
47
-
48
- instance_eval(&block) if block_given?
49
48
  end
50
49
 
51
50
  private
52
51
 
53
52
  ##
54
53
  # Notify the user of the backup operation results.
54
+ #
55
55
  # `status` indicates one of the following:
56
56
  #
57
57
  # `:success`
58
58
  # : The backup completed successfully.
59
- # : Notification will be sent if `on_success` was set to `true`
59
+ # : Notification will be sent if `on_success` is `true`.
60
60
  #
61
61
  # `:warning`
62
- # : The backup completed successfully, but warnings were logged
63
- # : Notification will be sent, including a copy of the current
64
- # : backup log, if `on_warning` was set to `true`
62
+ # : The backup completed successfully, but warnings were logged.
63
+ # : Notification will be sent if `on_warning` or `on_success` is `true`.
65
64
  #
66
65
  # `:failure`
67
66
  # : The backup operation failed.
68
- # : Notification will be sent, including the Exception which caused
69
- # : the failure, the Exception's backtrace, a copy of the current
70
- # : backup log and other information if `on_failure` was set to `true`
67
+ # : Notification will be sent if `on_warning` or `on_success` is `true`.
71
68
  #
72
69
  def notify!(status)
73
- name, color = case status
74
- when :success then ['Success', success_color]
75
- when :warning then ['Warning', warning_color]
76
- when :failure then ['Failure', failure_color]
77
- end
78
- message = "[Backup::%s] #{@model.label} (#{@model.trigger})" % name
70
+ tag, color = case status
71
+ when :success then ['[Backup::Success]', success_color]
72
+ when :warning then ['[Backup::Warning]', warning_color]
73
+ when :failure then ['[Backup::Failure]', failure_color]
74
+ end
75
+ message = "#{ tag } #{ model.label } (#{ model.trigger })"
79
76
  send_message(message, color)
80
77
  end
81
78
 
79
+ # Hipchat::Client will raise an error if unsuccessful.
82
80
  def send_message(msg, color)
83
81
  client = HipChat::Client.new(token)
84
- Array(rooms_notified).map {|r| r.split(',').map(&:strip) }.flatten.each do |room|
82
+ rooms_to_notify.each do |room|
85
83
  client[room].send(from, msg, :color => color, :notify => notify_users)
86
84
  end
87
85
  end
88
86
 
87
+ def rooms_to_notify
88
+ Array(rooms_notified).map {|r| r.split(',').map(&:strip) }.flatten
89
+ end
90
+
89
91
  end
90
92
  end
91
93
  end
@@ -17,11 +17,11 @@ module Backup
17
17
  #
18
18
  # [:sendmail - ::Mail::Sendmail]
19
19
  # Settings used by this method:
20
- # {#sendmail}, {#sendmail_args}
20
+ # {#sendmail_args}
21
21
  #
22
22
  # [:exim - ::Mail::Exim]
23
23
  # Settings used by this method:
24
- # {#exim}, {#exim_args}
24
+ # {#exim_args}
25
25
  #
26
26
  # [:file - ::Mail::FileDelivery]
27
27
  # Settings used by this method:
@@ -76,13 +76,6 @@ module Backup
76
76
  # Use a +SSL/TLS+ connection.
77
77
  attr_accessor :encryption
78
78
 
79
- attr_deprecate :enable_starttls_auto, :version => '3.2.0',
80
- :message => "Use #encryption instead.\n" +
81
- 'e.g. mail.encryption = :starttls',
82
- :action => lambda {|klass, val|
83
- klass.encryption = val ? :starttls : :none
84
- }
85
-
86
79
  ##
87
80
  # OpenSSL Verify Mode
88
81
  #
@@ -92,52 +85,44 @@ module Backup
92
85
  # Use +:none+ for a self-signed and/or wildcard certificate
93
86
  attr_accessor :openssl_verify_mode
94
87
 
95
- ##
96
- # Path to `sendmail` (if needed)
97
- #
98
- # When using the `:sendmail` `delivery_method` option,
99
- # this may be used to specify the absolute path to `sendmail`
100
- #
101
- # Example: '/usr/sbin/sendmail'
102
- attr_accessor :sendmail
103
-
104
88
  ##
105
89
  # Optional arguments to pass to `sendmail`
106
90
  #
107
- # Note that this will override the defaults set by the Mail gem (currently: '-i -t')
108
- # So, if set here, be sure to set all the arguments you require.
91
+ # Note that this will override the defaults set by the Mail gem
92
+ # (currently: '-i'). So, if set here, be sure to set all the arguments
93
+ # you require.
109
94
  #
110
- # Example: '-i -t -X/tmp/traffic.log'
95
+ # Example: '-i -X/tmp/traffic.log'
111
96
  attr_accessor :sendmail_args
112
97
 
113
- ##
114
- # Path to `exim` (if needed)
115
- #
116
- # When using the `:exim` `delivery_method` option,
117
- # this may be used to specify the absolute path to `exim`
118
- #
119
- # Example: '/usr/sbin/exim'
120
- attr_accessor :exim
121
-
122
98
  ##
123
99
  # Optional arguments to pass to `exim`
124
100
  #
125
- # Note that this will override the defaults set by the Mail gem (currently: '-i -t')
126
- # So, if set here, be sure to set all the arguments you require.
101
+ # Note that this will override the defaults set by the Mail gem
102
+ # (currently: '-i -t') So, if set here, be sure to set all the arguments
103
+ # you require.
127
104
  #
128
105
  # Example: '-i -t -X/tmp/traffic.log'
129
106
  attr_accessor :exim_args
130
107
 
131
108
  ##
132
- # Folder where mail will be kept when using the `:file` `delivery_method` option.
109
+ # Folder where mail will be kept when using the `:file` `delivery_method`.
133
110
  #
134
111
  # Default location is '$HOME/Backup/emails'
135
112
  attr_accessor :mail_folder
136
113
 
137
- def initialize(model, &block)
138
- super(model)
114
+ ##
115
+ # Array of statuses for which the log file should be attached.
116
+ #
117
+ # Available statuses are: `:success`, `:warning` and `:failure`.
118
+ # Default: [:warning, :failure]
119
+ attr_accessor :send_log_on
139
120
 
121
+ def initialize(model, &block)
122
+ super
140
123
  instance_eval(&block) if block_given?
124
+
125
+ @send_log_on ||= [:warning, :failure]
141
126
  end
142
127
 
143
128
  private
@@ -147,42 +132,43 @@ module Backup
147
132
  #
148
133
  # `status` indicates one of the following:
149
134
  #
150
- # [:success]
151
- # The backup completed successfully.
152
- # Notification will be sent if `on_success` was set to `true`
135
+ # `:success`
136
+ # : The backup completed successfully.
137
+ # : Notification will be sent if `on_success` is `true`.
153
138
  #
154
- # [:warning]
155
- # The backup completed successfully, but warnings were logged
156
- # Notification will be sent, including a copy of the current
157
- # backup log, if `on_warning` was set to `true`
139
+ # `:warning`
140
+ # : The backup completed successfully, but warnings were logged.
141
+ # : Notification will be sent, including a copy of the current
142
+ # : backup log, if `on_warning` or `on_success` is `true`.
158
143
  #
159
- # [:failure]
160
- # The backup operation failed.
161
- # Notification will be sent, including the Exception which caused
162
- # the failure, the Exception's backtrace, a copy of the current
163
- # backup log and other information if `on_failure` was set to `true`
144
+ # `:failure`
145
+ # : The backup operation failed.
146
+ # : Notification will be sent, including a copy of the current
147
+ # : backup log, if `on_failure` is `true`.
164
148
  #
165
149
  def notify!(status)
166
- name, send_log =
167
- case status
168
- when :success then [ 'Success', false ]
169
- when :warning then [ 'Warning', true ]
170
- when :failure then [ 'Failure', true ]
171
- end
150
+ tag = case status
151
+ when :success then '[Backup::Success]'
152
+ when :warning then '[Backup::Warning]'
153
+ when :failure then '[Backup::Failure]'
154
+ end
172
155
 
173
156
  email = new_email
174
- email.subject = "[Backup::%s] #{@model.label} (#{@model.trigger})" % name
175
- email.body = @template.result('notifier/mail/%s.erb' % status.to_s)
157
+ email.subject = "#{ tag } #{ model.label } (#{ model.trigger })"
158
+
159
+ send_log = send_log_on.include?(status)
160
+ template = Backup::Template.new({ :model => model, :send_log => send_log })
161
+ email.body = template.result('notifier/mail/%s.erb' % status.to_s)
176
162
 
177
163
  if send_log
178
164
  email.convert_to_multipart
179
- email.attachments["#{@model.time}.#{@model.trigger}.log"] = {
165
+ email.attachments["#{ model.time }.#{ model.trigger }.log"] = {
180
166
  :mime_type => 'text/plain;',
181
167
  :content => Logger.messages.map(&:formatted_lines).flatten.join("\n")
182
168
  }
183
169
  end
184
170
 
185
- email.deliver!
171
+ email.deliver! # raise error if unsuccessful
186
172
  end
187
173
 
188
174
  ##
@@ -208,12 +194,12 @@ module Backup
208
194
  }
209
195
  when 'sendmail'
210
196
  opts = {}
211
- opts.merge!(:location => File.expand_path(@sendmail)) if @sendmail
197
+ opts.merge!(:location => utility(:sendmail))
212
198
  opts.merge!(:arguments => @sendmail_args) if @sendmail_args
213
199
  opts
214
200
  when 'exim'
215
201
  opts = {}
216
- opts.merge!(:location => File.expand_path(@exim)) if @exim
202
+ opts.merge!(:location => utility(:exim))
217
203
  opts.merge!(:arguments => @exim_args) if @exim_args
218
204
  opts
219
205
  when 'file'
@@ -232,6 +218,25 @@ module Backup
232
218
  email
233
219
  end
234
220
 
221
+ attr_deprecate :enable_starttls_auto, :version => '3.2.0',
222
+ :message => "Use #encryption instead.\n" +
223
+ 'e.g. mail.encryption = :starttls',
224
+ :action => lambda {|klass, val|
225
+ klass.encryption = val ? :starttls : :none
226
+ }
227
+
228
+ attr_deprecate :sendmail, :version => '3.6.0',
229
+ :message => 'Use Backup::Utilities.configure instead.',
230
+ :action => lambda {|klass, val|
231
+ Utilities.configure { sendmail val }
232
+ }
233
+
234
+ attr_deprecate :exim, :version => '3.6.0',
235
+ :message => 'Use Backup::Utilities.configure instead.',
236
+ :action => lambda {|klass, val|
237
+ Utilities.configure { exim val }
238
+ }
239
+
235
240
  end
236
241
  end
237
242
  end