backup 3.0.21 → 3.0.22

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/Gemfile.lock +3 -1
  2. data/README.md +4 -3
  3. data/lib/backup.rb +8 -4
  4. data/lib/backup/config.rb +1 -1
  5. data/lib/backup/configuration/syncer/cloud.rb +23 -0
  6. data/lib/backup/configuration/syncer/cloud_files.rb +30 -0
  7. data/lib/backup/configuration/syncer/s3.rb +5 -11
  8. data/lib/backup/dependency.rb +6 -0
  9. data/lib/backup/notifier/twitter.rb +1 -1
  10. data/lib/backup/syncer/base.rb +25 -0
  11. data/lib/backup/syncer/cloud.rb +187 -0
  12. data/lib/backup/syncer/cloud_files.rb +56 -0
  13. data/lib/backup/syncer/rsync/base.rb +0 -26
  14. data/lib/backup/syncer/s3.rb +21 -102
  15. data/lib/backup/version.rb +1 -1
  16. data/spec/cli/utility_spec.rb +2 -2
  17. data/spec/configuration/syncer/cloud_files_spec.rb +44 -0
  18. data/spec/configuration/syncer/s3_spec.rb +0 -4
  19. data/spec/notifier/twitter_spec.rb +3 -3
  20. data/spec/syncer/cloud_files_spec.rb +192 -0
  21. data/spec/syncer/s3_spec.rb +155 -191
  22. data/templates/cli/utility/archive +20 -8
  23. data/templates/cli/utility/database/mongodb +3 -3
  24. data/templates/cli/utility/database/mysql +4 -4
  25. data/templates/cli/utility/database/postgresql +4 -4
  26. data/templates/cli/utility/database/redis +1 -1
  27. data/templates/cli/utility/encryptor/openssl +2 -2
  28. data/templates/cli/utility/notifier/campfire +3 -3
  29. data/templates/cli/utility/notifier/hipchat +6 -6
  30. data/templates/cli/utility/notifier/mail +7 -7
  31. data/templates/cli/utility/notifier/presently +4 -4
  32. data/templates/cli/utility/notifier/prowl +2 -2
  33. data/templates/cli/utility/notifier/twitter +4 -4
  34. data/templates/cli/utility/storage/cloud_files +22 -0
  35. data/templates/cli/utility/storage/dropbox +15 -10
  36. data/templates/cli/utility/storage/ftp +4 -4
  37. data/templates/cli/utility/storage/local +1 -1
  38. data/templates/cli/utility/storage/ninefold +3 -3
  39. data/templates/cli/utility/storage/rsync +4 -4
  40. data/templates/cli/utility/storage/s3 +6 -6
  41. data/templates/cli/utility/storage/scp +4 -4
  42. data/templates/cli/utility/storage/sftp +4 -4
  43. data/templates/cli/utility/syncer/cloud_files +48 -0
  44. data/templates/cli/utility/syncer/s3 +31 -1
  45. metadata +69 -39
  46. data/templates/cli/utility/storage/cloudfiles +0 -12
@@ -1,228 +1,192 @@
1
1
  # encoding: utf-8
2
-
3
2
  require File.expand_path('../../spec_helper.rb', __FILE__)
4
3
 
4
+ class Parallel; end
5
+
5
6
  describe Backup::Syncer::S3 do
