backup 3.0.16 → 3.0.18

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/.travis.yml +10 -0
  2. data/Gemfile.lock +50 -47
  3. data/Guardfile +3 -3
  4. data/README.md +136 -81
  5. data/backup.gemspec +3 -2
  6. data/bin/backup +36 -15
  7. data/lib/backup.rb +30 -20
  8. data/lib/backup/cli.rb +30 -2
  9. data/lib/backup/compressor/lzma.rb +63 -0
  10. data/lib/backup/configuration/compressor/lzma.rb +23 -0
  11. data/lib/backup/configuration/helpers.rb +10 -4
  12. data/lib/backup/configuration/notifier/mail.rb +5 -0
  13. data/lib/backup/configuration/storage/dropbox.rb +19 -4
  14. data/lib/backup/configuration/storage/ftp.rb +4 -0
  15. data/lib/backup/configuration/storage/local.rb +17 -0
  16. data/lib/backup/configuration/storage/ninefold.rb +20 -0
  17. data/lib/backup/configuration/storage/rsync.rb +4 -0
  18. data/lib/backup/database/postgresql.rb +12 -3
  19. data/lib/backup/database/redis.rb +5 -1
  20. data/lib/backup/dependency.rb +11 -12
  21. data/lib/backup/encryptor/gpg.rb +2 -0
  22. data/lib/backup/exception/command_failed.rb +8 -0
  23. data/lib/backup/finder.rb +49 -9
  24. data/lib/backup/notifier/mail.rb +7 -1
  25. data/lib/backup/notifier/twitter.rb +1 -1
  26. data/lib/backup/storage/dropbox.rb +93 -16
  27. data/lib/backup/storage/ftp.rb +10 -3
  28. data/lib/backup/storage/local.rb +78 -0
  29. data/lib/backup/storage/ninefold.rb +96 -0
  30. data/lib/backup/storage/rsync.rb +37 -20
  31. data/lib/backup/storage/s3.rb +1 -1
  32. data/lib/backup/storage/scp.rb +1 -1
  33. data/lib/backup/syncer/rsync.rb +1 -1
  34. data/lib/backup/version.rb +1 -1
  35. data/lib/templates/compressor/lzma +7 -0
  36. data/lib/templates/storage/dropbox +2 -2
  37. data/lib/templates/storage/ftp +8 -7
  38. data/lib/templates/storage/local +7 -0
  39. data/lib/templates/storage/ninefold +9 -0
  40. data/lib/templates/storage/rsync +1 -0
  41. data/spec/archive_spec.rb +0 -1
  42. data/spec/compressor/bzip2_spec.rb +0 -1
  43. data/spec/compressor/gzip_spec.rb +0 -1
  44. data/spec/compressor/lzma_spec.rb +58 -0
  45. data/spec/configuration/compressor/bzip2_spec.rb +28 -0
  46. data/spec/configuration/compressor/lzma_spec.rb +28 -0
  47. data/spec/configuration/database/mongodb_spec.rb +16 -0
  48. data/spec/configuration/database/mysql_spec.rb +17 -0
  49. data/spec/configuration/database/postgresql_spec.rb +17 -0
  50. data/spec/configuration/database/redis_spec.rb +16 -0
  51. data/spec/configuration/notifier/campfire_spec.rb +11 -0
  52. data/spec/configuration/notifier/mail_spec.rb +20 -0
  53. data/spec/configuration/notifier/presently_spec.rb +34 -0
  54. data/spec/configuration/notifier/twitter_spec.rb +12 -0
  55. data/spec/configuration/storage/dropbox_spec.rb +0 -6
  56. data/spec/configuration/storage/ftp_spec.rb +15 -12
  57. data/spec/configuration/storage/local_spec.rb +28 -0
  58. data/spec/configuration/storage/ninefold_spec.rb +31 -0
  59. data/spec/configuration/storage/rsync_spec.rb +2 -0
  60. data/spec/database/mongodb_spec.rb +0 -1
  61. data/spec/database/mysql_spec.rb +0 -1
  62. data/spec/database/postgresql_spec.rb +31 -11
  63. data/spec/database/redis_spec.rb +9 -4
  64. data/spec/encryptor/gpg_spec.rb +1 -1
  65. data/spec/encryptor/open_ssl_spec.rb +0 -1
  66. data/spec/logger_spec.rb +32 -24
  67. data/spec/model_spec.rb +15 -15
  68. data/spec/spec_helper.rb +8 -4
  69. data/spec/storage/base_spec.rb +0 -4
  70. data/spec/storage/cloudfiles_spec.rb +0 -1
  71. data/spec/storage/dropbox_spec.rb +44 -14
  72. data/spec/storage/ftp_spec.rb +26 -15
  73. data/spec/storage/local_spec.rb +83 -0
  74. data/spec/storage/ninefold_spec.rb +142 -0
  75. data/spec/storage/object_spec.rb +1 -1
  76. data/spec/storage/rsync_spec.rb +17 -7
  77. data/spec/storage/s3_spec.rb +4 -3
  78. data/spec/storage/scp_spec.rb +0 -1
  79. data/spec/storage/sftp_spec.rb +0 -1
  80. data/spec/syncer/rsync_spec.rb +8 -8
  81. metadata +62 -36
