cm-backup 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (133) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +20 -0
  3. data/bin/backup +5 -0
  4. data/lib/backup.rb +144 -0
  5. data/lib/backup/archive.rb +170 -0
  6. data/lib/backup/binder.rb +22 -0
  7. data/lib/backup/cleaner.rb +116 -0
  8. data/lib/backup/cli.rb +374 -0
  9. data/lib/backup/cloud_io/base.rb +41 -0
  10. data/lib/backup/cloud_io/cloud_files.rb +298 -0
  11. data/lib/backup/cloud_io/s3.rb +260 -0
  12. data/lib/backup/compressor/base.rb +35 -0
  13. data/lib/backup/compressor/bzip2.rb +39 -0
  14. data/lib/backup/compressor/custom.rb +53 -0
  15. data/lib/backup/compressor/gzip.rb +74 -0
  16. data/lib/backup/config.rb +119 -0
  17. data/lib/backup/config/dsl.rb +103 -0
  18. data/lib/backup/config/helpers.rb +143 -0
  19. data/lib/backup/database/base.rb +85 -0
  20. data/lib/backup/database/mongodb.rb +187 -0
  21. data/lib/backup/database/mysql.rb +192 -0
  22. data/lib/backup/database/openldap.rb +95 -0
  23. data/lib/backup/database/postgresql.rb +133 -0
  24. data/lib/backup/database/redis.rb +179 -0
  25. data/lib/backup/database/riak.rb +82 -0
  26. data/lib/backup/database/sqlite.rb +57 -0
  27. data/lib/backup/encryptor/base.rb +29 -0
  28. data/lib/backup/encryptor/gpg.rb +747 -0
  29. data/lib/backup/encryptor/open_ssl.rb +77 -0
  30. data/lib/backup/errors.rb +58 -0
  31. data/lib/backup/logger.rb +199 -0
  32. data/lib/backup/logger/console.rb +51 -0
  33. data/lib/backup/logger/fog_adapter.rb +29 -0
  34. data/lib/backup/logger/logfile.rb +133 -0
  35. data/lib/backup/logger/syslog.rb +116 -0
  36. data/lib/backup/model.rb +479 -0
  37. data/lib/backup/notifier/base.rb +128 -0
  38. data/lib/backup/notifier/campfire.rb +63 -0
  39. data/lib/backup/notifier/command.rb +102 -0
  40. data/lib/backup/notifier/datadog.rb +107 -0
  41. data/lib/backup/notifier/flowdock.rb +103 -0
  42. data/lib/backup/notifier/hipchat.rb +118 -0
  43. data/lib/backup/notifier/http_post.rb +117 -0
  44. data/lib/backup/notifier/mail.rb +249 -0
  45. data/lib/backup/notifier/nagios.rb +69 -0
  46. data/lib/backup/notifier/pagerduty.rb +81 -0
  47. data/lib/backup/notifier/prowl.rb +68 -0
  48. data/lib/backup/notifier/pushover.rb +74 -0
  49. data/lib/backup/notifier/ses.rb +105 -0
  50. data/lib/backup/notifier/slack.rb +148 -0
  51. data/lib/backup/notifier/twitter.rb +58 -0
  52. data/lib/backup/notifier/zabbix.rb +63 -0
  53. data/lib/backup/package.rb +55 -0
  54. data/lib/backup/packager.rb +107 -0
  55. data/lib/backup/pipeline.rb +124 -0
  56. data/lib/backup/splitter.rb +76 -0
  57. data/lib/backup/storage/base.rb +69 -0
  58. data/lib/backup/storage/cloud_files.rb +158 -0
  59. data/lib/backup/storage/cycler.rb +75 -0
  60. data/lib/backup/storage/dropbox.rb +212 -0
  61. data/lib/backup/storage/ftp.rb +112 -0
  62. data/lib/backup/storage/local.rb +64 -0
  63. data/lib/backup/storage/qiniu.rb +65 -0
  64. data/lib/backup/storage/rsync.rb +248 -0
  65. data/lib/backup/storage/s3.rb +156 -0
  66. data/lib/backup/storage/scp.rb +67 -0
  67. data/lib/backup/storage/sftp.rb +82 -0
  68. data/lib/backup/syncer/base.rb +70 -0
  69. data/lib/backup/syncer/cloud/base.rb +179 -0
  70. data/lib/backup/syncer/cloud/cloud_files.rb +83 -0
  71. data/lib/backup/syncer/cloud/local_file.rb +100 -0
  72. data/lib/backup/syncer/cloud/s3.rb +110 -0
  73. data/lib/backup/syncer/rsync/base.rb +54 -0
  74. data/lib/backup/syncer/rsync/local.rb +31 -0
  75. data/lib/backup/syncer/rsync/pull.rb +51 -0
  76. data/lib/backup/syncer/rsync/push.rb +205 -0
  77. data/lib/backup/template.rb +46 -0
  78. data/lib/backup/utilities.rb +224 -0
  79. data/lib/backup/version.rb +5 -0
  80. data/templates/cli/archive +28 -0
  81. data/templates/cli/compressor/bzip2 +4 -0
  82. data/templates/cli/compressor/custom +7 -0
  83. data/templates/cli/compressor/gzip +4 -0
  84. data/templates/cli/config +123 -0
  85. data/templates/cli/databases/mongodb +15 -0
  86. data/templates/cli/databases/mysql +18 -0
  87. data/templates/cli/databases/openldap +24 -0
  88. data/templates/cli/databases/postgresql +16 -0
  89. data/templates/cli/databases/redis +16 -0
  90. data/templates/cli/databases/riak +17 -0
  91. data/templates/cli/databases/sqlite +11 -0
  92. data/templates/cli/encryptor/gpg +27 -0
  93. data/templates/cli/encryptor/openssl +9 -0
  94. data/templates/cli/model +26 -0
  95. data/templates/cli/notifier/zabbix +15 -0
  96. data/templates/cli/notifiers/campfire +12 -0
  97. data/templates/cli/notifiers/command +32 -0
  98. data/templates/cli/notifiers/datadog +57 -0
  99. data/templates/cli/notifiers/flowdock +16 -0
  100. data/templates/cli/notifiers/hipchat +16 -0
  101. data/templates/cli/notifiers/http_post +32 -0
  102. data/templates/cli/notifiers/mail +24 -0
  103. data/templates/cli/notifiers/nagios +13 -0
  104. data/templates/cli/notifiers/pagerduty +12 -0
  105. data/templates/cli/notifiers/prowl +11 -0
  106. data/templates/cli/notifiers/pushover +11 -0
  107. data/templates/cli/notifiers/ses +15 -0
  108. data/templates/cli/notifiers/slack +22 -0
  109. data/templates/cli/notifiers/twitter +13 -0
  110. data/templates/cli/splitter +7 -0
  111. data/templates/cli/storages/cloud_files +11 -0
  112. data/templates/cli/storages/dropbox +20 -0
  113. data/templates/cli/storages/ftp +13 -0
  114. data/templates/cli/storages/local +8 -0
  115. data/templates/cli/storages/qiniu +12 -0
  116. data/templates/cli/storages/rsync +17 -0
  117. data/templates/cli/storages/s3 +16 -0
  118. data/templates/cli/storages/scp +15 -0
  119. data/templates/cli/storages/sftp +15 -0
  120. data/templates/cli/syncers/cloud_files +22 -0
  121. data/templates/cli/syncers/rsync_local +20 -0
  122. data/templates/cli/syncers/rsync_pull +28 -0
  123. data/templates/cli/syncers/rsync_push +28 -0
  124. data/templates/cli/syncers/s3 +27 -0
  125. data/templates/general/links +3 -0
  126. data/templates/general/version.erb +2 -0
  127. data/templates/notifier/mail/failure.erb +16 -0
  128. data/templates/notifier/mail/success.erb +16 -0
  129. data/templates/notifier/mail/warning.erb +16 -0
  130. data/templates/storage/dropbox/authorization_url.erb +6 -0
  131. data/templates/storage/dropbox/authorized.erb +4 -0
  132. data/templates/storage/dropbox/cache_file_written.erb +10 -0
  133. metadata +1077 -0
