Skip to content

Commit 8625d16

Browse files
committed
Validate buildpack filetype based on lifecycle
1 parent 4dc715a commit 8625d16

File tree

5 files changed

+107
-37
lines changed

5 files changed

+107
-37
lines changed

app/controllers/v3/buildpacks_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def upload
7777

7878
unauthorized! unless permission_queryer.can_write_globally?
7979

80-
message = BuildpackUploadMessage.create_from_params(hashed_params[:body])
80+
message = BuildpackUploadMessage.create_from_params(hashed_params[:body], buildpack.lifecycle)
8181
combine_messages(message.errors.full_messages) unless message.valid?
8282

8383
unprocessable!('Buildpack is locked') if buildpack.locked

app/messages/buildpack_upload_message.rb

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,15 @@ class MissingFilePathError < StandardError; end
1717
validate :is_not_empty
1818
validate :missing_file_path
1919

20-
def self.create_from_params(params)
21-
BuildpackUploadMessage.new(params.dup.symbolize_keys)
20+
attr_reader :lifecycle
21+
22+
def initialize(params, lifecycle)
23+
@lifecycle = lifecycle
24+
super(params)
25+
end
26+
27+
def self.create_from_params(params, lifecycle)
28+
BuildpackUploadMessage.new(params.dup.symbolize_keys, lifecycle)
2229
end
2330

2431
def nginx_fields
@@ -52,12 +59,18 @@ def is_archive
5259

5360
mime_bits = File.read(bits_path, 4)
5461

55-
return if mime_bits =~ /^#{VCAP::CloudController::GZIP_MIME}/ || mime_bits =~ /^#{VCAP::CloudController::ZIP_MIME}/
62+
if lifecycle == VCAP::CloudController::Lifecycles::BUILDPACK
63+
return if mime_bits =~ /^#{VCAP::CloudController::ZIP_MIME}/
64+
65+
errors.add(:base, "#{bits_name} is not a zip file. Buildpacks of lifecycle \"#{lifecycle}\" must be valid zip files.")
66+
elsif lifecycle == VCAP::CloudController::Lifecycles::CNB
67+
return if mime_bits =~ /^#{VCAP::CloudController::GZIP_MIME}/
5668

57-
mime_bits_at_offset = File.read(bits_path, 8, 257)
58-
return if mime_bits_at_offset =~ /^#{VCAP::CloudController::CNB_MIME}/
69+
mime_bits_at_offset = File.read(bits_path, 8, 257)
70+
return if mime_bits_at_offset =~ /^#{VCAP::CloudController::CNB_MIME}/
5971

60-
errors.add(:base, "#{bits_name} is not a zip or gzip archive or cnb file")
72+
errors.add(:base, "#{bits_name} is not a gzip archive or cnb file. Buildpacks of lifecycle \"#{lifecycle}\" must be valid gzip archives or cnb files.")
73+
end
6174
end
6275

6376
def missing_file_path

spec/support/test_cnb.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
require 'rubygems/package'
33

44
module TestCnb
5-
def self.create(name, file_count, file_size = 1024, &)
5+
def self.create(name, file_count, file_size=1024, &)
66
File.open(name, 'wb') do |file|
77
Gem::Package::TarWriter.new(file) do |tar|
88
file_count.times do |i|

spec/unit/actions/buildpack_upload_spec.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ module VCAP::CloudController
77

88
describe '#upload_async' do
99
let!(:buildpack) { VCAP::CloudController::Buildpack.create_from_hash({ name: 'upload_binary_buildpack', stack: nil, position: 0 }) }
10-
let(:message) { BuildpackUploadMessage.new({ 'bits_path' => '/tmp/path', 'bits_name' => 'buildpack.zip' }) }
10+
let(:message) { BuildpackUploadMessage.new({ 'bits_path' => '/tmp/path', 'bits_name' => 'buildpack.zip' }, VCAP::CloudController::Lifecycles::BUILDPACK) }
1111
let(:config) { Config.new({ name: 'local', index: '1' }) }
1212

1313
before do

spec/unit/messages/buildpack_upload_message_spec.rb

Lines changed: 85 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ module VCAP::CloudController
55
RSpec.describe BuildpackUploadMessage do
66
before { TestConfig.override(directories: { tmpdir: '/tmp/' }) }
77

8+
let(:lifecycle) { VCAP::CloudController::Lifecycles::BUILDPACK }
9+
810
describe 'validations' do
911
let(:stat_double) { instance_double(File::Stat, size: 2) }
1012

@@ -19,7 +21,7 @@ module VCAP::CloudController
1921
let(:opts) { { '<ngx_upload_module_dummy>' => '', bits_path: '/tmp/foobar', bits_name: 'buildpack.zip' } }
2022

2123
it 'is invalid' do
22-
upload_message = BuildpackUploadMessage.new(opts)
24+
upload_message = BuildpackUploadMessage.new(opts, lifecycle)
2325
expect(upload_message).not_to be_valid
2426
expect(upload_message.errors[:base]).to include('Uploaded bits are not a valid buildpack file')
2527
end
@@ -29,7 +31,7 @@ module VCAP::CloudController
2931
let(:opts) { { bits_path: '/tmp/foobar', bits_name: 'buildpack.zip' } }
3032