@@ -6,7 +6,6 @@ describe Backup::Database::Redis do
6
6
 
7
7
  before do
8
8
  Backup::Database::Redis.any_instance.stubs(:load_defaults!)
9
- Backup::Logger.stubs(:error)
10
9
  end
11
10
 
12
11
  let(:db) do
@@ -51,7 +50,7 @@ describe Backup::Database::Redis do
51
50
  end
52
51
 
53
52
  describe '#credential_options' do
54
- it 'should return the mongo syntax for the credential options' do
53
+ it 'should return the redis-cli syntax for the credential options' do
55
54
  db.credential_options.should == "-a 'secret'"
56
55
  end
57
56
  end
@@ -72,7 +71,7 @@ describe Backup::Database::Redis do
72
71
  end
73
72
 
74
73
  describe '#invoke_save!' do
75
- it 'should return the full mongodump string' do
74
+ it 'should return the full redis-cli string' do
76
75
  db.expects(:utility).with('redis-cli').returns('redis-cli')
77
76
  db.expects(:run).with("redis-cli -a 'secret' -h 'localhost' -p '123' -s '/redis.sock' --query SAVE")
78
77
  db.invoke_save!
@@ -86,11 +85,17 @@ describe Backup::Database::Redis do
86
85
  db.expects(:run).with("cp '#{ File.join('/var/lib/redis/db/mydatabase.rdb') }' '#{ File.join(Backup::TMP_PATH, Backup::TRIGGER, 'Redis', 'mydatabase.rdb') }'")
87
86
  db.copy!
88
87
  end
88
+
89
+ it 'should find the cp utility when utility_path is set' do
90
+ File.expects(:exist?).returns(true)
91
+ db.utility_path = '/usr/local/bin/redis-cli'
92
+ db.expects(:run).with { |v| v =~ %r{^/bin/cp .+} }
93
+ db.copy!
94
+ end
89
95
  end
90
96
 
91
97
  describe '#perform!' do
92
98
  before do
93
- Backup::Logger.stubs(:message)
94
99
  File.stubs(:exist?).returns(true)
95
100
  db.stubs(:utility).returns('redis-cli')
96
101
  db.stubs(:mkdir)
@@ -45,7 +45,7 @@ WNa3g2n0nokA7Zr5FA4GXoEaYivfbvGiyNpd6P4okH+//G2p+3FIryu5xz+89D1b
45
45
 
46
46
  describe '#write_tmp_file!' do
47
47
  it do
48
- tmp_file = mock('TmpFile')
48
+ tmp_file = Tempfile.new("foo")
49
49
  Tempfile.expects(:new).returns(tmp_file)
50
50
  tmp_file.expects(:write).with('secret')
51
51
  tmp_file.expects(:close)
@@ -54,7 +54,6 @@ describe Backup::Encryptor::OpenSSL do
54
54
  let(:encryptor) { Backup::Encryptor::OpenSSL.new }
55
55
  before do
56
56
  Backup::Model.extension = 'tar'
57
- Backup::Logger.stubs(:message)
58
57
  [:utility, :run, :rm].each { |method| encryptor.stubs(method) }
59
58
  end
60
59
 