@@ -0,0 +1,116 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ class Logger
5
+ class Syslog
6
+ class Options
7
+ ##
8
+ # Enables logging to the system's Syslog compatible logger.
9
+ #
10
+ # This may also be enabled using +--syslog+ on the command line.
11
+ #
12
+ # If +--no-syslog+ is used on the command line, this will be
13
+ # disabled and any settings here will be ignored.
14
+ #
15
+ # @param [Boolean, nil]
16
+ # @return [Boolean, nil] Default: +false+
17
+ attr_reader :enabled
18
+
19
+ ##
20
+ # Specify the identification string to be used with Syslog.
21
+ #
22
+ # @param [String]
23
+ # @return [String] Default: 'backup'
24
+ attr_accessor :ident
25
+
26
+ ##
27
+ # Specify the options to be used with Syslog.
28
+ #
29
+ # See the Ruby Standard Library documentation for +Syslog+ for more info.
30
+ # http://rdoc.info/stdlib/syslog/Syslog.open
31
+ #
32
+ # Note that setting this to +nil+ will cause this to default
33
+ # to a setting of +Syslog::LOG_PID | Syslog::LOG_CONS+
34
+ #
35
+ # @param [Integer]
36
+ # @return [Integer] Default: +Syslog::LOG_PID+
37
+ attr_accessor :options
38
+
39
+ ##
40
+ # Specify the facility to be used with Syslog.
41
+ #
42
+ # See the Ruby Standard Library documentation for +Syslog+ for more info.
43
+ # http://rdoc.info/stdlib/syslog/Syslog.open
44
+ #
45
+ # Note that setting this to +nil+ will cause this to default
46
+ # to a setting of +Syslog::LOG_USER+
47
+ #
48
+ # @param [Integer]
49
+ # @return [Integer] Default: +Syslog::LOG_LOCAL0+
50
+ attr_accessor :facility
51
+
52
+ ##
53
+ # Specify the priority level to be used for +:info+ messages.
54
+ #
55
+ # See the Ruby Standard Library documentation for +Syslog+ for more info.
56
+ # http://rdoc.info/stdlib/syslog/Syslog.log
57
+ #
58
+ # @param [Integer]
59
+ # @return [Integer] Default: +Syslog::LOG_INFO+
60
+ attr_accessor :info
61
+
62
+ ##
63
+ # Specify the priority level to be used for +:warn+ messages.
64
+ #
65
+ # See the Ruby Standard Library documentation for +Syslog+ for more info.
66
+ # http://rdoc.info/stdlib/syslog/Syslog.log
67
+ #
68
+ # @param [Integer]
69
+ # @return [Integer] Default: +Syslog::LOG_WARNING+
70
+ attr_accessor :warn
71
+
72
+ ##
73
+ # Specify the priority level to be used for +:error+ messages.
74
+ #
75
+ # See the Ruby Standard Library documentation for +Syslog+ for more info.
76
+ # http://rdoc.info/stdlib/syslog/Syslog.log
77
+ #
78
+ # @param [Integer]
79
+ # @return [Integer] Default: +Syslog::LOG_ERR+
80
+ attr_accessor :error
81
+
82
+ def initialize
83
+ @enabled = false
84
+ @ident = 'backup'
85
+ @options = ::Syslog::LOG_PID
86
+ @facility = ::Syslog::LOG_LOCAL0
87
+ @info = ::Syslog::LOG_INFO
88
+ @warn = ::Syslog::LOG_WARNING
89
+ @error = ::Syslog::LOG_ERR
90
+ end
91
+
92
+ def enabled?
93
+ !!enabled
94
+ end
95
+
96
+ def enabled=(val)
97
+ @enabled = val unless enabled.nil?
98
+ end
99
+ end
100
+
101
+ def initialize(options)
102
+ @options = options
103
+ end
104
+
105
+ ##
106
+ # Message lines are sent without formatting (timestamp, level),
107
+ # since Syslog will provide it's own timestamp and priority.
108
+ def log(message)
109
+ level = @options.send(message.level)
110
+ ::Syslog.open(@options.ident, @options.options, @options.facility) do |s|
111
+ message.lines.each {|line| s.log(level, '%s', line) }
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,479 @@
1
+ # encoding: utf-8
2
+
3
+ module Backup
4
+ class Model
5
+ class Error < Backup::Error; end
6
+ class FatalError < Backup::FatalError; end
7
+
8
+ class << self
9
+ ##
10
+ # The Backup::Model.all class method keeps track of all the models
11
+ # that have been instantiated. It returns the @all class variable,
12
+ # which contains an array of all the models
13
+ def all
14
+ @all ||= []
15
+ end
16
+
17
+ ##
18
+ # Return an Array of Models matching the given +trigger+.
19
+ def find_by_trigger(trigger)
20
+ trigger = trigger.to_s
21
+ if trigger.include?('*')
22
+ regex = /^#{ trigger.gsub('*', '(.*)') }$/
23
+ all.select {|model| regex =~ model.trigger }
24
+ else
25
+ all.select {|model| trigger == model.trigger }
26
+ end
27
+ end
28
+
29
+ # Allows users to create preconfigured models.
30
+ def preconfigure(&block)
31
+ @preconfigure ||= block
32
+ end
33
+
34
+ private
35
+
36
+ # used for testing
37
+ def reset!
38
+ @all = @preconfigure = nil
39
+ end
40
+ end
41
+
42
+ ##
43
+ # The trigger (stored as a String) is used as an identifier
44
+ # for initializing the backup process
45
+ attr_reader :trigger
46
+
47
+ ##
48
+ # The label (stored as a String) is used for a more friendly user output
49
+ attr_reader :label
50
+
51
+ ##
52
+ # Array of configured Database objects.
53
+ attr_reader :databases
54
+
55
+ ##
56
+ # Array of configured Archive objects.
57
+ attr_reader :archives
58
+
59
+ ##
60
+ # Array of configured Notifier objects.
61
+ attr_reader :notifiers
62
+
63
+ ##
64
+ # Array of configured Storage objects.
65
+ attr_reader :storages
66
+
67
+ ##
68
+ # Array of configured Syncer objects.
69
+ attr_reader :syncers
70
+
71
+ ##
72
+ # The configured Compressor, if any.
73
+ attr_reader :compressor
74
+
75
+ ##
76
+ # The configured Encryptor, if any.
77
+ attr_reader :encryptor
78
+
79
+ ##
80
+ # The configured Splitter, if any.
81
+ attr_reader :splitter
82
+
83
+ ##
84
+ # The final backup Package this model will create.
85
+ attr_reader :package
86
+
87
+ ##
88
+ # The time when the backup initiated (in format: 2011.02.20.03.29.59)
89
+ attr_reader :time
90
+
91
+ ##
92
+ # The time when the backup initiated (as a Time object)
93
+ attr_reader :started_at
94
+
95
+ ##
96
+ # The time when the backup finished (as a Time object)
97
+ attr_reader :finished_at
98
+
99
+ ##
100
+ # Result of this model's backup process.
101
+ #
102
+ # 0 = Job was successful
103
+ # 1 = Job was successful, but issued warnings
104
+ # 2 = Job failed, additional triggers may be performed
105
+ # 3 = Job failed, additional triggers will not be performed
106
+ attr_reader :exit_status
107
+
108
+ ##
109
+ # Exception raised by either a +before+ hook or one of the model's
110
+ # procedures that caused the model to fail. An exception raised by an
111
+ # +after+ hook would not be stored here. Therefore, it is possible for
112
+ # this to be +nil+ even if #exit_status is 2 or 3.
113
+ attr_reader :exception
114
+
115
+ def initialize(trigger, label, &block)
116
+ @trigger = trigger.to_s
117
+ @label = label.to_s
118
+ @package = Package.new(self)
119
+
120
+ @databases = []
121
+ @archives = []
122
+ @storages = []
123
+ @notifiers = []
124
+ @syncers = []
125
+
126
+ instance_eval(&self.class.preconfigure) if self.class.preconfigure
127
+ instance_eval(&block) if block_given?
128
+
129
+ # trigger all defined databases to generate their #dump_filename
130
+ # so warnings may be logged if `backup perform --check` is used
131
+ databases.each {|db| db.send(:dump_filename) }
132
+
133
+ Model.all << self
134
+ end
135
+
136
+ ##
137
+ # Adds an Archive. Multiple Archives may be added to the model.
138
+ def archive(name, &block)
139
+ @archives << Archive.new(self, name, &block)
140
+ end
141
+
142
+ ##
143
+ # Adds an Database. Multiple Databases may be added to the model.
144
+ def database(name, database_id = nil, &block)
145
+ @databases << get_class_from_scope(Database, name).
146
+ new(self, database_id, &block)
147
+ end
148
+
149
+ ##
150
+ # Adds an Storage. Multiple Storages may be added to the model.
151
+ def store_with(name, storage_id = nil, &block)
152
+ @storages << get_class_from_scope(Storage, name).
153
+ new(self, storage_id, &block)
154
+ end
155
+
156
+ ##
157
+ # Adds an Syncer. Multiple Syncers may be added to the model.
158
+ def sync_with(name, syncer_id = nil, &block)
159
+ @syncers << get_class_from_scope(Syncer, name).new(syncer_id, &block)
160
+ end
161
+
162
+ ##
163
+ # Adds an Notifier. Multiple Notifiers may be added to the model.
164
+ def notify_by(name, &block)
165
+ @notifiers << get_class_from_scope(Notifier, name).new(self, &block)
166
+ end
167
+
168
+ ##
169
+ # Adds an Encryptor. Only one Encryptor may be added to the model.
170
+ # This will be used to encrypt the final backup package.
171
+ def encrypt_with(name, &block)
172
+ @encryptor = get_class_from_scope(Encryptor, name).new(&block)
173
+ end
174
+
175
+ ##
176
+ # Adds an Compressor. Only one Compressor may be added to the model.
177
+ # This will be used to compress each individual Archive and Database
178
+ # stored within the final backup package.
179
+ def compress_with(name, &block)
180
+ @compressor = get_class_from_scope(Compressor, name).new(&block)
181
+ end
182
+
183
+ ##
184
+ # Adds a Splitter to split the final backup package into multiple files.
185
+ #
186
+ # +chunk_size+ is specified in MiB and must be given as an Integer.
187
+ # +suffix_length+ controls the number of characters used in the suffix
188
+ # (and the maximum number of chunks possible).
189
+ # ie. 1 (-a, -b), 2 (-aa, -ab), 3 (-aaa, -aab)
190
+ def split_into_chunks_of(chunk_size, suffix_length = 3)
191
+ if chunk_size.is_a?(Integer) && suffix_length.is_a?(Integer)
192
+ @splitter = Splitter.new(self, chunk_size, suffix_length)
193
+ else
194
+ raise Error, <<-EOS
195
+ Invalid arguments for #split_into_chunks_of()
196
+ +chunk_size+ (and optional +suffix_length+) must be Integers.
197
+ EOS
198
+ end
199
+ end
200
+
201
+ ##
202
+ # Defines a block of code to run before the model's procedures.
203
+ #
204
+ # Warnings logged within the before hook will elevate the model's
205
+ # exit_status to 1 and cause warning notifications to be sent.
206
+ #
207
+ # Raising an exception will abort the model and cause failure notifications
208
+ # to be sent. If the exception is a StandardError, exit_status will be 2.
209
+ # If the exception is not a StandardError, exit_status will be 3.
210
+ #
211
+ # If any exception is raised, any defined +after+ hook will be skipped.
212
+ def before(&block)
213
+ @before = block if block
214
+ @before
215
+ end
216
+
217
+ ##
218
+ # Defines a block of code to run after the model's procedures.
219
+ #
220
+ # This code is ensured to run, even if the model failed, **unless** a
221
+ # +before+ hook raised an exception and aborted the model.
222
+ #
223
+ # The code block will be passed the model's current exit_status:
224
+ #
225
+ # `0`: Success, no warnings.
226
+ # `1`: Success, but warnings were logged.
227
+ # `2`: Failure, but additional models/triggers will still be processed.
228
+ # `3`: Failure, no additional models/triggers will be processed.
229
+ #
230
+ # The model's exit_status may be elevated based on the after hook's
231
+ # actions, but will never be decreased.
232
+ #
233
+ # Warnings logged within the after hook may elevate the model's
234
+ # exit_status to 1 and cause warning notifications to be sent.
235
+ #
236
+ # Raising an exception may elevate the model's exit_status and cause
237
+ # failure notifications to be sent. If the exception is a StandardError,
238
+ # the exit_status will be elevated to 2. If the exception is not a
239
+ # StandardError, the exit_status will be elevated to 3.
240
+ def after(&block)
241
+ @after = block if block
242
+ @after
243
+ end
244
+
245
+ ##
246
+ # Performs the backup process
247
+ #
248
+ # Once complete, #exit_status will indicate the result of this process.
249
+ #
250
+ # If any errors occur during the backup process, all temporary files will
251
+ # be left in place. If the error occurs before Packaging, then the
252
+ # temporary folder (tmp_path/trigger) will remain and may contain all or
253
+ # some of the configured Archives and/or Database dumps. If the error
254
+ # occurs after Packaging, but before the Storages complete, then the final
255
+ # packaged files (located in the root of tmp_path) will remain.
256
+ #
257
+ # *** Important ***
258
+ # If an error occurs and any of the above mentioned temporary files remain,
259
+ # those files *** will be removed *** before the next scheduled backup for
260
+ # the same trigger.
261
+ def perform!
262
+ @started_at = Time.now.utc
263
+ @time = package.time = started_at.strftime("%Y.%m.%d.%H.%M.%S")
264
+
265
+ log!(:started)
266
+ before_hook
267
+
268
+ procedures.each do |procedure|
269
+ procedure.is_a?(Proc) ? procedure.call : procedure.each(&:perform!)
270
+ end
271
+
272
+ syncers.each(&:perform!)
273
+
274
+ rescue Interrupt
275
+ @interrupted = true
276
+ raise
277
+
278
+ rescue Exception => err
279
+ @exception = err
280
+
281
+ ensure
282
+ unless @interrupted
283
+ set_exit_status
284
+ @finished_at = Time.now.utc
285
+ log!(:finished)
286
+ after_hook
287
+ end
288
+ end
289
+
290
+ ##
291
+ # The duration of the backup process (in format: HH:MM:SS)
292
+ def duration
293
+ return unless finished_at
294
+ elapsed_time(started_at, finished_at)
295
+ end
296
+
297
+ private
298
+
299
+ ##
300
+ # Returns an array of procedures that will be performed if any
301
+ # Archives or Databases are configured for the model.
302
+ def procedures
303
+ return [] unless databases.any? || archives.any?
304
+
305
+ [lambda { prepare! }, databases, archives,
306
+ lambda { package! }, lambda { store! }, lambda { clean! }]
307
+ end
308
+
309
+ ##
310
+ # Clean any temporary files and/or package files left over
311
+ # from the last time this model/trigger was performed.
312
+ # Logs warnings if files exist and are cleaned.
313
+ def prepare!
314
+ Cleaner.prepare(self)
315
+ end
316
+
317
+ ##
318
+ # After all the databases and archives have been dumped and stored,
319
+ # these files will be bundled in to a .tar archive (uncompressed),
320
+ # which may be optionally Encrypted and/or Split into multiple "chunks".
321
+ # All information about this final archive is stored in the @package.
322
+ # Once complete, the temporary folder used during packaging is removed.
323
+ def package!
324
+ Packager.package!(self)
325
+ Cleaner.remove_packaging(self)
326
+ end
327
+
328
+ ##
329
+ # Attempts to use all configured Storages, even if some of them result in exceptions.
330
+ # Returns true or raises first encountered exception.
331
+ def store!
332
+ storage_results = storages.map do |storage|
333
+ begin
334
+ storage.perform!
335
+ rescue => ex
336
+ ex
337
+ end
338
+ end
339
+
340
+ first_exception, *other_exceptions = storage_results.select { |result| result.is_a? Exception }
341
+
342
+ if first_exception
343
+ other_exceptions.each do |exception|
344
+ Logger.error exception.to_s
345
+ Logger.error exception.backtrace.join('\n')
346
+ end
347
+ raise first_exception
348
+ else
349
+ true
350
+ end
351
+ end
352
+
353
+ ##
354
+ # Removes the final package file(s) once all configured Storages have run.
355
+ def clean!
356
+ Cleaner.remove_package(package)
357
+ end
358
+
359
+ ##
360
+ # Returns the class/model specified by +name+ inside of +scope+.
361
+ # +scope+ should be a Class/Module.
362
+ # +name+ may be Class/Module or String representation
363
+ # of any namespace which exists under +scope+.
364
+ #
365
+ # The 'Backup::Config::DSL' namespace is stripped from +name+,
366
+ # since this is the namespace where we define module namespaces
367
+ # for use with Model's DSL methods.
368
+ #
369
+ # Examples:
370
+ # get_class_from_scope(Backup::Database, 'MySQL')
371
+ # returns the class Backup::Database::MySQL
372
+ #
373
+ # get_class_from_scope(Backup::Syncer, Backup::Config::RSync::Local)
374
+ # returns the class Backup::Syncer::RSync::Local
375
+ #
376
+ def get_class_from_scope(scope, name)
377
+ klass = scope
378
+ name = name.to_s.sub(/^Backup::Config::DSL::/, '')
379
+ name.split('::').each do |chunk|
380
+ klass = klass.const_get(chunk)
381
+ end
382
+ klass
383
+ end
384
+
385
+ ##
386
+ # Sets or updates the model's #exit_status.
387
+ def set_exit_status
388
+ @exit_status = if exception
389
+ exception.is_a?(StandardError) ? 2 : 3
390
+ else
391
+ Logger.has_warnings? ? 1 : 0
392
+ end
393
+ end
394
+
395
+ ##
396
+ # Runs the +before+ hook.
397
+ # Any exception raised will be wrapped and re-raised, where it will be
398
+ # handled by #perform the same as an exception raised while performing
399
+ # the model's #procedures. Only difference is that an exception raised
400
+ # here will prevent any +after+ hook from being run.
401
+ def before_hook
402
+ return unless before
403
+
404
+ Logger.info 'Before Hook Starting...'
405
+ before.call
406
+ Logger.info 'Before Hook Finished.'
407
+
408
+ rescue Exception => err
409
+ @before_hook_failed = true
410
+ ex = err.is_a?(StandardError) ? Error : FatalError
411
+ raise ex.wrap(err, 'Before Hook Failed!')
412
+ end
413
+
414
+ ##
415
+ # Runs the +after+ hook.
416
+ # Any exception raised here will be logged only and the model's
417
+ # #exit_status will be elevated if neccessary.
418
+ def after_hook
419
+ return unless after && !@before_hook_failed
420
+
421
+ Logger.info 'After Hook Starting...'
422
+ after.call(exit_status)
423
+ Logger.info 'After Hook Finished.'
424
+
425
+ set_exit_status # in case hook logged warnings
426
+
427
+ rescue Exception => err
428
+ fatal = !err.is_a?(StandardError)
429
+ ex = fatal ? FatalError : Error
430
+ Logger.error ex.wrap(err, 'After Hook Failed!')
431
+ # upgrade exit_status if needed
432
+ (@exit_status = fatal ? 3 : 2) unless exit_status == 3
433
+ end
434
+
435
+ ##
436
+ # Logs messages when the model starts and finishes.
437
+ #
438
+ # #exception will be set here if #exit_status is > 1,
439
+ # since log(:finished) is called before the +after+ hook.
440
+ def log!(action)
441
+ case action
442
+ when :started
443
+ Logger.info "Performing Backup for '#{ label } (#{ trigger })'!\n" +
444
+ "[ backup #{ VERSION } : #{ RUBY_DESCRIPTION } ]"
445
+
446
+ when :finished
447
+ if exit_status > 1
448
+ ex = exit_status == 2 ? Error : FatalError
449
+ err = ex.wrap(exception, "Backup for #{ label } (#{ trigger }) Failed!")
450
+ Logger.error err
451
+ Logger.error "\nBacktrace:\n\s\s" + err.backtrace.join("\n\s\s") + "\n\n"
452
+
453
+ Cleaner.warnings(self)
454
+ else
455
+ msg = "Backup for '#{ label } (#{ trigger })' "
456
+ if exit_status == 1
457
+ msg << "Completed Successfully (with Warnings) in #{ duration }"
458
+ Logger.warn msg
459
+ else
460
+ msg << "Completed Successfully in #{ duration }"
461
+ Logger.info msg
462
+ end
463
+ end
464
+ end
465
+ end
466
+
467
+ ##
468
+ # Returns a string representing the elapsed time in HH:MM:SS.
469
+ def elapsed_time(start_time, finish_time)
470
+ duration = finish_time.to_i - start_time.to_i
471
+ hours = duration / 3600
472
+ remainder = duration - (hours * 3600)
473
+ minutes = remainder / 60
474
+ seconds = remainder - (minutes * 60)
475
+ '%02d:%02d:%02d' % [hours, minutes, seconds]
476
+ end
477
+
478
+ end
479
+ end