backup 3.5.1 → 3.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/backup/cli.rb +29 -6
- data/lib/backup/errors.rb +46 -81
- data/lib/backup/model.rb +184 -143
- data/lib/backup/notifier/base.rb +60 -29
- data/lib/backup/notifier/campfire.rb +23 -105
- data/lib/backup/notifier/hipchat.rb +19 -17
- data/lib/backup/notifier/mail.rb +64 -59
- data/lib/backup/notifier/prowl.rb +27 -19
- data/lib/backup/notifier/pushover.rb +26 -36
- data/lib/backup/notifier/twitter.rb +13 -15
- data/lib/backup/pipeline.rb +1 -3
- data/lib/backup/storage/s3.rb +0 -3
- data/lib/backup/utilities.rb +9 -8
- data/lib/backup/version.rb +1 -1
- data/templates/notifier/mail/failure.erb +2 -0
- data/templates/notifier/mail/success.erb +4 -0
- data/templates/notifier/mail/warning.erb +2 -0
- metadata +16 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d68dc83802f92387efeb5d0bfd6e89c7d5d32e49
|
4
|
+
data.tar.gz: a9c414e7eb6a1c329b49329d3c4fc5391ab78f84
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f801689681baae58593843de6828116bc688ece381912e47d6b74e8354f61bd437a13e0b36bf106331124de108788c626dd0fa1d16e09f9c04fff4834c9a747
|
7
|
+
data.tar.gz: ed9473697e3a81910ada2857f7c3533b341bc8ff0a9a6863db58d16cc408a2e29042ab4284bf0e44979dea0fa7dfebe68e48de10cc8b6a41a7c13ed62a321c1d
|
data/lib/backup/cli.rb
CHANGED
@@ -156,15 +156,38 @@ module Backup
|
|
156
156
|
exit(3)
|
157
157
|
end
|
158
158
|
|
159
|
-
|
160
|
-
|
161
|
-
warnings = errors = false
|
162
|
-
models.each do |model|
|
159
|
+
until models.empty?
|
160
|
+
model = models.shift
|
163
161
|
model.perform!
|
164
|
-
|
165
|
-
|
162
|
+
|
163
|
+
case model.exit_status
|
164
|
+
when 1
|
165
|
+
warnings = true
|
166
|
+
when 2
|
167
|
+
errors = true
|
168
|
+
unless models.empty?
|
169
|
+
Logger.info Errors::ModelError.new(<<-EOS)
|
170
|
+
Backup will now continue...
|
171
|
+
The following triggers will now be processed:
|
172
|
+
(#{ models.map {|m| m.trigger }.join(', ') })
|
173
|
+
EOS
|
174
|
+
end
|
175
|
+
when 3
|
176
|
+
fatal = true
|
177
|
+
unless models.empty?
|
178
|
+
Logger.error Errors::ModelFatalError.new(<<-EOS)
|
179
|
+
Backup will now exit.
|
180
|
+
The following triggers will not be processed:
|
181
|
+
(#{ models.map {|m| m.trigger }.join(', ') })
|
182
|
+
EOS
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
model.notifiers.each(&:perform!)
|
187
|
+
exit(3) if fatal
|
166
188
|
Logger.clear!
|
167
189
|
end
|
190
|
+
|
168
191
|
exit(errors ? 2 : 1) if errors || warnings
|
169
192
|
end
|
170
193
|
|
data/lib/backup/errors.rb
CHANGED
@@ -5,13 +5,21 @@ module Backup
|
|
5
5
|
# - automatically defines module namespaces referenced under Backup::Errors
|
6
6
|
# - any constant name referenced that ends with 'Error' will be created
|
7
7
|
# as a subclass of Backup::Errors::Error
|
8
|
+
# - any constant name referenced that ends with 'FatalError' will be created
|
9
|
+
# as a subclass of Backup::Errors::FatalError
|
10
|
+
#
|
8
11
|
# e.g.
|
9
12
|
# err = Backup::Errors::Foo::Bar::FooError.new('error message')
|
10
13
|
# err.message => "Foo::Bar::FooError: error message"
|
11
14
|
#
|
15
|
+
# err = Backup::Errors::Foo::Bar::FooFatalError.new('error message')
|
16
|
+
# err.message => "Foo::Bar::FooFatalError: error message"
|
17
|
+
#
|
12
18
|
module ErrorsHelper
|
13
19
|
def const_missing(const)
|
14
|
-
if const.to_s.end_with?('
|
20
|
+
if const.to_s.end_with?('FatalError')
|
21
|
+
module_eval("class #{const} < Backup::Errors::FatalError; end")
|
22
|
+
elsif const.to_s.end_with?('Error')
|
15
23
|
module_eval("class #{const} < Backup::Errors::Error; end")
|
16
24
|
else
|
17
25
|
module_eval("module #{const}; extend Backup::ErrorsHelper; end")
|
@@ -20,104 +28,61 @@ module Backup
|
|
20
28
|
end
|
21
29
|
end
|
22
30
|
|
23
|
-
##
|
24
|
-
# provides cascading errors with formatted messages
|
25
|
-
# see the specs for details
|
26
|
-
#
|
27
|
-
# e.g.
|
28
|
-
# module Backup
|
29
|
-
# begin
|
30
|
-
# begin
|
31
|
-
# begin
|
32
|
-
# raise Errors::ZoneAError, 'an error occurred in Zone A'
|
33
|
-
# rescue => err
|
34
|
-
# raise Errors::ZoneBError.wrap(err, <<-EOS)
|
35
|
-
# an error occurred in Zone B
|
36
|
-
#
|
37
|
-
# the following error should give a reason
|
38
|
-
# EOS
|
39
|
-
# end
|
40
|
-
# rescue => err
|
41
|
-
# raise Errors::ZoneCError.wrap(err)
|
42
|
-
# end
|
43
|
-
# rescue => err
|
44
|
-
# puts Errors::ZoneDError.wrap(err, 'an error occurred in Zone D')
|
45
|
-
# end
|
46
|
-
# end
|
47
|
-
#
|
48
|
-
# Outputs:
|
49
|
-
# ZoneDError: an error occurred in Zone D
|
50
|
-
# Reason: ZoneCError
|
51
|
-
# ZoneBError: an error occurred in Zone B
|
52
|
-
#
|
53
|
-
# the following error should give a reason
|
54
|
-
# Reason: ZoneAError
|
55
|
-
# an error occurred in Zone A
|
56
|
-
#
|
57
31
|
module Errors
|
58
32
|
extend ErrorsHelper
|
59
33
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
new(msg, orig_err)
|
64
|
-
end
|
34
|
+
# Provides cascading errors with formatted messages.
|
35
|
+
# See the specs for details.
|
36
|
+
module NestedExceptions
|
65
37
|
|
66
|
-
def
|
67
|
-
|
68
|
-
|
38
|
+
def self.included(klass)
|
39
|
+
klass.extend Module.new {
|
40
|
+
def wrap(wrapped_exception, msg = nil)
|
41
|
+
new(msg, wrapped_exception)
|
42
|
+
end
|
43
|
+
}
|
69
44
|
end
|
70
45
|
|
71
|
-
def
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
msg <<
|
81
|
-
|
46
|
+
def initialize(obj = nil, wrapped_exception = nil)
|
47
|
+
@wrapped_exception = wrapped_exception
|
48
|
+
msg = (obj.respond_to?(:to_str) ? obj.to_str : obj.to_s).
|
49
|
+
gsub(/^ */, ' ').strip
|
50
|
+
msg = clean_name(self.class.name) + (msg.empty? ? '' : ": #{ msg }")
|
51
|
+
|
52
|
+
if wrapped_exception
|
53
|
+
msg << "\n--- Wrapped Exception ---\n"
|
54
|
+
class_name = clean_name(wrapped_exception.class.name)
|
55
|
+
msg << class_name + ': ' unless
|
56
|
+
wrapped_exception.message.start_with? class_name
|
57
|
+
msg << wrapped_exception.message
|
82
58
|
end
|
83
59
|
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
private
|
88
|
-
|
89
|
-
def msg_prefix
|
90
|
-
@msg_prefix ||= class_name + ': '
|
60
|
+
super(msg)
|
61
|
+
set_backtrace(wrapped_exception.backtrace) if wrapped_exception
|
91
62
|
end
|
92
63
|
|
93
|
-
def
|
94
|
-
|
95
|
-
end
|
64
|
+
def exception(obj = nil)
|
65
|
+
return self if obj.nil? || equal?(obj)
|
96
66
|
|
97
|
-
|
98
|
-
|
67
|
+
ex = self.class.new(obj, @wrapped_exception)
|
68
|
+
ex.set_backtrace(backtrace) unless ex.backtrace
|
69
|
+
ex
|
99
70
|
end
|
100
71
|
|
101
|
-
|
102
|
-
return unless @orig_err
|
72
|
+
private
|
103
73
|
|
104
|
-
|
105
|
-
|
74
|
+
def clean_name(name)
|
75
|
+
name.sub(/^Backup::Errors::/, '')
|
106
76
|
end
|
107
77
|
|
108
|
-
|
109
|
-
return unless @orig_err
|
110
|
-
return @orig_err_msg unless @orig_err_msg.nil?
|
78
|
+
end
|
111
79
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
false : format_msg(msg)
|
116
|
-
end
|
80
|
+
class Error < StandardError
|
81
|
+
include NestedExceptions
|
82
|
+
end
|
117
83
|
|
118
|
-
|
119
|
-
|
120
|
-
end
|
84
|
+
class FatalError < Exception
|
85
|
+
include NestedExceptions
|
121
86
|
end
|
122
87
|
|
123
88
|
end
|
data/lib/backup/model.rb
CHANGED
@@ -34,35 +34,35 @@ module Backup
|
|
34
34
|
attr_reader :label
|
35
35
|
|
36
36
|
##
|
37
|
-
#
|
37
|
+
# Array of configured Database objects.
|
38
38
|
attr_reader :databases
|
39
39
|
|
40
40
|
##
|
41
|
-
#
|
41
|
+
# Array of configured Archive objects.
|
42
42
|
attr_reader :archives
|
43
43
|
|
44
44
|
##
|
45
|
-
#
|
45
|
+
# Array of configured Notifier objects.
|
46
46
|
attr_reader :notifiers
|
47
47
|
|
48
48
|
##
|
49
|
-
#
|
49
|
+
# Array of configured Storage objects.
|
50
50
|
attr_reader :storages
|
51
51
|
|
52
52
|
##
|
53
|
-
#
|
53
|
+
# Array of configured Syncer objects.
|
54
54
|
attr_reader :syncers
|
55
55
|
|
56
56
|
##
|
57
|
-
#
|
57
|
+
# The configured Compressor, if any.
|
58
58
|
attr_reader :compressor
|
59
59
|
|
60
60
|
##
|
61
|
-
#
|
61
|
+
# The configured Encryptor, if any.
|
62
62
|
attr_reader :encryptor
|
63
63
|
|
64
64
|
##
|
65
|
-
#
|
65
|
+
# The configured Splitter, if any.
|
66
66
|
attr_reader :splitter
|
67
67
|
|
68
68
|
##
|
@@ -74,17 +74,31 @@ module Backup
|
|
74
74
|
attr_reader :time
|
75
75
|
|
76
76
|
##
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
77
|
+
# Result of this model's backup process.
|
78
|
+
#
|
79
|
+
# 0 = Job was successful
|
80
|
+
# 1 = Job was successful, but issued warnings
|
81
|
+
# 2 = Job failed, additional triggers may be performed
|
82
|
+
# 3 = Job failed, additional triggers will not be performed
|
83
|
+
attr_reader :exit_status
|
84
|
+
|
85
|
+
##
|
86
|
+
# Exception raised by either a +before+ hook or one of the model's
|
87
|
+
# procedures that caused the model to fail. An exception raised by an
|
88
|
+
# +after+ hook would not be stored here. Therefore, it is possible for
|
89
|
+
# this to be +nil+ even if #exit_status is 2 or 3.
|
90
|
+
attr_reader :exception
|
91
|
+
|
80
92
|
def initialize(trigger, label, &block)
|
81
93
|
@trigger = trigger.to_s
|
82
94
|
@label = label.to_s
|
83
95
|
@package = Package.new(self)
|
84
96
|
|
85
|
-
|
86
|
-
|
87
|
-
|
97
|
+
@databases = []
|
98
|
+
@archives = []
|
99
|
+
@storages = []
|
100
|
+
@notifiers = []
|
101
|
+
@syncers = []
|
88
102
|
|
89
103
|
instance_eval(&block) if block_given?
|
90
104
|
|
@@ -96,31 +110,27 @@ module Backup
|
|
96
110
|
end
|
97
111
|
|
98
112
|
##
|
99
|
-
# Adds an
|
100
|
-
# to store during the backup process
|
113
|
+
# Adds an Archive. Multiple Archives may be added to the model.
|
101
114
|
def archive(name, &block)
|
102
115
|
@archives << Archive.new(self, name, &block)
|
103
116
|
end
|
104
117
|
|
105
118
|
##
|
106
|
-
# Adds
|
107
|
-
# to dump during the backup process
|
119
|
+
# Adds an Database. Multiple Databases may be added to the model.
|
108
120
|
def database(name, database_id = nil, &block)
|
109
121
|
@databases << get_class_from_scope(Database, name).
|
110
122
|
new(self, database_id, &block)
|
111
123
|
end
|
112
124
|
|
113
125
|
##
|
114
|
-
# Adds
|
115
|
-
# methods to use during the backup process
|
126
|
+
# Adds an Storage. Multiple Storages may be added to the model.
|
116
127
|
def store_with(name, storage_id = nil, &block)
|
117
128
|
@storages << get_class_from_scope(Storage, name).
|
118
129
|
new(self, storage_id, &block)
|
119
130
|
end
|
120
131
|
|
121
132
|
##
|
122
|
-
# Adds
|
123
|
-
# methods to use during the backup process
|
133
|
+
# Adds an Syncer. Multiple Syncers may be added to the model.
|
124
134
|
def sync_with(name, syncer_id = nil, &block)
|
125
135
|
##
|
126
136
|
# Warn user of DSL changes
|
@@ -147,29 +157,29 @@ module Backup
|
|
147
157
|
end
|
148
158
|
|
149
159
|
##
|
150
|
-
# Adds
|
151
|
-
# to use during the backup process
|
160
|
+
# Adds an Notifier. Multiple Notifiers may be added to the model.
|
152
161
|
def notify_by(name, &block)
|
153
162
|
@notifiers << get_class_from_scope(Notifier, name).new(self, &block)
|
154
163
|
end
|
155
164
|
|
156
165
|
##
|
157
|
-
# Adds an
|
166
|
+
# Adds an Encryptor. Only one Encryptor may be added to the model.
|
167
|
+
# This will be used to encrypt the final backup package.
|
158
168
|
def encrypt_with(name, &block)
|
159
169
|
@encryptor = get_class_from_scope(Encryptor, name).new(&block)
|
160
170
|
end
|
161
171
|
|
162
172
|
##
|
163
|
-
# Adds
|
173
|
+
# Adds an Compressor. Only one Compressor may be added to the model.
|
174
|
+
# This will be used to compress each individual Archive and Database
|
175
|
+
# stored within the final backup package.
|
164
176
|
def compress_with(name, &block)
|
165
177
|
@compressor = get_class_from_scope(Compressor, name).new(&block)
|
166
178
|
end
|
167
179
|
|
168
180
|
##
|
169
|
-
# Adds a
|
170
|
-
#
|
171
|
-
# The +chunk_size+ (in megabytes) will later determine
|
172
|
-
# in how many chunks the backup needs to be split into
|
181
|
+
# Adds a Splitter with the given +chunk_size+ in MB.
|
182
|
+
# This will split the final backup package into multiple files.
|
173
183
|
def split_into_chunks_of(chunk_size)
|
174
184
|
if chunk_size.is_a?(Integer)
|
175
185
|
@splitter = Splitter.new(self, chunk_size)
|
@@ -182,79 +192,97 @@ module Backup
|
|
182
192
|
end
|
183
193
|
|
184
194
|
##
|
185
|
-
#
|
186
|
-
|
187
|
-
#
|
188
|
-
#
|
189
|
-
|
190
|
-
#
|
191
|
-
#
|
192
|
-
#
|
193
|
-
|
194
|
-
#
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
# Optionally encrypts the packaged file with the configured encryptor
|
200
|
-
##
|
201
|
-
# [Compression]
|
202
|
-
# Optionally compresses the each Archive and Database dump with the configured compressor
|
203
|
-
##
|
204
|
-
# [Splitting]
|
205
|
-
# Optionally splits the backup file in to multiple smaller chunks before transferring them
|
206
|
-
##
|
207
|
-
# [Storages]
|
208
|
-
# Runs all (if any) storage objects to store the backups to remote locations
|
209
|
-
# and (if configured) it'll cycle the files on the remote location to limit the
|
210
|
-
# amount of backups stored on each individual location
|
211
|
-
##
|
212
|
-
# [Syncers]
|
213
|
-
# Runs all (if any) sync objects to store the backups to remote locations.
|
214
|
-
# A Syncer does not go through the process of packaging, compressing, encrypting backups.
|
215
|
-
# A Syncer directly transfers data from the filesystem to the remote location
|
195
|
+
# Defines a block of code to run before the model's procedures.
|
196
|
+
#
|
197
|
+
# Warnings logged within the before hook will elevate the model's
|
198
|
+
# exit_status to 1 and cause warning notifications to be sent.
|
199
|
+
#
|
200
|
+
# Raising an exception will abort the model and cause failure notifications
|
201
|
+
# to be sent. If the exception is a StandardError, exit_status will be 2.
|
202
|
+
# If the exception is not a StandardError, exit_status will be 3.
|
203
|
+
#
|
204
|
+
# If any exception is raised, any defined +after+ hook will be skipped.
|
205
|
+
def before(&block)
|
206
|
+
@before ||= block
|
207
|
+
end
|
208
|
+
|
216
209
|
##
|
217
|
-
#
|
218
|
-
#
|
219
|
-
#
|
210
|
+
# Defines a block of code to run after the model's procedures.
|
211
|
+
#
|
212
|
+
# This code is ensured to run, even if the model failed, **unless** a
|
213
|
+
# +before+ hook raised an exception and aborted the model.
|
214
|
+
#
|
215
|
+
# The code block will be passed the model's current exit_status:
|
216
|
+
#
|
217
|
+
# `0`: Success, no warnings.
|
218
|
+
# `1`: Success, but warnings were logged.
|
219
|
+
# `2`: Failure, but additional models/triggers will still be processed.
|
220
|
+
# `3`: Failure, no additional models/triggers will be processed.
|
221
|
+
#
|
222
|
+
# The model's exit_status may be elevated based on the after hook's
|
223
|
+
# actions, but will never be decreased.
|
224
|
+
#
|
225
|
+
# Warnings logged within the after hook may elevate the model's
|
226
|
+
# exit_status to 1 and cause warning notifications to be sent.
|
227
|
+
#
|
228
|
+
# Raising an exception may elevate the model's exit_status and cause
|
229
|
+
# failure notifications to be sent. If the exception is a StandardError,
|
230
|
+
# the exit_status will be elevated to 2. If the exception is not a
|
231
|
+
# StandardError, the exit_status will be elevated to 3.
|
232
|
+
def after(&block)
|
233
|
+
@after ||= block
|
234
|
+
end
|
235
|
+
|
220
236
|
##
|
221
|
-
#
|
222
|
-
#
|
223
|
-
#
|
224
|
-
#
|
225
|
-
# If
|
226
|
-
#
|
227
|
-
#
|
237
|
+
# Performs the backup process
|
238
|
+
#
|
239
|
+
# Once complete, #exit_status will indicate the result of this process.
|
240
|
+
#
|
241
|
+
# If any errors occur during the backup process, all temporary files will
|
242
|
+
# be left in place. If the error occurs before Packaging, then the
|
243
|
+
# temporary folder (tmp_path/trigger) will remain and may contain all or
|
244
|
+
# some of the configured Archives and/or Database dumps. If the error
|
245
|
+
# occurs after Packaging, but before the Storages complete, then the final
|
228
246
|
# packaged files (located in the root of tmp_path) will remain.
|
229
|
-
# *** Important *** If an error occurs and any of the above mentioned temporary files remain,
|
230
|
-
# those files *** will be removed *** before the next scheduled backup for the same trigger.
|
231
247
|
#
|
248
|
+
# *** Important ***
|
249
|
+
# If an error occurs and any of the above mentioned temporary files remain,
|
250
|
+
# those files *** will be removed *** before the next scheduled backup for
|
251
|
+
# the same trigger.
|
232
252
|
def perform!
|
233
253
|
@started_at = Time.now
|
234
254
|
@time = package.time = @started_at.strftime("%Y.%m.%d.%H.%M.%S")
|
235
|
-
log!(:started)
|
236
255
|
|
237
|
-
|
256
|
+
log!(:started)
|
257
|
+
before_hook
|
238
258
|
|
239
|
-
|
240
|
-
|
241
|
-
(procedure.call; next) if procedure.is_a?(Proc)
|
242
|
-
procedure.each(&:perform!)
|
243
|
-
end
|
259
|
+
procedures.each do |procedure|
|
260
|
+
procedure.is_a?(Proc) ? procedure.call : procedure.each(&:perform!)
|
244
261
|
end
|
245
262
|
|
246
263
|
syncers.each(&:perform!)
|
247
|
-
notifiers.each(&:perform!)
|
248
|
-
log!(:finished)
|
249
264
|
|
250
265
|
rescue Exception => err
|
251
|
-
|
252
|
-
|
253
|
-
|
266
|
+
@exception = err
|
267
|
+
|
268
|
+
ensure
|
269
|
+
set_exit_status
|
270
|
+
log!(:finished)
|
271
|
+
after_hook
|
254
272
|
end
|
255
273
|
|
256
274
|
private
|
257
275
|
|
276
|
+
##
|
277
|
+
# Returns an array of procedures that will be performed if any
|
278
|
+
# Archives or Databases are configured for the model.
|
279
|
+
def procedures
|
280
|
+
return [] unless databases.any? || archives.any?
|
281
|
+
|
282
|
+
[lambda { prepare! }, databases, archives,
|
283
|
+
lambda { package! }, storages, lambda { clean! }]
|
284
|
+
end
|
285
|
+
|
258
286
|
##
|
259
287
|
# Clean any temporary files and/or package files left over
|
260
288
|
# from the last time this model/trigger was performed.
|
@@ -280,18 +308,6 @@ module Backup
|
|
280
308
|
Cleaner.remove_package(package)
|
281
309
|
end
|
282
310
|
|
283
|
-
##
|
284
|
-
# Returns an array of procedures
|
285
|
-
def procedures
|
286
|
-
[databases, archives, lambda { package! }, storages, lambda { clean! }]
|
287
|
-
end
|
288
|
-
|
289
|
-
##
|
290
|
-
# Returns an Array of the names (String) of the procedure instance variables
|
291
|
-
def procedure_instance_variables
|
292
|
-
[:@databases, :@archives, :@storages, :@notifiers, :@syncers]
|
293
|
-
end
|
294
|
-
|
295
311
|
##
|
296
312
|
# Returns the class/model specified by +name+ inside of +scope+.
|
297
313
|
# +scope+ should be a Class/Module.
|
@@ -319,43 +335,84 @@ module Backup
|
|
319
335
|
end
|
320
336
|
|
321
337
|
##
|
322
|
-
#
|
323
|
-
def
|
338
|
+
# Sets or updates the model's #exit_status.
|
339
|
+
def set_exit_status
|
340
|
+
@exit_status = if exception
|
341
|
+
exception.is_a?(StandardError) ? 2 : 3
|
342
|
+
else
|
343
|
+
Logger.has_warnings? ? 1 : 0
|
344
|
+
end
|
345
|
+
end
|
346
|
+
|
347
|
+
##
|
348
|
+
# Runs the +before+ hook.
|
349
|
+
# Any exception raised will be wrapped and re-raised, where it will be
|
350
|
+
# handled by #perform the same as an exception raised while performing
|
351
|
+
# the model's #procedures. Only difference is that an exception raised
|
352
|
+
# here will prevent any +after+ hook from being run.
|
353
|
+
def before_hook
|
354
|
+
return unless before
|
355
|
+
|
356
|
+
Logger.info 'Before Hook Starting...'
|
357
|
+
before.call
|
358
|
+
Logger.info 'Before Hook Finished.'
|
359
|
+
|
360
|
+
rescue Exception => err
|
361
|
+
@before_hook_failed = true
|
362
|
+
ex = err.is_a?(StandardError) ?
|
363
|
+
Errors::Model::HookError : Errors::Model::HookFatalError
|
364
|
+
raise ex.wrap(err, 'Before Hook Failed!')
|
365
|
+
end
|
366
|
+
|
367
|
+
##
|
368
|
+
# Runs the +after+ hook.
|
369
|
+
# Any exception raised here will be logged only and the model's
|
370
|
+
# #exit_status will be elevated if neccessary.
|
371
|
+
def after_hook
|
372
|
+
return unless after && !@before_hook_failed
|
373
|
+
|
374
|
+
Logger.info 'After Hook Starting...'
|
375
|
+
after.call(exit_status)
|
376
|
+
Logger.info 'After Hook Finished.'
|
377
|
+
|
378
|
+
set_exit_status # in case hook logged warnings
|
379
|
+
|
380
|
+
rescue Exception => err
|
381
|
+
fatal = !err.is_a?(StandardError)
|
382
|
+
ex = fatal ? Errors::Model::HookFatalError : Errors::Model::HookError
|
383
|
+
Logger.error ex.wrap(err, 'After Hook Failed!')
|
384
|
+
# upgrade exit_status if needed
|
385
|
+
(@exit_status = fatal ? 3 : 2) unless exit_status == 3
|
386
|
+
end
|
387
|
+
|
388
|
+
##
|
389
|
+
# Logs messages when the model starts and finishes.
|
390
|
+
#
|
391
|
+
# #exception will be set here if #exit_status is > 1,
|
392
|
+
# since log(:finished) is called before the +after+ hook.
|
393
|
+
def log!(action)
|
324
394
|
case action
|
325
395
|
when :started
|
326
|
-
Logger.info "Performing Backup for '#{label} (#{trigger})'!\n" +
|
396
|
+
Logger.info "Performing Backup for '#{ label } (#{ trigger })'!\n" +
|
327
397
|
"[ backup #{ VERSION } : #{ RUBY_DESCRIPTION } ]"
|
328
398
|
|
329
399
|
when :finished
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
Logger.
|
334
|
-
|
335
|
-
Logger.info msg % 'Successfully'
|
336
|
-
end
|
337
|
-
|
338
|
-
when :failure
|
339
|
-
err = Errors::ModelError.wrap(exception, <<-EOS)
|
340
|
-
Backup for #{label} (#{trigger}) Failed!
|
341
|
-
An Error occured which has caused this Backup to abort before completion.
|
342
|
-
EOS
|
343
|
-
Logger.error err
|
344
|
-
Logger.error "\nBacktrace:\n\s\s" + err.backtrace.join("\n\s\s") + "\n\n"
|
400
|
+
if exit_status > 1
|
401
|
+
ex = exit_status == 2 ? Errors::ModelError : Errors::ModelFatalError
|
402
|
+
err = ex.wrap(exception, "Backup for #{ label } (#{ trigger }) Failed!")
|
403
|
+
Logger.error err
|
404
|
+
Logger.error "\nBacktrace:\n\s\s" + err.backtrace.join("\n\s\s") + "\n\n"
|
345
405
|
|
346
|
-
|
347
|
-
|
348
|
-
if exception.is_a?(StandardError)
|
349
|
-
Logger.info Errors::ModelError.new(<<-EOS)
|
350
|
-
If you have other Backup jobs (triggers) configured to run,
|
351
|
-
Backup will now attempt to continue...
|
352
|
-
EOS
|
406
|
+
Cleaner.warnings(self)
|
353
407
|
else
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
408
|
+
msg = "Backup for '#{ label } (#{ trigger })' "
|
409
|
+
if exit_status == 1
|
410
|
+
msg << "Completed Successfully (with Warnings) in #{ elapsed_time }"
|
411
|
+
Logger.warn msg
|
412
|
+
else
|
413
|
+
msg << "Completed Successfully in #{ elapsed_time }"
|
414
|
+
Logger.info msg
|
415
|
+
end
|
359
416
|
end
|
360
417
|
end
|
361
418
|
end
|
@@ -371,21 +428,5 @@ module Backup
|
|
371
428
|
'%02d:%02d:%02d' % [hours, minutes, seconds]
|
372
429
|
end
|
373
430
|
|
374
|
-
##
|
375
|
-
# Sends notifications when a backup fails.
|
376
|
-
# Errors are logged and rescued, since the error that caused the
|
377
|
-
# backup to fail could have been an error with a notifier.
|
378
|
-
def send_failure_notifications
|
379
|
-
notifiers.each do |n|
|
380
|
-
begin
|
381
|
-
n.perform!(true)
|
382
|
-
rescue Exception => err
|
383
|
-
Logger.error Errors::ModelError.wrap(err, <<-EOS)
|
384
|
-
#{ n.class } Failed to send notification of backup failure.
|
385
|
-
EOS
|
386
|
-
end
|
387
|
-
end
|
388
|
-
end
|
389
|
-
|
390
431
|
end
|
391
432
|
end
|