6
- let(:syncer) do
7
- Backup::Syncer::S3.new do |s3|
8
- s3.access_key_id = 'my_access_key_id'
9
- s3.secret_access_key = 'my_secret_access_key'
10
- s3.bucket = 'my-bucket'
11
- s3.path = "/my_backups"
12
-
13
- s3.directories do |directory|
14
- directory.add "/some/directory"
15
- directory.add "~/home/directory"
16
- end
7
+ describe '#perform!' do
8
+ let(:syncer) { Backup::Syncer::S3.new }
9
+ let(:connection) { stub('connection',
10
+ :directories => stub('directories', :get => bucket)) }
11
+ let(:bucket) { stub('bucket', :files => files) }
12
+ let(:files) { [] }
13
+ let(:content) { stub('content') }
14
+
15
+ before :each do
16
+ Fog::Storage.stubs(:new).returns connection
17
+ File.stubs(:open).returns content
18
+ File.stubs(:exist?).returns true
19
+ files.stubs(:create).returns true
20
+
21
+ syncer.directories << 'tmp'
22
+ syncer.path = 'storage'
23
+
24
+ Backup::Syncer::S3::SyncContext.any_instance.
25
+ stubs(:`).returns 'MD5(tmp/foo)= 123abcdef'
26
+ end
17
27
 
18
- s3.mirror = true
19
- s3.additional_options = ['--opt-a', '--opt-b']
28
+ it "respects the concurrency_type setting with threads" do
29
+ syncer.concurrency_type = :threads
30
+
31
+ Parallel.expects(:each).with(anything, {:in_threads => 2}, anything)
32
+
33
+ syncer.perform!
20
34
  end
21
- end
22
35
 
23
- describe '#initialize' do
36
+ it "respects the parallel thread count" do
37
+ syncer.concurrency_type = :threads
38
+ syncer.concurrency_level = 10
24
39
 
25
- it 'should have defined the configuration properly' do
26
- syncer.access_key_id.should == 'my_access_key_id'
27
- syncer.secret_access_key.should == 'my_secret_access_key'
28
- syncer.bucket.should == 'my-bucket'
29
- syncer.path.should == '/my_backups'
30
- syncer.directories.should == ["/some/directory", "~/home/directory"]
31
- syncer.mirror.should == true
32
- syncer.additional_options.should == ['--opt-a', '--opt-b']
40
+ Parallel.expects(:each).with(anything, {:in_threads => 10}, anything)
41
+
42
+ syncer.perform!
33
43
  end
34
44
 
35
- context 'when options are not set' do
36
- it 'should use default values' do
37
- syncer = Backup::Syncer::S3.new
38
- syncer.access_key_id.should == nil
39
- syncer.secret_access_key.should == nil
40
- syncer.bucket.should == nil
41
- syncer.path.should == 'backups'
42
- syncer.directories.should == []
43
- syncer.mirror.should == false
44
- syncer.additional_options.should == []
45
- end
45
+ it "respects the concurrency_type setting with processors" do
46
+ syncer.concurrency_type = :processes
47
+
48
+ Parallel.expects(:each).with(anything, {:in_processes => 2}, anything)
49
+
50
+ syncer.perform!
46
51
  end
47
52
 
48
- context 'when setting configuration defaults' do
49
- after { Backup::Configuration::Syncer::S3.clear_defaults! }
50
-
51
- it 'should use the configured defaults' do
52
- Backup::Configuration::Syncer::S3.defaults do |s3|
53
- s3.access_key_id = 'some_access_key_id'
54
- s3.secret_access_key = 'some_secret_access_key'
55
- s3.bucket = 'some_bucket'
56
- s3.path = 'some_path'
57
- #s3.directories = 'cannot_have_a_default_value'
58
- s3.mirror = 'some_mirror'
59
- s3.additional_options = 'some_additional_options'
60
- end
61
- syncer = Backup::Syncer::S3.new
62
- syncer.access_key_id.should == 'some_access_key_id'
63
- syncer.secret_access_key.should == 'some_secret_access_key'
64
- syncer.bucket.should == 'some_bucket'
65
- syncer.path.should == 'some_path'
66
- syncer.directories.should == []
67
- syncer.mirror.should == 'some_mirror'
68
- syncer.additional_options.should == 'some_additional_options'
53
+ it "respects the parallel thread count" do
54
+ syncer.concurrency_type = :processes
55
+ syncer.concurrency_level = 10
56
+
57
+ Parallel.expects(:each).with(anything, {:in_processes => 10}, anything)
58
+
59
+ syncer.perform!
60
+ end
61
+
62
+ context 'file exists locally' do
63
+ it "uploads a file if it does not exist remotely" do
64
+ files.expects(:create).with(:key => 'storage/tmp/foo', :body => content)
65
+
66
+ syncer.perform!
69
67
  end
70
68
 
71
- it 'should override the configured defaults' do
72
- Backup::Configuration::Syncer::S3.defaults do |s3|
73
- s3.access_key_id = 'old_access_key_id'
74
- s3.secret_access_key = 'old_secret_access_key'
75
- s3.bucket = 'old_bucket'
76
- s3.path = 'old_path'
77
- #s3.directories = 'cannot_have_a_default_value'
78
- s3.mirror = 'old_mirror'
79
- s3.additional_options = 'old_additional_options'
80
- end
81
- syncer = Backup::Syncer::S3.new do |s3|
82
- s3.access_key_id = 'new_access_key_id'
83
- s3.secret_access_key = 'new_secret_access_key'
84
- s3.bucket = 'new_bucket'
85
- s3.path = 'new_path'
86
- s3.directories = 'new_directories'
87
- s3.mirror = 'new_mirror'
88
- s3.additional_options = 'new_additional_options'
89
- end
90
-
91
- syncer.access_key_id.should == 'new_access_key_id'
92
- syncer.secret_access_key.should == 'new_secret_access_key'
93
- syncer.bucket.should == 'new_bucket'
94
- syncer.path.should == 'new_path'
95
- syncer.directories.should == 'new_directories'
96
- syncer.mirror.should == 'new_mirror'
97
- syncer.additional_options.should == 'new_additional_options'
69
+ it "uploads a file if it exists remotely with a different MD5" do
70
+ files << stub('file', :key => 'storage/tmp/foo', :etag => 'abcdef123')
71
+
72
+ files.expects(:create).with(:key => 'storage/tmp/foo', :body => content)
73
+
74
+ syncer.perform!
98
75
  end
99
- end # context 'when setting configuration defaults'
100
- end # describe '#initialize'
101
76
 
102
- describe '#perform!' do
103
- let(:s) { sequence '' }
77
+ it "does nothing if the file exists remotely with the same MD5" do
78
+ files << stub('file', :key => 'storage/tmp/foo', :etag => '123abcdef')
104
79
 
105
- before do
106
- syncer.expects(:utility).twice.with(:s3sync).returns('s3sync')
107
- syncer.expects(:options).twice.returns('options_output')
108
- end
80
+ files.expects(:create).never
109
81
 
110
- it 'should sync two directories' do
111
- syncer.expects(:set_environment_variables!).in_sequence(s)
112
-
113
- # first directory
114
- Backup::Logger.expects(:message).in_sequence(s).with(
115
- "Syncer::S3 started syncing '/some/directory'."
116
- )
117
- syncer.expects(:run).in_sequence(s).with(
118
- "s3sync options_output '/some/directory' 'my-bucket:my_backups'"
119
- ).returns('messages from stdout')
120
- Backup::Logger.expects(:silent).in_sequence(s).with('messages from stdout')
121
-
122
- # second directory
123
- Backup::Logger.expects(:message).in_sequence(s).with(
124
- "Syncer::S3 started syncing '~/home/directory'."
125
- )
126
- syncer.expects(:run).in_sequence(s).with(
127
- "s3sync options_output '#{ File.expand_path('~/home/directory') }' " +
128
- "'my-bucket:my_backups'"
129
- ).returns('messages from stdout')
130
- Backup::Logger.expects(:silent).in_sequence(s).with('messages from stdout')
131
-
132
- syncer.expects(:unset_environment_variables!).in_sequence(s)
82
+ syncer.perform!
83
+ end
133
84
 
134
- syncer.perform!
135
- end
136
- end # describe '#perform!'
85
+ it "skips the file if it no longer exists locally" do
86
+ File.stubs(:exist?).returns false
87
+
88
+ files.expects(:create).never
137
89
 
138
- describe '#directories' do
139
- context 'when no block is given' do
140
- it 'should return @directories' do
141
- syncer.directories.should ==
142
- ['/some/directory', '~/home/directory']
90
+ syncer.perform!
143
91
  end
144
- end
145
92
 
146
- context 'when a block is given' do
147
- it 'should evalute the block, allowing #add to add directories' do
148
- syncer.directories do
149
- add '/new/path'
150
- add '~/new/home/path'
151
- end
152
- syncer.directories.should == [
153
- '/some/directory',
154
- '~/home/directory',
155
- '/new/path',
156
- '~/new/home/path'
157
- ]
93
+ it "respects the given path" do
94
+ syncer.path = 'box'
95
+
96
+ files.expects(:create).with(:key => 'box/tmp/foo', :body => content)
97
+
98
+ syncer.perform!
158
99
  end
159
- end
160
- end # describe '#directories'
161
100
 
162
- describe '#add' do
163
- it 'should add the given path to @directories' do
164
- syncer.add '/my/path'
165
- syncer.directories.should ==
166
- ['/some/directory', '~/home/directory', '/my/path']
167
- end
168
- end
101
+ it "uploads the content of the local file" do
102
+ File.expects(:open).with('tmp/foo').returns content
169
103
 
170
- describe '#dest_path' do
171
- it 'should remove any preceeding "/" from @path' do
172
- syncer.send(:dest_path).should == 'my_backups'
173
- end
104
+ syncer.perform!
105
+ end
174
106
 
175
- it 'should set @dest_path' do
176
- syncer.send(:dest_path)
177
- syncer.instance_variable_get(:@dest_path).should == 'my_backups'
178
- end
107
+ it "creates the connection with the provided credentials" do
108
+ syncer.access_key_id = 'my-access'
109
+ syncer.secret_access_key = 'my-secret'
110
+ syncer.region = 'somewhere'
179
111
 
180
- it 'should return @dest_path if already set' do
181
- syncer.instance_variable_set(:@dest_path, 'foo')
182
- syncer.send(:dest_path).should == 'foo'
183
- end
184
- end
112
+ Fog::Storage.expects(:new).with(
113
+ :provider => 'AWS',
114
+ :aws_access_key_id => 'my-access',
115
+ :aws_secret_access_key => 'my-secret',
116
+ :region => 'somewhere'
117
+ ).returns connection
185
118
 
186
- describe '#options' do
187
- context 'when @mirror is true' do
188
- it 'should return the options with mirroring enabled' do
189
- syncer.send(:options).should ==
190
- '--verbose --recursive --delete --opt-a --opt-b'
119
+ syncer.perform!
191
120
  end
192
- end
193
121
 
194
- context 'when @mirror is false' do
195
- before { syncer.mirror = false }
196
- it 'should return the options without mirroring enabled' do
197
- syncer.send(:options).should ==
198
- '--verbose --recursive --opt-a --opt-b'
122
+ it "uses the bucket with the given name" do
123
+ syncer.bucket = 'leaky'
124
+
125
+ connection.directories.expects(:get).with('leaky').returns(bucket)
126
+
127
+ syncer.perform!
199
128
  end
200
- end
201
129
 
202
- context 'with no additional options' do
203
- before { syncer.additional_options = [] }
204
- it 'should return the options without additional options' do
205
- syncer.send(:options).should ==
206
- '--verbose --recursive --delete'
130
+ it "creates the bucket if one does not exist" do
131
+ syncer.bucket = 'leaky'
132
+ syncer.region = 'elsewhere'
133
+ connection.directories.stubs(:get).returns nil
134
+
135
+ connection.directories.expects(:create).
136
+ with(:key => 'leaky', :location => 'elsewhere').returns(bucket)
137
+
138
+ syncer.perform!
139
+ end
140
+
141
+ it "iterates over each directory" do
142
+ syncer.directories << 'files'
143
+
144
+ Backup::Syncer::S3::SyncContext.any_instance.expects(:`).
145
+ with('find tmp -print0 | xargs -0 openssl md5 2> /dev/null').
146
+ returns 'MD5(tmp/foo)= 123abcdef'
147
+ Backup::Syncer::S3::SyncContext.any_instance.expects(:`).
148
+ with('find files -print0 | xargs -0 openssl md5 2> /dev/null').
149
+ returns 'MD5(tmp/foo)= 123abcdef'
150
+
151
+ syncer.perform!
207
152
  end
208
153
  end
209
- end # describe '#options'
210
-
211
- describe 'changing environment variables' do
212
- before { @env = ENV }
213
- after { ENV.replace(@env) }
214
-
215
- it 'should set and unset environment variables' do
216
- syncer.send(:set_environment_variables!)
217
- ENV['AWS_ACCESS_KEY_ID'].should == 'my_access_key_id'
218
- ENV['AWS_SECRET_ACCESS_KEY'].should == 'my_secret_access_key'
219
- ENV['AWS_CALLING_FORMAT'].should == 'SUBDOMAIN'
220
-
221
- syncer.send(:unset_environment_variables!)
222
- ENV['AWS_ACCESS_KEY_ID'].should == nil
223
- ENV['AWS_SECRET_ACCESS_KEY'].should == nil
224
- ENV['AWS_CALLING_FORMAT'].should == nil
154
+
155
+ context 'file does not exist locally' do
156
+ let(:file) { stub('file', :key => 'storage/tmp/foo',
157
+ :etag => '123abcdef') }
158
+
159
+ before :each do
160
+ Backup::Syncer::S3::SyncContext.any_instance.
161
+ stubs(:`).returns ''
162
+ files << file
163
+ File.stubs(:exist?).returns false
164
+ end
165
+
166
+ it "removes the remote file when mirroring is turned on" do
167
+ syncer.mirror = true
168
+
169
+ file.expects(:destroy).once
170
+
171
+ syncer.perform!
172
+ end
173
+
174
+ it "leaves the remote file when mirroring is turned off" do
175
+ syncer.mirror = false
176
+
177
+ file.expects(:destroy).never
178
+
179
+ syncer.perform!
180
+ end
181
+
182
+ it "does not remove files not under one of the specified directories" do
183
+ file.stubs(:key).returns 'unsynced/tmp/foo'
184
+ syncer.mirror = true
185
+
186
+ file.expects(:destroy).never
187
+
188
+ syncer.perform!
189
+ end
225
190
  end
