Skip to content

Commit 38dbf3d

Browse files
[RFC0031] Add support for uploading buildpacks packaged as cnb (tar) files (#4342)
* Remove default buildpacks filter and change default sort to 'lifecycle' * Fix issue with manifest apply jobs determining incorrect lifecycle * Validate buildpack filetype based on lifecycle * Update docs * Support .cnb, .tgz, and .tar.gz as part of install_buildpacks Co-authored-by: Tom Kennedy <[email protected]>
1 parent 30ab628 commit 38dbf3d

28 files changed

+862
-91
lines changed

app/actions/app_apply_manifest.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def apply(app_guid, message)
5959

6060
app_update_message = message.app_update_message
6161
lifecycle = AppLifecycleProvider.provide_for_update(app_update_message, app)
62+
6263
AppUpdate.new(@user_audit_info, manifest_triggered: true).update(app, app_update_message, lifecycle)
6364

6465
update_routes(app, message)

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/controllers/v3/space_manifests_controller.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def apply_manifest
1616
can_write_space(space)
1717

1818
messages = parsed_app_manifests.map { |app_manifest| AppManifestMessage.create_from_yml(app_manifest) }
19+
1920
errors = messages.each_with_index.flat_map { |message, i| errors_for_message(message, i) }
2021
compound_error!(errors) unless errors.empty?
2122

app/fetchers/buildpack_list_fetcher.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ def filter(message, dataset)
1919
dataset = dataset.where(name: message.names) if message.requested?(:names)
2020

2121
dataset = NullFilterQueryGenerator.add_filter(dataset, :stack, message.stacks) if message.requested?(:stacks)
22-
dataset = dataset.where(lifecycle: message.requested?(:lifecycle) ? message.lifecycle : Config.config.get(:default_app_lifecycle))
22+
23+
dataset = dataset.where(lifecycle: message.lifecycle) if message.requested?(:lifecycle)
2324

2425
if message.requested?(:label_selector)
2526
dataset = LabelSelectorQueryGenerator.add_selector_queries(

app/messages/app_manifest_message.rb

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -123,16 +123,32 @@ def audit_hash
123123
end
124124

125125
def app_lifecycle_hash
126-
lifecycle_data = if requested?(:lifecycle) && @lifecycle == 'cnb'
127-
cnb_lifecycle_data
126+
lifecycle_type = if requested?(:lifecycle) && @lifecycle == 'cnb'
127+
Lifecycles::CNB
128+
elsif requested?(:lifecycle) && @lifecycle == 'buildpack'
129+
Lifecycles::BUILDPACK
128130
elsif requested?(:docker)
129-
docker_lifecycle_data
130-
elsif requested?(:buildpacks) || requested?(:buildpack) || requested?(:stack)
131-
buildpacks_lifecycle_data
131+
Lifecycles::DOCKER
132132
end
133133

134+
data = {
135+
buildpacks: requested_buildpacks,
136+
stack: @stack,
137+
credentials: @cnb_credentials
138+
}.compact
139+
140+
if lifecycle_type == Lifecycles::DOCKER
141+
lifecycle = {
142+
type: Lifecycles::DOCKER
143+
}
144+
elsif lifecycle_type || data.present?
145+
lifecycle = {}
146+
lifecycle[:type] = lifecycle_type if lifecycle_type.present?
147+
lifecycle[:data] = data if data.present?
148+
end
149+
134150
{
135-
lifecycle: lifecycle_data,
151+
lifecycle: lifecycle,
136152
metadata: requested?(:metadata) ? metadata : nil
137153
}.compact
138154
end
@@ -279,31 +295,6 @@ def service_bindings_attribute_mapping
279295
mapping
280296
end
281297

282-
def docker_lifecycle_data
283-
{ type: Lifecycles::DOCKER }
284-
end
285-
286-
def cnb_lifecycle_data
287-
{
288-
type: Lifecycles::CNB,
289-
data: {
290-
buildpacks: requested_buildpacks,
291-
stack: @stack,
292-
credentials: @cnb_credentials
293-
}.compact
294-
}
295-
end
296-
297-
def buildpacks_lifecycle_data
298-
{
299-
type: Lifecycles::BUILDPACK,
300-
data: {
301-
buildpacks: requested_buildpacks,
302-
stack: @stack
303-
}.compact
304-
}
305-
end
306-
307298
def should_autodetect?(buildpack)
308299
buildpack == 'default' || buildpack == 'null' || buildpack.nil?
309300
end

app/messages/buildpack_upload_message.rb

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
module VCAP::CloudController
44
GZIP_MIME = Regexp.new("\x1F\x8B\x08".force_encoding('binary'))
55
ZIP_MIME = Regexp.new("PK\x03\x04".force_encoding('binary'))
6+
CNB_MIME = Regexp.new("\x75\x73\x74\x61\x72\x00\x30\x30".force_encoding('binary'))
67

78
class BuildpackUploadMessage < BaseMessage
89
class MissingFilePathError < StandardError; end
@@ -16,8 +17,15 @@ class MissingFilePathError < StandardError; end
1617
validate :is_not_empty
1718
validate :missing_file_path
1819

19-
def self.create_from_params(params)
20-
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)
2129
end
2230

2331
def nginx_fields
@@ -51,9 +59,18 @@ def is_archive
5159

5260
mime_bits = File.read(bits_path, 4)
5361

54-
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}/
68+
69+
mime_bits_at_offset = File.read(bits_path, 8, 257)
70+
return if mime_bits_at_offset =~ /^#{VCAP::CloudController::CNB_MIME}/
5571

