buildpack-packager 2.3.4

Sign up to get free protection for your applications and to get access to all the features.
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