226
191
  end
227
-
228
192
  end
@@ -1,13 +1,25 @@
1
1
  ##
2
2
  # Archive [Archive]
3
3
  #
4
+ # Adding a file:
5
+ #
6
+ # archive.add "/path/to/a/file.rb"
7
+ #
8
+ # Adding an directory (including sub-directories):
9
+ #
10
+ # archive.add "/path/to/a/directory/"
11
+ #
12
+ # Excluding a file:
13
+ #
14
+ # archive.exclude "/path/to/an/excluded_file.rb"
15
+ #
16
+ # Excluding a directory (including sub-directories):
17
+ #
18
+ # archive.exclude "/path/to/an/excluded_directory/
19
+ #
4
20
  archive :my_archive do |archive|
5
- # add a file
6
- archive.add '/path/to/a/file.rb'
7
- # add a folder (including sub-folders)
8
- archive.add '/path/to/a/folder/'
9
- # exclude a file
10
- archive.exclude '/path/to/a/excluded_file.rb'
11
- # exclude a folder (including sub-folders)
12
- archive.exclude '/path/to/a/excluded_folder/'
21
+ archive.add "/path/to/a/file.rb"
22
+ archive.add "/path/to/a/folder/"
23
+ archive.exclude "/path/to/a/excluded_file.rb"
24
+ archive.exclude "/path/to/a/excluded_folder/"
13
25
  end
