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.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +5 -0
  5. data/LICENSE +176 -0
  6. data/README.md +180 -0
  7. data/Rakefile +6 -0
  8. data/bin/buildpack-packager +79 -0
  9. data/buildpack-packager.gemspec +33 -0
  10. data/doc/disconnected_environments.md +50 -0
  11. data/lib/buildpack/manifest_dependency.rb +14 -0
  12. data/lib/buildpack/manifest_validator.rb +88 -0
  13. data/lib/buildpack/packager.rb +55 -0
  14. data/lib/buildpack/packager/default_versions_presenter.rb +38 -0
  15. data/lib/buildpack/packager/dependencies_presenter.rb +41 -0
  16. data/lib/buildpack/packager/manifest_schema.yml +67 -0
  17. data/lib/buildpack/packager/package.rb +139 -0
  18. data/lib/buildpack/packager/table_presentation.rb +21 -0
  19. data/lib/buildpack/packager/version.rb +5 -0
  20. data/lib/buildpack/packager/zip_file_excluder.rb +31 -0
  21. data/lib/kwalify/parser/yaml-patcher.rb +70 -0
  22. data/spec/buildpack/packager_spec.rb +7 -0
  23. data/spec/fixtures/buildpack-with-uri-credentials/VERSION +1 -0
  24. data/spec/fixtures/buildpack-with-uri-credentials/manifest.yml +36 -0
  25. data/spec/fixtures/buildpack-without-uri-credentials/VERSION +1 -0
  26. data/spec/fixtures/buildpack-without-uri-credentials/manifest.yml +36 -0
  27. data/spec/fixtures/manifests/manifest_invalid-md6.yml +18 -0
  28. data/spec/fixtures/manifests/manifest_invalid-md6_and_defaults.yml +21 -0
  29. data/spec/fixtures/manifests/manifest_valid.yml +19 -0
  30. data/spec/helpers/cache_directory_helpers.rb +15 -0
  31. data/spec/helpers/fake_binary_hosting_helpers.rb +21 -0
  32. data/spec/helpers/file_system_helpers.rb +35 -0
  33. data/spec/integration/bin/buildpack_packager/download_caching_spec.rb +85 -0
  34. data/spec/integration/bin/buildpack_packager_spec.rb +378 -0
  35. data/spec/integration/buildpack/directory_name_spec.rb +89 -0
  36. data/spec/integration/buildpack/packager_spec.rb +454 -0
  37. data/spec/integration/default_versions_spec.rb +170 -0
  38. data/spec/integration/output_spec.rb +70 -0
  39. data/spec/spec_helper.rb +12 -0
  40. data/spec/unit/buildpack/packager/zip_file_excluder_spec.rb +68 -0
  41. data/spec/unit/manifest_dependency_spec.rb +41 -0
  42. data/spec/unit/manifest_validator_spec.rb +35 -0
  43. data/spec/unit/packager/package_spec.rb +196 -0
  44. 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