backup 3.0.27 → 3.1.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.
Files changed (166) hide show
  1. data/LICENSE.md +1 -1
  2. data/README.md +139 -386
  3. data/bin/backup +1 -7
  4. data/lib/backup.rb +3 -9
  5. data/lib/backup/archive.rb +26 -20
  6. data/lib/backup/cleaner.rb +2 -2
  7. data/lib/backup/cli.rb +366 -0
  8. data/lib/backup/compressor/base.rb +2 -2
  9. data/lib/backup/compressor/gzip.rb +35 -1
  10. data/lib/backup/config.rb +1 -2
  11. data/lib/backup/database/base.rb +2 -2
  12. data/lib/backup/database/mongodb.rb +3 -3
  13. data/lib/backup/database/mysql.rb +3 -2
  14. data/lib/backup/database/postgresql.rb +3 -2
  15. data/lib/backup/database/riak.rb +18 -5
  16. data/lib/backup/dependency.rb +144 -93
  17. data/lib/backup/encryptor/base.rb +2 -2
  18. data/lib/backup/logger.rb +108 -110
  19. data/lib/backup/logger/console.rb +51 -0
  20. data/lib/backup/logger/logfile.rb +113 -0
  21. data/lib/backup/logger/syslog.rb +116 -0
  22. data/lib/backup/model.rb +67 -65
  23. data/lib/backup/notifier/base.rb +1 -1
  24. data/lib/backup/notifier/hipchat.rb +1 -1
  25. data/lib/backup/notifier/mail.rb +1 -1
  26. data/lib/backup/notifier/pushover.rb +6 -3
  27. data/lib/backup/packager.rb +4 -4
  28. data/lib/backup/pipeline.rb +17 -3
  29. data/lib/backup/splitter.rb +2 -2
  30. data/lib/backup/storage/base.rb +2 -2
  31. data/lib/backup/storage/cloudfiles.rb +2 -2
  32. data/lib/backup/storage/dropbox.rb +4 -4
  33. data/lib/backup/storage/ftp.rb +2 -2
  34. data/lib/backup/storage/local.rb +2 -2
  35. data/lib/backup/storage/ninefold.rb +2 -2
  36. data/lib/backup/storage/rsync.rb +3 -3
  37. data/lib/backup/storage/s3.rb +2 -2
  38. data/lib/backup/storage/scp.rb +2 -6
  39. data/lib/backup/storage/sftp.rb +2 -5
  40. data/lib/backup/syncer/base.rb +1 -1
  41. data/lib/backup/syncer/cloud/base.rb +15 -8
  42. data/lib/backup/syncer/rsync/local.rb +1 -1
  43. data/lib/backup/syncer/rsync/pull.rb +1 -1
  44. data/lib/backup/syncer/rsync/push.rb +1 -1
  45. data/lib/backup/utilities.rb +211 -0
  46. data/lib/backup/version.rb +1 -1
  47. data/templates/cli/{utility/archive → archive} +4 -8
  48. data/templates/cli/{utility/compressor → compressor}/bzip2 +0 -0
  49. data/templates/cli/{utility/compressor → compressor}/custom +0 -0
  50. data/templates/cli/{utility/compressor → compressor}/gzip +0 -0
  51. data/templates/cli/{utility/compressor → compressor}/lzma +0 -0
  52. data/templates/cli/{utility/compressor → compressor}/pbzip2 +0 -0
  53. data/templates/cli/config +68 -0
  54. data/templates/cli/{utility/database → database}/mongodb +1 -1
  55. data/templates/cli/{utility/database → database}/mysql +1 -1
  56. data/templates/cli/{utility/database → database}/postgresql +1 -1
  57. data/templates/cli/{utility/database → database}/redis +0 -0
  58. data/templates/cli/database/riak +20 -0
  59. data/templates/cli/{utility/encryptor → encryptor}/gpg +0 -0
  60. data/templates/cli/{utility/encryptor → encryptor}/openssl +0 -0
  61. data/templates/cli/{utility/model.erb → model.erb} +4 -4
  62. data/templates/cli/{utility/notifier → notifier}/campfire +0 -0
  63. data/templates/cli/{utility/notifier → notifier}/hipchat +0 -0
  64. data/templates/cli/{utility/notifier → notifier}/mail +0 -0
  65. data/templates/cli/{utility/notifier → notifier}/prowl +0 -0
  66. data/templates/cli/{utility/notifier → notifier}/pushover +0 -0
  67. data/templates/cli/{utility/notifier → notifier}/twitter +0 -0
  68. data/templates/cli/{utility/splitter → splitter} +0 -0
  69. data/templates/cli/{utility/storage → storage}/cloud_files +0 -0
  70. data/templates/cli/{utility/storage → storage}/dropbox +0 -0
  71. data/templates/cli/{utility/storage → storage}/ftp +0 -0
  72. data/templates/cli/{utility/storage → storage}/local +0 -0
  73. data/templates/cli/{utility/storage → storage}/ninefold +0 -0
  74. data/templates/cli/{utility/storage → storage}/rsync +0 -0
  75. data/templates/cli/{utility/storage → storage}/s3 +0 -0
  76. data/templates/cli/{utility/storage → storage}/scp +0 -0
  77. data/templates/cli/{utility/storage → storage}/sftp +0 -0
  78. data/templates/cli/{utility/syncer → syncer}/cloud_files +0 -0
  79. data/templates/cli/{utility/syncer → syncer}/rsync_local +0 -0
  80. data/templates/cli/{utility/syncer → syncer}/rsync_pull +0 -0
  81. data/templates/cli/{utility/syncer → syncer}/rsync_push +0 -0
  82. data/templates/cli/{utility/syncer → syncer}/s3 +0 -0
  83. metadata +55 -131
  84. data/.gitignore +0 -8
  85. data/.travis.yml +0 -10
  86. data/Gemfile +0 -28
  87. data/Guardfile +0 -23
  88. data/backup.gemspec +0 -32
  89. data/lib/backup/cli/helpers.rb +0 -93
  90. data/lib/backup/cli/utility.rb +0 -255
  91. data/spec-live/.gitignore +0 -6
  92. data/spec-live/README +0 -7
  93. data/spec-live/backups/config.rb +0 -83
  94. data/spec-live/backups/config.yml.template +0 -46
  95. data/spec-live/backups/models.rb +0 -184
  96. data/spec-live/compressor/custom_spec.rb +0 -30
  97. data/spec-live/compressor/gzip_spec.rb +0 -30
  98. data/spec-live/encryptor/gpg_keys.rb +0 -239
  99. data/spec-live/encryptor/gpg_spec.rb +0 -287
  100. data/spec-live/notifier/mail_spec.rb +0 -121
  101. data/spec-live/spec_helper.rb +0 -151
  102. data/spec-live/storage/dropbox_spec.rb +0 -151
  103. data/spec-live/storage/local_spec.rb +0 -83
  104. data/spec-live/storage/scp_spec.rb +0 -193
  105. data/spec-live/syncer/cloud/s3_spec.rb +0 -124
  106. data/spec/archive_spec.rb +0 -335
  107. data/spec/cleaner_spec.rb +0 -312
  108. data/spec/cli/helpers_spec.rb +0 -301
  109. data/spec/cli/utility_spec.rb +0 -411
  110. data/spec/compressor/base_spec.rb +0 -52
  111. data/spec/compressor/bzip2_spec.rb +0 -217
  112. data/spec/compressor/custom_spec.rb +0 -106
  113. data/spec/compressor/gzip_spec.rb +0 -217
  114. data/spec/compressor/lzma_spec.rb +0 -123
  115. data/spec/compressor/pbzip2_spec.rb +0 -165
  116. data/spec/config_spec.rb +0 -321
  117. data/spec/configuration/helpers_spec.rb +0 -247
  118. data/spec/configuration/store_spec.rb +0 -39
  119. data/spec/configuration_spec.rb +0 -62
  120. data/spec/database/base_spec.rb +0 -63
  121. data/spec/database/mongodb_spec.rb +0 -510
  122. data/spec/database/mysql_spec.rb +0 -411
  123. data/spec/database/postgresql_spec.rb +0 -353
  124. data/spec/database/redis_spec.rb +0 -334
  125. data/spec/database/riak_spec.rb +0 -176
  126. data/spec/dependency_spec.rb +0 -51
  127. data/spec/encryptor/base_spec.rb +0 -40
  128. data/spec/encryptor/gpg_spec.rb +0 -909
  129. data/spec/encryptor/open_ssl_spec.rb +0 -148
  130. data/spec/errors_spec.rb +0 -306
  131. data/spec/logger_spec.rb +0 -367
  132. data/spec/model_spec.rb +0 -666
  133. data/spec/notifier/base_spec.rb +0 -104
  134. data/spec/notifier/campfire_spec.rb +0 -217
  135. data/spec/notifier/hipchat_spec.rb +0 -211
  136. data/spec/notifier/mail_spec.rb +0 -316
  137. data/spec/notifier/prowl_spec.rb +0 -138
  138. data/spec/notifier/pushover_spec.rb +0 -123
  139. data/spec/notifier/twitter_spec.rb +0 -153
  140. data/spec/package_spec.rb +0 -61
  141. data/spec/packager_spec.rb +0 -213
  142. data/spec/pipeline_spec.rb +0 -259
  143. data/spec/spec_helper.rb +0 -60
  144. data/spec/splitter_spec.rb +0 -120
  145. data/spec/storage/base_spec.rb +0 -166
  146. data/spec/storage/cloudfiles_spec.rb +0 -254
  147. data/spec/storage/cycler_spec.rb +0 -247
  148. data/spec/storage/dropbox_spec.rb +0 -480
  149. data/spec/storage/ftp_spec.rb +0 -271
  150. data/spec/storage/local_spec.rb +0 -259
  151. data/spec/storage/ninefold_spec.rb +0 -343
  152. data/spec/storage/rsync_spec.rb +0 -362
  153. data/spec/storage/s3_spec.rb +0 -245
  154. data/spec/storage/scp_spec.rb +0 -233
  155. data/spec/storage/sftp_spec.rb +0 -244
  156. data/spec/syncer/base_spec.rb +0 -109
  157. data/spec/syncer/cloud/base_spec.rb +0 -515
  158. data/spec/syncer/cloud/cloud_files_spec.rb +0 -181
  159. data/spec/syncer/cloud/s3_spec.rb +0 -174
  160. data/spec/syncer/rsync/base_spec.rb +0 -98
  161. data/spec/syncer/rsync/local_spec.rb +0 -149
  162. data/spec/syncer/rsync/pull_spec.rb +0 -98
  163. data/spec/syncer/rsync/push_spec.rb +0 -333
  164. data/spec/version_spec.rb +0 -21
  165. data/templates/cli/utility/config +0 -32
  166. data/templates/cli/utility/database/riak +0 -11
