backup 3.5.1 → 3.6.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.
@@ -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