3133
it 'is valid' do
32-
upload_message = BuildpackUploadMessage.new(opts)
34+
upload_message = BuildpackUploadMessage.new(opts, lifecycle)
3335
expect(upload_message).to be_valid
3436
end
3537
end
@@ -38,7 +40,7 @@ module VCAP::CloudController
3840
let(:opts) { { bits_path: '../tmp/mango/pear', bits_name: 'buildpack.zip' } }
3941

4042
it 'is valid' do
41-
upload_message = BuildpackUploadMessage.new(opts)
43+
upload_message = BuildpackUploadMessage.new(opts, lifecycle)
4244
expect(upload_message).to be_valid
4345
end
4446
end
@@ -47,7 +49,7 @@ module VCAP::CloudController
4749
let(:opts) { { bits_path: '../tmp-not!/mango/pear' } }
4850

4951
it 'is not valid' do
50-
upload_message = BuildpackUploadMessage.new(opts)
52+
upload_message = BuildpackUploadMessage.new(opts, lifecycle)
5153
expect(upload_message).not_to be_valid
5254
expect(upload_message.errors[:bits_path]).to include('is invalid')
5355
end
@@ -57,7 +59,7 @@ module VCAP::CloudController
5759
let(:opts) { {} }
5860

5961
it 'is not valid' do
60-
upload_message = BuildpackUploadMessage.new(opts)
62+
upload_message = BuildpackUploadMessage.new(opts, lifecycle)
6163
expect(upload_message).not_to be_valid
6264
expect(upload_message.errors[:base]).to include('A buildpack zip file must be uploaded as \'bits\'')
6365
end
@@ -67,7 +69,7 @@ module VCAP::CloudController
6769
let(:opts) { { bits_path: '/tmp/bar', unexpected: 'foo' } }
6870

6971
it 'is not valid' do
70-
message = BuildpackUploadMessage.new(opts)
72+
message = BuildpackUploadMessage.new(opts, lifecycle)
7173

7274
expect(message).not_to be_valid
7375
expect(message.errors.full_messages[0]).to include("Unknown field(s): 'unexpected'")
@@ -78,7 +80,7 @@ module VCAP::CloudController
7880
let(:opts) { { bits_path: '/secret/file', bits_name: 'buildpack.zip' } }
7981

8082
it 'is not valid' do
81-
message = BuildpackUploadMessage.new(opts)
83+
message = BuildpackUploadMessage.new(opts, lifecycle)
8284

8385
expect(message).not_to be_valid
8486
expect(message.errors.full_messages[0]).to include('Bits path is invalid')
@@ -89,7 +91,7 @@ module VCAP::CloudController
8991
let(:opts) { { bits_path: '/tmp/bar' } }
9092

9193
it 'is not valid' do
92-
upload_message = BuildpackUploadMessage.new(opts)
94+
upload_message = BuildpackUploadMessage.new(opts, lifecycle)
9395
expect(upload_message).not_to be_valid
9496
expect(upload_message.errors[:base]).to include('A buildpack zip file must be uploaded as \'bits\'')
9597
end
@@ -100,30 +102,85 @@ module VCAP::CloudController
100102

101103
it 'is not valid' do
102104
allow(File).to receive(:read).and_return("PX\x03\x04".force_encoding('binary'))
103-
upload_message = BuildpackUploadMessage.new(opts)
105+
upload_message = BuildpackUploadMessage.new(opts, lifecycle)
104106
expect(upload_message).not_to be_valid
105-
expect(upload_message.errors.full_messages[0]).to include('buildpack.abcd is not a zip or gzip archive or cnb file')
107+
expect(upload_message.errors.full_messages[0]).to include('buildpack.abcd is not a zip file. Buildpacks of lifecycle "buildpack" must be valid zip files.')
106108
end
107109
end
108110

109-
context 'when the file is a tgz' do
110-
let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.tgz' } }
111+
context 'buildpacks' do
112+
let(:lifecycle) { VCAP::CloudController::Lifecycles::BUILDPACK }
111113

112-
it 'is valid' do
113-
allow(File).to receive(:read).and_return("\x1F\x8B\x08".force_encoding('binary'))
114-
upload_message = BuildpackUploadMessage.new(opts)
115-
expect(upload_message).to be_valid
114+
context 'when the file is a zip' do
115+
let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.zip' } }
116+
117+
it 'is valid' do
118+
allow(File).to receive(:read).and_return("PK\x03\x04".force_encoding('binary'))
119+
upload_message = BuildpackUploadMessage.new(opts, lifecycle)
120+
expect(upload_message).to be_valid
121+
end
116122
end
117-
end
118123

119-
context 'when the file is a cnb/tar' do
120-
let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.cnb' } }
124+
context 'when the file is a tgz' do
125+
let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.tgz' } }
121126