@@ -5,49 +5,57 @@ require 'timecop'
5
5
 
6
6
  describe Backup::Logger do
7
7
  before do
8
- Timecop.freeze( Time.now )
8
+ Timecop.freeze(Time.now)
9
+
10
+ [:message, :error, :warn, :normal, :silent].each do |message_type|
11
+ Backup::Logger.unstub(message_type)
12
+ end
9
13
  end
10
14
 
11
- context 'when logging regular messages' do
12
- it do
13
- Backup::Logger.expects(:puts).with("[#{ Time.now.strftime("%Y/%m/%d %H:%M:%S") }][\e[32mmessage\e[0m] This has been logged.")
15
+ describe 'logging messages to STDOUT and a log file' do
16
+ before do
14
17
  File.expects(:open).with(File.join(Backup::LOG_PATH, 'backup.log'), 'a')
18
+ end
15
19
 
16
- Backup::Logger.message "This has been logged."
20
+ context 'when logging regular messages' do
21
+ it do
22
+ Backup::Logger.expects(:puts).with("[#{ Time.now.strftime("%Y/%m/%d %H:%M:%S") }][\e[32mmessage\e[0m] This has been logged.")
23
+
24
+ Backup::Logger.message "This has been logged."
25
+ end
17
26
  end
18
- end
19
27
 
20
- context 'when logging error messages' do
21
- it do
22
- Backup::Logger.expects(:puts).with("[#{ Time.now.strftime("%Y/%m/%d %H:%M:%S") }][\e[31merror\e[0m] This has been logged.")
23
- File.expects(:open).with(File.join(Backup::LOG_PATH, 'backup.log'), 'a')
28
+ context 'when logging error messages' do
29
+ it do
30
+ Backup::Logger.expects(:puts).with("[#{ Time.now.strftime("%Y/%m/%d %H:%M:%S") }][\e[31merror\e[0m] This has been logged.")
24
31
 
25
- Backup::Logger.error "This has been logged."
32
+ Backup::Logger.error "This has been logged."
33
+ end
26
34
  end
27
- end
28
35
 
29
- context 'when logging warn messages' do
30
- it do
31
- Backup::Logger.expects(:puts).with("[#{ Time.now.strftime("%Y/%m/%d %H:%M:%S") }][\e[33mwarning\e[0m] This has been logged.")
32
- File.expects(:open).with(File.join(Backup::LOG_PATH, 'backup.log'), 'a')
36
+ context 'when logging warn messages' do
37
+ it do
38
+ Backup::Logger.expects(:puts).with("[#{ Time.now.strftime("%Y/%m/%d %H:%M:%S") }][\e[33mwarning\e[0m] This has been logged.")
33
39
 
34
- Backup::Logger.warn "This has been logged."
40
+ Backup::Logger.warn "This has been logged."
41
+ end
35
42
  end
36
- end
37
43
 
38
- context 'when logging silent messages' do
39
- it do
40
- Backup::Logger.expects(:puts).never
41
- File.expects(:open).with(File.join(Backup::LOG_PATH, 'backup.log'), 'a')
44
+ context 'when logging silent messages' do
45
+ it do
46
+ Backup::Logger.expects(:puts).never
42
47
 
43
- Backup::Logger.silent "This has been logged."
48
+ Backup::Logger.silent "This has been logged."
49
+ end
44
50
  end
45
51
  end
46
52
 
47
- context 'when quieted' do
53
+ describe 'logging messages to log file and not STDOUT' do
48
54
  it do
49
55
  Backup::Logger.send(:const_set, :QUIET, true)
56
+
50
57
  Backup::Logger.expects(:puts).never
58
+ File.expects(:open).times(4).with(File.join(Backup::LOG_PATH, 'backup.log'), 'a')
51
59
 
52
60
  Backup::Logger.message "This has been logged."
53
61
  Backup::Logger.error "This has been logged."
@@ -5,30 +5,32 @@ require File.dirname(__FILE__) + '/spec_helper'
5
5
  describe Backup::Model do
6
6
 
7
7
  before do
8
+ # stub out the creation of an archive, for this spec's purpose
9
+ Backup::Archive.stubs(:new).returns(true)
10
+
11
+ # create mockup classes for testing the behavior of Backup::Model
8
12
  class Backup::Database::TestDatabase
