buildpack-packager 2.3.4
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.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/Gemfile +5 -0
- data/LICENSE +176 -0
- data/README.md +180 -0
- data/Rakefile +6 -0
- data/bin/buildpack-packager +79 -0
- data/buildpack-packager.gemspec +33 -0
- data/doc/disconnected_environments.md +50 -0
- data/lib/buildpack/manifest_dependency.rb +14 -0
- data/lib/buildpack/manifest_validator.rb +88 -0
- data/lib/buildpack/packager.rb +55 -0
- data/lib/buildpack/packager/default_versions_presenter.rb +38 -0
- data/lib/buildpack/packager/dependencies_presenter.rb +41 -0
- data/lib/buildpack/packager/manifest_schema.yml +67 -0
- data/lib/buildpack/packager/package.rb +139 -0
- data/lib/buildpack/packager/table_presentation.rb +21 -0
- data/lib/buildpack/packager/version.rb +5 -0
- data/lib/buildpack/packager/zip_file_excluder.rb +31 -0
- data/lib/kwalify/parser/yaml-patcher.rb +70 -0
- data/spec/buildpack/packager_spec.rb +7 -0
- data/spec/fixtures/buildpack-with-uri-credentials/VERSION +1 -0
- data/spec/fixtures/buildpack-with-uri-credentials/manifest.yml +36 -0
- data/spec/fixtures/buildpack-without-uri-credentials/VERSION +1 -0
- data/spec/fixtures/buildpack-without-uri-credentials/manifest.yml +36 -0
- data/spec/fixtures/manifests/manifest_invalid-md6.yml +18 -0
- data/spec/fixtures/manifests/manifest_invalid-md6_and_defaults.yml +21 -0
- data/spec/fixtures/manifests/manifest_valid.yml +19 -0
- data/spec/helpers/cache_directory_helpers.rb +15 -0
- data/spec/helpers/fake_binary_hosting_helpers.rb +21 -0
- data/spec/helpers/file_system_helpers.rb +35 -0
- data/spec/integration/bin/buildpack_packager/download_caching_spec.rb +85 -0
- data/spec/integration/bin/buildpack_packager_spec.rb +378 -0
- data/spec/integration/buildpack/directory_name_spec.rb +89 -0
- data/spec/integration/buildpack/packager_spec.rb +454 -0
- data/spec/integration/default_versions_spec.rb +170 -0
- data/spec/integration/output_spec.rb +70 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/unit/buildpack/packager/zip_file_excluder_spec.rb +68 -0
- data/spec/unit/manifest_dependency_spec.rb +41 -0
- data/spec/unit/manifest_validator_spec.rb +35 -0
- data/spec/unit/packager/package_spec.rb +196 -0
- metadata +235 -0
@@ -0,0 +1,454 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
module Buildpack
|
5
|
+
describe Packager do
|
6
|
+
let(:tmp_dir) do
|
7
|
+
dir = FileUtils.mkdir_p(File.join(Dir.mktmpdir, rand.to_s[2..-1]))[0]
|
8
|
+
puts dir
|
9
|
+
dir
|
10
|
+
end
|
11
|
+
let(:buildpack_dir) { File.join(tmp_dir, 'sample-buildpack-root-dir') }
|
12
|
+
let(:cache_dir) { File.join(tmp_dir, 'cache-dir') }
|
13
|
+
let(:file_location) do
|
14
|
+
location = File.join(tmp_dir, 'sample_host')
|
15
|
+
File.write(location, 'contents!')
|
16
|
+
location
|
17
|
+
end
|
18
|
+
let(:translated_file_location) { 'file___' + file_location.gsub(/[:\/]/, '_') }
|
19
|
+
|
20
|
+
let(:md5) { Digest::MD5.file(file_location).hexdigest }
|
21
|
+
|
22
|
+
let(:options) do
|
23
|
+
{
|
24
|
+
root_dir: buildpack_dir,
|
25
|
+
mode: buildpack_mode,
|
26
|
+
cache_dir: cache_dir,
|
27
|
+
manifest_path: manifest_path
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
let(:manifest_path) { 'manifest.yml' }
|
32
|
+
let(:manifest) do
|
33
|
+
{
|
34
|
+
exclude_files: [],
|
35
|
+
language: 'sample',
|
36
|
+
url_to_dependency_map: [{
|
37
|
+
match: 'ruby-(d+.d+.d+)',
|
38
|
+
name: 'ruby',
|
39
|
+
version: '$1'
|
40
|
+
}],
|
41
|
+
dependencies: [{
|
42
|
+
'version' => '1.0',
|
43
|
+
'name' => 'etc_host',
|
44
|
+
'md5' => md5,
|
45
|
+
'uri' => "file://#{file_location}",
|
46
|
+
'cf_stacks' => ['cflinuxfs2']
|
47
|
+
}]
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
let(:buildpack_files) do
|
52
|
+
[
|
53
|
+
'VERSION',
|
54
|
+
'README.md',
|
55
|
+
'lib/sai.to',
|
56
|
+
'lib/rash',
|
57
|
+
'log/log.txt',
|
58
|
+
'first-level/log/log.txt',
|
59
|
+
'log.txt',
|
60
|
+
'blog.txt',
|
61
|
+
'blog/blog.txt',
|
62
|
+
'.gitignore',
|
63
|
+
'.gitmodules',
|
64
|
+
'lib/.git'
|
65
|
+
]
|
66
|
+
end
|
67
|
+
|
68
|
+
let(:git_files) { ['.gitignore', '.gitmodules', 'lib/.git'] }
|
69
|
+
let(:cached_file) { File.join(cache_dir, translated_file_location) }
|
70
|
+
|
71
|
+
def create_manifest(options = {})
|
72
|
+
manifest.merge!(options)
|
73
|
+
File.write(File.join(buildpack_dir, manifest_path), manifest.to_yaml)
|
74
|
+
end
|
75
|
+
|
76
|
+
before do
|
77
|
+
make_fake_files(buildpack_dir, buildpack_files)
|
78
|
+
buildpack_files << 'manifest.yml'
|
79
|
+
create_manifest
|
80
|
+
`echo "1.2.3" > #{File.join(buildpack_dir, 'VERSION')}`
|
81
|
+
|
82
|
+
@pwd ||= Dir.pwd
|
83
|
+
Dir.chdir(buildpack_dir)
|
84
|
+
end
|
85
|
+
|
86
|
+
after do
|
87
|
+
Dir.chdir(@pwd)
|
88
|
+
FileUtils.remove_entry tmp_dir
|
89
|
+
end
|
90
|
+
|
91
|
+
describe '#list' do
|
92
|
+
let(:buildpack_mode) { :list }
|
93
|
+
|
94
|
+
context 'default manifest.yml' do
|
95
|
+
specify do
|
96
|
+
create_manifest
|
97
|
+
table = Packager.list(options)
|
98
|
+
expect(table.to_s).to match(/etc_host.*1\.0.*cflinuxfs2/)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context 'alternate manifest path' do
|
103
|
+
let(:manifest_path) { 'my-manifest.yml' }
|
104
|
+
|
105
|
+
specify do
|
106
|
+
create_manifest
|
107
|
+
table = Packager.list(options)
|
108
|
+
expect(table.to_s).to match(/etc_host.*1\.0.*cflinuxfs2/)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'sorted output' do
|
113
|
+
def create_manifest_dependency_skeleton(dependencies)
|
114
|
+
manifest = {}
|
115
|
+
manifest['dependencies'] = []
|
116
|
+
dependencies.each do |dependency|
|
117
|
+
manifest['dependencies'].push('name' => dependency.first,
|
118
|
+
'version' => dependency.last,
|
119
|
+
'cf_stacks' => ['cflinuxfs2'])
|
120
|
+
end
|
121
|
+
File.write(File.join(buildpack_dir, manifest_path), manifest.to_yaml)
|
122
|
+
end
|
123
|
+
|
124
|
+
%w(go hhvm jruby node php python ruby).each do |interpreter|
|
125
|
+
it "sorts #{interpreter} interpreter first" do
|
126
|
+
create_manifest_dependency_skeleton([
|
127
|
+
['aaaaa', '1.0'],
|
128
|
+
[interpreter, '1.0'],
|
129
|
+
['zzzzz', '1.0']
|
130
|
+
])
|
131
|
+
table = Packager.list(options)
|
132
|
+
stdout = table.to_s.split("\n")
|
133
|
+
|
134
|
+
position_of_a = stdout.index(stdout.grep(/aaaaa/).first)
|
135
|
+
position_of_interpreter = stdout.index(stdout.grep(/ #{interpreter} /).first)
|
136
|
+
position_of_z = stdout.index(stdout.grep(/zzzzz/).first)
|
137
|
+
|
138
|
+
expect(position_of_interpreter).to be < position_of_a
|
139
|
+
expect(position_of_interpreter).to be < position_of_z
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
it 'sorts using `name` as secondary key' do
|
144
|
+
create_manifest_dependency_skeleton([
|
145
|
+
['b_foobar', '1.0'],
|
146
|
+
['a_foobar', '1.0'],
|
147
|
+
['c_foobar', '1.0']
|
148
|
+
])
|
149
|
+
table = Packager.list(options)
|
150
|
+
stdout = table.to_s.split("\n")
|
151
|
+
|
152
|
+
position_of_a = stdout.index(stdout.grep(/a_foobar/).first)
|
153
|
+
position_of_b = stdout.index(stdout.grep(/b_foobar/).first)
|
154
|
+
position_of_c = stdout.index(stdout.grep(/c_foobar/).first)
|
155
|
+
|
156
|
+
expect(position_of_a).to be < position_of_b
|
157
|
+
expect(position_of_b).to be < position_of_c
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'sorts using `version` as secondary key' do
|
161
|
+
create_manifest_dependency_skeleton([
|
162
|
+
['foobar', '1.1'],
|
163
|
+
['foobar', '1.2'],
|
164
|
+
['foobar', '1.0']
|
165
|
+
])
|
166
|
+
table = Packager.list(options)
|
167
|
+
stdout = table.to_s.split("\n")
|
168
|
+
|
169
|
+
position_of_10 = stdout.index(stdout.grep(/1\.0/).first)
|
170
|
+
position_of_11 = stdout.index(stdout.grep(/1\.1/).first)
|
171
|
+
position_of_12 = stdout.index(stdout.grep(/1\.2/).first)
|
172
|
+
|
173
|
+
expect(position_of_10).to be < position_of_11
|
174
|
+
expect(position_of_11).to be < position_of_12
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
describe 'a well formed zip file name' do
|
180
|
+
context 'an uncached buildpack' do
|
181
|
+
let(:buildpack_mode) { :uncached }
|
182
|
+
|
183
|
+
specify do
|
184
|
+
Packager.package(options)
|
185
|
+
|
186
|
+
expect(all_files(buildpack_dir)).to include('sample_buildpack-v1.2.3.zip')
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
context 'a cached buildpack' do
|
191
|
+
let(:buildpack_mode) { :cached }
|
192
|
+
|
193
|
+
specify do
|
194
|
+
puts `ls #{buildpack_dir}`
|
195
|
+
|
196
|
+
puts Packager.package(options)
|
197
|
+
|
198
|
+
expect(all_files(buildpack_dir)).to include('sample_buildpack-cached-v1.2.3.zip')
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
describe 'the zip file contents' do
|
204
|
+
context 'an uncached buildpack' do
|
205
|
+
let(:buildpack_mode) { :uncached }
|
206
|
+
|
207
|
+
specify do
|
208
|
+
Packager.package(options)
|
209
|
+
|
210
|
+
zip_file_path = File.join(buildpack_dir, 'sample_buildpack-v1.2.3.zip')
|
211
|
+
zip_contents = get_zip_contents(zip_file_path)
|
212
|
+
|
213
|
+
expect(zip_contents).to match_array(buildpack_files - git_files)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
context 'a cached buildpack' do
|
218
|
+
let(:buildpack_mode) { :cached }
|
219
|
+
|
220
|
+
specify do
|
221
|
+
Packager.package(options)
|
222
|
+
|
223
|
+
zip_file_path = File.join(buildpack_dir, 'sample_buildpack-cached-v1.2.3.zip')
|
224
|
+
zip_contents = get_zip_contents(zip_file_path)
|
225
|
+
dependencies = ["dependencies/#{translated_file_location}"]
|
226
|
+
|
227
|
+
expect(zip_contents).to match_array(buildpack_files + dependencies - git_files)
|
228
|
+
end
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
describe 'excluded files' do
|
233
|
+
let(:buildpack_mode) { :uncached }
|
234
|
+
|
235
|
+
context 'when specifying files for exclusion' do
|
236
|
+
it 'excludes .git files from zip files' do
|
237
|
+
create_manifest(exclude_files: ['.gitignore'])
|
238
|
+
Packager.package(options)
|
239
|
+
|
240
|
+
zip_file_path = File.join(buildpack_dir, 'sample_buildpack-v1.2.3.zip')
|
241
|
+
zip_contents = get_zip_contents(zip_file_path)
|
242
|
+
|
243
|
+
expect(zip_contents).to_not include('.gitignore')
|
244
|
+
expect(zip_contents).to_not include('.gitmodules')
|
245
|
+
expect(zip_contents).to_not include('lib/.git')
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
context 'when using a directory pattern in exclude_files' do
|
250
|
+
it 'excludes directories with that name' do
|
251
|
+
create_manifest(exclude_files: ['log/'])
|
252
|
+
Packager.package(options)
|
253
|
+
|
254
|
+
zip_file_path = File.join(buildpack_dir, 'sample_buildpack-v1.2.3.zip')
|
255
|
+
zip_contents = get_zip_contents(zip_file_path)
|
256
|
+
|
257
|
+
expect(zip_contents).to_not include('first-level/log/log.txt')
|
258
|
+
expect(zip_contents).to_not include('log/log.txt')
|
259
|
+
expect(zip_contents).to include('blog/blog.txt')
|
260
|
+
expect(zip_contents).to include('log.txt')
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
context 'when using glob patterns in exclude_files' do
|
265
|
+
it 'can accept glob patterns' do
|
266
|
+
create_manifest(exclude_files: ['*log.txt'])
|
267
|
+
Packager.package(options)
|
268
|
+
|
269
|
+
zip_file_path = File.join(buildpack_dir, 'sample_buildpack-v1.2.3.zip')
|
270
|
+
zip_contents = get_zip_contents(zip_file_path)
|
271
|
+
|
272
|
+
expect(zip_contents).to_not include('log.txt')
|
273
|
+
expect(zip_contents).to_not include('log/log.txt')
|
274
|
+
expect(zip_contents).to_not include('first-level/log/log.txt')
|
275
|
+
expect(zip_contents).to_not include('blog/blog.txt')
|
276
|
+
expect(zip_contents).to_not include('blog.txt')
|
277
|
+
end
|
278
|
+
|
279
|
+
it 'does not do fuzzy matching by default' do
|
280
|
+
create_manifest(exclude_files: ['log.txt'])
|
281
|
+
Packager.package(options)
|
282
|
+
|
283
|
+
zip_file_path = File.join(buildpack_dir, 'sample_buildpack-v1.2.3.zip')
|
284
|
+
zip_contents = get_zip_contents(zip_file_path)
|
285
|
+
|
286
|
+
expect(zip_contents).to_not include('log.txt')
|
287
|
+
expect(zip_contents).to include('blog.txt')
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
describe 'caching of dependencies' do
|
293
|
+
context 'an uncached buildpack' do
|
294
|
+
let(:buildpack_mode) { :uncached }
|
295
|
+
|
296
|
+
specify do
|
297
|
+
Packager.package(options)
|
298
|
+
|
299
|
+
expect(File).to_not exist(cached_file)
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
context 'a cached buildpack' do
|
304
|
+
let(:buildpack_mode) { :cached }
|
305
|
+
|
306
|
+
context 'by default' do
|
307
|
+
specify do
|
308
|
+
Packager.package(options)
|
309
|
+
expect(File).to exist(cached_file)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
context 'with the force download enabled' do
|
314
|
+
context 'and the cached file does not exist' do
|
315
|
+
it 'will overwrite the cache file' do
|
316
|
+
expect(File).to_not exist(cached_file)
|
317
|
+
|
318
|
+
Packager.package(options.merge(force_download: true))
|
319
|
+
|
320
|
+
expect(File).to exist(cached_file)
|
321
|
+
expect(Digest::MD5.file(cached_file).hexdigest).to eq md5
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
context 'and the request fails' do
|
326
|
+
let(:file_location) { 'fake-file-that-no-one-should-have.txt' }
|
327
|
+
let(:md5) { nil }
|
328
|
+
|
329
|
+
it 'does not cache the file' do
|
330
|
+
expect(File).to_not exist(cached_file)
|
331
|
+
|
332
|
+
expect do
|
333
|
+
Packager.package(options.merge(force_download: true))
|
334
|
+
end.to raise_error(RuntimeError)
|
335
|
+
|
336
|
+
expect(File).to_not exist(cached_file)
|
337
|
+
end
|
338
|
+
|
339
|
+
it 'raises an error about a failed download' do
|
340
|
+
expect do
|
341
|
+
Packager.package(options.merge(force_download: true))
|
342
|
+
end.to raise_error(RuntimeError, 'Failed to download file from file://fake-file-that-no-one-should-have.txt')
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
context 'on subsequent calls' do
|
347
|
+
context 'and they are successful' do
|
348
|
+
it 'does not use the cached file and overwrites it' do
|
349
|
+
Packager.package(options.merge(force_download: true))
|
350
|
+
File.write(cached_file, 'asdf')
|
351
|
+
|
352
|
+
Packager.package(options.merge(force_download: true))
|
353
|
+
expect(Digest::MD5.file(cached_file).hexdigest).to eq md5
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
context 'and they fail' do
|
358
|
+
it 'does not override the cached file' do
|
359
|
+
Packager.package(options.merge(force_download: true))
|
360
|
+
File.write(cached_file, 'asdf')
|
361
|
+
|
362
|
+
File.delete(file_location)
|
363
|
+
|
364
|
+
expect do
|
365
|
+
Packager.package(options.merge(force_download: true))
|
366
|
+
end.to raise_error(RuntimeError)
|
367
|
+
|
368
|
+
expect(File.read(cached_file)).to eq 'asdf'
|
369
|
+
end
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|
373
|
+
|
374
|
+
context 'with the force download disabled' do
|
375
|
+
context 'and the cached file does not exist' do
|
376
|
+
it 'will write the cache file' do
|
377
|
+
Packager.package(options.merge(force_download: false))
|
378
|
+
expect(File).to exist(cached_file)
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
context 'on subsequent calls' do
|
383
|
+
it 'does use the cached file' do
|
384
|
+
Packager.package(options.merge(force_download: false))
|
385
|
+
|
386
|
+
expect_any_instance_of(Packager::Package).not_to receive(:download_file)
|
387
|
+
Packager.package(options.merge(force_download: false))
|
388
|
+
end
|
389
|
+
end
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
describe 'when checking checksums' do
|
395
|
+
context 'with an invalid MD5' do
|
396
|
+
let(:md5) { 'wompwomp' }
|
397
|
+
|
398
|
+
context 'in cached mode' do
|
399
|
+
let(:buildpack_mode) { :cached }
|
400
|
+
|
401
|
+
it 'raises an error' do
|
402
|
+
expect do
|
403
|
+
Packager.package(options)
|
404
|
+
end.to raise_error(Packager::CheckSumError)
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
context 'in uncached mode' do
|
409
|
+
let(:buildpack_mode) { :uncached }
|
410
|
+
|
411
|
+
it 'does not raise an error' do
|
412
|
+
expect do
|
413
|
+
Packager.package(options)
|
414
|
+
end.to_not raise_error
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
describe 'existence of zip' do
|
421
|
+
let(:buildpack_mode) { :uncached }
|
422
|
+
|
423
|
+
context 'zip is installed' do
|
424
|
+
specify do
|
425
|
+
expect { Packager.package(options) }.not_to raise_error
|
426
|
+
end
|
427
|
+
end
|
428
|
+
|
429
|
+
context 'zip is not installed' do
|
430
|
+
before do
|
431
|
+
allow(Open3).to receive(:capture3)
|
432
|
+
.with('which zip')
|
433
|
+
.and_return(['', '', 'exit 1'])
|
434
|
+
end
|
435
|
+
|
436
|
+
specify do
|
437
|
+
expect { Packager.package(options) }.to raise_error(RuntimeError)
|
438
|
+
end
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
442
|
+
describe 'avoid changing state of buildpack folder, other than creating the artifact (.zip)' do
|
443
|
+
context 'create an cached buildpack' do
|
444
|
+
let(:buildpack_mode) { :cached }
|
445
|
+
|
446
|
+
specify 'user does not see dependencies directory in their buildpack folder' do
|
447
|
+
Packager.package(options)
|
448
|
+
|
449
|
+
expect(all_files(buildpack_dir)).not_to include('dependencies')
|
450
|
+
end
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|