data/spec/logger_spec.rb DELETED
@@ -1,367 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require File.expand_path('../spec_helper.rb', __FILE__)
4
-
5
- describe Backup::Logger do
6
- let(:logger_time) { Time.now.strftime("%Y/%m/%d %H:%M:%S") }
7
- let(:logfile_path) { File.join(Backup::Config.log_path, 'backup.log') }
8
- let(:logfile_mock) { mock }
9
- let(:s) { sequence '' }
10
-
11
- before do
12
- Timecop.freeze(Time.now)
13
-
14
- # stubbed in spec_helper
15
- [:message, :error, :warn, :normal, :silent].each do |message_type|
16
- subject.unstub(message_type)
17
- end
18
-
19
- subject.quiet = nil
20
- subject.send(:remove_instance_variable, :@messages) rescue nil
21
- subject.send(:remove_instance_variable, :@has_warnings) rescue nil
22
- end
23
-
24
- describe '#message' do
25
- it 'sends a regular message to the console and log file' do
26
- subject.expects(:loggify).in_sequence(s).
27
- with('regular message', :message, :green).
28
- returns(:green_regular_message)
29
- subject.expects(:to_console).in_sequence(s).
30
- with(:green_regular_message)
31
- subject.expects(:loggify).in_sequence(s).
32
- with('regular message', :message).
33
- returns(:uncolored_regular_message)
34
- subject.expects(:to_file).in_sequence(s).
35
- with(:uncolored_regular_message)
36
-
37
- subject.message('regular message')
38
- end
39
- end
40
-
41
- describe '#error' do
42
- it 'sends an error message to the console (stderr) and log file' do
43
- subject.expects(:loggify).in_sequence(s).
44
- with('error message', :error, :red).
45
- returns(:red_error_message)
46
- subject.expects(:to_console).in_sequence(s).
47
- with(:red_error_message, true)
48
- subject.expects(:loggify).in_sequence(s).
49
- with('error message', :error).
50
- returns(:uncolored_error_message)
51
- subject.expects(:to_file).in_sequence(s).
52
- with(:uncolored_error_message)
53
-
54
- subject.error('error message')
55
- end
56
- end
57
-
58
- describe '#warn' do
59
- it 'sends a warning message to the console (stderr) and log file' do
60
- subject.expects(:loggify).in_sequence(s).
61
- with('warning message', :warning, :yellow).
62
- returns(:yellow_warning_message)
63
- subject.expects(:to_console).in_sequence(s).
64
- with(:yellow_warning_message, true)
65
- subject.expects(:loggify).in_sequence(s).
66
- with('warning message', :warning).
67
- returns(:uncolored_warning_message)
68
- subject.expects(:to_file).in_sequence(s).
69
- with(:uncolored_warning_message)
70
-
71
- subject.warn('warning message')
72
- end
73
-
74
- it 'sets has_warnings? to true' do
75
- subject.stubs(:to_console)
76
- subject.stubs(:to_file)
77
- expect { subject.warn('warning') }.
78
- to change{ subject.has_warnings? }.from(false).to(true)
79
- end
80
- end
81
-
82
- describe '#normal' do
83
- it 'sends a normal, unformatted message to the console and log file' do
84
- subject.expects(:loggify).in_sequence(s).
85
- with('normal message').
86
- returns(:unformatted_message)
87
- subject.expects(:to_console).in_sequence(s).
88
- with(:unformatted_message)
89
- subject.expects(:loggify).in_sequence(s).
90
- with('normal message').
91
- returns(:unformatted_message)
92
- subject.expects(:to_file).in_sequence(s).
93
- with(:unformatted_message)
94
-
95
- subject.normal('normal message')
96
- end
97
- end
98
-
99
- describe '#silent' do
100
- it 'sends a silent message to the log file' do
101
- subject.expects(:to_console).never
102
- subject.expects(:loggify).in_sequence(s).
103
- with('silent message', :silent).
104
- returns(:silent_message)
105
- subject.expects(:to_file).in_sequence(s).
106
- with(:silent_message)
107
-
108
- subject.silent('silent message')
109
- end
110
- end
111
-
112
- describe '#messages' do
113
-
114
- it 'returns an empty array if no messages have been sent' do
115
- subject.messages.should == []
116
- end
117
-
118
- it 'returns an array of all lines sent to the log file' do
119
- File.stubs(:open).yields(stub(:puts))
120
- strings = ['an array', 'of message', 'strings']
121
- subject.send(:to_file, strings)
122
- subject.messages.should == strings
123
- end
124
-
125
- it 'does not track lines sent to the console' do
126
- subject.stubs(:puts)
127
- strings = ['an array', 'of message', 'strings']
128
- subject.send(:to_console, strings)
129
- subject.messages.should == []
130
- end
131
-
132
- end # describe '#messages'
133
-
134
- describe '#clear!' do
135
- it 'should clear the log and reset has_warnings?' do
136
- subject.messages << 'foo'
137
- subject.instance_variable_set(:@has_warnings, true)
138
- subject.messages.count.should == 1
139
- subject.has_warnings?.should be_true
140
-
141
- subject.clear!
142
- subject.messages.should be_empty
143
- subject.has_warnings?.should be_false
144
- end
145
- end # describe '#clear!'
146
-
147
- describe '#truncate!' do
148
- context 'when log file does not exist' do
149
- before { File.stubs(:exist?).returns(false) }
150
- it 'should do nothing' do
151
- File.expects(:stat).never
152
- subject.truncate!
153
- end
154
- end
155
-
156
- context 'when log file is <= max_bytes' do
157
- before { File.stubs(:exist?).returns(true) }
158
- it 'should do nothing' do
159
- stat = mock
160
- File.expects(:stat).twice.with(
161
- File.join(Backup::Config.log_path, 'backup.log')
162
- ).returns(stat)
163
-
164
- [1, 2].each do |size|
165
- stat.expects(:size).returns(size)
166
-
167
- FileUtils.expects(:mv).never
168
- File.expects(:open).never
169
- FileUtils.expects(:rm_f).never
170
-
171
- subject.truncate!(2)
172
- end
173
- end
174
- end
175
-
176
- context 'when log file is > max_bytes' do
177
- before do
178
- FileUtils.unstub(:mkdir_p)
179
- FileUtils.unstub(:mv)
180
- FileUtils.unstub(:rm)
181
- FileUtils.unstub(:rm_f)
182
- end
183
-
184
- after do
185
- Backup::Config.send(:reset!)
186
- end
187
-
188
- it 'should truncate the file, removing older lines' do
189
- max_bytes = 5_000
190
- line_len = 120
191
- Dir.mktmpdir do |dir|
192
- Backup::Config.update(:root_path => dir)
193
- FileUtils.mkdir_p(Backup::Config.log_path)
194
- log_file = File.join(Backup::Config.log_path, 'backup.log')
195
-
196
- lineno = 0
197
- over_max = (max_bytes * 1.25).to_i
198
- File.open(log_file, 'w') do |f|
199
- until f.pos > over_max
200
- f.puts "#{ lineno += 1 }: ".ljust(line_len, 'x')
201
- end
202
- end
203
- File.stat(log_file).size.
204
- should be_within(line_len + 2).of(over_max)
205
-
206
- subject.truncate!(max_bytes)
207
-
208
- File.stat(log_file).size.
209
- should be_within(line_len + 2).of(max_bytes)
210
-
211
- File.readlines(log_file).last.chomp.
212
- should == "#{ lineno }: ".ljust(line_len, 'x')
213
-
214
- File.exist?(log_file + '~').should be_false
215
- end
216
- end
217
- end
218
-
219
- end # describe '#truncate!'
220
-
221
- describe '#loggify' do
222
-
223
- it 'returns an array of strings split on newline separators' do
224
- str_aa = "first line\nsecond line"
225
- str_ab = "first line\nsecond line\n"
226
- expected_a = ["[#{logger_time}][msg_type] first line",
227
- "[#{logger_time}][msg_type] second line"]
228
-
229
- str_b = 'string with no newline'
230
- expected_b = ["[#{logger_time}][msg_type] string with no newline"]
231
-
232
- subject.send(:loggify, str_aa, :msg_type).should == expected_a
233
- subject.send(:loggify, str_ab, :msg_type).should == expected_a
234
- subject.send(:loggify, str_b, :msg_type).should == expected_b
235
- end
236
-
237
- it 'formats a string with color if color is given' do
238
- green_type = ["[#{logger_time}][#{"\e[32mmsg_type\e[0m"}] foo"]
239
- yellow_type = ["[#{logger_time}][#{"\e[33mmsg_type\e[0m"}] foo"]
240
- red_type = ["[#{logger_time}][#{"\e[31mmsg_type\e[0m"}] foo"]
241
-
242
- subject.send(:loggify, 'foo', :msg_type, :green ).should == green_type
243
- subject.send(:loggify, 'foo', :msg_type, :yellow).should == yellow_type
244
- subject.send(:loggify, 'foo', :msg_type, :red ).should == red_type
245
- end
246
-
247
- it 'does not colorize if no color given' do
248
- no_color = ["[#{logger_time}][msg_type] foo"]
249
- subject.send(:loggify, 'foo', :msg_type).should == no_color
250
- end
251
-
252
- it 'accepts blank lines in the message' do
253
- str = "first line\n\nthird line"
254
- expected = ["[#{logger_time}][msg_type] first line",
255
- "[#{logger_time}][msg_type] ",
256
- "[#{logger_time}][msg_type] third line"]
257
-
258
- subject.send(:loggify, str, :msg_type).should == expected
259
- end
260
-
261
- it 'accepts an object responding to #to_s for the message' do
262
- obj = StandardError.new("first line\nsecond line")
263
- expected = ["[#{logger_time}][msg_type] first line",
264
- "[#{logger_time}][msg_type] second line"]
265
-
266
- subject.send(:loggify, obj, :msg_type).should == expected
267
- end
268
-
269
- it 'returns an unformatted lines if type is not given' do
270
- str_a = 'single line'
271
- str_b = "first line\n\nthird line"
272
- expected_a = ['single line']
273
- expected_b = ['first line', '', 'third line']
274
-
275
- subject.send(:loggify, str_a).should == expected_a
276
- subject.send(:loggify, str_b).should == expected_b
277
- end
278
-
279
- end # describe '#loggify'
280
-
281
- describe '#to_console' do
282
-
283
- context 'when +stderr+ is not set (false)' do
284
- it 'writes an array of strings to stdout' do
285
- lines = [ 'line one', 'line two', 'line three']
286
- lines.each {|line| subject.expects(:puts).with(line).in_sequence(s) }
287
- subject.send(:to_console, lines)
288
- end
289
- end
290
-
291
- context 'when +stderr+ is set (true)' do
292
- it 'writes an array of strings to stdout' do
293
- lines = [ 'line one', 'line two', 'line three']
294
- lines.each {|line| Kernel.expects(:warn).with(line).in_sequence(s) }
295
- subject.send(:to_console, lines, true)
296
- end
297
- end
298
-
299
- it 'returns nil if quiet? is true' do
300
- subject.quiet = true
301
- subject.expects(:puts).never
302
- subject.send(:to_console, 'a string')
303
- end
304
-
305
- end # describe '#to_console'
306
-
307
- describe '#to_file' do
308
-
309
- it 'writes an array of strings to the log file' do
310
- lines = ['line one', 'line two', 'line three']
311
- File.stubs(:open).yields(logfile_mock)
312
- lines.each {|line| logfile_mock.expects(:puts).with(line).in_sequence(s) }
313
- subject.send(:to_file, lines)
314
- end
315
-
316
- it 'appends each line written to #messages' do
317
- lines = ['line one', 'line two', 'line three']
318
- File.stubs(:open)
319
- a_mock = mock
320
- subject.expects(:messages).returns(a_mock)
321
- a_mock.expects(:push).with('line one', 'line two', 'line three')
322
- subject.send(:to_file, lines)
323
- end
324
-
325
- it 'only opens the log file once to append multiple lines' do
326
- lines = ['line one', 'line two', 'line three']
327
- File.expects(:open).once.with(logfile_path, 'a').yields(logfile_mock)
328
- logfile_mock.expects(:puts).times(3)
329
- subject.send(:to_file, lines)
330
- end
331
-
332
- end # describe '#to_file'
333
-
334
- describe 'color methods' do
335
-
336
- it 'color methods send strings to #colorize with color codes' do
337
- colors = [ [:green, 32], [:yellow, 33], [:red, 31] ]
338
- colors.each do |color, code|
339
- subject.expects(:colorize).with('foo', code).in_sequence(s)
340
- end
341
- colors.each {|color, code| subject.send(color, 'foo') }
342
- end
343
-
344
- it '#colorize adds the code to the string' do
345
- [32, 33, 31].each do |code|
346
- subject.send(:colorize, 'foo', code).
347
- should == "\e[#{code}mfoo\e[0m"
348
- end
349
- end
350
-
351
- end # color methods
352
-
353
- describe '#quiet' do
354
- it 'should be nil by default' do
355
- # of course, 'before' is setting it to nil ;)
356
- subject.quiet.should be_nil
357
- end
358
-
359
- it 'can be set true/false' do
360
- subject.quiet = true
361
- subject.quiet.should be_true
362
- subject.quiet = false
363
- subject.quiet.should be_false
364
- end
365
- end
366
-
367
- end
data/spec/model_spec.rb DELETED
@@ -1,666 +0,0 @@
1
- # encoding: utf-8
2
-
3
- require File.expand_path('../spec_helper.rb', __FILE__)
4
-
5
- describe 'Backup::Model' do
6
- let(:model) { Backup::Model.new(:test_trigger, 'test label') }
7
- let(:s) { sequence '' }
8
-
9
- before do
10
- Backup::Model.all.clear
11
- end
12
-
13
- describe '.all' do
14
- it 'should be an empty array by default' do
15
- Backup::Model.all.should == []
16
- end
17
- end
18
-
19
- describe 'finder methods' do
20
- before do
21
- [:a, :b, :c, :b, :d].each_with_index do |sym, i|
22
- Backup::Model.new("trigger_#{sym}", "label#{i}")
23
- end
24
- end
25
-
26
- describe '.find' do
27
- it 'should return the first matching model' do
28
- Backup::Model.find('trigger_b').label.should == 'label1'
29
- end
30
-
31
- it 'should accept symbols' do
32
- Backup::Model.find(:trigger_b).label.should == 'label1'
33
- end
34
-
35
- it 'should raise an error if trigger is not found' do
36
- expect do
37
- Backup::Model.find(:f)
38
- end.to raise_error(
39
- Backup::Errors::Model::MissingTriggerError,
40
- "Model::MissingTriggerError: Could not find trigger 'f'."
41
- )
42
- end
43
- end
44
-
45
- describe '.find_matching' do
46
- it 'should find all triggers matching a wildcard' do
47
- Backup::Model.find_matching('tri*_b').count.should be(2)
48
- Backup::Model.find_matching('trigg*').count.should be(5)
49
- end
50
-
51
- it 'should return an empty array if no matches are found' do
52
- Backup::Model.find_matching('foo*').should == []
53
- end
54
- end
55
-
56
- end # describe 'finder methods'
57
-
58
- describe '#initialize' do
59
-
60
- it 'should convert trigger to a string' do
61
- Backup::Model.new(:foo, :bar).trigger.should == 'foo'
62
- end
63
-
64
- it 'should convert label to a string' do
65
- Backup::Model.new(:foo, :bar).label.should == 'bar'
66
- end
67
-
68
- it 'should set all procedure variables to an empty array' do
69
- model.send(:procedure_instance_variables).each do |var|
70
- model.instance_variable_get(var).should == []
71
- end
72
- end
73
-
74
- it 'should accept and instance_eval a block' do
75
- block = lambda {|model| throw(:instance, model) }
76
- caught = catch(:instance) do
77
- Backup::Model.new('gotcha', '', &block)
78
- end
79
- caught.trigger.should == 'gotcha'
80
- end
81
-
82
- it 'should add itself to Model.all' do
83
- Backup::Model.all.should == [model]
84
- end
85
-
86
- end # describe '#initialize'
87
-
88
- describe 'DSL Methods' do
89
-
90
- module Fake
91
- module NoArg
92
- class Base
93
- attr_accessor :block_arg
94
- def initialize(&block)
95
- instance_eval(&block) if block_given?
96
- end
97
- end
98
- end
99
- module OneArg
100
- class Base
101
- attr_accessor :arg1, :block_arg
102
- def initialize(arg1, &block)
103
- @arg1 = arg1
104
- instance_eval(&block) if block_given?
105
- end
106
- end
107
- end
108
- module TwoArgs
109
- class Base
110
- attr_accessor :arg1, :arg2, :block_arg
111
- def initialize(arg1, arg2, &block)
112
- @arg1 = arg1
113
- @arg2 = arg2
114
- instance_eval(&block) if block_given?
115
- end
116
- end
117
- end
118
- end
119
-
120
- # Set +const+ to +replacement+ for the calling block
121
- def using_fake(const, replacement)
122
- orig = Backup.const_get(const)
123
- Backup.send(:remove_const, const)
124
- Backup.const_set(const, replacement)
125
- yield
126
- Backup.send(:remove_const, const)
127
- Backup.const_set(const, orig)
128
- end
129
-
130
- describe '#archive' do
131
- it 'should add archives' do
132
- using_fake('Archive', Fake::TwoArgs::Base) do
133
- model.archive('foo') {|a| a.block_arg = :foo }
134
- model.archive('bar') {|a| a.block_arg = :bar }
135
- model.archives.count.should == 2
136
- a1, a2 = model.archives
137
- a1.arg1.should be(model)
138
- a1.arg2.should == 'foo'
139
- a1.block_arg.should == :foo
140
- a2.arg1.should be(model)
141
- a2.arg2.should == 'bar'
142
- a2.block_arg.should == :bar
143
- end
144
- end
145
- end
146
-
147
- describe '#database' do
148
- it 'should add databases' do
149
- using_fake('Database', Fake::OneArg) do
150
- model.database('Base') {|a| a.block_arg = :foo }
151
- model.database('Base') {|a| a.block_arg = :bar }
152
- model.databases.count.should be(2)
153
- d1, d2 = model.databases
154
- d1.arg1.should be(model)
155
- d1.block_arg.should == :foo
156
- d2.arg1.should be(model)
157
- d2.block_arg.should == :bar
158
- end
159
- end
160
-
161
- it 'should accept a nested class name' do
162
- using_fake('Database', Fake) do
163
- model.database('OneArg::Base')
164
- model.databases.first.should be_an_instance_of Fake::OneArg::Base
165
- end
166
- end
167
- end
168
-
169
- describe '#store_with' do
170
- it 'should add storages' do
171
- using_fake('Storage', Fake::TwoArgs) do
172
- model.store_with('Base', 'foo') {|a| a.block_arg = :foo }
173
- model.store_with('Base', 'bar') {|a| a.block_arg = :bar }
174
- model.storages.count.should be(2)
175
- s1, s2 = model.storages
176
- s1.arg1.should be(model)
177
- s1.arg2.should == 'foo'
178
- s1.block_arg.should == :foo
179
- s2.arg1.should be(model)
180
- s2.arg2.should == 'bar'
181
- s2.block_arg.should == :bar
182
- end
183
- end
184
-
185
- it 'should accept a nested class name' do
186
- using_fake('Storage', Fake) do
187
- model.store_with('TwoArgs::Base')
188
- model.storages.first.should be_an_instance_of Fake::TwoArgs::Base
189
- end
190
- end
191
- end
192
-
193
- describe '#sync_with' do
194
- it 'should add syncers' do
195
- using_fake('Syncer', Fake::NoArg) do
196
- model.sync_with('Base') {|a| a.block_arg = :foo }
197
- model.sync_with('Base') {|a| a.block_arg = :bar }
198
- model.syncers.count.should be(2)
199
- s1, s2 = model.syncers
200
- s1.block_arg.should == :foo
201
- s2.block_arg.should == :bar
202
- end
203
- end
204
-
205
- it 'should accept a nested class name' do
206
- using_fake('Syncer', Fake) do
207
- model.sync_with('NoArg::Base')
208
- model.syncers.first.should be_an_instance_of Fake::NoArg::Base
209
- end
210
- end
211
-
212
- it 'should warn user of change from RSync to RSync::Push' do
213
- Backup::Logger.expects(:warn)
214
- model.sync_with('Backup::Config::RSync')
215
- model.syncers.first.should
216
- be_an_instance_of Backup::Syncer::RSync::Push
217
- end
218
-
219
- it 'should warn user of change from S3 to Cloud::S3' do
220
- Backup::Logger.expects(:warn)
221
- model.sync_with('Backup::Config::S3')
222
- model.syncers.first.should
223
- be_an_instance_of Backup::Syncer::Cloud::S3
224
- end
225
-
226
- it 'should warn user of change from CloudFiles to Cloud::CloudFiles' do
227
- Backup::Logger.expects(:warn)
228
- model.sync_with('Backup::Config::CloudFiles')
229
- model.syncers.first.should
230
- be_an_instance_of Backup::Syncer::Cloud::CloudFiles
231
- end
232
- end
233
-
234
- describe '#notify_by' do
235
- it 'should add notifiers' do
236
- using_fake('Notifier', Fake::OneArg) do
237
- model.notify_by('Base') {|a| a.block_arg = :foo }
238
- model.notify_by('Base') {|a| a.block_arg = :bar }
239
- model.notifiers.count.should be(2)
240
- n1, n2 = model.notifiers
241
- n1.arg1.should be(model)
242
- n1.block_arg.should == :foo
243
- n2.arg1.should be(model)
244
- n2.block_arg.should == :bar
245
- end
246
- end
247
-
248
- it 'should accept a nested class name' do
249
- using_fake('Notifier', Fake) do
250
- model.notify_by('OneArg::Base')
251
- model.notifiers.first.should be_an_instance_of Fake::OneArg::Base
252
- end
253
- end
254
- end
255
-
256
- describe '#encrypt_with' do
257
- it 'should add an encryptor' do
258
- using_fake('Encryptor', Fake::NoArg) do
259
- model.encrypt_with('Base') {|a| a.block_arg = :foo }
260
- model.encryptor.should be_an_instance_of Fake::NoArg::Base
261
- model.encryptor.block_arg.should == :foo
262
- end
263
- end
264
-
265
- it 'should accept a nested class name' do
266
- using_fake('Encryptor', Fake) do
267
- model.encrypt_with('NoArg::Base')
268
- model.encryptor.should be_an_instance_of Fake::NoArg::Base
269
- end
270
- end
271
- end
272
-
273
- describe '#compress_with' do
274
- it 'should add a compressor' do
275
- using_fake('Compressor', Fake::NoArg) do
276
- model.compress_with('Base') {|a| a.block_arg = :foo }
277
- model.compressor.should be_an_instance_of Fake::NoArg::Base
278
- model.compressor.block_arg.should == :foo
279
- end
280
- end
281
-
282
- it 'should accept a nested class name' do
283
- using_fake('Compressor', Fake) do
284
- model.compress_with('NoArg::Base')
285
- model.compressor.should be_an_instance_of Fake::NoArg::Base
286
- end
287
- end
288
- end
289
-
290
- describe '#split_into_chunks_of' do
291
- it 'should add a splitter' do
292
- using_fake('Splitter', Fake::TwoArgs::Base) do
293
- model.split_into_chunks_of(123)
294
- model.splitter.should be_an_instance_of Fake::TwoArgs::Base
295
- model.splitter.arg1.should be(model)
296
- model.splitter.arg2.should == 123
297
- end
298
- end
299
-
300
- it 'should raise an error if chunk_size is not an Integer' do
301
- expect do
302
- model.split_into_chunks_of('345')
303
- end.to raise_error {|err|
304
- err.should be_an_instance_of Backup::Errors::Model::ConfigurationError
305
- err.message.should match(/must be an Integer/)
306
- }
307
- end
308
- end
309
-
310
- end # describe 'DSL Methods'
311
-
312
- describe '#prepare!' do
313
- it 'should prepare for the backup' do
314
- FileUtils.expects(:mkdir_p).with(
315
- File.join(Backup::Config.data_path, 'test_trigger')
316
- )
317
- Backup::Cleaner.expects(:prepare).with(model)
318
-
319
- model.prepare!
320
- end
321
- end
322
-
323
- describe '#perform!' do
324
- let(:procedure_a) { lambda {} }
325
- let(:procedure_b) { mock }
326
- let(:procedure_c) { mock }
327
- let(:procedure_d) { lambda {} }
328
- let(:procedure_e) { lambda {} }
329
- let(:procedure_f) { mock }
330
- let(:procedures) do
331
- [ procedure_a, [procedure_b, procedure_c],
332
- procedure_d, procedure_e, [procedure_f] ]
333
- end
334
- let(:syncer_a) { mock }
335
- let(:syncer_b) { mock }
336
- let(:syncers) { [syncer_a, syncer_b] }
337
- let(:notifier_a) { mock }
338
- let(:notifier_b) { mock }
339
- let(:notifiers) { [notifier_a, notifier_b] }
340
-
341
- it 'should set the @time and @started_at variables' do
342
- Timecop.freeze(Time.now)
343
- started_at = Time.now
344
- time = started_at.strftime("%Y.%m.%d.%H.%M.%S")
345
- model.expects(:log!).with(:started)
346
- model.expects(:log!).with(:finished)
347
-
348
- model.perform!
349
- model.time.should == time
350
- model.instance_variable_get(:@started_at).should == started_at
351
- end
352
-
353
- context 'when no errors occur' do
354
- before do
355
- model.expects(:procedures).returns(procedures)
356
- model.expects(:syncers).returns(syncers)
357
- model.expects(:notifiers).returns(notifiers)
358
- end
359
-
360
- context 'when databases are configured' do
361
- before do
362
- model.instance_variable_set(:@databases, [true])
363
- end
364
-
365
- it 'should perform all procedures' do
366
- model.expects(:log!).in_sequence(s).with(:started)
367
-
368
- procedure_a.expects(:call).in_sequence(s)
369
- procedure_b.expects(:perform!).in_sequence(s)
370
- procedure_c.expects(:perform!).in_sequence(s)
371
- procedure_d.expects(:call).in_sequence(s)
372
- procedure_e.expects(:call).in_sequence(s)
373
- procedure_f.expects(:perform!).in_sequence(s)
374
-
375
- syncer_a.expects(:perform!).in_sequence(s)
376
- syncer_b.expects(:perform!).in_sequence(s)
377
-
378
- notifier_a.expects(:perform!).in_sequence(s)
379
- notifier_b.expects(:perform!).in_sequence(s)
380
-
381
- model.expects(:log!).in_sequence(s).with(:finished)
382
-
383
- model.perform!
384
- end
385
- end
386
-
387
- context 'when archives are configured' do
388
- before do
389
- model.instance_variable_set(:@archives, [true])
390
- end
391
-
392
- it 'should perform all procedures' do
393
- model.expects(:log!).in_sequence(s).with(:started)
394
-
395
- procedure_a.expects(:call).in_sequence(s)
396
- procedure_b.expects(:perform!).in_sequence(s)
397
- procedure_c.expects(:perform!).in_sequence(s)
398
- procedure_d.expects(:call).in_sequence(s)
399
- procedure_e.expects(:call).in_sequence(s)
400
- procedure_f.expects(:perform!).in_sequence(s)
401
-
402
- syncer_a.expects(:perform!).in_sequence(s)
403
- syncer_b.expects(:perform!).in_sequence(s)
404
-
405
- notifier_a.expects(:perform!).in_sequence(s)
406
- notifier_b.expects(:perform!).in_sequence(s)
407
-
408
- model.expects(:log!).in_sequence(s).with(:finished)
409
-
410
- model.perform!
411
- end
412
- end
413
-
414
- end # context 'when no errors occur'
415
-
416
- # for the purposes of testing the error handling, we're just going to
417
- # stub the first thing this method calls and raise an error
418
- context 'when errors occur' do
419
- let(:error_a) { mock }
420
- let(:error_b) { mock }
421
- let(:notifier) { mock }
422
-
423
- before do
424
- error_a.stubs(:backtrace).returns(['many', 'backtrace', 'lines'])
425
- end
426
-
427
- it 'logs, notifies and continues if a StandardError is rescued' do
428
- Time.stubs(:now).raises(StandardError, 'non-fatal error')
429
-
430
- Backup::Errors::ModelError.expects(:wrap).in_sequence(s).with do |err, msg|
431
- err.message.should == 'non-fatal error'
432
- msg.should match(/Backup for test label \(test_trigger\) Failed!/)
433
- end.returns(error_a)
434
- Backup::Logger.expects(:error).in_sequence(s).with(error_a)
435
- Backup::Logger.expects(:error).in_sequence(s).with(
436
- "\nBacktrace:\n\s\smany\n\s\sbacktrace\n\s\slines\n\n"
437
- )
438
-
439
- Backup::Cleaner.expects(:warnings).in_sequence(s).with(model)
440
-
441
- Backup::Errors::ModelError.expects(:new).in_sequence(s).with do |msg|
442
- msg.should match(/Backup will now attempt to continue/)
443
- end.returns(error_b)
444
- Backup::Logger.expects(:message).in_sequence(s).with(error_b)
445
-
446
- # notifiers called, but any Exception is ignored
447
- notifier.expects(:perform!).with(true).raises(Exception)
448
- model.expects(:notifiers).returns([notifier])
449
-
450
- # returns to allow next trigger to run
451
- expect { model.perform! }.not_to raise_error
452
- end
453
-
454
- it 'logs, notifies and exits if an Exception is rescued' do
455
- Time.stubs(:now).raises(Exception, 'fatal error')
456
-
457
- Backup::Errors::ModelError.expects(:wrap).in_sequence(s).with do |err, msg|
458
- err.message.should == 'fatal error'
459
- msg.should match(/Backup for test label \(test_trigger\) Failed!/)
460
- end.returns(error_a)
461
- Backup::Logger.expects(:error).in_sequence(s).with(error_a)
462
- Backup::Logger.expects(:error).in_sequence(s).with(
463
- "\nBacktrace:\n\s\smany\n\s\sbacktrace\n\s\slines\n\n"
464
- )
465
-
466
- Backup::Cleaner.expects(:warnings).in_sequence(s).with(model)
467
-
468
- Backup::Errors::ModelError.expects(:new).in_sequence(s).with do |msg|
469
- msg.should match(/Backup will now exit/)
470
- end.returns(error_b)
471
- Backup::Logger.expects(:error).in_sequence(s).with(error_b)
472
-
473
- expect do
474
- # notifiers called, but any Exception is ignored
475
- notifier = mock
476
- notifier.expects(:perform!).with(true).raises(Exception)
477
- model.expects(:notifiers).returns([notifier])
478
- end.not_to raise_error
479
-
480
- expect do
481
- model.perform!
482
- end.to raise_error(SystemExit) {|exit| exit.status.should be(1) }
483
- end
484
-
485
- end # context 'when errors occur'
486
-
487
- end # describe '#perform!'
488
-
489
- describe '#package!' do
490
- it 'should package the backup' do
491
- Backup::Packager.expects(:package!).in_sequence(s).with(model)
492
- Backup::Cleaner.expects(:remove_packaging).in_sequence(s).with(model)
493
-
494
- model.send(:package!)
495
- model.package.should be_an_instance_of Backup::Package
496
- end
497
- end
498
-
499
- describe '#clean' do
500
- it 'should remove the final packaged files' do
501
- package = mock
502
- model.instance_variable_set(:@package, package)
503
- Backup::Cleaner.expects(:remove_package).with(package)
504
-
505
- model.send(:clean!)
506
- end
507
- end
508
-
509
- describe '#procedures' do
510
- it 'should return an array of specific, ordered procedures' do
511
- model.stubs(:databases).returns(:databases)
512
- model.stubs(:archives).returns(:archives)
513
- model.stubs(:package!).returns(:package)
514
- model.stubs(:storages).returns(:storages)
515
- model.stubs(:clean!).returns(:clean)
516
-
517
- one, two, three, four, five = model.send(:procedures)
518
- one.should == :databases
519
- two.should == :archives
520
- three.call.should == :package
521
- four.should == :storages
522
- five.call.should == :clean
523
- end
524
- end
525
-
526
- describe '#procedure_instance_variables' do
527
- # these are all set to an empty Array in #initialize
528
- it 'should return an array of Array holding instance variables' do
529
- model.send(:procedure_instance_variables).should ==
530
- [:@databases, :@archives, :@storages, :@notifiers, :@syncers]
531
- end
532
- end
533
-
534
- describe '#get_class_from_scope' do
535
-
536
- module Fake
537
- module TestScope
538
- class TestKlass; end
539
- end
540
- end
541
- module TestScope
542
- module TestKlass; end
543
- end
544
-
545
- context 'when name is given as a string' do
546
- it 'should return the constant for the given scope and name' do
547
- model.send(
548
- :get_class_from_scope,
549
- Fake,
550
- 'TestScope'
551
- ).should == Fake::TestScope
552
- end
553
-
554
- it 'should accept a nested class name' do
555
- model.send(
556
- :get_class_from_scope,
557
- Fake,
558
- 'TestScope::TestKlass'
559
- ).should == Fake::TestScope::TestKlass
560
- end
561
- end
562
-
563
- context 'when name is given as a module' do
564
- it 'should return the constant for the given scope and name' do
565
- model.send(
566
- :get_class_from_scope,
567
- Fake,
568
- TestScope
569
- ).should == Fake::TestScope
570
- end
571
-
572
- it 'should accept a nested class name' do
573
- model.send(
574
- :get_class_from_scope,
575
- Fake,
576
- TestScope::TestKlass
577
- ).should == Fake::TestScope::TestKlass
578
- end
579
- end
580
-
581
- context 'when name is given as a module defined under Backup::Config' do
582
- # this is necessary since the specs in spec/config_spec.rb
583
- # remove all the constants from Backup::Config as part of those tests.
584
- before(:all) do
585
- module Backup::Config
586
- module TestScope
587
- module TestKlass; end
588
- end
589
- end
590
- end
591
-
592
- it 'should return the constant for the given scope and name' do
593
- model.send(
594
- :get_class_from_scope,
595
- Fake,
596
- Backup::Config::TestScope
597
- ).should == Fake::TestScope
598
- end
599
-
600
- it 'should accept a nested class name' do
601
- model.send(
602
- :get_class_from_scope,
603
- Fake,
604
- Backup::Config::TestScope::TestKlass
605
- ).should == Fake::TestScope::TestKlass
606
- end
607
- end
608
-
609
- end # describe '#get_class_from_scope'
610
-
611
- describe '#log!' do
612
- context 'when action is :started' do
613
- it 'should log that the backup has started with the version' do
614
- Backup::Logger.expects(:message).with(
615
- "Performing Backup for 'test label (test_trigger)'!\n" +
616
- "[ backup #{ Backup::Version.current } : #{ RUBY_DESCRIPTION } ]"
617
- )
618
- model.send(:log!, :started)
619
- end
620
- end
621
-
622
- context 'when action is :finished' do
623
- before { model.expects(:elapsed_time).returns('01:02:03') }
624
- context 'when warnings were issued' do
625
- before { Backup::Logger.expects(:has_warnings?).returns(true) }
626
- it 'should log a warning that the backup has finished with warnings' do
627
- Backup::Logger.expects(:warn).with(
628
- "Backup for 'test label (test_trigger)' " +
629
- "Completed Successfully (with Warnings) in 01:02:03"
630
- )
631
- model.send(:log!, :finished)
632
- end
633
- end
634
-
635
- context 'when no warnings were issued' do
636
- it 'should log that the backup has finished with the elapsed time' do
637
- Backup::Logger.expects(:message).with(
638
- "Backup for 'test label (test_trigger)' " +
639
- "Completed Successfully in 01:02:03"
640
- )
641
- model.send(:log!, :finished)
642
- end
643
- end
644
- end
645
- end # describe '#log!'
646
-
647
- describe '#elapsed_time' do
648
- it 'should return a string representing the elapsed time' do
649
- Timecop.freeze(Time.now)
650
- { 0 => '00:00:00', 1 => '00:00:01', 59 => '00:00:59',
651
- 60 => '00:01:00', 61 => '00:01:01', 119 => '00:01:59',
652
- 3540 => '00:59:00', 3541 => '00:59:01', 3599 => '00:59:59',
653
- 3600 => '01:00:00', 3601 => '01:00:01', 3659 => '01:00:59',
654
- 3660 => '01:01:00', 3661 => '01:01:01', 3719 => '01:01:59',
655
- 7140 => '01:59:00', 7141 => '01:59:01', 7199 => '01:59:59',
656
- 212400 => '59:00:00', 212401 => '59:00:01', 212459 => '59:00:59',
657
- 212460 => '59:01:00', 212461 => '59:01:01', 212519 => '59:01:59',
658
- 215940 => '59:59:00', 215941 => '59:59:01', 215999 => '59:59:59'
659
- }.each do |duration, expected|
660
- model.instance_variable_set(:@started_at, Time.now - duration)
661
- model.send(:elapsed_time).should == expected
662
- end
663
- end
664
- end
665
-
666
- end