122-
it 'is valid' do
123-
values = ["\x0".force_encoding('binary'), "\x75\x73\x74\x61\x72\x00\x30\x30".force_encoding('binary')]
124-
allow(File).to receive(:read).and_return(*values)
125-
upload_message = BuildpackUploadMessage.new(opts)
126-
expect(upload_message).to be_valid
127+
it 'is not valid' do
128+
allow(File).to receive(:read).and_return("\x1F\x8B\x08".force_encoding('binary'))
129+
upload_message = BuildpackUploadMessage.new(opts, lifecycle)
130+
expect(upload_message).not_to be_valid
131+
expect(upload_message.errors.full_messages[0]).to include('buildpack.tgz is not a zip file. Buildpacks of lifecycle "buildpack" must be valid zip files.')
132+
end
133+
end
134+
135+
context 'when the file is a cnb/tar' do
136+
let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.cnb' } }
137+
138+
it 'is not valid' do
139+
values = ["\x0".force_encoding('binary'), "\x75\x73\x74\x61\x72\x00\x30\x30".force_encoding('binary')]
140+
allow(File).to receive(:read).and_return(*values)
141+
upload_message = BuildpackUploadMessage.new(opts, lifecycle)
142+
expect(upload_message).not_to be_valid
143+
expect(upload_message.errors.full_messages[0]).to include('buildpack.cnb is not a zip file. Buildpacks of lifecycle "buildpack" must be valid zip files.')
144+
end
145+
end
146+
end
147+
148+
context 'cloud native buildpacks (cnb)' do
149+
let(:lifecycle) { VCAP::CloudController::Lifecycles::CNB }
150+
151+
context 'buildpacks' do
152+
context 'when the file is a zip' do
153+
let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.zip' } }
154+
155+
it 'is valid' do
156+
allow(File).to receive(:read).and_return("PK\x03\x04".force_encoding('binary'))
157+
upload_message = BuildpackUploadMessage.new(opts, lifecycle)
158+
expect(upload_message).not_to be_valid
159+
expect(upload_message.errors.full_messages[0]).to
160+
include('buildpack.zip is not a gzip archive or cnb file. Buildpacks of lifecycle "cnb" must be valid gzip archives or cnb files.')
161+
end
162+
end
163+
164+
context 'when the file is a tgz' do
165+
let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.tgz' } }
166+
167+
it 'is valid' do
168+
allow(File).to receive(:read).and_return("\x1F\x8B\x08".force_encoding('binary'))
169+
upload_message = BuildpackUploadMessage.new(opts, lifecycle)
170+
expect(upload_message).to be_valid
171+
end
172+
end
173+
174+
context 'when the file is a cnb/tar' do
175+
let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.cnb' } }
176+
177+
it 'is valid' do
178+
values = ["\x0".force_encoding('binary'), "\x75\x73\x74\x61\x72\x00\x30\x30".force_encoding('binary')]
179+
allow(File).to receive(:read).and_return(*values)
180+
upload_message = BuildpackUploadMessage.new(opts, lifecycle)
181+
expect(upload_message).to be_valid
182+
end
183+
end
127184
end
128185
end
129186

@@ -132,15 +189,15 @@ module VCAP::CloudController
132189
let(:stat_double) { instance_double(File::Stat, size: 0) }
133190

134191
it 'is not valid' do
135-
upload_message = BuildpackUploadMessage.new(opts)
192+
upload_message = BuildpackUploadMessage.new(opts, lifecycle)
136193
expect(upload_message).not_to be_valid
137194
expect(upload_message.errors.full_messages[0]).to include('buildpack.zip cannot be empty')
138195
end
139196
end
140197
end
141198

142199
describe '#bits_path=' do
143-
subject(:upload_message) { BuildpackUploadMessage.new(bits_path: 'not-nil') }
200+
subject(:upload_message) { BuildpackUploadMessage.new({ bits_path: 'not-nil' }, lifecycle) }
144201

145202
context 'when the bits_path is relative' do
146203
it 'makes it absolute (within the tmpdir)' do
@@ -162,15 +219,15 @@ module VCAP::CloudController
162219
let(:params) { { 'bits_path' => '/tmp/foobar', 'bits_name' => 'buildpack.zip' } }
163220

164221
it 'returns the correct BuildpackUploadMessage' do
165-
message = BuildpackUploadMessage.create_from_params(params)
222+
message = BuildpackUploadMessage.create_from_params(params, lifecycle)
166223

167224
expect(message).to be_a(BuildpackUploadMessage)
168225
expect(message.bits_path).to eq('/tmp/foobar')
169226
expect(message.bits_name).to eq('buildpack.zip')
170227
end
171228

172229
it 'converts requested keys to symbols' do
173-
message = BuildpackUploadMessage.create_from_params(params)
230+
message = BuildpackUploadMessage.create_from_params(params, lifecycle)
174231

175232
expect(message).to be_requested(:bits_path)
176233
expect(message).to be_requested(:bits_name)

0 commit comments

Comments
 (0)