backup 3.0.21 → 3.0.22
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +3 -1
- data/README.md +4 -3
- data/lib/backup.rb +8 -4
- data/lib/backup/config.rb +1 -1
- data/lib/backup/configuration/syncer/cloud.rb +23 -0
- data/lib/backup/configuration/syncer/cloud_files.rb +30 -0
- data/lib/backup/configuration/syncer/s3.rb +5 -11
- data/lib/backup/dependency.rb +6 -0
- data/lib/backup/notifier/twitter.rb +1 -1
- data/lib/backup/syncer/base.rb +25 -0
- data/lib/backup/syncer/cloud.rb +187 -0
- data/lib/backup/syncer/cloud_files.rb +56 -0
- data/lib/backup/syncer/rsync/base.rb +0 -26
- data/lib/backup/syncer/s3.rb +21 -102
- data/lib/backup/version.rb +1 -1
- data/spec/cli/utility_spec.rb +2 -2
- data/spec/configuration/syncer/cloud_files_spec.rb +44 -0
- data/spec/configuration/syncer/s3_spec.rb +0 -4
- data/spec/notifier/twitter_spec.rb +3 -3
- data/spec/syncer/cloud_files_spec.rb +192 -0
- data/spec/syncer/s3_spec.rb +155 -191
- data/templates/cli/utility/archive +20 -8
- data/templates/cli/utility/database/mongodb +3 -3
- data/templates/cli/utility/database/mysql +4 -4
- data/templates/cli/utility/database/postgresql +4 -4
- data/templates/cli/utility/database/redis +1 -1
- data/templates/cli/utility/encryptor/openssl +2 -2
- data/templates/cli/utility/notifier/campfire +3 -3
- data/templates/cli/utility/notifier/hipchat +6 -6
- data/templates/cli/utility/notifier/mail +7 -7
- data/templates/cli/utility/notifier/presently +4 -4
- data/templates/cli/utility/notifier/prowl +2 -2
- data/templates/cli/utility/notifier/twitter +4 -4
- data/templates/cli/utility/storage/cloud_files +22 -0
- data/templates/cli/utility/storage/dropbox +15 -10
- data/templates/cli/utility/storage/ftp +4 -4
- data/templates/cli/utility/storage/local +1 -1
- data/templates/cli/utility/storage/ninefold +3 -3
- data/templates/cli/utility/storage/rsync +4 -4
- data/templates/cli/utility/storage/s3 +6 -6
- data/templates/cli/utility/storage/scp +4 -4
- data/templates/cli/utility/storage/sftp +4 -4
- data/templates/cli/utility/syncer/cloud_files +48 -0
- data/templates/cli/utility/syncer/s3 +31 -1
- metadata +69 -39
- data/templates/cli/utility/storage/cloudfiles +0 -12
data/spec/syncer/s3_spec.rb
CHANGED
@@ -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
|
-
|
7
|
-
Backup::Syncer::S3.new
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
-
|
19
|
-
|
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
|
-
|
36
|
+
it "respects the parallel thread count" do
|
37
|
+
syncer.concurrency_type = :threads
|
38
|
+
syncer.concurrency_level = 10
|
24
39
|
|
25
|
-
|
26
|
-
|
27
|
-
syncer.
|
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
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
syncer
|
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
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
-
|
103
|
-
|
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
|
-
|
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
|
-
|
111
|
-
|
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
|
-
|
135
|
-
|
136
|
-
|
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
|
-
|
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
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
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
|
-
|
163
|
-
|
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
|
-
|
171
|
-
|
172
|
-
syncer.send(:dest_path).should == 'my_backups'
|
173
|
-
end
|
104
|
+
syncer.perform!
|
105
|
+
end
|
174
106
|
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
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
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
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
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
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
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
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
|
-
|
6
|
-
archive.add
|
7
|
-
|
8
|
-
archive.
|
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 = [
|
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 =
|
17
|
-
# db.mongo_utility =
|
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 = [
|
12
|
-
db.only_tables = [
|
13
|
-
db.additional_options = [
|
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 =
|
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 = [
|
12
|
-
db.only_tables = [
|
13
|
-
db.additional_options = [
|
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 =
|
16
|
+
# db.pg_dump_utility = "/opt/local/bin/pg_dump"
|
17
17
|
end
|