@@ -8,11 +8,11 @@
8
8
  db.host = "localhost"
9
9
  db.port = 5432
10
10
  db.ipv6 = false
11
- db.only_collections = ['only', 'these' 'collections']
11
+ db.only_collections = ["only", "these" "collections"]
12
12
  db.additional_options = []
13
13
  db.lock = false
14
14
  # Optional: Use to set the location of these utilities
15
15
  # if they cannot be found by their name in your $PATH
16
- # db.mongodump_utility = '/opt/local/bin/mongodump'
17
- # db.mongo_utility = '/opt/local/bin/mongo'
16
+ # db.mongodump_utility = "/opt/local/bin/mongodump"
17
+ # db.mongo_utility = "/opt/local/bin/mongo"
18
18
  end
@@ -8,10 +8,10 @@
8
8
  db.host = "localhost"
9
9
  db.port = 3306
10
10
  db.socket = "/tmp/mysql.sock"
11
- db.skip_tables = ['skip', 'these', 'tables']
12
- db.only_tables = ['only', 'these' 'tables']
13
- db.additional_options = ['--quick', '--single-transaction']
11
+ db.skip_tables = ["skip", "these", "tables"]
12
+ db.only_tables = ["only", "these" "tables"]
13
+ db.additional_options = ["--quick", "--single-transaction"]
14
14
  # Optional: Use to set the location of this utility
15
15
  # if it cannot be found by name in your $PATH
16
- # db.mysqldump_utility = '/opt/local/bin/mysqldump'
16
+ # db.mysqldump_utility = "/opt/local/bin/mysqldump"
17
17
  end
@@ -8,10 +8,10 @@
8
8
  db.host = "localhost"
9
9
  db.port = 5432
10
10
  db.socket = "/tmp/pg.sock"
11
- db.skip_tables = ['skip', 'these', 'tables']
12
- db.only_tables = ['only', 'these' 'tables']
13
- db.additional_options = ['-xc', '-E=utf8']
11
+ db.skip_tables = ["skip", "these", "tables"]
12
+ db.only_tables = ["only", "these" "tables"]
13
+ db.additional_options = ["-xc", "-E=utf8"]
14
14
  # Optional: Use to set the location of this utility
15
15
  # if it cannot be found by name in your $PATH
16
- # db.pg_dump_utility = '/opt/local/bin/pg_dump'
16
+ # db.pg_dump_utility = "/opt/local/bin/pg_dump"
17
17
  end