right_publish 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +4 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +114 -0
- data/README.rdoc +113 -0
- data/Rakefile +48 -0
- data/VERSION +1 -0
- data/bin/right_publish +8 -0
- data/lib/right_publish/profile.rb +96 -0
- data/lib/right_publish/repo.rb +159 -0
- data/lib/right_publish/repos/apt.rb +178 -0
- data/lib/right_publish/repos/gem.rb +44 -0
- data/lib/right_publish/repos/yum.rb +167 -0
- data/lib/right_publish/storage.rb +90 -0
- data/lib/right_publish/stores/local.rb +29 -0
- data/lib/right_publish/stores/s3.rb +35 -0
- data/lib/right_publish.rb +97 -0
- data/right_publish.gemspec +101 -0
- data/spec/repo_manager_spec.rb +65 -0
- data/spec/repo_spec.rb +74 -0
- data/spec/repos/apt_spec.rb +288 -0
- data/spec/repos/gem_spec.rb +79 -0
- data/spec/repos/yum_spec.rb +282 -0
- data/spec/spec_helper.rb +30 -0
- data/spec/storage_manager_spec.rb +71 -0
- data/spec/storage_spec.rb +317 -0
- data/spec/stores/local_spec.rb +56 -0
- data/spec/stores/s3_spec.rb +44 -0
- metadata +274 -0
@@ -0,0 +1,282 @@
|
|
1
|
+
require 'right_publish/repo'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe RightPublish::YumRepo do
|
5
|
+
|
6
|
+
def pkg_parts(path)
|
7
|
+
result = {:name=>nil, :version=>nil, :arch=>nil}
|
8
|
+
if /([A-Za-z\-+]+)-([0-9][A-Za-z0-9\-\.]*)\.(#{RightPublish::YumRepo::PKG_TYPES.join('|')})\.rpm\Z/.match(path)
|
9
|
+
result[:name] = $1
|
10
|
+
result[:version] = $2
|
11
|
+
result[:arch] = $3
|
12
|
+
end
|
13
|
+
result
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
before(:each) do
|
18
|
+
@repo_subdir = 'fake/'
|
19
|
+
@repo = flexmock(RightPublish::RepoManager.get_repository(:yum_repo)) do |obj|
|
20
|
+
obj.should_receive(:fetch)
|
21
|
+
obj.should_receive(:store)
|
22
|
+
obj.should_receive(:do_in_subdir).and_return {|dir,block| block.call}
|
23
|
+
obj.should_receive(:yum_prune)
|
24
|
+
obj.should_receive(:pkg_parts).and_return { |pkg| pkg_parts(pkg) }
|
25
|
+
end
|
26
|
+
|
27
|
+
flexmock(RightPublish::Profile) do |obj|
|
28
|
+
obj.should_receive(:log).and_return { |str,dbg| }
|
29
|
+
end
|
30
|
+
|
31
|
+
flexmock(File) { |obj| obj.should_receive(:file?).and_return(true) }
|
32
|
+
end
|
33
|
+
|
34
|
+
[false, true].each do |should_sign|
|
35
|
+
context "#publish#{" and sign" if should_sign}" do
|
36
|
+
|
37
|
+
# Test the EPEL (not flat) repo profile type
|
38
|
+
context "EPEL layout" do
|
39
|
+
|
40
|
+
def build_expectations(pkgs, expected_dists)
|
41
|
+
pkgs = Array(pkgs)
|
42
|
+
|
43
|
+
pkgs.each do |pkg|
|
44
|
+
pkg_arch = pkg_parts(pkg)[:arch]
|
45
|
+
dest_archs = case pkg_arch
|
46
|
+
when RightPublish::YumRepo::SRC_ALL_ARCH
|
47
|
+
[RightPublish::YumRepo::SRC_ALL_PATH]
|
48
|
+
when RightPublish::YumRepo::BIN_ALL_ARCH
|
49
|
+
RightPublish::YumRepo::ARCHITECTURES
|
50
|
+
else
|
51
|
+
[pkg_arch]
|
52
|
+
end
|
53
|
+
|
54
|
+
expected_dists.each_pair do |dist,releases|
|
55
|
+
releases.each do |release|
|
56
|
+
dest_archs.each do |arch|
|
57
|
+
dist_path = File.join(@repo_subdir, @epel_version.to_s, dist.to_s, release, arch)
|
58
|
+
@install_expectations[dist_path] ||= []
|
59
|
+
@install_expectations[dist_path] << pkg
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
@repo.should_receive(:install_file).and_return do |pkg,dir|
|
65
|
+
@install_expectations.should be_has_key(dir)
|
66
|
+
@install_expectations[dir].delete(pkg)
|
67
|
+
@install_expectations.delete(dir) if @install_expectations[dir].empty?
|
68
|
+
end
|
69
|
+
system_calls = @install_expectations.size
|
70
|
+
system_sign_calls = (@profile.config[:yum_repo][:gpg_key_id]) ? 1 : 0
|
71
|
+
@repo.should_receive(:system).times(system_calls).and_return(0)
|
72
|
+
@repo.should_receive(:shellout_with_password).times(system_sign_calls).and_return(0)
|
73
|
+
end
|
74
|
+
|
75
|
+
before(:each) do
|
76
|
+
@epel_dists = {:el=>['5','6'],:fc=>['12','13']}
|
77
|
+
@epel_version = 1
|
78
|
+
@install_expectations = {}
|
79
|
+
@profile = flexmock(RightPublish::Profile) do |obj|
|
80
|
+
obj.should_receive(:config).and_return {
|
81
|
+
{ :verbose=>false,
|
82
|
+
:remote_storage=>{:provider=>'mockremote'},
|
83
|
+
:local_storage=>{:provider=>'mocklocal'},
|
84
|
+
:yum_repo=>{
|
85
|
+
:epel=>@epel_version,
|
86
|
+
:subdir=>@repo_subdir,
|
87
|
+
:dists=>@epel_dists,
|
88
|
+
:gpg_key_id=>should_sign,
|
89
|
+
:gpg_password=>'fakepass'}
|
90
|
+
}
|
91
|
+
}
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
after(:each) do
|
96
|
+
@install_expectations.should be_empty
|
97
|
+
end
|
98
|
+
|
99
|
+
# Test for publish noarch packages (multicasting)
|
100
|
+
context "NoArch Packages" do
|
101
|
+
it "installs a single noarch rpm file to all distribution/architectures" do
|
102
|
+
rpm_file = 'foo-1.noarch.rpm'
|
103
|
+
build_expectations(rpm_file,@epel_dists)
|
104
|
+
@repo.publish(rpm_file, nil)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "installs a single src rpm file to all dists" do
|
108
|
+
rpm_file = 'foo-1.src.rpm'
|
109
|
+
build_expectations(rpm_file,@epel_dists)
|
110
|
+
@repo.publish(rpm_file, nil)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "installs a list of noarch rpm files to all distribution/architectures" do
|
114
|
+
rpm_files = ['foo-1.noarch.rpm', 'bar-2.noarch.rpm']
|
115
|
+
build_expectations(rpm_files,@epel_dists)
|
116
|
+
@repo.publish(rpm_files, nil)
|
117
|
+
end
|
118
|
+
|
119
|
+
it "globs and installs a directory of noarch rpm files to all distributions/architectures" do
|
120
|
+
rpm_files = ['foo-1.noarch.rpm', 'bar-2.src.rpm']
|
121
|
+
build_expectations(rpm_files,@epel_dists)
|
122
|
+
flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
|
123
|
+
flexmock(Dir) { |obj| obj.should_receive(:glob).and_return(rpm_files) }
|
124
|
+
@repo.publish('somedir/', nil)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "fails with no files in a dir" do
|
128
|
+
flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
|
129
|
+
flexmock(Dir) { |obj| obj.should_receive(:glob).and_return([]) }
|
130
|
+
@repo.should_receive(:system).never
|
131
|
+
expect { @repo.publish('some_dir/', nil) }.to raise_error(RuntimeError)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "fails with files missing a rpm extenstion" do
|
135
|
+
rpm_files = ['foo-1.noarch.rpm', 'bar-2.noarch.deb']
|
136
|
+
@repo.should_receive(:system).never
|
137
|
+
expect { @repo.publish(rpm_files, nil) }.to raise_error(RuntimeError)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "fails with a mix of noarch and binary rpms" do
|
141
|
+
rpm_files = ['foo-1.noarch.rpm', 'bar-2.x86_64.rpm']
|
142
|
+
@repo.should_receive(:system).never
|
143
|
+
expect { @repo.publish(rpm_files, nil) }.to raise_error(RuntimeError)
|
144
|
+
end
|
145
|
+
end # end NoArch context
|
146
|
+
|
147
|
+
context "Architecture Dependant Packages" do
|
148
|
+
before(:each) do
|
149
|
+
@target_distribution_map = {:fc=>['12']}
|
150
|
+
@target_distribution_str = 'fc/12'
|
151
|
+
end
|
152
|
+
|
153
|
+
it "installs a single x86_64 rpm file to a dist" do
|
154
|
+
rpm_file = 'foo-1.x86_64.rpm'
|
155
|
+
build_expectations(rpm_file,@target_distribution_map)
|
156
|
+
@repo.publish(rpm_file, @target_distribution_str)
|
157
|
+
end
|
158
|
+
|
159
|
+
it "installs a list of x86_64 rpms to a dist" do
|
160
|
+
rpm_files = ['foo-1.x86_64.rpm', 'bar-2.x86_64.rpm']
|
161
|
+
build_expectations(rpm_files,@target_distribution_map)
|
162
|
+
@repo.publish(rpm_files, @target_distribution_str)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "installs a directory mixed of x86_64 and i386 rpms to a dist" do
|
166
|
+
rpm_files = ['foo-1.x86_64.rpm', 'bar-2.i386.rpm']
|
167
|
+
build_expectations(rpm_files,@target_distribution_map)
|
168
|
+
flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
|
169
|
+
flexmock(Dir) { |obj| obj.should_receive(:glob).and_return(rpm_files) }
|
170
|
+
@repo.publish('some_dir/', @target_distribution_str)
|
171
|
+
end
|
172
|
+
|
173
|
+
it "fails with no files in a dir" do
|
174
|
+
flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
|
175
|
+
flexmock(Dir) { |obj| obj.should_receive(:glob).and_return([]) }
|
176
|
+
@repo.should_receive(:system).never
|
177
|
+
expect { @repo.publish('some_dir/', @target_distribution_str) }.to raise_error(RuntimeError)
|
178
|
+
end
|
179
|
+
|
180
|
+
it "fails with files missing a rpm extension" do
|
181
|
+
rpm_files = ['foo-1.x86_64.rpm', 'bar-2.i386.deb']
|
182
|
+
@repo.should_receive(:system).never
|
183
|
+
expect { @repo.publish(rpm_files, @target_distribution_str) }.to raise_error(RuntimeError)
|
184
|
+
end
|
185
|
+
|
186
|
+
it "fails with unknown architectures" do
|
187
|
+
rpm_files = ['foo-1.ppc.rpm', 'bar-2.ppc.rpm']
|
188
|
+
@repo.should_receive(:system).never
|
189
|
+
expect { @repo.publish(rpm_files, @target_distribution_str) }.to raise_error(RuntimeError)
|
190
|
+
end
|
191
|
+
|
192
|
+
it "fails when not provided with a target" do
|
193
|
+
rpm_files = ['foo-1.x86_64.rpm', 'bar-2.i386.rpm']
|
194
|
+
@repo.should_receive(:system).never
|
195
|
+
expect { @repo.publish(rpm_files, nil) }.to raise_error(RuntimeError)
|
196
|
+
end
|
197
|
+
end # end Architecture Dependant context
|
198
|
+
end # end EPEL context
|
199
|
+
|
200
|
+
# Test the Flat repo profile type
|
201
|
+
context "Flat layout" do
|
202
|
+
def build_expectations(pkgs)
|
203
|
+
@install_expectations = Array(pkgs)
|
204
|
+
@repo.should_receive(:install_file).and_return do |pkg,dir|
|
205
|
+
@install_expectations.delete(pkg)
|
206
|
+
end
|
207
|
+
system_calls = 1
|
208
|
+
system_sign_calls = (@profile.config[:yum_repo][:gpg_key_id]) ? 1 : 0
|
209
|
+
@repo.should_receive(:system).times(system_calls).and_return(0)
|
210
|
+
@repo.should_receive(:shellout_with_password).times(system_sign_calls).and_return(0)
|
211
|
+
end
|
212
|
+
|
213
|
+
before(:each) do
|
214
|
+
@epel_version = false
|
215
|
+
@install_expectations = []
|
216
|
+
@profile = flexmock(RightPublish::Profile) do |obj|
|
217
|
+
obj.should_receive(:config).and_return {
|
218
|
+
{ :verbose=>false,
|
219
|
+
:remote_storage=>{:provider=>'mockremote'},
|
220
|
+
:local_storage=>{:provider=>'mocklocal'},
|
221
|
+
:yum_repo=>{
|
222
|
+
:epel=>@epel_version,
|
223
|
+
:subdir=>@repo_subdir,
|
224
|
+
:gpg_key_id=>should_sign,
|
225
|
+
:gpg_password=>'fakepass'}
|
226
|
+
}
|
227
|
+
}
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
after(:each) do
|
232
|
+
@install_expectations.should be_empty
|
233
|
+
end
|
234
|
+
|
235
|
+
it "installs a single rpm file to repo" do
|
236
|
+
rpm_file = 'foo-1.noarch.rpm'
|
237
|
+
build_expectations(rpm_file)
|
238
|
+
@repo.publish(rpm_file, nil)
|
239
|
+
end
|
240
|
+
|
241
|
+
it "installs a single src rpm file to repo" do
|
242
|
+
rpm_file = 'foo-1.src.rpm'
|
243
|
+
build_expectations(rpm_file)
|
244
|
+
@repo.publish(rpm_file, nil)
|
245
|
+
end
|
246
|
+
|
247
|
+
it "installs a list of rpm files to repo" do
|
248
|
+
rpm_files = ['foo-1.x86_64.rpm', 'bar-2.x86_64.rpm']
|
249
|
+
build_expectations(rpm_files)
|
250
|
+
@repo.publish(rpm_files, nil)
|
251
|
+
end
|
252
|
+
|
253
|
+
it "globs and installs a directory of rpm files to repo" do
|
254
|
+
rpm_files = ['foo-1.noarch.rpm', 'bar-2.src.rpm']
|
255
|
+
build_expectations(rpm_files)
|
256
|
+
flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
|
257
|
+
flexmock(Dir) { |obj| obj.should_receive(:glob).and_return(rpm_files) }
|
258
|
+
@repo.publish('somedir/', nil)
|
259
|
+
end
|
260
|
+
|
261
|
+
it "succeeds with a mix of noarch and binary rpms" do
|
262
|
+
rpm_files = ['foo-1.noarch.rpm', 'bar-2.x86_64.rpm']
|
263
|
+
build_expectations(rpm_files)
|
264
|
+
@repo.publish(rpm_files, nil)
|
265
|
+
end
|
266
|
+
|
267
|
+
it "fails with no files in a dir" do
|
268
|
+
flexmock(File) { |obj| obj.should_receive(:directory?).and_return(true) }
|
269
|
+
flexmock(Dir) { |obj| obj.should_receive(:glob).and_return([]) }
|
270
|
+
@repo.should_receive(:system).never
|
271
|
+
expect { @repo.publish('some_dir/', nil) }.to raise_error(RuntimeError)
|
272
|
+
end
|
273
|
+
|
274
|
+
it "fails with files missing a rpm extenstion" do
|
275
|
+
rpm_files = ['foo-1.i386.rpm', 'bar-2.noarch.deb']
|
276
|
+
@repo.should_receive(:system).never
|
277
|
+
expect { @repo.publish('some_dir/', nil) }.to raise_error(RuntimeError)
|
278
|
+
end
|
279
|
+
end # end Flat context
|
280
|
+
end # end #publish context
|
281
|
+
end
|
282
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# Copyright (c) 2012- RightScale Inc
|
2
|
+
#
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
# a copy of this software and associated documentation files (the
|
5
|
+
# "Software"), to deal in the Software without restriction, including
|
6
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
# the following conditions:
|
10
|
+
#
|
11
|
+
# The above copyright notice and this permission notice shall be
|
12
|
+
# included in all copies or substantial portions of the Software.
|
13
|
+
#
|
14
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
21
|
+
|
22
|
+
require 'rubygems'
|
23
|
+
require 'bundler/setup'
|
24
|
+
require 'flexmock'
|
25
|
+
require 'ruby-debug'
|
26
|
+
require 'simplecov' unless RUBY_VERSION =~ /1\.8/
|
27
|
+
|
28
|
+
RSpec.configure do |config|
|
29
|
+
config.mock_with :flexmock
|
30
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'right_publish/storage'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe RightPublish::StorageManager do
|
5
|
+
|
6
|
+
def is_registered?(name, key)
|
7
|
+
@storage_types = RightPublish::StorageManager.storage_types
|
8
|
+
@storage_types.should have_key(name)
|
9
|
+
@storage_types[name].should equal(key)
|
10
|
+
RightPublish::StorageManager.get_storage(key)::STORAGE_KEY.should be_equal(key)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should provide a hash of known storage types" do
|
14
|
+
RightPublish::StorageManager.storage_types.should be_instance_of(Hash)
|
15
|
+
end
|
16
|
+
|
17
|
+
it "returns nil for unknown storage types" do
|
18
|
+
RightPublish::StorageManager.get_storage(:doesnotexist_storage).should be_equal(nil)
|
19
|
+
end
|
20
|
+
|
21
|
+
it "should know about the local storage" do
|
22
|
+
is_registered?('local', :local_storage)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should know about the s3 storage" do
|
26
|
+
is_registered?('s3', :s3_storage)
|
27
|
+
end
|
28
|
+
|
29
|
+
context ".register_storage" do
|
30
|
+
it "rejects storage without a STORAGE_KEY" do
|
31
|
+
class MissingKey
|
32
|
+
end
|
33
|
+
expect { RightPublish::StorageManager.register_storage(MissingKey) }.to raise_error(NameError)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "rejects storage whose key does not end in _storage" do
|
37
|
+
class BadKey
|
38
|
+
STORAGE_KEY = :bad_key
|
39
|
+
end
|
40
|
+
expect { RightPublish::StorageManager.register_storage(BadKey) }.to raise_error(TypeError)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "rejects storage who do not provide .get_directories" do
|
44
|
+
class NoDirectories
|
45
|
+
STORAGE_KEY = :nooptions_storage
|
46
|
+
end
|
47
|
+
expect { RightPublish::StorageManager.register_storage(NoDirectories) }.to raise_error(TypeError)
|
48
|
+
end
|
49
|
+
|
50
|
+
it "rejects storage who do not provide options" do
|
51
|
+
class NoOptions
|
52
|
+
STORAGE_KEY = :nooptions_storage
|
53
|
+
def self.get_directories()
|
54
|
+
end
|
55
|
+
end
|
56
|
+
expect { RightPublish::StorageManager.register_storage(NoOptions) }.to raise_error(NameError)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "accpets storage who are properly defined" do
|
60
|
+
class GoodStorage
|
61
|
+
STORAGE_KEY = :good_storage
|
62
|
+
STORAGE_OPTIONS = {}
|
63
|
+
def self.get_directories()
|
64
|
+
end
|
65
|
+
end
|
66
|
+
RightPublish::StorageManager.register_storage(GoodStorage).should be_equal(nil)
|
67
|
+
is_registered?('good', :good_storage)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,317 @@
|
|
1
|
+
require 'right_publish/storage'
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe RightPublish::Storage do
|
5
|
+
|
6
|
+
# Create a File entity.
|
7
|
+
def make_file(name, expects, copy, destroy)
|
8
|
+
file = flexmock("File:#{name}")
|
9
|
+
|
10
|
+
# We can set some expectations on it like if we believe we're
|
11
|
+
# going to be removed from Storage.
|
12
|
+
destroy_calls = 0
|
13
|
+
munge_body = false
|
14
|
+
if expects.instance_of? Hash
|
15
|
+
destroy_calls = (expects.has_key?(:destroy) && expects[:destroy] && 1) || 0
|
16
|
+
munge_body = (expects.has_key?(:munge_body) && expects[:munge_body]) || false
|
17
|
+
end
|
18
|
+
|
19
|
+
# For simplicity use the filename as the "contents" but provide a
|
20
|
+
# way to corrupt it to force testing content, not name matching.
|
21
|
+
file.should_receive(:body).and_return { (munge_body && 'data_mismatch') || name }
|
22
|
+
|
23
|
+
# Use the copy callback to insert a copy of ourselves at a different
|
24
|
+
# path (Remote duplication).
|
25
|
+
file.should_receive(:copy).and_return do |dir_key,dest|
|
26
|
+
new_file = make_file(dest, nil, copy, destroy)
|
27
|
+
copy.call(new_file)
|
28
|
+
end
|
29
|
+
|
30
|
+
# Use the destroy callback to remove outselves from the Files obj.
|
31
|
+
file.should_receive(:destroy).times(destroy_calls).and_return { destroy.call(name) }
|
32
|
+
file.should_receive(:key).and_return(name)
|
33
|
+
file
|
34
|
+
end
|
35
|
+
|
36
|
+
# Create a Fog Storage Mock
|
37
|
+
def make_storage(path, file_hash, expectations)
|
38
|
+
# List of File mocks bound to this storage instance
|
39
|
+
files = []
|
40
|
+
|
41
|
+
# These procedures are used as callbacks for the File mocks to copy
|
42
|
+
# or destroy themselves and have a side-effect on the about Array
|
43
|
+
create_proc = Proc.new { |file| files << file }
|
44
|
+
delete_proc = Proc.new { |file| files.delete_if { |f| f.key == file } }
|
45
|
+
|
46
|
+
# Create the File mocks and store in the Array
|
47
|
+
file_hash.each_pair do |name,expects|
|
48
|
+
files << make_file(name, expects, create_proc, delete_proc )
|
49
|
+
end
|
50
|
+
|
51
|
+
# Fog has a weird notion of a 'Files' and 'Directory' object being
|
52
|
+
# distinct. We care here because create is called on the 'Files'
|
53
|
+
# rather then on the 'Directory' and it is Enumerable.
|
54
|
+
file_list = flexmock(files)
|
55
|
+
file_list.should_receive(:create).
|
56
|
+
times(expectations[:create_call]).
|
57
|
+
and_return do |args|
|
58
|
+
new_file = make_file( args[:key], nil, create_proc, delete_proc )
|
59
|
+
files << new_file
|
60
|
+
new_file
|
61
|
+
end
|
62
|
+
|
63
|
+
# The only thing ever called on the Storage object is the
|
64
|
+
# #get_directories interface, or at least that's all we use.
|
65
|
+
obj = flexmock('Storage')
|
66
|
+
obj.should_receive(:get_directories).and_return do
|
67
|
+
# The Directory object is synonymous to an S3 bucket object
|
68
|
+
# and is responsible for providing the Files enumerable and
|
69
|
+
# calculating the MD5 hash of a given file.
|
70
|
+
f = flexmock('Directory')
|
71
|
+
f.should_receive(:compute_md5).and_return {|file| Digest::MD5.hexdigest(File.basename(file.body))}
|
72
|
+
f.should_receive(:files).and_return(file_list)
|
73
|
+
f.should_receive(:key).and_return(path)
|
74
|
+
f
|
75
|
+
end
|
76
|
+
obj
|
77
|
+
end
|
78
|
+
|
79
|
+
# In order to match we just make sure that the sizes of the Files
|
80
|
+
# objects match and that every File within A is also within B. We don't
|
81
|
+
# have to check the reverse since the sizes implies it.
|
82
|
+
def storage_match?(a,b)
|
83
|
+
a_files = a.get_directories.files
|
84
|
+
b_files = b.get_directories.files
|
85
|
+
|
86
|
+
return false if a_files.count != b_files.count
|
87
|
+
a_files.all? { |a_f|
|
88
|
+
name = a_f.key
|
89
|
+
b_files.any? { |b_f| name == b_f.key }
|
90
|
+
}
|
91
|
+
end
|
92
|
+
|
93
|
+
before(:each) do
|
94
|
+
flexmock(RightPublish::Profile) do |obj|
|
95
|
+
obj.should_receive(:log).and_return { |str,debug|
|
96
|
+
}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context ".sync_dirs" do
|
101
|
+
it "handles source AND destination storages that are empty" do
|
102
|
+
src_storage = make_storage(
|
103
|
+
'foo',
|
104
|
+
{},
|
105
|
+
{:create_call=>0} )
|
106
|
+
|
107
|
+
# First copy of file1 will use create, second will be duped
|
108
|
+
dest_storage = make_storage(
|
109
|
+
'bar',
|
110
|
+
{},
|
111
|
+
{:create_call=>0} )
|
112
|
+
|
113
|
+
RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
|
114
|
+
storage_match?(src_storage, dest_storage).should be_true
|
115
|
+
end
|
116
|
+
|
117
|
+
it "does nothing with identical directories" do
|
118
|
+
src_storage = make_storage(
|
119
|
+
'foo',
|
120
|
+
{ 'dir1/file1'=>nil,
|
121
|
+
'dir1/file2'=>nil,
|
122
|
+
'file3'=>nil },
|
123
|
+
{:create_call=>0} )
|
124
|
+
|
125
|
+
dest_storage = make_storage(
|
126
|
+
'bar',
|
127
|
+
{ 'dir1/file1'=>nil,
|
128
|
+
'dir1/file2'=>nil,
|
129
|
+
'file3'=>nil },
|
130
|
+
{:create_call=>0} )
|
131
|
+
|
132
|
+
RightPublish::Storage.sync_dirs(src_storage, dest_storage)
|
133
|
+
storage_match?(src_storage, dest_storage).should be_true
|
134
|
+
end
|
135
|
+
|
136
|
+
it "copies a new file to the destination" do
|
137
|
+
src_storage = make_storage(
|
138
|
+
'foo',
|
139
|
+
{ 'dir1/file1'=>nil,
|
140
|
+
'dir1/file2'=>nil,
|
141
|
+
'file3'=>nil,
|
142
|
+
'file4'=>nil },
|
143
|
+
{:create_call=>0} )
|
144
|
+
|
145
|
+
dest_storage = make_storage(
|
146
|
+
'bar',
|
147
|
+
{ 'dir1/file1'=>nil,
|
148
|
+
'dir1/file2'=>nil,
|
149
|
+
'file3'=>nil },
|
150
|
+
{:create_call=>1} )
|
151
|
+
|
152
|
+
RightPublish::Storage.sync_dirs(src_storage, dest_storage)
|
153
|
+
storage_match?(src_storage, dest_storage).should be_true
|
154
|
+
end
|
155
|
+
|
156
|
+
it "handles source storages that are empty" do
|
157
|
+
src_storage = make_storage(
|
158
|
+
'foo',
|
159
|
+
{},
|
160
|
+
{:create_call=>0} )
|
161
|
+
|
162
|
+
# First copy of file1 will use create, second will be duped
|
163
|
+
dest_storage = make_storage(
|
164
|
+
'bar',
|
165
|
+
{ 'dir1/file1'=>{:destroy=>true} },
|
166
|
+
{:create_call=>0} )
|
167
|
+
|
168
|
+
RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
|
169
|
+
storage_match?(src_storage, dest_storage).should be_true
|
170
|
+
end
|
171
|
+
|
172
|
+
it "handles destination storages that are empty" do
|
173
|
+
src_storage = make_storage(
|
174
|
+
'foo',
|
175
|
+
{ 'dir1/file1'=>nil },
|
176
|
+
{:create_call=>0} )
|
177
|
+
|
178
|
+
# First copy of file1 will use create, second will be duped
|
179
|
+
dest_storage = make_storage(
|
180
|
+
'bar',
|
181
|
+
{},
|
182
|
+
{:create_call=>1} )
|
183
|
+
|
184
|
+
RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
|
185
|
+
storage_match?(src_storage, dest_storage).should be_true
|
186
|
+
end
|
187
|
+
|
188
|
+
it "leaves an extraneous file in the destination w/o :sweep" do
|
189
|
+
src_storage = make_storage(
|
190
|
+
'foo',
|
191
|
+
{ 'dir1/file1'=>nil,
|
192
|
+
'dir1/file2'=>nil,
|
193
|
+
'file3'=>nil },
|
194
|
+
{:create_call=>0} )
|
195
|
+
|
196
|
+
dest_storage = make_storage(
|
197
|
+
'bar',
|
198
|
+
{ 'dir1/file1'=>nil,
|
199
|
+
'dir1/file2'=>nil,
|
200
|
+
'file3'=>nil,
|
201
|
+
'file4'=>nil },
|
202
|
+
{:create_call=>0} )
|
203
|
+
|
204
|
+
RightPublish::Storage.sync_dirs(src_storage, dest_storage)
|
205
|
+
|
206
|
+
# We should NOT match here. Not sure what the usecase of this
|
207
|
+
# is, but it's tested anyways.
|
208
|
+
storage_match?(src_storage, dest_storage).should be_false
|
209
|
+
end
|
210
|
+
|
211
|
+
it "destroys an extraneous file from the destination w/ :sweep" do
|
212
|
+
src_storage = make_storage(
|
213
|
+
'foo',
|
214
|
+
{ 'dir1/file1'=>nil,
|
215
|
+
'dir1/file2'=>nil,
|
216
|
+
'file3'=>nil },
|
217
|
+
{:create_call=>0} )
|
218
|
+
|
219
|
+
dest_storage = make_storage(
|
220
|
+
'bar',
|
221
|
+
{ 'dir1/file1'=>nil,
|
222
|
+
'dir1/file2'=>nil,
|
223
|
+
'file3'=>nil,
|
224
|
+
'file4'=>{:destroy=>true} },
|
225
|
+
{:create_call=>0} )
|
226
|
+
|
227
|
+
RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
|
228
|
+
storage_match?(src_storage, dest_storage).should be_true
|
229
|
+
end
|
230
|
+
|
231
|
+
it "destroys and copies files as needed to synchronize directories" do
|
232
|
+
src_storage = make_storage(
|
233
|
+
'foo',
|
234
|
+
{ 'dir1/file1'=>nil,
|
235
|
+
'dir1/file2'=>nil,
|
236
|
+
'file3'=>nil,
|
237
|
+
'file4'=>nil,
|
238
|
+
'file6'=>nil },
|
239
|
+
{:create_call=>0} )
|
240
|
+
|
241
|
+
dest_storage = make_storage(
|
242
|
+
'bar',
|
243
|
+
{ 'dir1/file1'=>nil,
|
244
|
+
'dir1/file2'=>nil,
|
245
|
+
'file3'=>nil,
|
246
|
+
'file5'=>{:destroy=>true},
|
247
|
+
'dir2/file2'=>{:destroy=>true} },
|
248
|
+
{:create_call=>2} )
|
249
|
+
|
250
|
+
RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
|
251
|
+
storage_match?(src_storage, dest_storage).should be_true
|
252
|
+
end
|
253
|
+
|
254
|
+
it "uses the remote copy facilities when making duplicates" do
|
255
|
+
src_storage = make_storage(
|
256
|
+
'foo',
|
257
|
+
{ 'dir1/file1'=>nil,
|
258
|
+
'dir1/file2'=>nil,
|
259
|
+
'dir2/file1'=>nil,
|
260
|
+
'file4'=>nil },
|
261
|
+
{:create_call=>0} )
|
262
|
+
|
263
|
+
# There are two duplicates, 'file1', in the local store. To save
|
264
|
+
# time/data we just call copy on the file that already exists in
|
265
|
+
# the remote, notice the '0' create_calls.
|
266
|
+
dest_storage = make_storage(
|
267
|
+
'bar',
|
268
|
+
{ 'dir1/file1'=>nil,
|
269
|
+
'dir1/file2'=>nil,
|
270
|
+
'file4'=>nil },
|
271
|
+
{:create_call=>0} )
|
272
|
+
|
273
|
+
RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
|
274
|
+
storage_match?(src_storage, dest_storage).should be_true
|
275
|
+
end
|
276
|
+
|
277
|
+
it "uses the content of the file, not the name to determine equality" do
|
278
|
+
src_storage = make_storage(
|
279
|
+
'foo',
|
280
|
+
{ 'dir1/file1'=>nil,
|
281
|
+
'dir1/file2'=>nil,
|
282
|
+
'file4'=>nil },
|
283
|
+
{:create_call=>0} )
|
284
|
+
|
285
|
+
# :munge_body cause the MD5 sum to mismatch the file name
|
286
|
+
dest_storage = make_storage(
|
287
|
+
'bar',
|
288
|
+
{ 'dir1/file1'=>{:munge_body=>true,:destroy=>true},
|
289
|
+
'dir1/file2'=>nil,
|
290
|
+
'file4'=>nil },
|
291
|
+
{:create_call=>1} )
|
292
|
+
|
293
|
+
RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
|
294
|
+
storage_match?(src_storage, dest_storage).should be_true
|
295
|
+
end
|
296
|
+
|
297
|
+
it "uses create AND duplicate routines with multiple new identical files" do
|
298
|
+
src_storage = make_storage(
|
299
|
+
'foo',
|
300
|
+
{ 'dir1/file1'=>nil,
|
301
|
+
'dir1/file2'=>nil,
|
302
|
+
'dir2/dir3/file2'=>nil,
|
303
|
+
'file2'=>nil },
|
304
|
+
{:create_call=>0} )
|
305
|
+
|
306
|
+
# First copy of file1 will use create, the rest will be duped
|
307
|
+
dest_storage = make_storage(
|
308
|
+
'bar',
|
309
|
+
{ 'dir1/file1'=>nil },
|
310
|
+
{:create_call=>1} )
|
311
|
+
|
312
|
+
RightPublish::Storage.sync_dirs(src_storage, dest_storage, :sweep=>true)
|
313
|
+
storage_match?(src_storage, dest_storage).should be_true
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|