56-
errors.add(:base, "#{bits_name} is not a zip or gzip archive")
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
5774
end
5875

5976
def missing_file_path

app/messages/buildpacks_list_message.rb

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ class BuildpacksListMessage < MetadataListMessage
2121

2222
def initialize(params={})
2323
super
24-
pagination_options.default_order_by = :position
24+
pagination_options.default_order_by = :lifecycle
25+
pagination_options.secondary_default_order_by = :position
2526
end
2627

2728
def self.from_params(params)
@@ -33,7 +34,7 @@ def to_param_hash
3334
end
3435

3536
def valid_order_by_values
36-
super << :position
37+
super + %i[position lifecycle]
3738
end
3839
end
3940

docs/v3/source/includes/api_resources/_buildpacks.erb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
"filename": null,
99
"stack": "windows64",
1010
"position": 42,
11+
"lifecycle": "buildpack",
1112
"enabled": true,
1213
"locked": false,
1314
"metadata": {
@@ -52,6 +53,7 @@
5253
"filename": null,
5354
"stack": "my-stack",
5455
"position": 1,
56+
"lifecycle": "cnb",
5557
"enabled": true,
5658
"locked": false,
5759
"metadata": {

docs/v3/source/includes/resources/apps/_update.md.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Content-Type: application/json
3939
Name | Type | Description
4040
---- | ---- | -----------
4141
**name** | _string_ | Name of the app
42-
**lifecycle** | [_lifecycle object_](#the-lifecycle-object) | Lifecycle to be used when updating the app; note: `data` is a required field in lifecycle if lifecycle is updated
42+
**lifecycle** | [_lifecycle object_](#the-lifecycle-object) | Lifecycle to be used when updating the app; note: `data` is a required field in lifecycle if lifecycle is updated. `type` may NOT be changed from its current value.
4343
**metadata.labels** | [_label object_](#labels) | Labels applied to the app
4444
**metadata.annotations** | [_annotation object_](#annotations) | Annotations applied to the app
4545

docs/v3/source/includes/resources/buildpacks/_create.md.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ Name | Type | Description
4444
----------- | -------- | ----------------------------------------------------------------------------- | -------
4545
**stack** | _string_ | The name of the stack that the buildpack will use | null
4646
**position** | _integer_ | The order in which the buildpacks are checked during buildpack auto-detection | 1
47+
**lifecycle**| _string_ | The version of buildpack the buildpack will use. `buildpack` indicates [Classic Buildpacks](https://docs.cloudfoundry.org/buildpacks/classic.html). `cnb` indicates [Cloud Native Buildpacks](https://docs.cloudfoundry.org/buildpacks/cnb/) | buildpack
4748
**enabled** | _boolean_ | Whether or not the buildpack will be used for staging | true
4849
**locked** | _boolean_ | Whether or not the buildpack is locked to prevent updating the bits | false
4950
**metadata.labels** | [_label object_](#labels) | Labels applied to the buildpack

0 commit comments

Comments
 (0)