9
13
  def initialize(&block); end
10
14
  end
11
15
  class Backup::Storage::TestStorage
12
16
  def initialize(&block); end
13
17
  end
14
- class Backup::Archive
15
- def initialize(name, &block); end
16
- end
17
- class Backup::Compressor::Gzip
18
+ class Backup::Compressor::TestGzip
18
19
  def initialize(&block); end
19
20
  end
20
- class Backup::Compressor::SevenZip
21
+ class Backup::Compressor::TestSevenZip
21
22
  def initialize(&block); end
22
23
  end
23
- class Backup::Encryptor::OpenSSL
24
+ class Backup::Encryptor::TestOpenSSL
24
25
  def initialize(&block); end
25
26
  end
26
- class Backup::Encryptor::GPG
27
+ class Backup::Encryptor::TestGPG
27
28
  def initialize(&block); end
28
29
  end
29
30
  class Backup::Notifier::TestMail
30
31
  def initialize(&block); end
31
32
  end
33
+
32
34
  end
33
35
 
34
36
  let(:model) { Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') {} }
@@ -143,7 +145,7 @@ describe Backup::Model do
143
145
  describe '#compress_with' do
144
146
  it 'should add a compressor to the array of compressors to use' do
145
147
  model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
146
- compress_with('Gzip')
148
+ compress_with('TestGzip')
147
149
  end
148
150
 
149
151
  model.compressors.count.should == 1
@@ -151,8 +153,8 @@ describe Backup::Model do
151
153
 
152
154
  it 'should add a compressor to the array of compressors to use' do
153
155
  model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
154
- compress_with('Gzip')
155
- compress_with('SevenZip')
156
+ compress_with('TestGzip')
157
+ compress_with('TestSevenZip')
156
158
  end
157
159
 
158
160
  model.compressors.count.should == 2
@@ -162,7 +164,7 @@ describe Backup::Model do
162
164
  describe '#encrypt_with' do
163
165
  it 'should add a encryptor to the array of encryptors to use' do
164
166
  model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
165
- encrypt_with('OpenSSL')
167
+ encrypt_with('TestOpenSSL')
166
168
  end
167
169
 
168
170
  model.encryptors.count.should == 1
@@ -170,8 +172,8 @@ describe Backup::Model do
170
172
 
171
173
  it 'should add a encryptor to the array of encryptors to use' do
172
174
  model = Backup::Model.new('mysql-s3', 'MySQL S3 Backup for MyApp') do
173
- encrypt_with('OpenSSL')
174
- encrypt_with('GPG')
175
+ encrypt_with('TestOpenSSL')
176
+ encrypt_with('TestGPG')
175
177
  end
176
178
 
177
179
  model.encryptors.count.should == 2
@@ -200,7 +202,6 @@ describe Backup::Model do
200
202
  describe '#package!' do
201
203
  before do
202
204
  [:utility, :run].each { |method| model.stubs(method) }
203
- Backup::Logger.stubs(:message)
204
205
  end
205
206
 
206
207
  it 'should package the folder' do
@@ -218,7 +219,6 @@ describe Backup::Model do
218
219
  describe '#clean!' do
219
220
  before do
220
221
  [:utility, :run, :rm].each { |method| model.stubs(method) }
221
- Backup::Logger.stubs(:message)
222
222
  end
223
223
 
224
224
  it 'should remove the temporary files and folders that were created' do
@@ -8,10 +8,14 @@ require File.expand_path( '../../lib/backup', __FILE__ )
8
8
  # Use Mocha to mock with RSpec
9
9
  RSpec.configure do |config|
10
10
  config.mock_with :mocha
11
+ config.before(:each) do
12
+ FileUtils.stubs(:mkdir_p)
13
+ [:message, :error, :warn, :normal, :silent].each do |message_type|
14
+ Backup::Logger.stubs(message_type)
15
+ end
16
+ end
11
17
  end
12
18
 
13
- # FIXTURES_PATH = File.join( File.dirname(__FILE__), 'fixtures' )
14
-
15
19
  Backup.send(:remove_const, :TRIGGER) if defined? Backup::TRIGGER
16
20
  Backup.send(:remove_const, :TIME) if defined? Backup::TIME
17
21
 
@@ -20,6 +24,6 @@ module Backup
20
24
  TIME = Time.now.strftime("%Y.%m.%d.%H.%M.%S")
21
25
  end
22
26
 
23
- unless @put_ruby_version
24
- puts @put_ruby_version = "\n\nRuby version: #{ENV['rvm_ruby_string']}\n\n"
27
+ unless @_put_ruby_version
28
+ puts @_put_ruby_version = "\n\nRuby version: #{ENV['rvm_ruby_string']}\n\n"
25
29
  end
@@ -5,10 +5,6 @@ require File.dirname(__FILE__) + '/../spec_helper'
5
5
  describe Backup::Storage::Base do
6
6
  let(:base) { Backup::Storage::Base.new }
7
7
 
8
- before do
9
- Backup::Logger.stubs(:message)
10
- end
11
-
12
8
  it do
13
9
  storage_object = mock
14
10
  Backup::Storage::Object.expects(:new).with('Base').returns(storage_object)
@@ -65,7 +65,6 @@ describe Backup::Storage::CloudFiles do
65
65
  let(:connection) { mock('Fog::Storage') }
66
66
  before do
67
67
  Fog::Storage.stubs(:new).returns(connection)
68
- Backup::Logger.stubs(:message)
69
68
  end
70
69
 
71
70
  it 'should transfer the provided file to the container' do
@@ -6,8 +6,6 @@ describe Backup::Storage::Dropbox do
6
6
 
7
7
  let(:db) do
8
8
  Backup::Storage::Dropbox.new do |db|
9
- db.email = 'my@email.com'
10
- db.password = 'my_password'
11
9
  db.api_key = 'my_api_key'
12
10
  db.api_secret = 'my_secret'
13
11
  db.keep = 20
@@ -22,11 +20,10 @@ describe Backup::Storage::Dropbox do
22
20
 
23
21
  before do
24
22
  Backup::Configuration::Storage::Dropbox.clear_defaults!
23
+ STDIN.stubs(:gets)
25
24
  end
26
25
 
27
26
  it 'should have defined the configuration properly' do
28
- db.email.should == 'my@email.com'
29
- db.password.should == 'my_password'
30
27
  db.api_key.should == 'my_api_key'
31
28
  db.api_secret.should == 'my_secret'
32
29
  db.path.should == 'backups'
@@ -57,21 +54,54 @@ describe Backup::Storage::Dropbox do
57
54
  end
58
55
 
59
56
  describe '#connection' do
60
- it do
61
- session = mock("Dropbox::Session")
62
- Dropbox::Session.expects(:new).with('my_api_key', 'my_secret').returns(session)
63
- session.expects(:mode=).with(:dropbox)
64
- session.expects(:authorizing_user=).with('my@email.com')
65
- session.expects(:authorizing_password=).with('my_password')
66
- session.expects(:authorize!)
67
-
68
- db.send(:connection)
57
+ context "when the session cache has not yet been written" do
58
+ before do
59
+ db.stubs(:gets)
60
+ end
61
+
62
+ it do
63
+ session = mock("Dropbox::Session")
64
+ Dropbox::Session.expects(:new).with('my_api_key', 'my_secret').returns(session)
65
+ session.expects(:mode=).with(:dropbox)
66
+ session.expects(:authorize)
67
+ session.expects(:authorize_url)
68
+ db.expects(:cache_exists?).returns(false)
69
+ db.expects(:write_cache!).with(session)
70
+ db.send(:connection)
71
+ end
72
+ end
73
+
74
+ context "when the session cache has already been written" do
75
+ before do
76
+ db.stubs(:gets)
77
+ end
78
+
79
+ it "should load the session from cache, instead of creating a new one" do
80
+ db.expects(:cache_exists?).returns(true)
81
+ File.expects(:read).with("#{ENV['HOME']}/Backup/.cache/my_api_keymy_secret").returns("foo")
82
+ session = mock("Dropbox::Session")
83
+ session.expects(:authorized?).returns(true)
84
+ Dropbox::Session.expects(:deserialize).with("foo").returns(session)
85
+
86
+ db.expects(:create_write_and_return_new_session!).never
87
+ db.send(:connection)
88
+ end
89
+
90
+ it "should load it from cache, but if it's invalid/corrupt, the create a session anyway" do
91
+ db.expects(:cache_exists?).returns(true)
92
+ File.expects(:read).with("#{ENV['HOME']}/Backup/.cache/my_api_keymy_secret").returns("foo")
93
+ session = mock("Dropbox::Session")
94
+ session.expects(:authorized?).returns(false)
95
+ Dropbox::Session.expects(:deserialize).with("foo").returns(session)
96
+
97
+ db.expects(:create_write_and_return_new_session!)
98
+ db.send(:connection)
99
+ end
69
100
  end
70
101
  end
71
102
 
72
103
  describe '#transfer!' do
73
104
  before do
74
- Backup::Logger.stubs(:message)
75
105
  connection.stubs(:upload)
76
106
  connection.stubs(:delete)
77
107
  end
@@ -6,12 +6,13 @@ describe Backup::Storage::FTP do
6
6
 
7
7
  let(:ftp) do
8
8
  Backup::Storage::FTP.new do |ftp|
9
- ftp.username = 'my_username'
10
- ftp.password = 'my_password'
11
- ftp.ip = '123.45.678.90'
12
- ftp.port = 21
13
- ftp.path = '~/backups/'
14
- ftp.keep = 20
9
+ ftp.username = 'my_username'
10
+ ftp.password = 'my_password'
11
+ ftp.ip = '123.45.678.90'
12
+ ftp.port = 21
13
+ ftp.path = '~/backups/'
14
+ ftp.keep = 20
15
+ ftp.passive_mode = false
15
16
  end
16
17
  end
17
18
 
@@ -20,12 +21,13 @@ describe Backup::Storage::FTP do
20
21
  end
21
22
 
22
23
  it 'should have defined the configuration properly' do
23
- ftp.username.should == 'my_username'
24
- ftp.password.should == 'my_password'
25
- ftp.ip.should == '123.45.678.90'
26
- ftp.port.should == 21
27
- ftp.path.should == 'backups/'
28
- ftp.keep.should == 20
24
+ ftp.username.should == 'my_username'
25
+ ftp.password.should == 'my_password'
26
+ ftp.ip.should == '123.45.678.90'
27
+ ftp.port.should == 21
28
+ ftp.path.should == 'backups/'
29
+ ftp.keep.should == 20
30
+ ftp.passive_mode.should == false
29
31
  end
30
32
 
31
33
  it 'should use the defaults if a particular attribute has not been defined' do
@@ -48,11 +50,14 @@ describe Backup::Storage::FTP do
48
50
 
49
51
  it 'should have its own defaults' do
50
52
  ftp = Backup::Storage::FTP.new
51
- ftp.port.should == 21
52
- ftp.path.should == 'backups'
53
+ ftp.port.should == 21
54
+ ftp.path.should == 'backups'
55
+ ftp.passive_mode.should == false
53
56
  end
54
57
 
55
58
  describe '#connection' do
59
+ let(:connection) { mock('Fog::Storage') }
60
+
56
61
  it 'should establish a connection to the remote server using the provided ip address and credentials' do
57
62
  Net::FTP.expects(:new).with('123.45.678.90', 'my_username', 'my_password')
58
63
  ftp.send(:connection)
@@ -64,6 +69,13 @@ describe Backup::Storage::FTP do
64
69
  ftp.send(:connection)
65
70
  Net::FTP::FTP_PORT.should == 40
66
71
  end
72
+
73
+ it 'configures net/ftp to use passive mode if passive_mode set to true' do
74
+ ftp.passive_mode = true
75
+ Net::FTP.stubs(:new).returns(connection)
76
+ connection.expects(:passive=).with(true)
77
+ ftp.send(:connection)
78
+ end
67
79
  end
68
80
 
69
81
  describe '#transfer!' do
@@ -72,7 +84,6 @@ describe Backup::Storage::FTP do
72
84
  before do
73
85
  Net::FTP.stubs(:new).returns(connection)
74
86
  ftp.stubs(:create_remote_directories!)
75
- Backup::Logger.stubs(:message)
76
87
  end
77
88
 
78
89
  it 'should transfer the provided file to the path' do