ext_backup 5.0.0.beta.2.1

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