diff --git a/app/actions/build_create.rb b/app/actions/build_create.rb
index d870e59441d..80c6bd79fa4 100644
--- a/app/actions/build_create.rb
+++ b/app/actions/build_create.rb
@@ -95,7 +95,6 @@ def create_and_stage(package:, lifecycle:, metadata: nil, start_after_staging: f
def requested_buildpacks_disabled!(lifecycle)
return if lifecycle.type == Lifecycles::DOCKER
- return if lifecycle.type == Lifecycles::CNB
admin_buildpack_records = lifecycle.buildpack_infos.map(&:buildpack_record).compact
disabled_buildpacks = admin_buildpack_records.reject(&:enabled)
diff --git a/app/actions/buildpack_create.rb b/app/actions/buildpack_create.rb
index fd8cc1a8d6f..387b3344fc0 100644
--- a/app/actions/buildpack_create.rb
+++ b/app/actions/buildpack_create.rb
@@ -14,6 +14,7 @@ def create(message)
buildpack = Buildpack.create(
name: message.name,
stack: message.stack,
+ lifecycle: (message.lifecycle.nil? ? Config.config.get(:default_app_lifecycle) : message.lifecycle),
enabled: (message.enabled.nil? ? DEFAULT_ENABLED : message.enabled),
locked: (message.locked.nil? ? DEFAULT_LOCKED : message.locked)
)
@@ -28,7 +29,9 @@ def create(message)
def validation_error!(error, create_message)
error!(%(Stack '#{create_message.stack}' does not exist)) if error.errors.on(:stack)&.include?(:buildpack_stack_does_not_exist)
- error!(%(Buildpack with name '#{error.model.name}' and stack '#{error.model.stack}' already exists)) if error.errors.on(%i[name stack])&.include?(:unique)
+ if error.errors.on(%i[name stack lifecycle])&.include?(:unique)
+ error!(%(Buildpack with name '#{error.model.name}', stack '#{error.model.stack}' and lifecycle '#{error.model.lifecycle}' already exists))
+ end
error!(%(Buildpack with name '#{error.model.name}' and an unassigned stack already exists)) if error.errors.on(:stack)&.include?(:unique)
error!(error.message)
diff --git a/app/actions/buildpack_update.rb b/app/actions/buildpack_update.rb
index 791dd281703..b0b9b8674c6 100644
--- a/app/actions/buildpack_update.rb
+++ b/app/actions/buildpack_update.rb
@@ -27,7 +27,10 @@ def validation_error!(error, message)
error!(%(Stack '#{message.stack}' does not exist)) if error.errors.on(:stack)&.include?(:buildpack_stack_does_not_exist)
error!(%(Buildpack stack cannot be changed)) if error.errors.on(:stack)&.include?(:buildpack_cant_change_stacks)
error!(%(Buildpack with name '#{error.model.name}' and an unassigned stack already exists)) if error.errors.on(:stack)&.include?(:unique)
- error!(%(Buildpack with name '#{error.model.name}' and stack '#{error.model.stack}' already exists)) if error.errors.on(%i[name stack])&.include?(:unique)
+
+ if error.errors.on(%i[name stack lifecycle])&.include?(:unique)
+ error!(%(Buildpack with name '#{error.model.name}', stack '#{error.model.stack}' and lifecycle '#{error.model.lifecycle}' already exists))
+ end
error!(error.message)
end
diff --git a/app/controllers/runtime/buildpacks_controller.rb b/app/controllers/runtime/buildpacks_controller.rb
index f13ae080c44..461cb3b669a 100644
--- a/app/controllers/runtime/buildpacks_controller.rb
+++ b/app/controllers/runtime/buildpacks_controller.rb
@@ -7,17 +7,18 @@ def self.dependencies
define_attributes do
attribute :name, String
attribute :stack, String, default: nil
+ attribute :lifecycle, String, default: nil, exclude_in: :update
attribute :position, Integer, default: 0
attribute :enabled, Message::Boolean, default: true
attribute :locked, Message::Boolean, default: false
end
- query_parameters :name, :stack
+ query_parameters :name, :stack, :lifecycle
def self.translate_validation_exception(e, attributes)
- buildpack_errors = e.errors.on(%i[name stack])
+ buildpack_errors = e.errors.on(%i[name stack lifecycle])
if buildpack_errors && buildpack_errors.include?(:unique)
- CloudController::Errors::ApiError.new_from_details('BuildpackNameStackTaken', attributes['name'], attributes['stack'])
+ CloudController::Errors::ApiError.new_from_details('BuildpackNameStackLifecycleTaken', attributes['name'], attributes['stack'], attributes['lifecycle'])
else
CloudController::Errors::ApiError.new_from_details('BuildpackInvalid', e.errors.full_messages)
end
diff --git a/app/controllers/v3/buildpacks_controller.rb b/app/controllers/v3/buildpacks_controller.rb
index 28386823917..2916970fa59 100644
--- a/app/controllers/v3/buildpacks_controller.rb
+++ b/app/controllers/v3/buildpacks_controller.rb
@@ -77,7 +77,7 @@ def upload
unauthorized! unless permission_queryer.can_write_globally?
- message = BuildpackUploadMessage.create_from_params(hashed_params[:body])
+ message = BuildpackUploadMessage.create_from_params(hashed_params[:body], buildpack.lifecycle)
combine_messages(message.errors.full_messages) unless message.valid?
unprocessable!('Buildpack is locked') if buildpack.locked
diff --git a/app/fetchers/buildpack_lifecycle_fetcher.rb b/app/fetchers/buildpack_lifecycle_fetcher.rb
index 72ca45f43da..f5d399a64e2 100644
--- a/app/fetchers/buildpack_lifecycle_fetcher.rb
+++ b/app/fetchers/buildpack_lifecycle_fetcher.rb
@@ -1,17 +1,19 @@
+require 'cloud_controller/diego/lifecycles/lifecycles'
+
module VCAP::CloudController
class BuildpackLifecycleFetcher
class << self
- def fetch(buildpack_names, stack_name)
+ def fetch(buildpack_names, stack_name, lifecycle=Config.config.get(:default_app_lifecycle))
{
stack: Stack.find(name: stack_name),
- buildpack_infos: ordered_buildpacks(buildpack_names, stack_name)
+ buildpack_infos: ordered_buildpacks(buildpack_names, stack_name, lifecycle)
}
end
private
- def ordered_buildpacks(buildpack_names, stack_name)
- buildpacks_with_stacks, buildpacks_without_stacks = Buildpack.list_admin_buildpacks(stack_name).partition(&:stack)
+ def ordered_buildpacks(buildpack_names, stack_name, lifecycle)
+ buildpacks_with_stacks, buildpacks_without_stacks = Buildpack.list_admin_buildpacks(stack_name, lifecycle).partition(&:stack)
buildpack_names.map do |buildpack_name|
buildpack_record = buildpacks_with_stacks.find { |b| b.name == buildpack_name } || buildpacks_without_stacks.find { |b| b.name == buildpack_name }
diff --git a/app/fetchers/buildpack_list_fetcher.rb b/app/fetchers/buildpack_list_fetcher.rb
index c312432df17..1c3713b269c 100644
--- a/app/fetchers/buildpack_list_fetcher.rb
+++ b/app/fetchers/buildpack_list_fetcher.rb
@@ -1,5 +1,6 @@
require 'cloud_controller/paging/sequel_paginator'
require 'cloud_controller/paging/paginated_result'
+require 'cloud_controller/diego/lifecycles/lifecycles'
require 'fetchers/null_filter_query_generator'
require 'fetchers/base_list_fetcher'
@@ -19,6 +20,8 @@ def filter(message, dataset)
dataset = NullFilterQueryGenerator.add_filter(dataset, :stack, message.stacks) if message.requested?(:stacks)
+ dataset = dataset.where(lifecycle: message.lifecycle) if message.requested?(:lifecycle)
+
if message.requested?(:label_selector)
dataset = LabelSelectorQueryGenerator.add_selector_queries(
label_klass: BuildpackLabelModel,
diff --git a/app/jobs/runtime/buildpack_installer_factory.rb b/app/jobs/runtime/buildpack_installer_factory.rb
index 8565e5cf702..fd03d1ef91b 100644
--- a/app/jobs/runtime/buildpack_installer_factory.rb
+++ b/app/jobs/runtime/buildpack_installer_factory.rb
@@ -37,8 +37,7 @@ def plan(buildpack_name, manifest_fields)
planned_jobs = []
- found_buildpacks = Buildpack.where(name: buildpack_name).all
-
+ found_buildpacks = Buildpack.where(name: buildpack_name, lifecycle: manifest_fields[0][:options][:lifecycle]).all
ensure_no_attempt_to_upgrade_a_stackless_locked_buildpack(buildpack_name, found_buildpacks, manifest_fields)
manifest_fields.each do |buildpack_fields|
diff --git a/app/jobs/runtime/create_buildpack_installer.rb b/app/jobs/runtime/create_buildpack_installer.rb
index aefd135544d..5448d49eef4 100644
--- a/app/jobs/runtime/create_buildpack_installer.rb
+++ b/app/jobs/runtime/create_buildpack_installer.rb
@@ -10,7 +10,7 @@ def perform
buildpacks_lock = Locking[name: 'buildpacks']
buildpacks_lock.db.transaction do
buildpacks_lock.lock!
- buildpack = Buildpack.create(name: name, stack: stack_name)
+ buildpack = Buildpack.create(name: name, stack: stack_name, lifecycle: options[:lifecycle])
end
begin
buildpack_uploader.upload_buildpack(buildpack, file, File.basename(file))
diff --git a/app/messages/app_manifest_message.rb b/app/messages/app_manifest_message.rb
index a2fa25dc55e..ee4757fd13f 100644
--- a/app/messages/app_manifest_message.rb
+++ b/app/messages/app_manifest_message.rb
@@ -72,7 +72,6 @@ def self.underscore_keys(hash)
validate :validate_buildpack_and_buildpacks_combination!
validate :validate_docker_enabled!
validate :validate_cnb_enabled!
- validate :validate_cnb_buildpacks!
validate :validate_docker_buildpacks_combination!
validate :validate_service_bindings_message!, if: ->(record) { record.requested?(:services) }
validate :validate_env_update_message!, if: ->(record) { record.requested?(:env) }
@@ -128,7 +127,7 @@ def app_lifecycle_hash
cnb_lifecycle_data
elsif requested?(:docker)
docker_lifecycle_data
- else
+ elsif requested?(:buildpacks) || requested?(:buildpack) || requested?(:stack)
buildpacks_lifecycle_data
end
@@ -285,15 +284,6 @@ def docker_lifecycle_data
end
def cnb_lifecycle_data
- return unless requested?(:buildpacks) || requested?(:buildpack) || requested?(:stack)
-
- if requested?(:buildpacks)
- requested_buildpacks = @buildpacks
- elsif requested?(:buildpack)
- requested_buildpacks = []
- requested_buildpacks.push(@buildpack)
- end
-
{
type: Lifecycles::CNB,
data: {
@@ -305,16 +295,8 @@ def cnb_lifecycle_data
end
def buildpacks_lifecycle_data
- return unless requested?(:buildpacks) || requested?(:buildpack) || requested?(:stack)
-
- if requested?(:buildpacks)
- requested_buildpacks = @buildpacks
- elsif requested?(:buildpack)
- requested_buildpacks = []
- requested_buildpacks.push(@buildpack) unless should_autodetect?(@buildpack)
- end
-
{
+ type: Lifecycles::BUILDPACK,
data: {
buildpacks: requested_buildpacks,
stack: @stack
@@ -485,13 +467,6 @@ def validate_cnb_enabled!
errors.add(:base, e.message)
end
- def validate_cnb_buildpacks!
- return unless @lifecycle == 'cnb'
- return if requested?(:lifecycle) && (requested?(:buildpack) || requested?(:buildpacks))
-
- errors.add(:base, 'Buildpack(s) must be specified when using Cloud Native Buildpacks')
- end
-
def validate_docker_buildpacks_combination!
return unless requested?(:docker) && (requested?(:buildpack) || requested?(:buildpacks))
@@ -501,5 +476,15 @@ def validate_docker_buildpacks_combination!
def add_process_error!(error_message, type)
errors.add(:base, %(Process "#{type}": #{error_message}))
end
+
+ def requested_buildpacks
+ return nil unless requested?(:buildpacks) || requested?(:buildpack)
+ return @buildpacks if requested?(:buildpacks)
+
+ buildpacks = []
+ buildpacks.push(@buildpack) if requested?(:buildpack) && !should_autodetect?(@buildpack)
+
+ buildpacks
+ end
end
end
diff --git a/app/messages/buildpack_create_message.rb b/app/messages/buildpack_create_message.rb
index b935ef2075d..9838c57ae33 100644
--- a/app/messages/buildpack_create_message.rb
+++ b/app/messages/buildpack_create_message.rb
@@ -1,12 +1,13 @@
require 'messages/metadata_base_message'
require 'messages/validators'
+require 'cloud_controller/diego/lifecycles/lifecycles'
module VCAP::CloudController
class BuildpackCreateMessage < MetadataBaseMessage
MAX_BUILDPACK_NAME_LENGTH = 250
MAX_STACK_LENGTH = 250
- register_allowed_keys %i[name stack position enabled locked]
+ register_allowed_keys %i[name stack position enabled locked lifecycle]
validates_with NoAdditionalKeysValidator
validates :name,
@@ -32,5 +33,10 @@ class BuildpackCreateMessage < MetadataBaseMessage
validates :locked,
allow_nil: true,
boolean: true
+
+ validates :lifecycle,
+ string: true,
+ allow_nil: true,
+ inclusion: { in: [VCAP::CloudController::Lifecycles::BUILDPACK, VCAP::CloudController::Lifecycles::CNB], message: 'must be either "buildpack" or "cnb"' }
end
end
diff --git a/app/messages/buildpack_upload_message.rb b/app/messages/buildpack_upload_message.rb
index e6e58f3f50a..0742ea88102 100644
--- a/app/messages/buildpack_upload_message.rb
+++ b/app/messages/buildpack_upload_message.rb
@@ -1,21 +1,31 @@
require 'messages/base_message'
module VCAP::CloudController
+ GZIP_MIME = Regexp.new("\x1F\x8B\x08".force_encoding('binary'))
+ ZIP_MIME = Regexp.new("PK\x03\x04".force_encoding('binary'))
+ CNB_MIME = Regexp.new("\x75\x73\x74\x61\x72\x00\x30\x30".force_encoding('binary'))
+
class BuildpackUploadMessage < BaseMessage
class MissingFilePathError < StandardError; end
-
register_allowed_keys %i[bits_path bits_name upload_start_time]
validates_with NoAdditionalKeysValidator
validate :nginx_fields
validate :bits_path_in_tmpdir
- validate :is_zip
+ validate :is_archive
validate :is_not_empty
validate :missing_file_path
- def self.create_from_params(params)
- BuildpackUploadMessage.new(params.dup.symbolize_keys)
+ attr_reader :lifecycle
+
+ def initialize(params, lifecycle)
+ @lifecycle = lifecycle
+ super(params)
+ end
+
+ def self.create_from_params(params, lifecycle)
+ BuildpackUploadMessage.new(params.dup.symbolize_keys, lifecycle)
end
def nginx_fields
@@ -43,10 +53,24 @@ def tmpdir
VCAP::CloudController::Config.config.get(:directories, :tmpdir)
end
- def is_zip
+ def is_archive
return unless bits_name
+ return unless bits_path
+
+ mime_bits = File.read(bits_path, 4)
+
+ if lifecycle == VCAP::CloudController::Lifecycles::BUILDPACK
+ return if mime_bits =~ /^#{VCAP::CloudController::ZIP_MIME}/
+
+ errors.add(:base, "#{bits_name} is not a zip file. Buildpacks of lifecycle \"#{lifecycle}\" must be valid zip files.")
+ elsif lifecycle == VCAP::CloudController::Lifecycles::CNB
+ return if mime_bits =~ /^#{VCAP::CloudController::GZIP_MIME}/
+
+ mime_bits_at_offset = File.read(bits_path, 8, 257)
+ return if mime_bits_at_offset =~ /^#{VCAP::CloudController::CNB_MIME}/
- errors.add(:base, "#{bits_name} is not a zip") unless File.extname(bits_name) == '.zip'
+ 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.")
+ end
end
def missing_file_path
diff --git a/app/messages/buildpacks_list_message.rb b/app/messages/buildpacks_list_message.rb
index 24234055310..293ec500248 100644
--- a/app/messages/buildpacks_list_message.rb
+++ b/app/messages/buildpacks_list_message.rb
@@ -5,18 +5,24 @@ class BuildpacksListMessage < MetadataListMessage
register_allowed_keys %i[
stacks
names
+ lifecycle
page
per_page
]
validates :names, array: true, allow_nil: true
validates :stacks, array: true, allow_nil: true
+ validates :lifecycle,
+ string: true,
+ allow_nil: true,
+ inclusion: { in: [VCAP::CloudController::Lifecycles::BUILDPACK, VCAP::CloudController::Lifecycles::CNB], message: 'must be either "buildpack" or "cnb"' }
validates_with NoAdditionalParamsValidator
def initialize(params={})
super
- pagination_options.default_order_by = :position
+ pagination_options.default_order_by = :lifecycle
+ pagination_options.secondary_default_order_by = :position
end
def self.from_params(params)
@@ -28,7 +34,7 @@ def to_param_hash
end
def valid_order_by_values
- super << :position
+ super + %i[position lifecycle]
end
end
diff --git a/app/models/runtime/buildpack.rb b/app/models/runtime/buildpack.rb
index 2e99a2e366b..9dec15560c6 100644
--- a/app/models/runtime/buildpack.rb
+++ b/app/models/runtime/buildpack.rb
@@ -1,23 +1,35 @@
+require 'cloud_controller/diego/lifecycles/lifecycles'
+
module VCAP::CloudController
class Buildpack < Sequel::Model
- plugin :list
+ plugin :list, scope: :lifecycle
+ plugin :after_initialize
- export_attributes :name, :stack, :position, :enabled, :locked, :filename
- import_attributes :name, :stack, :position, :enabled, :locked, :filename, :key
+ export_attributes :name, :stack, :position, :enabled, :locked, :filename, :lifecycle
+ import_attributes :name, :stack, :position, :enabled, :locked, :filename, :lifecycle, :key
PACKAGE_STATES = [
CREATED_STATE = 'AWAITING_UPLOAD'.freeze,
READY_STATE = 'READY'.freeze
].map(&:freeze).freeze
+ def after_initialize
+ self.lifecycle ||= Config.config.get(:default_app_lifecycle)
+ end
+
one_to_many :labels, class: 'VCAP::CloudController::BuildpackLabelModel', key: :resource_guid, primary_key: :guid
one_to_many :annotations, class: 'VCAP::CloudController::BuildpackAnnotationModel', key: :resource_guid, primary_key: :guid
add_association_dependencies labels: :destroy
add_association_dependencies annotations: :destroy
- def self.list_admin_buildpacks(stack_name=nil)
+ def self.user_visibility_filter(_user)
+ full_dataset_filter
+ end
+
+ def self.list_admin_buildpacks(stack_name=nil, lifecycle=Config.config.get(:default_app_lifecycle))
scoped = exclude(key: nil).exclude(key: '')
+ scoped = scoped.filter(lifecycle:)
if stack_name.present?
scoped = scoped.filter(Sequel.or([
[:stack, stack_name],
@@ -31,17 +43,14 @@ def self.at_last_position
where(position: max(:position)).first
end
- def self.user_visibility_filter(_user)
- full_dataset_filter
- end
-
def validate
- validates_unique %i[name stack]
+ validates_unique %i[name stack lifecycle]
validates_format(/\A(\w|-)+\z/, :name, message: 'can only contain alphanumeric characters')
validate_stack_existence
validate_stack_change
validate_multiple_nil_stacks
+ validate_lifecycle_change
end
def locked?
@@ -75,7 +84,7 @@ def state
def validate_multiple_nil_stacks
return unless stack.nil?
- errors.add(:stack, :unique) if Buildpack.exclude(guid:).where(name: name, stack: nil).present?
+ errors.add(:stack, :unique) if Buildpack.exclude(guid:).where(name: name, stack: nil, lifecycle: lifecycle).present?
end
def validate_stack_change
@@ -84,6 +93,12 @@ def validate_stack_change
errors.add(:stack, :buildpack_cant_change_stacks) if column_changes.key?(:stack)
end
+ def validate_lifecycle_change
+ return if initial_value(:lifecycle).nil?
+
+ errors.add(:lifecycle, :buildpack_cant_change_lifecycle) if column_changes.key?(:lifecycle)
+ end
+
def validate_stack_existence
return unless stack
diff --git a/app/models/runtime/cnb_lifecycle_data_model.rb b/app/models/runtime/cnb_lifecycle_data_model.rb
index 4b53328e195..63bd6a32142 100644
--- a/app/models/runtime/cnb_lifecycle_data_model.rb
+++ b/app/models/runtime/cnb_lifecycle_data_model.rb
@@ -44,7 +44,7 @@ def buildpacks
def buildpack_models
if buildpack_lifecycle_buildpacks.present?
buildpack_lifecycle_buildpacks.map do |buildpack|
- CustomBuildpack.new(buildpack.name)
+ Buildpack.find(name: buildpack.name) || CustomBuildpack.new(buildpack.name)
end
else
[]
@@ -64,11 +64,7 @@ def first_custom_buildpack_url
end
def using_custom_buildpack?
- true
- end
-
- def attributes_from_buildpack(buildpack_name)
- { buildpack_url: buildpack_name, admin_buildpack_name: nil }
+ buildpack_lifecycle_buildpacks.any?(&:custom?)
end
def to_hash
@@ -96,5 +92,44 @@ def credentials
def credentials=(creds)
self.registry_credentials_json = Oj.dump(creds)
end
+
+ private
+
+ def attributes_from_buildpack_name(buildpack_name)
+ if UriUtils.is_cnb_buildpack_uri?(buildpack_name)
+ { buildpack_url: buildpack_name, admin_buildpack_name: nil }
+ else
+ { buildpack_url: nil, admin_buildpack_name: buildpack_name }
+ end
+ end
+
+ def attributes_from_buildpack_key(key)
+ admin_buildpack = Buildpack.find(key:)
+ if admin_buildpack
+ { buildpack_url: nil, admin_buildpack_name: admin_buildpack.name }
+ elsif UriUtils.is_cnb_buildpack_uri?(key)
+ { buildpack_url: key, admin_buildpack_name: nil }
+ else
+ {} # Will fail a validity check downstream
+ end
+ end
+
+ def attributes_from_buildpack_hash(buildpack)
+ {
+ buildpack_name: buildpack[:name],
+ version: buildpack[:version]
+ }.merge(buildpack[:key] ? attributes_from_buildpack_key(buildpack[:key]) : attributes_from_buildpack_name(buildpack[:name]))
+ end
+
+ def attributes_from_buildpack(buildpack)
+ if buildpack.is_a?(String)
+ attributes_from_buildpack_name buildpack
+ elsif buildpack.is_a?(Hash)
+ attributes_from_buildpack_hash buildpack
+ else
+ # Don't set anything -- this will fail later on a validity check
+ {}
+ end
+ end
end
end
diff --git a/app/presenters/v3/buildpack_presenter.rb b/app/presenters/v3/buildpack_presenter.rb
index bd084815b5b..ec32547b700 100644
--- a/app/presenters/v3/buildpack_presenter.rb
+++ b/app/presenters/v3/buildpack_presenter.rb
@@ -13,6 +13,7 @@ def to_hash
name: buildpack.name,
stack: buildpack.stack,
state: buildpack.state,
+ lifecycle: buildpack.lifecycle,
filename: buildpack.filename,
position: buildpack.position,
enabled: buildpack.enabled,
diff --git a/db/migrations/20250424095500_cnb_system_buildpacks.rb b/db/migrations/20250424095500_cnb_system_buildpacks.rb
new file mode 100644
index 00000000000..de803d63c5c
--- /dev/null
+++ b/db/migrations/20250424095500_cnb_system_buildpacks.rb
@@ -0,0 +1,11 @@
+require 'cloud_controller/diego/lifecycles/lifecycles'
+
+Sequel.migration do
+ up do
+ add_column :buildpacks, :lifecycle, String, size: 16, if_not_exists: true, default: VCAP::CloudController::Lifecycles::BUILDPACK
+ end
+
+ down do
+ drop_column :buildpacks, :lifecycle, if_exists: true
+ end
+end
diff --git a/docs/v3/source/includes/api_resources/_buildpacks.erb b/docs/v3/source/includes/api_resources/_buildpacks.erb
index c2586026f10..2caea659bf6 100644
--- a/docs/v3/source/includes/api_resources/_buildpacks.erb
+++ b/docs/v3/source/includes/api_resources/_buildpacks.erb
@@ -8,6 +8,7 @@
"filename": null,
"stack": "windows64",
"position": 42,
+ "lifecycle": "buildpack",
"enabled": true,
"locked": false,
"metadata": {
@@ -52,6 +53,7 @@
"filename": null,
"stack": "my-stack",
"position": 1,
+ "lifecycle": "cnb",
"enabled": true,
"locked": false,
"metadata": {
diff --git a/docs/v3/source/includes/resources/buildpacks/_create.md.erb b/docs/v3/source/includes/resources/buildpacks/_create.md.erb
index 00e818e08e3..dbe4eb8907e 100644
--- a/docs/v3/source/includes/resources/buildpacks/_create.md.erb
+++ b/docs/v3/source/includes/resources/buildpacks/_create.md.erb
@@ -44,6 +44,7 @@ Name | Type | Description
----------- | -------- | ----------------------------------------------------------------------------- | -------
**stack** | _string_ | The name of the stack that the buildpack will use | null
**position** | _integer_ | The order in which the buildpacks are checked during buildpack auto-detection | 1
+**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
**enabled** | _boolean_ | Whether or not the buildpack will be used for staging | true
**locked** | _boolean_ | Whether or not the buildpack is locked to prevent updating the bits | false
**metadata.labels** | [_label object_](#labels) | Labels applied to the buildpack
diff --git a/docs/v3/source/includes/resources/buildpacks/_list.md.erb b/docs/v3/source/includes/resources/buildpacks/_list.md.erb
index bfad835b683..3fe885d0478 100644
--- a/docs/v3/source/includes/resources/buildpacks/_list.md.erb
+++ b/docs/v3/source/includes/resources/buildpacks/_list.md.erb
@@ -34,7 +34,8 @@ Name | Type | Description
**per_page** | _integer_ | Number of results per page;
valid values are 1 through 5000
**names** | _list of strings_ | Comma-delimited list of buildpack names to filter by
**stacks**| _list of strings_ | Comma-delimited list of stack names to filter by
-**order_by** | _string_ | Value to sort by; defaults to ascending. Prepend with `-` to sort descending.
Valid values are `created_at`, `updated_at`, and `position`
+**lifecycle**| _string_ | Type of buildpack. Valid values are `buildpack` and `cnb`
+**order_by** | _string_ | Value to sort by; defaults to ascending. Prepend with `-` to sort descending.
Valid values are `created_at`, `updated_at`, `lifecycle`, and `position`
**label_selector** | _string_ | A query string containing a list of [label selector](#labels-and-selectors) requirements
**created_ats** | _[timestamp](#timestamps)_ | Timestamp to filter by. When filtering on equality, several comma-delimited timestamps may be passed. Also supports filtering with [relational operators](#relational-operators)
**updated_ats** | _[timestamp](#timestamps)_ | Timestamp to filter by. When filtering on equality, several comma-delimited timestamps may be passed. Also supports filtering with [relational operators](#relational-operators)
diff --git a/docs/v3/source/includes/resources/buildpacks/_object.md.erb b/docs/v3/source/includes/resources/buildpacks/_object.md.erb
index 462f3b4a778..aa8918501f7 100644
--- a/docs/v3/source/includes/resources/buildpacks/_object.md.erb
+++ b/docs/v3/source/includes/resources/buildpacks/_object.md.erb
@@ -15,7 +15,8 @@ Name | Type | Description
**updated_at** | _[timestamp](#timestamps)_ | The time with zone when the object was last updated
**name** | _string_ | The name of the buildpack; to be used by app buildpack field (only alphanumeric characters)
**state** | _string_ | The state of the buildpack; valid states are: `AWAITING_UPLOAD`, `READY`
-**stack** | _string_ | The name of the stack that the buildpack will use
+**stack** | _string_ | The name of the stack that the buildpack uses
+**lifecycle** | _string_ | The version of buildpacks the buildpack uses. `buildpack` indicates [Classic Buildpacks](https://docs.cloudfoundry.org/buildpacks/classic.html). `cnb` indicates [Cloud Native Buildpacks](https://docs.cloudfoundry.org/buildpacks/cnb/)
**filename** | _string_ | The filename of the buildpack
**position** | _integer_ | The order in which the buildpacks are checked during buildpack auto-detection
**enabled** | _boolean_ | Whether or not the buildpack can be used for staging
diff --git a/errors/v2.yml b/errors/v2.yml
index e57c6b48dac..cf4bf28bea2 100644
--- a/errors/v2.yml
+++ b/errors/v2.yml
@@ -964,9 +964,9 @@
message: "Encountered an error while attempting to sync cloud controller with the service broker's catalog: %s"
290000:
- name: BuildpackNameStackTaken
+ name: BuildpackNameStackLifecycleTaken
http_code: 422
- message: "The buildpack name %s is already in use for the stack %s"
+ message: "The buildpack name %s is already in use for the stack %s and the lifecycle %s"
290001:
name: BuildpackNameTaken
diff --git a/lib/cloud_controller/config_schemas/base/api_schema.rb b/lib/cloud_controller/config_schemas/base/api_schema.rb
index 93f5cfc541d..97875bc829b 100644
--- a/lib/cloud_controller/config_schemas/base/api_schema.rb
+++ b/lib/cloud_controller/config_schemas/base/api_schema.rb
@@ -378,7 +378,7 @@ class ApiSchema < VCAP::Config
internal_route_vip_range: String,
- default_app_lifecycle: String,
+ default_app_lifecycle: enum('buildpack', 'cnb'),
custom_metric_tag_prefix_list: Array,
optional(:cc_service_key_client_name) => String,
diff --git a/lib/cloud_controller/config_schemas/base/worker_schema.rb b/lib/cloud_controller/config_schemas/base/worker_schema.rb
index 84726503417..46de1630e03 100644
--- a/lib/cloud_controller/config_schemas/base/worker_schema.rb
+++ b/lib/cloud_controller/config_schemas/base/worker_schema.rb
@@ -191,7 +191,8 @@ class WorkerSchema < VCAP::Config
max_labels_per_resource: Integer,
max_annotations_per_resource: Integer,
internal_route_vip_range: String,
- custom_metric_tag_prefix_list: Array
+ custom_metric_tag_prefix_list: Array,
+ default_app_lifecycle: enum('buildpack', 'cnb')
}
end
# rubocop:enable Metrics/BlockLength
diff --git a/lib/cloud_controller/diego/buildpack/lifecycle_protocol.rb b/lib/cloud_controller/diego/buildpack/lifecycle_protocol.rb
index 8122ac7c03e..4305a62581d 100644
--- a/lib/cloud_controller/diego/buildpack/lifecycle_protocol.rb
+++ b/lib/cloud_controller/diego/buildpack/lifecycle_protocol.rb
@@ -23,6 +23,10 @@ def desired_lrp_builder(config, process)
def new_lifecycle_data(_)
LifecycleData.new
end
+
+ def type
+ VCAP::CloudController::Lifecycles::BUILDPACK
+ end
end
end
end
diff --git a/lib/cloud_controller/diego/buildpack/staging_action_builder.rb b/lib/cloud_controller/diego/buildpack/staging_action_builder.rb
index a0fe002a977..f00d67491ba 100644
--- a/lib/cloud_controller/diego/buildpack/staging_action_builder.rb
+++ b/lib/cloud_controller/diego/buildpack/staging_action_builder.rb
@@ -8,58 +8,7 @@ module Diego
module Buildpack
class StagingActionBuilder < VCAP::CloudController::Diego::StagingActionBuilder
def initialize(config, staging_details, lifecycle_data)
- super(config, staging_details, lifecycle_data, 'buildpack', '/tmp/app', '/tmp/output-cache')
- end
-
- def additional_image_layers
- lifecycle_data[:buildpacks].
- reject { |buildpack| buildpack[:name] == 'custom' }.
- map do |buildpack|
- layer = {
- name: buildpack[:name],
- url: buildpack[:url],
- destination_path: buildpack_path(buildpack[:key]),
- layer_type: ::Diego::Bbs::Models::ImageLayer::Type::SHARED,
- media_type: ::Diego::Bbs::Models::ImageLayer::MediaType::ZIP
- }
- if buildpack[:sha256]
- layer[:digest_algorithm] = ::Diego::Bbs::Models::ImageLayer::DigestAlgorithm::SHA256
- layer[:digest_value] = buildpack[:sha256]
- end
-
- ::Diego::Bbs::Models::ImageLayer.new(layer.compact)
- end
- end
-
- def cached_dependencies
- return nil if @config.get(:diego, :enable_declarative_asset_downloads)
-
- dependencies = [
- ::Diego::Bbs::Models::CachedDependency.new(
- from: LifecycleBundleUriGenerator.uri(config.get(:diego, :lifecycle_bundles)[lifecycle_bundle_key]),
- to: '/tmp/lifecycle',
- cache_key: "buildpack-#{lifecycle_stack}-lifecycle"
- )
- ]
-
- others = lifecycle_data[:buildpacks].map do |buildpack|
- next if buildpack[:name] == 'custom'
-
- buildpack_dependency = {
- name: buildpack[:name],
- from: buildpack[:url],
- to: buildpack_path(buildpack[:key]),
- cache_key: buildpack[:key]
- }
- if buildpack[:sha256]
- buildpack_dependency[:checksum_algorithm] = 'sha256'
- buildpack_dependency[:checksum_value] = buildpack[:sha256]
- end
-
- ::Diego::Bbs::Models::CachedDependency.new(buildpack_dependency.compact)
- end.compact
-
- dependencies.concat(others)
+ super(config, staging_details, lifecycle_data, 'buildpack', '/tmp/app', '/tmp/output-cache', ::Diego::Bbs::Models::ImageLayer::MediaType::ZIP)
end
def task_environment_variables
@@ -96,14 +45,6 @@ def platform_options_env
arr
end
-
- def buildpack_path(buildpack_key)
- if config.get(:staging, :legacy_md5_buildpack_paths_enabled)
- "/tmp/buildpacks/#{OpenSSL::Digest::MD5.hexdigest(buildpack_key)}"
- else
- "/tmp/buildpacks/#{Digest::XXH64.hexdigest(buildpack_key)}"
- end
- end
end
end
end
diff --git a/lib/cloud_controller/diego/buildpack_entry_generator.rb b/lib/cloud_controller/diego/buildpack_entry_generator.rb
index a4a85be256e..ebd66c3923b 100644
--- a/lib/cloud_controller/diego/buildpack_entry_generator.rb
+++ b/lib/cloud_controller/diego/buildpack_entry_generator.rb
@@ -1,8 +1,9 @@
module VCAP::CloudController
module Diego
class BuildpackEntryGenerator
- def initialize(blobstore_url_generator)
+ def initialize(blobstore_url_generator, type)
@blobstore_url_generator = blobstore_url_generator
+ @type = type
end
def buildpack_entries(buildpack_infos, stack_name)
@@ -26,7 +27,7 @@ def custom_buildpack_entry(buildpack)
end
def default_admin_buildpacks(stack_name)
- VCAP::CloudController::Buildpack.list_admin_buildpacks(stack_name).
+ VCAP::CloudController::Buildpack.list_admin_buildpacks(stack_name, @type).
select(&:enabled).
collect { |buildpack| admin_buildpack_entry(buildpack) }
end
diff --git a/lib/cloud_controller/diego/cnb/lifecycle_data.rb b/lib/cloud_controller/diego/cnb/lifecycle_data.rb
index c238c863322..63ff1fd642c 100644
--- a/lib/cloud_controller/diego/cnb/lifecycle_data.rb
+++ b/lib/cloud_controller/diego/cnb/lifecycle_data.rb
@@ -4,7 +4,7 @@ module CNB
class LifecycleData
attr_accessor :app_bits_download_uri, :build_artifacts_cache_download_uri, :build_artifacts_cache_upload_uri,
:buildpacks, :app_bits_checksum, :droplet_upload_uri, :stack, :buildpack_cache_checksum,
- :credentials
+ :credentials, :auto_detect
def message
message = {
@@ -13,7 +13,8 @@ def message
droplet_upload_uri:,
buildpacks:,
stack:,
- app_bits_checksum:
+ app_bits_checksum:,
+ auto_detect:
}
message[:build_artifacts_cache_download_uri] = build_artifacts_cache_download_uri if build_artifacts_cache_download_uri
message[:buildpack_cache_checksum] = buildpack_cache_checksum if buildpack_cache_checksum
@@ -36,7 +37,8 @@ def schema
buildpacks: Array,
stack: String,
app_bits_checksum: Hash,
- optional(:credentials) => String
+ optional(:credentials) => String,
+ auto_detect: bool
}
end
end
diff --git a/lib/cloud_controller/diego/cnb/lifecycle_protocol.rb b/lib/cloud_controller/diego/cnb/lifecycle_protocol.rb
index 7f9507987c3..ae095673b8b 100644
--- a/lib/cloud_controller/diego/cnb/lifecycle_protocol.rb
+++ b/lib/cloud_controller/diego/cnb/lifecycle_protocol.rb
@@ -25,9 +25,14 @@ def desired_lrp_builder(config, process)
def new_lifecycle_data(staging_details)
lifecycle_data = LifecycleData.new
lifecycle_data.credentials = staging_details.lifecycle.credentials
+ lifecycle_data.auto_detect = staging_details.lifecycle.buildpack_infos.empty?
lifecycle_data
end
+
+ def type
+ VCAP::CloudController::Lifecycles::CNB
+ end
end
end
end
diff --git a/lib/cloud_controller/diego/cnb/staging_action_builder.rb b/lib/cloud_controller/diego/cnb/staging_action_builder.rb
index 28f6fb2f6a5..439c57d7fd8 100644
--- a/lib/cloud_controller/diego/cnb/staging_action_builder.rb
+++ b/lib/cloud_controller/diego/cnb/staging_action_builder.rb
@@ -8,19 +8,7 @@ module Diego
module CNB
class StagingActionBuilder < VCAP::CloudController::Diego::StagingActionBuilder
def initialize(config, staging_details, lifecycle_data)
- super(config, staging_details, lifecycle_data, 'cnb', '/home/vcap/workspace', '/tmp/cache-output.tgz')
- end
-
- def cached_dependencies
- return nil if @config.get(:diego, :enable_declarative_asset_downloads)
-
- [
- ::Diego::Bbs::Models::CachedDependency.new(
- from: LifecycleBundleUriGenerator.uri(config.get(:diego, :lifecycle_bundles)[lifecycle_bundle_key]),
- to: '/tmp/lifecycle',
- cache_key: "#{@prefix}-#{lifecycle_stack}-lifecycle"
- )
- ]
+ super(config, staging_details, lifecycle_data, 'cnb', '/home/vcap/workspace', '/tmp/cache-output.tgz', ::Diego::Bbs::Models::ImageLayer::MediaType::TGZ)
end
def task_environment_variables
@@ -42,8 +30,10 @@ def stage_action
'--cache-output', '/tmp/cache-output.tgz'
]
+ args.push('--auto-detect') if lifecycle_data[:auto_detect]
lifecycle_data[:buildpacks].each do |buildpack|
- args.push('--buildpack', buildpack[:url])
+ args.push('--buildpack', buildpack[:url]) if buildpack[:name] == 'custom'
+ args.push('--buildpack', buildpack[:key]) unless buildpack[:name] == 'custom'
end
env_vars = BbsEnvironmentBuilder.build(staging_details.environment_variables)
diff --git a/lib/cloud_controller/diego/docker/staging_action_builder.rb b/lib/cloud_controller/diego/docker/staging_action_builder.rb
index d01fa05b070..c4c259899b2 100644
--- a/lib/cloud_controller/diego/docker/staging_action_builder.rb
+++ b/lib/cloud_controller/diego/docker/staging_action_builder.rb
@@ -67,6 +67,10 @@ def cached_dependencies
]
end
+ def additional_image_layers
+ []
+ end
+
def stack
"preloaded:#{config.get(:diego, :docker_staging_stack)}"
end
diff --git a/lib/cloud_controller/diego/lifecycle_protocol.rb b/lib/cloud_controller/diego/lifecycle_protocol.rb
index c36cd94bd0f..e2f0f2b7f7e 100644
--- a/lib/cloud_controller/diego/lifecycle_protocol.rb
+++ b/lib/cloud_controller/diego/lifecycle_protocol.rb
@@ -19,7 +19,7 @@ class InvalidDownloadUri < StandardError; end
def initialize(blobstore_url_generator=::CloudController::DependencyLocator.instance.blobstore_url_generator,
droplet_url_generator=::CloudController::DependencyLocator.instance.droplet_url_generator)
@blobstore_url_generator = blobstore_url_generator
- @buildpack_entry_generator = BuildpackEntryGenerator.new(@blobstore_url_generator)
+ @buildpack_entry_generator = BuildpackEntryGenerator.new(@blobstore_url_generator, type)
@droplet_url_generator = droplet_url_generator
end
diff --git a/lib/cloud_controller/diego/lifecycles/app_buildpack_lifecycle.rb b/lib/cloud_controller/diego/lifecycles/app_buildpack_lifecycle.rb
index aa9d28e1e54..419c9fc9c6c 100644
--- a/lib/cloud_controller/diego/lifecycles/app_buildpack_lifecycle.rb
+++ b/lib/cloud_controller/diego/lifecycles/app_buildpack_lifecycle.rb
@@ -8,7 +8,7 @@ class AppBuildpackLifecycle < AppBaseLifecycle
def initialize(message)
@message = message
- db_result = BuildpackLifecycleFetcher.fetch(buildpacks, stack)
+ db_result = BuildpackLifecycleFetcher.fetch(buildpacks, stack, type)
@validator = BuildpackLifecycleDataValidator.new({
buildpack_infos: db_result[:buildpack_infos],
stack: db_result[:stack]
diff --git a/lib/cloud_controller/diego/lifecycles/app_cnb_lifecycle.rb b/lib/cloud_controller/diego/lifecycles/app_cnb_lifecycle.rb
index 139e9c4131a..a4eae0f65dd 100644
--- a/lib/cloud_controller/diego/lifecycles/app_cnb_lifecycle.rb
+++ b/lib/cloud_controller/diego/lifecycles/app_cnb_lifecycle.rb
@@ -1,11 +1,22 @@
+require 'cloud_controller/diego/lifecycles/buildpack_info'
+require 'cloud_controller/diego/lifecycles/buildpack_lifecycle_data_validator'
require 'cloud_controller/diego/lifecycles/app_base_lifecycle'
+require 'fetchers/buildpack_lifecycle_fetcher'
module VCAP::CloudController
class AppCNBLifecycle < AppBaseLifecycle
def initialize(message)
@message = message
+
+ db_result = BuildpackLifecycleFetcher.fetch(buildpacks, stack, type)
+ @validator = BuildpackLifecycleDataValidator.new({
+ buildpack_infos: db_result[:buildpack_infos],
+ stack: db_result[:stack]
+ })
end
+ delegate :valid?, :errors, to: :validator
+
def create_lifecycle_data_model(app)
CNBLifecycleDataModel.create(
buildpacks:,
@@ -15,14 +26,6 @@ def create_lifecycle_data_model(app)
)
end
- def valid?
- message.is_a?(AppUpdateMessage) || !buildpacks.empty?
- end
-
- def errors
- []
- end
-
def update_lifecycle_data_credentials(app)
return unless message.buildpack_data.requested?(:credentials)
@@ -36,5 +39,9 @@ def type
def credentials
message.buildpack_data.credentials
end
+
+ private
+
+ attr_reader :validator
end
end
diff --git a/lib/cloud_controller/diego/lifecycles/buildpack_info.rb b/lib/cloud_controller/diego/lifecycles/buildpack_info.rb
index 0dbd3194317..0cae5333d22 100644
--- a/lib/cloud_controller/diego/lifecycles/buildpack_info.rb
+++ b/lib/cloud_controller/diego/lifecycles/buildpack_info.rb
@@ -7,7 +7,7 @@ class BuildpackInfo
def initialize(buildpack_name_or_url, buildpack_record)
@buildpack = buildpack_name_or_url
@buildpack_record = buildpack_record
- @buildpack_url = buildpack_name_or_url if UriUtils.is_buildpack_uri?(buildpack_name_or_url)
+ @buildpack_url = buildpack_name_or_url if UriUtils.is_buildpack_uri?(buildpack_name_or_url) || UriUtils.is_cnb_buildpack_uri?(buildpack_name_or_url)
end
def buildpack_exists_in_db?
diff --git a/lib/cloud_controller/diego/lifecycles/lifecycle_base.rb b/lib/cloud_controller/diego/lifecycles/lifecycle_base.rb
index 4a691418c1d..671d0bd3d92 100644
--- a/lib/cloud_controller/diego/lifecycles/lifecycle_base.rb
+++ b/lib/cloud_controller/diego/lifecycles/lifecycle_base.rb
@@ -9,7 +9,7 @@ def initialize(package, staging_message)
@staging_message = staging_message
@package = package
- db_result = BuildpackLifecycleFetcher.fetch(buildpacks_to_use, staging_stack)
+ db_result = BuildpackLifecycleFetcher.fetch(buildpacks_to_use, staging_stack, type)
@buildpack_infos = db_result[:buildpack_infos]
@validator = BuildpackLifecycleDataValidator.new({ buildpack_infos: buildpack_infos, stack: db_result[:stack] })
end
diff --git a/lib/cloud_controller/diego/staging_action_builder.rb b/lib/cloud_controller/diego/staging_action_builder.rb
index d1dbdff9522..3cca3aca34b 100644
--- a/lib/cloud_controller/diego/staging_action_builder.rb
+++ b/lib/cloud_controller/diego/staging_action_builder.rb
@@ -10,13 +10,14 @@ class StagingActionBuilder
attr_reader :config, :lifecycle_data, :staging_details
- def initialize(config, staging_details, lifecycle_data, prefix, app_destination_path, cache_source)
+ def initialize(config, staging_details, lifecycle_data, prefix, app_destination_path, cache_source, bp_media_type)
@config = config
@staging_details = staging_details
@lifecycle_data = lifecycle_data
@prefix = prefix
@app_destination_path = app_destination_path
@cache_source = cache_source
+ @bp_media_type = bp_media_type
end
def action
@@ -35,8 +36,55 @@ def action
serial(actions)
end
+ def cached_dependencies
+ return nil if @config.get(:diego, :enable_declarative_asset_downloads)
+
+ dependencies = [
+ ::Diego::Bbs::Models::CachedDependency.new(
+ from: LifecycleBundleUriGenerator.uri(config.get(:diego, :lifecycle_bundles)[lifecycle_bundle_key]),
+ to: '/tmp/lifecycle',
+ cache_key: "#{@prefix}-#{lifecycle_stack}-lifecycle"
+ )
+ ]
+
+ others = lifecycle_data[:buildpacks].map do |buildpack|
+ next if buildpack[:name] == 'custom'
+
+ buildpack_dependency = {
+ name: buildpack[:name],
+ from: buildpack[:url],
+ to: buildpack_path(buildpack[:key]),
+ cache_key: buildpack[:key]
+ }
+ if buildpack[:sha256]
+ buildpack_dependency[:checksum_algorithm] = 'sha256'
+ buildpack_dependency[:checksum_value] = buildpack[:sha256]
+ end
+
+ ::Diego::Bbs::Models::CachedDependency.new(buildpack_dependency.compact)
+ end.compact
+
+ dependencies.concat(others)
+ end
+
def additional_image_layers
- []
+ lifecycle_data[:buildpacks].
+ reject { |buildpack| buildpack[:name] == 'custom' }.
+ map do |buildpack|
+ layer = {
+ name: buildpack[:name],
+ url: buildpack[:url],
+ destination_path: buildpack_path(buildpack[:key]),
+ layer_type: ::Diego::Bbs::Models::ImageLayer::Type::SHARED,
+ media_type: @bp_media_type
+ }
+ if buildpack[:sha256]
+ layer[:digest_algorithm] = ::Diego::Bbs::Models::ImageLayer::DigestAlgorithm::SHA256
+ layer[:digest_value] = buildpack[:sha256]
+ end
+
+ ::Diego::Bbs::Models::ImageLayer.new(layer.compact)
+ end
end
def image_layers
diff --git a/lib/cloud_controller/install_buildpacks.rb b/lib/cloud_controller/install_buildpacks.rb
index 7986ea157a2..1dbd8cb8c50 100644
--- a/lib/cloud_controller/install_buildpacks.rb
+++ b/lib/cloud_controller/install_buildpacks.rb
@@ -12,12 +12,12 @@ def install(buildpacks)
CloudController::DependencyLocator.instance.buildpack_blobstore.ensure_bucket_exists
job_factory = VCAP::CloudController::Jobs::Runtime::BuildpackInstallerFactory.new
- buildpack_install_jobs = []
-
factory_options = []
buildpacks.each do |bpack|
buildpack_opts = bpack.deep_symbolize_keys
+ buildpack_opts[:lifecycle] = Lifecycles::BUILDPACK if buildpack_opts[:lifecycle].nil?
+
buildpack_name = buildpack_opts.delete(:name)
if buildpack_name.nil?
logger.error "A name must be specified for the buildpack_opts: #{buildpack_opts}"
@@ -39,15 +39,10 @@ def install(buildpacks)
logger.error "File not found: #{buildpack_file}, for the buildpack_opts: #{bpack}"
next
end
-
- detected_stack = VCAP::CloudController::Buildpacks::StackNameExtractor.extract_from_file(buildpack_file)
- factory_options << { name: buildpack_name, file: buildpack_file, options: buildpack_opts, stack: detected_stack }
+ factory_options << { name: buildpack_name, file: buildpack_file, options: buildpack_opts, stack: detected_stack(buildpack_file, buildpack_opts) }
end
- buildpacks_by_name = factory_options.group_by { |options| options[:name] }
- buildpacks_by_name.each do |name, buildpack_options|
- buildpack_install_jobs << job_factory.plan(name, buildpack_options)
- end
+ buildpack_install_jobs = generate_install_jobs(factory_options, job_factory)
buildpack_install_jobs.flatten!
run_canary(buildpack_install_jobs)
@@ -60,6 +55,23 @@ def logger
private
+ def generate_install_jobs(factory_options, job_factory)
+ buildpack_install_jobs = []
+ buildpacks_by_lifecycle = factory_options.group_by { |options| options[:options][:lifecycle] }
+ buildpacks_by_lifecycle.each_value do |options|
+ options.group_by { |opts| opts[:name] }.each do |name, buildpack_options|
+ buildpack_install_jobs << job_factory.plan(name, buildpack_options)
+ end
+ end
+ buildpack_install_jobs
+ end
+
+ def detected_stack(file, opts)
+ return opts[:stack] if opts[:lifecycle] == Lifecycles::CNB
+
+ VCAP::CloudController::Buildpacks::StackNameExtractor.extract_from_file(file)
+ end
+
def buildpack_zip(package, zipfile)
return zipfile if zipfile
diff --git a/lib/cloud_controller/paging/pagination_options.rb b/lib/cloud_controller/paging/pagination_options.rb
index 0555db155dc..d07c7124e43 100644
--- a/lib/cloud_controller/paging/pagination_options.rb
+++ b/lib/cloud_controller/paging/pagination_options.rb
@@ -12,7 +12,7 @@ class PaginationOptions
DIRECTION_DEFAULT = 'asc'.freeze
VALID_DIRECTIONS = %w[asc desc].freeze
- attr_writer :order_by, :order_direction, :default_order_by
+ attr_writer :order_by, :order_direction, :default_order_by, :secondary_default_order_by
attr_accessor :page, :per_page
def initialize(params)
@@ -38,6 +38,12 @@ def order_direction
@order_direction || DIRECTION_DEFAULT
end
+ def secondary_order_by
+ return if @order_by && @order_by != default_order_by
+
+ @secondary_default_order_by
+ end
+
def keys
%i[page per_page order_by order_direction]
end
diff --git a/lib/cloud_controller/paging/sequel_paginator.rb b/lib/cloud_controller/paging/sequel_paginator.rb
index 46d8acf933c..c104b0de08b 100644
--- a/lib/cloud_controller/paging/sequel_paginator.rb
+++ b/lib/cloud_controller/paging/sequel_paginator.rb
@@ -13,6 +13,8 @@ def get_page(dataset, pagination_options)
order_type = Sequel.send(order_direction, Sequel.qualify(table_name, order_by))
dataset = dataset.order(order_type)
+ secondary_order_by = pagination_options.secondary_order_by
+ dataset = dataset.order_append(Sequel.send(order_direction, Sequel.qualify(table_name, secondary_order_by))) if secondary_order_by
dataset = dataset.order_append(Sequel.send(order_direction, Sequel.qualify(table_name, :guid))) if order_by != 'id' && has_guid_column
distinct_opt = dataset.opts[:distinct]
diff --git a/lib/cloud_controller/upload_buildpack.rb b/lib/cloud_controller/upload_buildpack.rb
index cda53101a6e..d435e5ef519 100644
--- a/lib/cloud_controller/upload_buildpack.rb
+++ b/lib/cloud_controller/upload_buildpack.rb
@@ -1,4 +1,5 @@
require 'vcap/digester'
+require 'cloud_controller/diego/lifecycles/lifecycles'
module VCAP::CloudController
class UploadBuildpack
@@ -55,8 +56,10 @@ def upload_buildpack(buildpack, bits_file_path, new_filename)
private
def raise_translated_api_error(buildpack)
- raise CloudController::Errors::ApiError.new_from_details('BuildpackNameStackTaken', buildpack.name, buildpack.stack) if buildpack.errors.on(%i[name stack]).try(:include?,
- :unique)
+ if buildpack.errors.on(%i[name stack lifecycle]).try(:include?, :unique)
+ raise CloudController::Errors::ApiError.new_from_details('BuildpackNameStackLifecycleTaken', buildpack.name, buildpack.stack, buildpack.lifecycle)
+ end
+
raise CloudController::Errors::ApiError.new_from_details('BuildpackStacksDontMatch', buildpack.stack, buildpack.initial_value(:stack)) if buildpack.errors.on(:stack).try(
:include?, :buildpack_cant_change_stacks
)
@@ -69,6 +72,8 @@ def determine_new_stack(buildpack, bits_file_path)
extracted_stack = Buildpacks::StackNameExtractor.extract_from_file(bits_file_path)
[extracted_stack, buildpack.stack].find(&:present?)
rescue CloudController::Errors::BuildpackError => e
+ return buildpack.stack if buildpack.lifecycle == Lifecycles::CNB
+
raise CloudController::Errors::ApiError.new_from_details('BuildpackZipError', e.message)
end
diff --git a/spec/request/buildpacks_spec.rb b/spec/request/buildpacks_spec.rb
index 78a0d859bd7..a11a3850b71 100644
--- a/spec/request/buildpacks_spec.rb
+++ b/spec/request/buildpacks_spec.rb
@@ -28,6 +28,7 @@
order_by: 'updated_at',
names: 'foo',
stacks: 'cf',
+ lifecycle: 'buildpack',
label_selector: 'foo,bar',
guids: 'foo,bar',
created_ats: "#{Time.now.utc.iso8601},#{Time.now.utc.iso8601}",
@@ -92,23 +93,26 @@
let!(:stack2) { VCAP::CloudController::Stack.make }
let!(:stack3) { VCAP::CloudController::Stack.make }
+ let!(:buildpack4) { VCAP::CloudController::Buildpack.make(stack: stack1.name, position: 2, lifecycle: 'cnb') }
+ let!(:buildpack5) { VCAP::CloudController::Buildpack.make(stack: stack1.name, position: 1, lifecycle: 'cnb') }
+
let!(:buildpack1) { VCAP::CloudController::Buildpack.make(stack: stack1.name, position: 1) }
let!(:buildpack2) { VCAP::CloudController::Buildpack.make(stack: stack2.name, position: 3) }
let!(:buildpack3) { VCAP::CloudController::Buildpack.make(stack: stack3.name, position: 2) }
- it 'returns a paginated list of buildpacks, sorted by position' do
+ it 'returns a paginated list of buildpacks, sorted by lifecycle and position' do
get '/v3/buildpacks?page=1&per_page=2', nil, headers
expect(parsed_response).to be_a_response_like(
{
'pagination' => {
- 'total_results' => 3,
- 'total_pages' => 2,
+ 'total_results' => 5,
+ 'total_pages' => 3,
'first' => {
'href' => "#{link_prefix}/v3/buildpacks?page=1&per_page=2"
},
'last' => {
- 'href' => "#{link_prefix}/v3/buildpacks?page=2&per_page=2"
+ 'href' => "#{link_prefix}/v3/buildpacks?page=3&per_page=2"
},
'next' => {
'href' => "#{link_prefix}/v3/buildpacks?page=2&per_page=2"
@@ -118,6 +122,76 @@
'resources' => [
{
'guid' => buildpack1.guid,
+ 'lifecycle' => 'buildpack',
+ 'created_at' => iso8601,
+ 'updated_at' => iso8601,
+ 'name' => buildpack1.name,
+ 'state' => buildpack1.state,
+ 'filename' => buildpack1.filename,
+ 'stack' => buildpack1.stack,
+ 'position' => 1,
+ 'enabled' => true,
+ 'locked' => false,
+ 'metadata' => { 'labels' => {}, 'annotations' => {} },
+ 'links' => {
+ 'self' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack1.guid}"
+ },
+ 'upload' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack1.guid}/upload",
+ 'method' => 'POST'
+ }
+ }
+ },
+ {
+ 'guid' => buildpack3.guid,
+ 'lifecycle' => 'buildpack',
+ 'created_at' => iso8601,
+ 'updated_at' => iso8601,
+ 'name' => buildpack3.name,
+ 'state' => buildpack3.state,
+ 'filename' => buildpack3.filename,
+ 'stack' => buildpack3.stack,
+ 'position' => 2,
+ 'enabled' => true,
+ 'locked' => false,
+ 'metadata' => { 'labels' => {}, 'annotations' => {} },
+ 'links' => {
+ 'self' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack3.guid}"
+ },
+ 'upload' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack3.guid}/upload",
+ 'method' => 'POST'
+ }
+ }
+ }
+ ]
+ }
+ )
+ end
+
+ it 'with no filters, returns a list of buildpacks, sorted by lifecycle and position' do
+ get '/v3/buildpacks', nil, headers
+
+ expect(parsed_response).to be_a_response_like(
+ {
+ 'pagination' => {
+ 'total_results' => 5,
+ 'total_pages' => 1,
+ 'first' => {
+ 'href' => "#{link_prefix}/v3/buildpacks?page=1&per_page=50"
+ },
+ 'last' => {
+ 'href' => "#{link_prefix}/v3/buildpacks?page=1&per_page=50"
+ },
+ 'next' => nil,
+ 'previous' => nil
+ },
+ 'resources' => [
+ {
+ 'guid' => buildpack1.guid,
+ 'lifecycle' => 'buildpack',
'created_at' => iso8601,
'updated_at' => iso8601,
'name' => buildpack1.name,
@@ -140,6 +214,7 @@
},
{
'guid' => buildpack3.guid,
+ 'lifecycle' => 'buildpack',
'created_at' => iso8601,
'updated_at' => iso8601,
'name' => buildpack3.name,
@@ -159,6 +234,75 @@
'method' => 'POST'
}
}
+ },
+ {
+ 'guid' => buildpack2.guid,
+ 'lifecycle' => 'buildpack',
+ 'created_at' => iso8601,
+ 'updated_at' => iso8601,
+ 'name' => buildpack2.name,
+ 'state' => buildpack2.state,
+ 'filename' => buildpack2.filename,
+ 'stack' => buildpack2.stack,
+ 'position' => 3,
+ 'enabled' => true,
+ 'locked' => false,
+ 'metadata' => { 'labels' => {}, 'annotations' => {} },
+ 'links' => {
+ 'self' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack2.guid}"
+ },
+ 'upload' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack2.guid}/upload",
+ 'method' => 'POST'
+ }
+ }
+ },
+ {
+ 'guid' => buildpack5.guid,
+ 'lifecycle' => 'cnb',
+ 'created_at' => iso8601,
+ 'updated_at' => iso8601,
+ 'name' => buildpack5.name,
+ 'state' => buildpack5.state,
+ 'filename' => buildpack5.filename,
+ 'stack' => buildpack5.stack,
+ 'position' => 1,
+ 'enabled' => true,
+ 'locked' => false,
+ 'metadata' => { 'labels' => {}, 'annotations' => {} },
+ 'links' => {
+ 'self' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack5.guid}"
+ },
+ 'upload' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack5.guid}/upload",
+ 'method' => 'POST'
+ }
+ }
+ },
+ {
+ 'guid' => buildpack4.guid,
+ 'lifecycle' => 'cnb',
+ 'created_at' => iso8601,
+ 'updated_at' => iso8601,
+ 'name' => buildpack4.name,
+ 'state' => buildpack4.state,
+ 'filename' => buildpack4.filename,
+ 'stack' => buildpack4.stack,
+ 'position' => 2,
+ 'enabled' => true,
+ 'locked' => false,
+ 'metadata' => { 'labels' => {}, 'annotations' => {} },
+ 'links' => {
+ 'self' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack4.guid}"
+ },
+ 'upload' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack4.guid}/upload",
+ 'method' => 'POST'
+ }
+ }
}
]
}
@@ -185,6 +329,7 @@
'resources' => [
{
'guid' => buildpack1.guid,
+ 'lifecycle' => 'buildpack',
'created_at' => iso8601,
'updated_at' => iso8601,
'name' => buildpack1.name,
@@ -210,6 +355,146 @@
)
end
+ it 'returns a paginated list of buildpacks filtered by lifecycle' do
+ get '/v3/buildpacks?lifecycle=buildpack&per_page=2', nil, headers
+
+ expect(parsed_response).to be_a_response_like(
+ {
+ 'pagination' => {
+ 'total_results' => 3,
+ 'total_pages' => 2,
+ 'first' => {
+ 'href' => "#{link_prefix}/v3/buildpacks?lifecycle=buildpack&page=1&per_page=2"
+ },
+ 'last' => {
+ 'href' => "#{link_prefix}/v3/buildpacks?lifecycle=buildpack&page=2&per_page=2"
+ },
+ 'next' => {
+ 'href' => "#{link_prefix}/v3/buildpacks?lifecycle=buildpack&page=2&per_page=2"
+ },
+ 'previous' => nil
+ },
+ 'resources' => [
+ {
+ 'guid' => buildpack1.guid,
+ 'lifecycle' => 'buildpack',
+ 'created_at' => iso8601,
+ 'updated_at' => iso8601,
+ 'name' => buildpack1.name,
+ 'state' => buildpack1.state,
+ 'filename' => buildpack1.filename,
+ 'stack' => buildpack1.stack,
+ 'position' => 1,
+ 'enabled' => true,
+ 'locked' => false,
+ 'metadata' => { 'labels' => {}, 'annotations' => {} },
+ 'links' => {
+ 'self' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack1.guid}"
+ },
+ 'upload' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack1.guid}/upload",
+ 'method' => 'POST'
+ }
+ }
+ },
+ {
+ 'guid' => buildpack3.guid,
+ 'lifecycle' => 'buildpack',
+ 'created_at' => iso8601,
+ 'updated_at' => iso8601,
+ 'name' => buildpack3.name,
+ 'state' => buildpack3.state,
+ 'filename' => buildpack3.filename,
+ 'stack' => buildpack3.stack,
+ 'position' => 2,
+ 'enabled' => true,
+ 'locked' => false,
+ 'metadata' => { 'labels' => {}, 'annotations' => {} },
+ 'links' => {
+ 'self' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack3.guid}"
+ },
+ 'upload' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack3.guid}/upload",
+ 'method' => 'POST'
+ }
+ }
+ }
+ ]
+ }
+ )
+ end
+
+ it 'returns a list of buildpacks filtered by lifecycle' do
+ get '/v3/buildpacks?lifecycle=cnb', nil, headers
+
+ expect(parsed_response).to be_a_response_like(
+ {
+ 'pagination' => {
+ 'total_results' => 2,
+ 'total_pages' => 1,
+ 'first' => {
+ 'href' => "#{link_prefix}/v3/buildpacks?lifecycle=cnb&page=1&per_page=50"
+ },
+ 'last' => {
+ 'href' => "#{link_prefix}/v3/buildpacks?lifecycle=cnb&page=1&per_page=50"
+ },
+ 'next' => nil,
+ 'previous' => nil
+ },
+ 'resources' => [
+ {
+ 'guid' => buildpack5.guid,
+ 'lifecycle' => 'cnb',
+ 'created_at' => iso8601,
+ 'updated_at' => iso8601,
+ 'name' => buildpack5.name,
+ 'state' => buildpack5.state,
+ 'filename' => buildpack5.filename,
+ 'stack' => buildpack5.stack,
+ 'position' => 1,
+ 'enabled' => true,
+ 'locked' => false,
+ 'metadata' => { 'labels' => {}, 'annotations' => {} },
+ 'links' => {
+ 'self' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack5.guid}"
+ },
+ 'upload' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack5.guid}/upload",
+ 'method' => 'POST'
+ }
+ }
+ },
+ {
+ 'guid' => buildpack4.guid,
+ 'lifecycle' => 'cnb',
+ 'created_at' => iso8601,
+ 'updated_at' => iso8601,
+ 'name' => buildpack4.name,
+ 'state' => buildpack4.state,
+ 'filename' => buildpack4.filename,
+ 'stack' => buildpack4.stack,
+ 'position' => 2,
+ 'enabled' => true,
+ 'locked' => false,
+ 'metadata' => { 'labels' => {}, 'annotations' => {} },
+ 'links' => {
+ 'self' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack4.guid}"
+ },
+ 'upload' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack4.guid}/upload",
+ 'method' => 'POST'
+ }
+ }
+ }
+ ]
+ }
+ )
+ end
+
it 'orders by position' do
get "/v3/buildpacks?names=#{buildpack1.name},#{buildpack3.name}&order_by=-position", nil, headers
@@ -230,6 +515,7 @@
'resources' => [
{
'guid' => buildpack3.guid,
+ 'lifecycle' => 'buildpack',
'created_at' => iso8601,
'updated_at' => iso8601,
'name' => buildpack3.name,
@@ -252,6 +538,7 @@
},
{
'guid' => buildpack1.guid,
+ 'lifecycle' => 'buildpack',
'created_at' => iso8601,
'updated_at' => iso8601,
'name' => buildpack1.name,
@@ -276,6 +563,147 @@
}
)
end
+
+ it 'reverse orders by lifecycle (with position as secondary)' do
+ get '/v3/buildpacks?order_by=-lifecycle', nil, headers
+
+ expect(parsed_response).to be_a_response_like(
+ expect(parsed_response).to(be_a_response_like(
+ {
+ 'pagination' => {
+ 'total_results' => 5,
+ 'total_pages' => 1,
+ 'first' => {
+ 'href' => "#{link_prefix}/v3/buildpacks?order_by=-lifecycle&page=1&per_page=50"
+ },
+ 'last' => {
+ 'href' => "#{link_prefix}/v3/buildpacks?order_by=-lifecycle&page=1&per_page=50"
+ },
+ 'next' => nil,
+ 'previous' => nil
+ },
+ 'resources' => [
+ {
+ 'guid' => buildpack4.guid,
+ 'lifecycle' => 'cnb',
+ 'created_at' => iso8601,
+ 'updated_at' => iso8601,
+ 'name' => buildpack4.name,
+ 'state' => buildpack4.state,
+ 'filename' => buildpack4.filename,
+ 'stack' => buildpack4.stack,
+ 'position' => 2,
+ 'enabled' => true,
+ 'locked' => false,
+ 'metadata' => { 'labels' => {}, 'annotations' => {} },
+ 'links' => {
+ 'self' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack4.guid}"
+ },
+ 'upload' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack4.guid}/upload",
+ 'method' => 'POST'
+ }
+ }
+ },
+ {
+ 'guid' => buildpack5.guid,
+ 'lifecycle' => 'cnb',
+ 'created_at' => iso8601,
+ 'updated_at' => iso8601,
+ 'name' => buildpack5.name,
+ 'state' => buildpack5.state,
+ 'filename' => buildpack5.filename,
+ 'stack' => buildpack5.stack,
+ 'position' => 1,
+ 'enabled' => true,
+ 'locked' => false,
+ 'metadata' => { 'labels' => {}, 'annotations' => {} },
+ 'links' => {
+ 'self' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack5.guid}"
+ },
+ 'upload' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack5.guid}/upload",
+ 'method' => 'POST'
+ }
+ }
+ },
+ {
+ 'guid' => buildpack2.guid,
+ 'lifecycle' => 'buildpack',
+ 'created_at' => iso8601,
+ 'updated_at' => iso8601,
+ 'name' => buildpack2.name,
+ 'state' => buildpack2.state,
+ 'filename' => buildpack2.filename,
+ 'stack' => buildpack2.stack,
+ 'position' => 3,
+ 'enabled' => true,
+ 'locked' => false,
+ 'metadata' => { 'labels' => {}, 'annotations' => {} },
+ 'links' => {
+ 'self' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack2.guid}"
+ },
+ 'upload' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack2.guid}/upload",
+ 'method' => 'POST'
+ }
+ }
+ },
+ {
+ 'guid' => buildpack3.guid,
+ 'lifecycle' => 'buildpack',
+ 'created_at' => iso8601,
+ 'updated_at' => iso8601,
+ 'name' => buildpack3.name,
+ 'state' => buildpack3.state,
+ 'filename' => buildpack3.filename,
+ 'stack' => buildpack3.stack,
+ 'position' => 2,
+ 'enabled' => true,
+ 'locked' => false,
+ 'metadata' => { 'labels' => {}, 'annotations' => {} },
+ 'links' => {
+ 'self' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack3.guid}"
+ },
+ 'upload' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack3.guid}/upload",
+ 'method' => 'POST'
+ }
+ }
+ },
+ {
+ 'guid' => buildpack1.guid,
+ 'lifecycle' => 'buildpack',
+ 'created_at' => iso8601,
+ 'updated_at' => iso8601,
+ 'name' => buildpack1.name,
+ 'state' => buildpack1.state,
+ 'filename' => buildpack1.filename,
+ 'stack' => buildpack1.stack,
+ 'position' => 1,
+ 'enabled' => true,
+ 'locked' => false,
+ 'metadata' => { 'labels' => {}, 'annotations' => {} },
+ 'links' => {
+ 'self' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack1.guid}"
+ },
+ 'upload' => {
+ 'href' => "#{link_prefix}/v3/buildpacks/#{buildpack1.guid}/upload",
+ 'method' => 'POST'
+ }
+ }
+ }
+
+ ]
+ }
+ ))
+ )
+ end
end
context 'permissions' do
@@ -360,6 +788,7 @@
'enabled' => params[:enabled],
'locked' => params[:locked],
'guid' => buildpack.guid,
+ 'lifecycle' => 'buildpack',
'created_at' => iso8601,
'updated_at' => iso8601,
'metadata' => {
@@ -477,6 +906,7 @@
'enabled' => buildpack.enabled,
'locked' => buildpack.locked,
'guid' => buildpack.guid,
+ 'lifecycle' => 'buildpack',
'created_at' => iso8601,
'updated_at' => iso8601,
'metadata' => { 'labels' => {}, 'annotations' => {} },
diff --git a/spec/support/test_cnb.rb b/spec/support/test_cnb.rb
new file mode 100644
index 00000000000..b17a5c35321
--- /dev/null
+++ b/spec/support/test_cnb.rb
@@ -0,0 +1,16 @@
+require 'zlib'
+require 'rubygems/package'
+
+module TestCnb
+ def self.create(name, file_count, file_size=1024, &)
+ File.open(name, 'wb') do |file|
+ Gem::Package::TarWriter.new(file) do |tar|
+ file_count.times do |i|
+ tar.add_file_simple("test_#{i}", 0o644, file_size) do |f|
+ f.write('A' * file_size)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/support/test_tgz.rb b/spec/support/test_tgz.rb
new file mode 100644
index 00000000000..15d1f44d738
--- /dev/null
+++ b/spec/support/test_tgz.rb
@@ -0,0 +1,18 @@
+require 'zlib'
+require 'rubygems/package'
+
+module TestTgz
+ def self.create(tgz_name, file_count, file_size=1024, &)
+ File.open(tgz_name, 'wb') do |file|
+ Zlib::GzipWriter.wrap(file) do |gzip|
+ Gem::Package::TarWriter.new(gzip) do |tar|
+ file_count.times do |i|
+ tar.add_file_simple("ziptest_#{i}", 0o644, file_size) do |f|
+ f.write('A' * file_size)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/unit/actions/app_apply_manifest_spec.rb b/spec/unit/actions/app_apply_manifest_spec.rb
index 8e9d43e6bd7..f0761d7cfab 100644
--- a/spec/unit/actions/app_apply_manifest_spec.rb
+++ b/spec/unit/actions/app_apply_manifest_spec.rb
@@ -119,6 +119,7 @@ module VCAP::CloudController
end
describe 'using cnb type' do
+ let(:message) { AppManifestMessage.create_from_yml({ name: 'blah', buildpack: buildpack.name, lifecycle: 'cnb' }) }
let(:app) { AppModel.make(:cnb) }
it 'calls AppUpdate with the correct arguments' do
diff --git a/spec/unit/actions/buildpack_create_spec.rb b/spec/unit/actions/buildpack_create_spec.rb
index 3a0c3dbf2ac..c9f01d6471c 100644
--- a/spec/unit/actions/buildpack_create_spec.rb
+++ b/spec/unit/actions/buildpack_create_spec.rb
@@ -19,7 +19,8 @@ module VCAP::CloudController
name: 'the-name',
stack: 'the-stack',
enabled: false,
- locked: true
+ locked: true,
+ lifecycle: Lifecycles::BUILDPACK
)
buildpack = BuildpackCreate.new.create(message)
@@ -28,6 +29,7 @@ module VCAP::CloudController
expect(buildpack.position).to eq(1)
expect(buildpack.enabled).to be(false)
expect(buildpack.locked).to be(true)
+ expect(buildpack.lifecycle).to eq(Lifecycles::BUILDPACK)
end
end
@@ -114,6 +116,19 @@ module VCAP::CloudController
end
end
+ context 'when lifecycle is provided' do
+ it 'creates a buildpack with locked set to true' do
+ message = BuildpackCreateMessage.new(
+ name: 'the-name',
+ stack: 'the-stack',
+ lifecycle: Lifecycles::CNB
+ )
+ buildpack = BuildpackCreate.new.create(message)
+
+ expect(buildpack.lifecycle).to eq(Lifecycles::CNB)
+ end
+ end
+
context 'when a model validation fails' do
it 'raises an error' do
errors = Sequel::Model::Errors.new
@@ -163,7 +178,7 @@ module VCAP::CloudController
message = BuildpackCreateMessage.new(name: name, stack: 'the-stack')
expect do
BuildpackCreate.new.create(message)
- end.to raise_error(BuildpackCreate::Error, "Buildpack with name 'the-name' and stack 'the-stack' already exists")
+ end.to raise_error(BuildpackCreate::Error, "Buildpack with name 'the-name', stack 'the-stack' and lifecycle 'buildpack' already exists")
end
end
end
diff --git a/spec/unit/actions/buildpack_update_spec.rb b/spec/unit/actions/buildpack_update_spec.rb
index 0c6f4ef94aa..5958e3bcfda 100644
--- a/spec/unit/actions/buildpack_update_spec.rb
+++ b/spec/unit/actions/buildpack_update_spec.rb
@@ -120,13 +120,13 @@ module VCAP::CloudController
end
end
- it 'raises a human-friendly error when name and stack conflict' do
+ it 'raises a human-friendly error when name, stack and lifecycle conflict' do
expect(buildpack1.stack).to eq buildpack2.stack
message = BuildpackUpdateMessage.new(name: buildpack1.name)
expect do
BuildpackUpdate.new.update(buildpack2, message)
- end.to raise_error(BuildpackUpdate::Error, "Buildpack with name '#{buildpack1.name}' and stack '#{buildpack1.stack}' already exists")
+ end.to raise_error(BuildpackUpdate::Error, "Buildpack with name '#{buildpack1.name}', stack '#{buildpack1.stack}' and lifecycle '#{buildpack1.lifecycle}' already exists")
end
it 're-raises when there is an unknown error' do
diff --git a/spec/unit/actions/buildpack_upload_spec.rb b/spec/unit/actions/buildpack_upload_spec.rb
index cf3c5f4aa8a..642e56e1428 100644
--- a/spec/unit/actions/buildpack_upload_spec.rb
+++ b/spec/unit/actions/buildpack_upload_spec.rb
@@ -7,7 +7,7 @@ module VCAP::CloudController
describe '#upload_async' do
let!(:buildpack) { VCAP::CloudController::Buildpack.create_from_hash({ name: 'upload_binary_buildpack', stack: nil, position: 0 }) }
- let(:message) { BuildpackUploadMessage.new({ 'bits_path' => '/tmp/path', 'bits_name' => 'buildpack.zip' }) }
+ let(:message) { BuildpackUploadMessage.new({ 'bits_path' => '/tmp/path', 'bits_name' => 'buildpack.zip' }, VCAP::CloudController::Lifecycles::BUILDPACK) }
let(:config) { Config.new({ name: 'local', index: '1' }) }
before do
diff --git a/spec/unit/controllers/runtime/buildpacks_controller_spec.rb b/spec/unit/controllers/runtime/buildpacks_controller_spec.rb
index 80c819a77b7..0eecd522bdd 100644
--- a/spec/unit/controllers/runtime/buildpacks_controller_spec.rb
+++ b/spec/unit/controllers/runtime/buildpacks_controller_spec.rb
@@ -25,7 +25,8 @@ def ordered_buildpacks
stack: { type: 'string' },
position: { type: 'integer', default: 0 },
enabled: { type: 'bool', default: true },
- locked: { type: 'bool', default: false }
+ locked: { type: 'bool', default: false },
+ lifecycle: { type: 'string' }
})
end
diff --git a/spec/unit/controllers/v3/buildpacks_controller_spec.rb b/spec/unit/controllers/v3/buildpacks_controller_spec.rb
index bb6e280eaf3..89fcc22444f 100644
--- a/spec/unit/controllers/v3/buildpacks_controller_spec.rb
+++ b/spec/unit/controllers/v3/buildpacks_controller_spec.rb
@@ -52,8 +52,9 @@
let!(:stack1) { VCAP::CloudController::Stack.make }
let!(:stack2) { VCAP::CloudController::Stack.make }
- let!(:buildpack1) { VCAP::CloudController::Buildpack.make(stack: stack1.name) }
- let!(:buildpack2) { VCAP::CloudController::Buildpack.make(stack: stack2.name) }
+ let!(:buildpack1) { VCAP::CloudController::Buildpack.make(stack: stack1.name, position: 2) }
+ let!(:buildpack2) { VCAP::CloudController::Buildpack.make(stack: stack2.name, position: 1) }
+ let!(:buildpack3) { VCAP::CloudController::Buildpack.make(stack: stack1.name, lifecycle: 'cnb', position: 1) }
before do
set_current_user(user)
@@ -62,8 +63,15 @@
it 'renders a paginated list of buildpacks' do
get :index
- expect(parsed_body['resources'].first['guid']).to eq(buildpack1.guid)
- expect(parsed_body['resources'].second['guid']).to eq(buildpack2.guid)
+ expect(parsed_body['resources'].first['guid']).to eq(buildpack2.guid)
+ expect(parsed_body['resources'].second['guid']).to eq(buildpack1.guid)
+ expect(parsed_body['resources'].third['guid']).to eq(buildpack3.guid)
+ end
+
+ it 'renders a lifecycle filtered list of buildpacks' do
+ get :index, params: { lifecycle: 'cnb' }
+
+ expect(parsed_body['resources'].first['guid']).to eq(buildpack3.guid)
end
it 'renders a name filtered list of buildpacks' do
@@ -83,9 +91,10 @@
it 'renders an ordered list of buildpacks' do
get :index, params: { order_by: '-position' }
- expect(parsed_body['resources']).to have(2).buildpack
- expect(parsed_body['resources'].first['position']).to eq(buildpack2.position)
- expect(parsed_body['resources'].second['position']).to eq(buildpack1.position)
+ expect(parsed_body['resources']).to have(3).buildpack
+ expect(parsed_body['resources'].first['position']).to eq(2)
+ expect(parsed_body['resources'].second['position']).to eq(1)
+ expect(parsed_body['resources'].third['position']).to eq(1)
end
it 'eager loads associated resources that the presenter specifies' do
@@ -512,8 +521,10 @@
let(:buildpack_bits_name) { 'buildpack.zip' }
before do
- allow(File).to receive(:stat).and_return(stat_double)
- # allow(VCAP::CloudController::BuildpackUpload).to receive(:new).and_return(uploader)
+ allow(File).to receive_messages(
+ stat: stat_double,
+ read: "PK\x03\x04".force_encoding('binary')
+ )
end
describe 'permissions' do
diff --git a/spec/unit/fetchers/buildpack_list_fetcher_spec.rb b/spec/unit/fetchers/buildpack_list_fetcher_spec.rb
index df60a1470f6..c3891f6bc6f 100644
--- a/spec/unit/fetchers/buildpack_list_fetcher_spec.rb
+++ b/spec/unit/fetchers/buildpack_list_fetcher_spec.rb
@@ -14,6 +14,9 @@ module VCAP::CloudController
let!(:buildpack2) { Buildpack.make(stack: stack2.name) }
let!(:buildpack3) { Buildpack.make(stack: stack3.name) }
let!(:buildpack4) { Buildpack.make(stack: stack1.name) }
+ let!(:buildpack5) { Buildpack.make(stack: stack1.name, lifecycle: 'cnb') }
+ let!(:buildpack6) { Buildpack.make(stack: stack2.name, lifecycle: 'cnb') }
+ let!(:buildpack7) { Buildpack.make(stack: nil, lifecycle: 'cnb') }
let!(:buildpack_without_stack) { Buildpack.make(stack: nil) }
let(:message) { BuildpacksListMessage.from_params(filters) }
@@ -35,7 +38,7 @@ module VCAP::CloudController
let(:filters) { {} }
it 'fetches all the buildpacks' do
- expect(subject).to contain_exactly(buildpack1, buildpack2, buildpack3, buildpack4, buildpack_without_stack)
+ expect(subject).to contain_exactly(buildpack1, buildpack2, buildpack3, buildpack4, buildpack_without_stack, buildpack5, buildpack6, buildpack7)
end
end
@@ -60,7 +63,7 @@ module VCAP::CloudController
end
it 'returns all of the desired buildpacks' do
- expect(subject).to contain_exactly(buildpack_without_stack)
+ expect(subject).to contain_exactly(buildpack_without_stack, buildpack7)
end
end
@@ -70,7 +73,27 @@ module VCAP::CloudController
end
it 'returns all of the desired buildpacks' do
- expect(subject).to contain_exactly(buildpack2, buildpack_without_stack)
+ expect(subject).to contain_exactly(buildpack2, buildpack_without_stack, buildpack6, buildpack7)
+ end
+ end
+
+ context 'when filtering by lifecycle' do
+ let(:filters) do
+ { 'lifecycle' => 'cnb' }
+ end
+
+ it 'returns all buildpacks with the cnb lifecycle' do
+ expect(subject).to contain_exactly(buildpack5, buildpack6, buildpack7)
+ end
+ end
+
+ context 'when filtering by lifecycle and stack' do
+ let(:filters) do
+ { 'lifecycle' => 'cnb', 'stacks' => stack1.name }
+ end
+
+ it 'returns all buildpacks with the cnb lifecycle' do
+ expect(subject).to contain_exactly(buildpack5)
end
end
end
diff --git a/spec/unit/jobs/deserialization_spec.rb b/spec/unit/jobs/deserialization_spec.rb
index c8d15dedc47..31b350a3e41 100644
--- a/spec/unit/jobs/deserialization_spec.rb
+++ b/spec/unit/jobs/deserialization_spec.rb
@@ -178,6 +178,7 @@ module Jobs
- :lifecycle
extra_keys: []
lifecycle:
+ :type: buildpack
:data:
:buildpacks:
- ruby
diff --git a/spec/unit/jobs/runtime/buildpack_installer_factory_spec.rb b/spec/unit/jobs/runtime/buildpack_installer_factory_spec.rb
index 481eaca0171..144b640f442 100644
--- a/spec/unit/jobs/runtime/buildpack_installer_factory_spec.rb
+++ b/spec/unit/jobs/runtime/buildpack_installer_factory_spec.rb
@@ -6,7 +6,7 @@ module Jobs::Runtime
describe '#plan' do
let(:name) { 'the-buildpack' }
let(:file) { 'the-file' }
- let(:opts) { { enabled: true, locked: false, position: 1 } }
+ let(:opts) { { enabled: true, locked: false, position: 1, lifecycle: 'buildpack' } }
let(:factory) { BuildpackInstallerFactory.new }
let(:jobs) { factory.plan(name, buildpack_fields) }
diff --git a/spec/unit/lib/cloud_controller/diego/buildpack_entry_generator_spec.rb b/spec/unit/lib/cloud_controller/diego/buildpack_entry_generator_spec.rb
index 22d4b513782..fa869e91e55 100644
--- a/spec/unit/lib/cloud_controller/diego/buildpack_entry_generator_spec.rb
+++ b/spec/unit/lib/cloud_controller/diego/buildpack_entry_generator_spec.rb
@@ -4,7 +4,7 @@
module VCAP::CloudController
module Diego
RSpec.describe BuildpackEntryGenerator do
- subject(:buildpack_entry_generator) { BuildpackEntryGenerator.new(blobstore_url_generator) }
+ subject(:buildpack_entry_generator) { BuildpackEntryGenerator.new(blobstore_url_generator, Lifecycles::BUILDPACK) }
let(:admin_buildpack_download_url) { 'http://admin-buildpack.example.com' }
let(:app_package_download_url) { 'http://app-package.example.com' }
diff --git a/spec/unit/lib/cloud_controller/diego/cnb/lifecycle_data_spec.rb b/spec/unit/lib/cloud_controller/diego/cnb/lifecycle_data_spec.rb
index ebb24b9384d..d584a338a8a 100644
--- a/spec/unit/lib/cloud_controller/diego/cnb/lifecycle_data_spec.rb
+++ b/spec/unit/lib/cloud_controller/diego/cnb/lifecycle_data_spec.rb
@@ -16,6 +16,7 @@ module CNB
data.buildpack_cache_checksum = 'bp-cache-checksum'
data.app_bits_checksum = { type: 'sha256', value: 'package-checksum' }
data.credentials = '{"registry":{"username":"password"}}'
+ data.auto_detect = false
data
end
@@ -29,7 +30,8 @@ module CNB
stack: 'stack',
buildpack_cache_checksum: 'bp-cache-checksum',
app_bits_checksum: { type: 'sha256', value: 'package-checksum' },
- credentials: '{"registry":{"username":"password"}}'
+ credentials: '{"registry":{"username":"password"}}',
+ auto_detect: false
}
end
@@ -98,7 +100,7 @@ module CNB
expect do
data.message
end.to raise_error(
- Membrane::SchemaValidationError, /{ #{key} => Expected instance of (String|Array|Hash), given an instance of NilClass }/
+ Membrane::SchemaValidationError, /{ #{key} => Expected instance of (String|Array|Hash|true or false), given .*}/
)
end
end
diff --git a/spec/unit/lib/cloud_controller/diego/cnb/lifecycle_protocol_spec.rb b/spec/unit/lib/cloud_controller/diego/cnb/lifecycle_protocol_spec.rb
index 2fcd24b6cc9..6f8cac99972 100644
--- a/spec/unit/lib/cloud_controller/diego/cnb/lifecycle_protocol_spec.rb
+++ b/spec/unit/lib/cloud_controller/diego/cnb/lifecycle_protocol_spec.rb
@@ -77,6 +77,16 @@ module CNB
end
end
+ context 'when buildpack_infos is empty' do
+ let(:buildpack_infos) { [] }
+
+ it 'sets auto_detect: true' do
+ lifecycle_data = lifecycle_protocol.lifecycle_data(staging_details)
+
+ expect(lifecycle_data[:auto_detect]).to be true
+ end
+ end
+
context 'when the generated message has invalid data' do
let(:buildpack_infos) { [] }
@@ -208,9 +218,6 @@ module CNB
end
it 'creates a diego DesiredLrpBuilder' do
- puts 'hello'
- puts 'world!'
-
expect(VCAP::CloudController::Diego::CNB::DesiredLrpBuilder).to receive(:new).with(
config,
builder_opts
diff --git a/spec/unit/lib/cloud_controller/diego/cnb/staging_action_builder_spec.rb b/spec/unit/lib/cloud_controller/diego/cnb/staging_action_builder_spec.rb
index 3da080341f4..3ceb1548cfe 100644
--- a/spec/unit/lib/cloud_controller/diego/cnb/staging_action_builder_spec.rb
+++ b/spec/unit/lib/cloud_controller/diego/cnb/staging_action_builder_spec.rb
@@ -131,8 +131,8 @@ module CNB
let(:buildpacks) do
[
- { url: 'gcr.io/paketo-buildpacks/node-start', skip_detect: false },
- { url: 'gcr.io/paketo-buildpacks/node-engine', skip_detect: false }
+ { name: 'custom', url: 'gcr.io/paketo-buildpacks/node-start', skip_detect: false },
+ { name: 'custom', url: 'gcr.io/paketo-buildpacks/node-engine', skip_detect: false }
]
end
@@ -240,6 +240,56 @@ module CNB
end
end
end
+
+ context('when system-buildpacks are used') do
+ let(:buildpacks) do
+ [
+ { name: 'node-cnb', key: 'node-key', skip_detect: false },
+ { name: 'java-cnb', key: 'java-key', skip_detect: false }
+ ]
+ end
+
+ before do
+ lifecycle_data[:auto_detect] = true
+ end
+
+ let(:run_staging_action) do
+ ::Diego::Bbs::Models::RunAction.new(
+ path: '/tmp/lifecycle/builder',
+ user: 'vcap',
+ args: ['--cache-dir', '/tmp/cache', '--cache-output', '/tmp/cache-output.tgz', '--auto-detect', '--buildpack', 'node-key', '--buildpack',
+ 'java-key', '--pass-env-var', 'FOO', '--pass-env-var', 'BAR'],
+ env: bbs_env
+ )
+ end
+
+ it 'returns the buildpack staging action without download actions' do
+ result = builder.action
+
+ serial_action = result.serial_action
+ actions = serial_action.actions
+
+ expect(actions[1].run_action).to eq(run_staging_action)
+ expect(builder.cached_dependencies).to include(::Diego::Bbs::Models::CachedDependency.new(
+ name: 'node-cnb',
+ from: '',
+ to: '/tmp/buildpacks/0dcf6bb539d77cbc',
+ cache_key: 'node-key',
+ log_source: '',
+ checksum_algorithm: '',
+ checksum_value: ''
+ ))
+ expect(builder.cached_dependencies).to include(::Diego::Bbs::Models::CachedDependency.new(
+ name: 'java-cnb',
+ from: '',
+ to: '/tmp/buildpacks/be0ef1aa1092a6db',
+ cache_key: 'java-key',
+ log_source: '',
+ checksum_algorithm: '',
+ checksum_value: ''
+ ))
+ end
+ end
end
describe '#cached_dependencies' do
diff --git a/spec/unit/lib/cloud_controller/diego/lifecycles/app_cnb_lifecycle_spec.rb b/spec/unit/lib/cloud_controller/diego/lifecycles/app_cnb_lifecycle_spec.rb
index 47fa8d320ca..27e28307c53 100644
--- a/spec/unit/lib/cloud_controller/diego/lifecycles/app_cnb_lifecycle_spec.rb
+++ b/spec/unit/lib/cloud_controller/diego/lifecycles/app_cnb_lifecycle_spec.rb
@@ -43,7 +43,7 @@ module VCAP::CloudController
let(:lifecycle_request_data) { { buildpacks: ['docker://nodejs', 'http://buildpack.com', 'http://other.com'] } }
before do
- Buildpack.make(name: 'custom-bp')
+ Buildpack.make(name: 'custom-bp', lifecycle: 'cnb')
end
it 'uses all of the buildpacks' do
@@ -96,12 +96,25 @@ module VCAP::CloudController
end
describe '#validation' do
- context 'with no buildpacks' do
- let(:lifecycle_request_data) { {} }
+ context 'with unknown admin buildpack' do
+ let(:lifecycle_request_data) { { buildpacks: %w[foo] } }
it 'invalid' do
expect(lifecycle.valid?).to be(false)
end
+ end
+
+ context 'with buildpacks' do
+ before do
+ Buildpack.make(name: 'foo', lifecycle: 'cnb')
+ Buildpack.make(name: 'bar', lifecycle: 'cnb')
+ end
+
+ let(:lifecycle_request_data) { { buildpacks: ['foo', 'bar', 'docker://nodejs:latest'] } }
+
+ it 'valid' do
+ expect(lifecycle.valid?).to be(true)
+ end
context 'during an update' do
let(:message) { VCAP::CloudController::AppUpdateMessage.new(request) }
@@ -111,14 +124,6 @@ module VCAP::CloudController
end
end
end
-
- context 'with buildpacks' do
- let(:lifecycle_request_data) { { buildpacks: %w[foo bar] } }
-
- it 'valid' do
- expect(lifecycle.valid?).to be(true)
- end
- end
end
end
end
diff --git a/spec/unit/lib/cloud_controller/diego/lifecycles/app_lifecycle_provider_spec.rb b/spec/unit/lib/cloud_controller/diego/lifecycles/app_lifecycle_provider_spec.rb
index e795f9033a8..31b809e0c1b 100644
--- a/spec/unit/lib/cloud_controller/diego/lifecycles/app_lifecycle_provider_spec.rb
+++ b/spec/unit/lib/cloud_controller/diego/lifecycles/app_lifecycle_provider_spec.rb
@@ -37,6 +37,16 @@ module VCAP::CloudController
expect(AppLifecycleProvider.provide_for_create(message)).to be_a(AppBuildpackLifecycle)
end
end
+
+ context 'default_app_lifecycle is set to cnb' do
+ before do
+ TestConfig.override(default_app_lifecycle: 'cnb')
+ end
+
+ it 'returns a AppCNBLifecycle' do
+ expect(AppLifecycleProvider.provide_for_create(message)).to be_a(AppCNBLifecycle)
+ end
+ end
end
end
diff --git a/spec/unit/lib/cloud_controller/diego/lifecycles/buildpack_lifecycle_spec.rb b/spec/unit/lib/cloud_controller/diego/lifecycles/buildpack_lifecycle_spec.rb
index 464890abf19..44a4e27508d 100644
--- a/spec/unit/lib/cloud_controller/diego/lifecycles/buildpack_lifecycle_spec.rb
+++ b/spec/unit/lib/cloud_controller/diego/lifecycles/buildpack_lifecycle_spec.rb
@@ -169,7 +169,7 @@ module VCAP::CloudController
it 'returns the expected value' do
expect(buildpack_lifecycle.buildpack_infos).to eq(stubbed_data[:buildpack_infos])
- expect(BuildpackLifecycleFetcher).to have_received(:fetch).with(%w[cool-buildpack rad-buildpack], Stack.default.name)
+ expect(BuildpackLifecycleFetcher).to have_received(:fetch).with(%w[cool-buildpack rad-buildpack], Stack.default.name, 'buildpack')
end
end
diff --git a/spec/unit/lib/cloud_controller/diego/lifecycles/cnb_lifecycle_spec.rb b/spec/unit/lib/cloud_controller/diego/lifecycles/cnb_lifecycle_spec.rb
index 1ad626ab78b..96dcac5ef86 100644
--- a/spec/unit/lib/cloud_controller/diego/lifecycles/cnb_lifecycle_spec.rb
+++ b/spec/unit/lib/cloud_controller/diego/lifecycles/cnb_lifecycle_spec.rb
@@ -16,10 +16,15 @@ module VCAP::CloudController
context 'when the user specifies buildpacks' do
let(:request_data) do
{
- buildpacks: %w[docker://cool-buildpack docker://rad-buildpack]
+ buildpacks: %w[docker://nodejs cool-buildpack]
}
end
+ before do
+ Buildpack.make(name: 'cool-buildpack', lifecycle: 'cnb')
+ Buildpack.make(name: 'rad-buildpack')
+ end
+
it 'uses the buildpacks from the user' do
build = BuildModel.make(:cnb)
@@ -29,7 +34,7 @@ module VCAP::CloudController
data_model = VCAP::CloudController::CNBLifecycleDataModel.last
- expect(data_model.buildpacks).to eq(%w[docker://cool-buildpack docker://rad-buildpack])
+ expect(data_model.buildpacks).to eq(%w[docker://nodejs cool-buildpack])
expect(data_model.build).to eq(build)
end
end
@@ -166,15 +171,18 @@ module VCAP::CloudController
let(:stubbed_data) { { stack: app.lifecycle_data.stack, buildpack_infos: [instance_double(BuildpackInfo)] } }
let(:request_data) do
{
- buildpacks: %w[docker://cool-buildpack docker://rad-buildpack]
+ buildpacks: %w[cool-buildpack docker://rad-buildpack]
}
end
+ before do
+ allow(BuildpackLifecycleFetcher).to receive(:fetch).and_return(stubbed_data)
+ end
+
it 'returns the expected value' do
- expect(cnb_lifecycle.buildpack_infos).to have(2).items
+ expect(cnb_lifecycle.buildpack_infos).to eq(stubbed_data[:buildpack_infos])
- expect(cnb_lifecycle.buildpack_infos[0].buildpack_url).to eq('docker://cool-buildpack')
- expect(cnb_lifecycle.buildpack_infos[1].buildpack_url).to eq('docker://rad-buildpack')
+ expect(BuildpackLifecycleFetcher).to have_received(:fetch).with(%w[cool-buildpack docker://rad-buildpack], app.lifecycle_data.stack, 'cnb')
end
end
end
diff --git a/spec/unit/lib/cloud_controller/install_buildpacks_spec.rb b/spec/unit/lib/cloud_controller/install_buildpacks_spec.rb
index 7cb68107abe..e3b7d9f8320 100644
--- a/spec/unit/lib/cloud_controller/install_buildpacks_spec.rb
+++ b/spec/unit/lib/cloud_controller/install_buildpacks_spec.rb
@@ -46,13 +46,13 @@ module VCAP::CloudController
let(:buildpack2_file) { 'abuildpack2.zip' }
let(:buildpack1a_fields) do
- { name: 'buildpack1', file: buildpack1a_file, stack: 'cflinuxfs11', options: {} }
+ { name: 'buildpack1', file: buildpack1a_file, stack: 'cflinuxfs11', options: { lifecycle: Lifecycles::BUILDPACK } }
end
let(:buildpack1b_fields) do
- { name: 'buildpack1', file: buildpack1b_file, stack: 'cflinuxfs12', options: {} }
+ { name: 'buildpack1', file: buildpack1b_file, stack: 'cflinuxfs12', options: { lifecycle: Lifecycles::BUILDPACK } }
end
let(:buildpack2_fields) do
- { name: 'buildpack2', file: buildpack2_file, stack: nil, options: {} }
+ { name: 'buildpack2', file: buildpack2_file, stack: nil, options: { lifecycle: Lifecycles::BUILDPACK } }
end
before do
@@ -153,7 +153,7 @@ module VCAP::CloudController
# call install
# verify that job_factory.plan was called with the right file
expect(File).to receive(:file?).with('another.zip').and_return(true)
- expect(job_factory).to receive(:plan).with('buildpack1', [{ name: 'buildpack1', file: 'another.zip', stack: nil, options: {} }])
+ expect(job_factory).to receive(:plan).with('buildpack1', [{ name: 'buildpack1', file: 'another.zip', stack: nil, options: { lifecycle: Lifecycles::BUILDPACK } }])
installer.install(TestConfig.config_instance.get(:install_buildpacks))
end
@@ -167,7 +167,7 @@ module VCAP::CloudController
it 'succeeds when no package is specified' do
TestConfig.config[:install_buildpacks][0].delete('package')
expect(File).to receive(:file?).with('another.zip').and_return(true)
- expect(job_factory).to receive(:plan).with('buildpack1', [{ name: 'buildpack1', file: 'another.zip', stack: nil, options: {} }])
+ expect(job_factory).to receive(:plan).with('buildpack1', [{ name: 'buildpack1', file: 'another.zip', stack: nil, options: { lifecycle: Lifecycles::BUILDPACK } }])
installer.install(TestConfig.config_instance.get(:install_buildpacks))
end
@@ -225,6 +225,7 @@ module VCAP::CloudController
stack: nil,
options: {
enabled: true,
+ lifecycle: Lifecycles::BUILDPACK,
locked: false,
position: 5
} }])
diff --git a/spec/unit/lib/cloud_controller/paging/pagination_options_spec.rb b/spec/unit/lib/cloud_controller/paging/pagination_options_spec.rb
index 7197c0b7e5c..a122662c729 100644
--- a/spec/unit/lib/cloud_controller/paging/pagination_options_spec.rb
+++ b/spec/unit/lib/cloud_controller/paging/pagination_options_spec.rb
@@ -104,6 +104,34 @@ module VCAP::CloudController
end
end
+ context 'when secondary_default_order_by is configured' do
+ before do
+ pagination_options.secondary_default_order_by = 'id'
+ pagination_options.default_order_by = 'something'
+ end
+
+ context 'when order_by is not configured by the user' do
+ it 'secondary_order_by returns the secondary_default_order_by' do
+ pagination_options.order_by = nil
+ expect(pagination_options.secondary_order_by).to eq('id')
+ end
+ end
+
+ context 'when order_by is configured by the user' do
+ it 'secondary_order_by returns nil' do
+ pagination_options.order_by = 'first_name'
+ expect(pagination_options.secondary_order_by).to be_nil
+ end
+ end
+
+ context 'when order_by is configured by the user to be the same as the default' do
+ it 'secondary_order_by returns the secondary_default_order_b' do
+ pagination_options.order_by = 'something'
+ expect(pagination_options.secondary_order_by).to eq('id')
+ end
+ end
+ end
+
context 'order_direction' do
context 'when the order_direction is nil' do
let(:order_direction) { nil }
diff --git a/spec/unit/lib/cloud_controller/paging/sequel_paginator_spec.rb b/spec/unit/lib/cloud_controller/paging/sequel_paginator_spec.rb
index 7f74df6d7d6..c1ef2d196cd 100644
--- a/spec/unit/lib/cloud_controller/paging/sequel_paginator_spec.rb
+++ b/spec/unit/lib/cloud_controller/paging/sequel_paginator_spec.rb
@@ -143,6 +143,50 @@ class TableWithoutGuid < Sequel::Model(:table_without_guid); end
expect(paginated_result.records.first.keys).to match_array(AppModel.columns)
end
+ it 'orders by secondary_default_order_by if using default order_by' do
+ Space.make(guid: '1')
+ Space.make(guid: '2')
+ Space.make(guid: '3')
+ Space.make(guid: '4')
+ options = { page: page, per_page: 4, order_direction: 'asc' }
+ app_model1.update(guid: '1', space_guid: '2', name: 'yourapp')
+ app_model2.update(guid: '2', space_guid: '1', name: 'yourapp')
+ app_model3.update(guid: '3', space_guid: '3', name: 'myapp')
+ app_model4.update(guid: '4', space_guid: '4', name: 'myapp')
+ pagination_options = PaginationOptions.new(options)
+ pagination_options.default_order_by = 'name'
+ pagination_options.secondary_default_order_by = 'space_guid'
+
+ paginated_result = paginator.get_page(dataset, pagination_options)
+
+ expect(paginated_result.records[0].guid).to eq(app_model3.guid)
+ expect(paginated_result.records[1].guid).to eq(app_model4.guid)
+ expect(paginated_result.records[2].guid).to eq(app_model2.guid)
+ expect(paginated_result.records[3].guid).to eq(app_model1.guid)
+ end
+
+ it 'does not order by secondary_default_order_by if order_by is set' do
+ Space.make(guid: '1')
+ Space.make(guid: '2')
+ Space.make(guid: '3')
+ Space.make(guid: '4')
+ options = { page: page, order_by: 'name', per_page: 4, order_direction: 'asc' }
+ app_model1.update(guid: '1', space_guid: '2', name: 'yourapp')
+ app_model2.update(guid: '2', space_guid: '1', name: 'yourapp')
+ app_model3.update(guid: '3', space_guid: '3', name: 'myapp')
+ app_model4.update(guid: '4', space_guid: '4', name: 'myapp')
+ pagination_options = PaginationOptions.new(options)
+ pagination_options.default_order_by = 'guid'
+ pagination_options.secondary_default_order_by = 'space_guid'
+
+ paginated_result = paginator.get_page(dataset, pagination_options)
+
+ expect(paginated_result.records[0].guid).to eq(app_model3.guid)
+ expect(paginated_result.records[1].guid).to eq(app_model4.guid)
+ expect(paginated_result.records[2].guid).to eq(app_model1.guid)
+ expect(paginated_result.records[3].guid).to eq(app_model2.guid)
+ end
+
it 'orders by GUID as a secondary field when available' do
options = { page: page, per_page: 2, order_by: 'created_at', order_direction: 'asc' }
app_model1.update(guid: '1', created_at: '2019-12-25T13:00:00Z')
diff --git a/spec/unit/lib/cloud_controller/upload_buildpack_spec.rb b/spec/unit/lib/cloud_controller/upload_buildpack_spec.rb
index 6a0a1cbf214..507f56f9abd 100644
--- a/spec/unit/lib/cloud_controller/upload_buildpack_spec.rb
+++ b/spec/unit/lib/cloud_controller/upload_buildpack_spec.rb
@@ -9,9 +9,13 @@ module VCAP::CloudController
let(:tmpdir) { Dir.mktmpdir }
let(:filename) { 'file.zip' }
+ let(:tgz_filename) { 'file.tgz' }
+ let(:cnb_filename) { 'file.cnb' }
let(:sha_valid_zip) { Digester.new(algorithm: OpenSSL::Digest::SHA256).digest_file(valid_zip) }
let(:sha_valid_zip2) { Digester.new(algorithm: OpenSSL::Digest::SHA256).digest_file(valid_zip2) }
+ let(:sha_valid_tgz) { Digester.new(algorithm: OpenSSL::Digest::SHA256).digest_file(valid_tgz) }
+ let(:sha_valid_cnb) { Digester.new(algorithm: OpenSSL::Digest::SHA256).digest_file(valid_cnb) }
let(:valid_zip_manifest_stack) { nil }
let(:valid_zip) do
@@ -34,6 +38,20 @@ module VCAP::CloudController
Rack::Test::UploadedFile.new(zip_file)
end
+ let(:valid_tgz) do
+ tgz_name = File.join(tmpdir, tgz_filename)
+ TestTgz.create(tgz_name, 3, 1024)
+ tgz_file = File.new(tgz_name)
+ Rack::Test::UploadedFile.new(tgz_file)
+ end
+
+ let(:valid_cnb) do
+ cnb_name = File.join(tmpdir, cnb_filename)
+ TestCnb.create(cnb_name, 3, 1024)
+ cnb_file = File.new(cnb_name)
+ Rack::Test::UploadedFile.new(cnb_file)
+ end
+
let(:staging_timeout) { TestConfig.config_instance.get(:staging, :timeout_in_seconds) }
let(:expected_sha_valid_zip) { "#{buildpack.guid}_#{sha_valid_zip}" }
@@ -122,7 +140,8 @@ module VCAP::CloudController
VCAP::CloudController::Buildpack.create(name: buildpack.name, stack: valid_zip_manifest_stack)
expect do
upload_buildpack.upload_buildpack(buildpack, valid_zip, filename)
- end.to raise_error(CloudController::Errors::ApiError, /The buildpack name #{buildpack.name} is already in use for the stack #{valid_zip_manifest_stack}/)
+ end.to raise_error(CloudController::Errors::ApiError,
+ /The buildpack name #{buildpack.name} is already in use for the stack #{valid_zip_manifest_stack} and the lifecycle #{buildpack.lifecycle}/)
end
end
end
@@ -142,6 +161,28 @@ module VCAP::CloudController
end
end
+ context 'lifecycle: gzip cnb' do
+ let!(:buildpack) { VCAP::CloudController::Buildpack.create_from_hash({ name: 'upload_cnb_buildpack', stack: 'cider', position: 0, lifecycle: 'cnb' }) }
+ let(:expected_sha_valid_tgz) { "#{buildpack.guid}_#{sha_valid_tgz}" }
+
+ it 'uploads' do
+ expect(buildpack_blobstore).to receive(:cp_to_blobstore).with(valid_tgz, expected_sha_valid_tgz)
+
+ upload_buildpack.upload_buildpack(buildpack, valid_tgz, tgz_filename)
+ end
+ end
+
+ context 'lifecycle: cnb' do
+ let!(:buildpack) { VCAP::CloudController::Buildpack.create_from_hash({ name: 'upload_cnb_buildpack', stack: 'cider', position: 0, lifecycle: 'cnb' }) }
+ let(:expected_sha_valid_cnb) { "#{buildpack.guid}_#{sha_valid_cnb}" }
+
+ it 'uploads' do
+ expect(buildpack_blobstore).to receive(:cp_to_blobstore).with(valid_cnb, expected_sha_valid_cnb)
+
+ upload_buildpack.upload_buildpack(buildpack, valid_cnb, cnb_filename)
+ end
+ end
+
it 'updates the buildpack filename' do
expect do
upload_buildpack.upload_buildpack(buildpack, valid_zip, filename)
diff --git a/spec/unit/messages/app_manifest_message_spec.rb b/spec/unit/messages/app_manifest_message_spec.rb
index 67ce16b5d4f..3680e2de7bc 100644
--- a/spec/unit/messages/app_manifest_message_spec.rb
+++ b/spec/unit/messages/app_manifest_message_spec.rb
@@ -1114,22 +1114,6 @@ module VCAP::CloudController
expect(message.errors.full_messages).to match_array(error_messages)
end
end
-
- context 'when cnb: true and no buildpacks provided' do
- before do
- FeatureFlag.make(name: 'diego_cnb', enabled: true, error_message: nil)
- end
-
- let(:params_from_yaml) { { name: 'eugene', lifecycle: 'cnb' } }
-
- it 'is not valid' do
- message = AppManifestMessage.create_from_yml(params_from_yaml)
-
- expect(message).not_to be_valid
- expect(message.errors).to have(1).items
- expect(message.errors.full_messages).to include('Buildpack(s) must be specified when using Cloud Native Buildpacks')
- end
- end
end
describe '.create_from_yml' do
diff --git a/spec/unit/messages/buildpack_create_message_spec.rb b/spec/unit/messages/buildpack_create_message_spec.rb
index 6ba6501d35a..36f766a63fa 100644
--- a/spec/unit/messages/buildpack_create_message_spec.rb
+++ b/spec/unit/messages/buildpack_create_message_spec.rb
@@ -156,6 +156,17 @@ module VCAP::CloudController
it { is_expected.to be_valid }
end
end
+
+ describe 'lifecycle' do
+ context 'when the lifecycle is invalid' do
+ let(:params) { { name: 'cnb-test', enabled: true, lifecycle: 'foo' } }
+
+ it 'is not valid' do
+ expect(subject).not_to be_valid
+ expect(subject.errors[:lifecycle]).to include('must be either "buildpack" or "cnb"')
+ end
+ end
+ end
end
end
end
diff --git a/spec/unit/messages/buildpack_upload_message_spec.rb b/spec/unit/messages/buildpack_upload_message_spec.rb
index 16505280363..fee9b05a1c3 100644
--- a/spec/unit/messages/buildpack_upload_message_spec.rb
+++ b/spec/unit/messages/buildpack_upload_message_spec.rb
@@ -5,18 +5,23 @@ module VCAP::CloudController
RSpec.describe BuildpackUploadMessage do
before { TestConfig.override(directories: { tmpdir: '/tmp/' }) }
+ let(:lifecycle) { VCAP::CloudController::Lifecycles::BUILDPACK }
+
describe 'validations' do
let(:stat_double) { instance_double(File::Stat, size: 2) }
before do
- allow(File).to receive(:stat).and_return(stat_double)
+ allow(File).to receive_messages(
+ stat: stat_double,
+ read: "PK\x03\x04".force_encoding('binary')
+ )
end
context 'when the param is set' do
let(:opts) { { '' => '', bits_path: '/tmp/foobar', bits_name: 'buildpack.zip' } }
it 'is invalid' do
- upload_message = BuildpackUploadMessage.new(opts)
+ upload_message = BuildpackUploadMessage.new(opts, lifecycle)
expect(upload_message).not_to be_valid
expect(upload_message.errors[:base]).to include('Uploaded bits are not a valid buildpack file')
end
@@ -26,7 +31,7 @@ module VCAP::CloudController
let(:opts) { { bits_path: '/tmp/foobar', bits_name: 'buildpack.zip' } }
it 'is valid' do
- upload_message = BuildpackUploadMessage.new(opts)
+ upload_message = BuildpackUploadMessage.new(opts, lifecycle)
expect(upload_message).to be_valid
end
end
@@ -35,7 +40,7 @@ module VCAP::CloudController
let(:opts) { { bits_path: '../tmp/mango/pear', bits_name: 'buildpack.zip' } }
it 'is valid' do
- upload_message = BuildpackUploadMessage.new(opts)
+ upload_message = BuildpackUploadMessage.new(opts, lifecycle)
expect(upload_message).to be_valid
end
end
@@ -44,7 +49,7 @@ module VCAP::CloudController
let(:opts) { { bits_path: '../tmp-not!/mango/pear' } }
it 'is not valid' do
- upload_message = BuildpackUploadMessage.new(opts)
+ upload_message = BuildpackUploadMessage.new(opts, lifecycle)
expect(upload_message).not_to be_valid
expect(upload_message.errors[:bits_path]).to include('is invalid')
end
@@ -54,7 +59,7 @@ module VCAP::CloudController
let(:opts) { {} }
it 'is not valid' do
- upload_message = BuildpackUploadMessage.new(opts)
+ upload_message = BuildpackUploadMessage.new(opts, lifecycle)
expect(upload_message).not_to be_valid
expect(upload_message.errors[:base]).to include('A buildpack zip file must be uploaded as \'bits\'')
end
@@ -64,7 +69,7 @@ module VCAP::CloudController
let(:opts) { { bits_path: '/tmp/bar', unexpected: 'foo' } }
it 'is not valid' do
- message = BuildpackUploadMessage.new(opts)
+ message = BuildpackUploadMessage.new(opts, lifecycle)
expect(message).not_to be_valid
expect(message.errors.full_messages[0]).to include("Unknown field(s): 'unexpected'")
@@ -75,7 +80,7 @@ module VCAP::CloudController
let(:opts) { { bits_path: '/secret/file', bits_name: 'buildpack.zip' } }
it 'is not valid' do
- message = BuildpackUploadMessage.new(opts)
+ message = BuildpackUploadMessage.new(opts, lifecycle)
expect(message).not_to be_valid
expect(message.errors.full_messages[0]).to include('Bits path is invalid')
@@ -86,19 +91,96 @@ module VCAP::CloudController
let(:opts) { { bits_path: '/tmp/bar' } }
it 'is not valid' do
- upload_message = BuildpackUploadMessage.new(opts)
+ upload_message = BuildpackUploadMessage.new(opts, lifecycle)
expect(upload_message).not_to be_valid
expect(upload_message.errors[:base]).to include('A buildpack zip file must be uploaded as \'bits\'')
end
end
context 'when the file is not a zip' do
- let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.tgz' } }
+ let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.abcd' } }
it 'is not valid' do
- upload_message = BuildpackUploadMessage.new(opts)
+ allow(File).to receive(:read).and_return("PX\x03\x04".force_encoding('binary'))
+ upload_message = BuildpackUploadMessage.new(opts, lifecycle)
expect(upload_message).not_to be_valid
- expect(upload_message.errors.full_messages[0]).to include('buildpack.tgz is not a zip')
+ 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.')
+ end
+ end
+
+ context 'buildpacks' do
+ let(:lifecycle) { VCAP::CloudController::Lifecycles::BUILDPACK }
+
+ context 'when the file is a zip' do
+ let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.zip' } }
+
+ it 'is valid' do
+ allow(File).to receive(:read).and_return("PK\x03\x04".force_encoding('binary'))
+ upload_message = BuildpackUploadMessage.new(opts, lifecycle)
+ expect(upload_message).to be_valid
+ end
+ end
+
+ context 'when the file is a tgz' do
+ let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.tgz' } }
+
+ it 'is not valid' do
+ allow(File).to receive(:read).and_return("\x1F\x8B\x08".force_encoding('binary'))
+ upload_message = BuildpackUploadMessage.new(opts, lifecycle)
+ expect(upload_message).not_to be_valid
+ 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.')
+ end
+ end
+
+ context 'when the file is a cnb/tar' do
+ let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.cnb' } }
+
+ it 'is not valid' do
+ values = ["\x0".force_encoding('binary'), "\x75\x73\x74\x61\x72\x00\x30\x30".force_encoding('binary')]
+ allow(File).to receive(:read).and_return(*values)
+ upload_message = BuildpackUploadMessage.new(opts, lifecycle)
+ expect(upload_message).not_to be_valid
+ 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.')
+ end
+ end
+ end
+
+ context 'cloud native buildpacks (cnb)' do
+ let(:lifecycle) { VCAP::CloudController::Lifecycles::CNB }
+
+ context 'buildpacks' do
+ context 'when the file is a zip' do
+ let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.zip' } }
+
+ it 'is valid' do
+ allow(File).to receive(:read).and_return("PK\x03\x04".force_encoding('binary'))
+ upload_message = BuildpackUploadMessage.new(opts, lifecycle)
+ expect(upload_message).not_to be_valid
+ expect(upload_message.errors.full_messages[0]).
+ to include('buildpack.zip is not a gzip archive or cnb file. Buildpacks of lifecycle "cnb" must be valid gzip archives or cnb files.')
+ end
+ end
+
+ context 'when the file is a tgz' do
+ let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.tgz' } }
+
+ it 'is valid' do
+ allow(File).to receive(:read).and_return("\x1F\x8B\x08".force_encoding('binary'))
+ upload_message = BuildpackUploadMessage.new(opts, lifecycle)
+ expect(upload_message).to be_valid
+ end
+ end
+
+ context 'when the file is a cnb/tar' do
+ let(:opts) { { bits_path: '/tmp/bar', bits_name: 'buildpack.cnb' } }
+
+ it 'is valid' do
+ values = ["\x0".force_encoding('binary'), "\x75\x73\x74\x61\x72\x00\x30\x30".force_encoding('binary')]
+ allow(File).to receive(:read).and_return(*values)
+ upload_message = BuildpackUploadMessage.new(opts, lifecycle)
+ expect(upload_message).to be_valid
+ end
+ end
end
end
@@ -107,7 +189,7 @@ module VCAP::CloudController
let(:stat_double) { instance_double(File::Stat, size: 0) }
it 'is not valid' do
- upload_message = BuildpackUploadMessage.new(opts)
+ upload_message = BuildpackUploadMessage.new(opts, lifecycle)
expect(upload_message).not_to be_valid
expect(upload_message.errors.full_messages[0]).to include('buildpack.zip cannot be empty')
end
@@ -115,7 +197,7 @@ module VCAP::CloudController
end
describe '#bits_path=' do
- subject(:upload_message) { BuildpackUploadMessage.new(bits_path: 'not-nil') }
+ subject(:upload_message) { BuildpackUploadMessage.new({ bits_path: 'not-nil' }, lifecycle) }
context 'when the bits_path is relative' do
it 'makes it absolute (within the tmpdir)' do
@@ -137,7 +219,7 @@ module VCAP::CloudController
let(:params) { { 'bits_path' => '/tmp/foobar', 'bits_name' => 'buildpack.zip' } }
it 'returns the correct BuildpackUploadMessage' do
- message = BuildpackUploadMessage.create_from_params(params)
+ message = BuildpackUploadMessage.create_from_params(params, lifecycle)
expect(message).to be_a(BuildpackUploadMessage)
expect(message.bits_path).to eq('/tmp/foobar')
@@ -145,7 +227,7 @@ module VCAP::CloudController
end
it 'converts requested keys to symbols' do
- message = BuildpackUploadMessage.create_from_params(params)
+ message = BuildpackUploadMessage.create_from_params(params, lifecycle)
expect(message).to be_requested(:bits_path)
expect(message).to be_requested(:bits_name)
diff --git a/spec/unit/messages/buildpacks_list_message_spec.rb b/spec/unit/messages/buildpacks_list_message_spec.rb
index 974641ecf4b..605c4fbac5e 100644
--- a/spec/unit/messages/buildpacks_list_message_spec.rb
+++ b/spec/unit/messages/buildpacks_list_message_spec.rb
@@ -8,6 +8,7 @@ module VCAP::CloudController
{
'names' => 'name1,name2',
'stacks' => 'stack1,stack2',
+ 'lifecycle' => 'buildpack',
'label_selector' => 'foo=bar',
'page' => 1,
'per_page' => 5
@@ -21,6 +22,7 @@ module VCAP::CloudController
expect(message.stacks).to eq(%w[stack1 stack2])
expect(message.names).to eq(%w[name1 name2])
+ expect(message.lifecycle).to eq('buildpack')
expect(message.label_selector).to eq('foo=bar')
expect(message.requirements.first.key).to eq('foo')
expect(message.page).to eq(1)
@@ -32,6 +34,7 @@ module VCAP::CloudController
expect(message).to be_requested(:stacks)
expect(message).to be_requested(:names)
+ expect(message).to be_requested(:lifecycle)
expect(message).to be_requested(:label_selector)
expect(message).to be_requested(:page)
expect(message).to be_requested(:per_page)
@@ -43,6 +46,7 @@ module VCAP::CloudController
{
names: %w[name1 name2],
stacks: %w[stack1 stack2],
+ lifecycle: 'buildpack',
label_selector: 'foo=bar',
page: 1,
per_page: 5
@@ -50,7 +54,7 @@ module VCAP::CloudController
end
it 'excludes the pagination keys' do
- expected_params = %i[names stacks label_selector]
+ expected_params = %i[names stacks label_selector lifecycle]
expect(BuildpacksListMessage.from_params(opts).to_param_hash.keys).to match_array(expected_params)
end
end
@@ -61,7 +65,8 @@ module VCAP::CloudController
BuildpacksListMessage.from_params({
names: [],
stacks: [],
- label_selector: ''
+ label_selector: '',
+ lifecycle: 'buildpack'
})
end.not_to raise_error
end
@@ -101,6 +106,12 @@ module VCAP::CloudController
and_call_original
message.valid?
end
+
+ it 'validates lifecycle' do
+ message = BuildpacksListMessage.from_params lifecycle: 'foo'
+ expect(message).not_to be_valid
+ expect(message.errors[:lifecycle].length).to eq 1
+ end
end
end
end
diff --git a/spec/unit/models/runtime/buildpack_spec.rb b/spec/unit/models/runtime/buildpack_spec.rb
index 794c7b3a9ab..200c8645a13 100644
--- a/spec/unit/models/runtime/buildpack_spec.rb
+++ b/spec/unit/models/runtime/buildpack_spec.rb
@@ -9,7 +9,7 @@ def ordered_buildpacks
it { is_expected.to have_timestamp_columns }
describe 'Validations' do
- it { is_expected.to validate_uniqueness %i[name stack] }
+ it { is_expected.to validate_uniqueness %i[name stack lifecycle] }
describe 'stack' do
let(:stack) { Stack.make(name: 'happy') }
@@ -74,11 +74,21 @@ def ordered_buildpacks
end
end
end
+
+ describe 'lifecycle' do
+ it 'cannot be changed once it is set' do
+ buildpack = Buildpack.create(name: 'test', lifecycle: 'cnb')
+ buildpack.lifecycle = 'buildpack'
+
+ expect(buildpack).not_to be_valid
+ expect(buildpack.errors.on(:lifecycle)).to include(:buildpack_cant_change_lifecycle)
+ end
+ end
end
describe 'Serialization' do
- it { is_expected.to export_attributes :name, :stack, :position, :enabled, :locked, :filename }
- it { is_expected.to import_attributes :name, :stack, :position, :enabled, :locked, :filename, :key }
+ it { is_expected.to export_attributes :name, :stack, :position, :enabled, :locked, :filename, :lifecycle }
+ it { is_expected.to import_attributes :name, :stack, :position, :enabled, :locked, :filename, :lifecycle, :key }
it 'does not string mung(e)?' do
expect(Buildpack.new(name: "my_custom_buildpack\r\n").to_json).to eq '"my_custom_buildpack\r\n"'
@@ -91,6 +101,7 @@ def ordered_buildpacks
let(:buildpack_file_1) { Tempfile.new('admin buildpack 1') }
let(:buildpack_file_2) { Tempfile.new('admin buildpack 2') }
let(:buildpack_file_3) { Tempfile.new('admin buildpack 3') }
+ let(:buildpack_file_cnb) { Tempfile.new('admin buildpack cnb') }
let(:buildpack_blobstore) { CloudController::DependencyLocator.instance.buildpack_blobstore }
@@ -103,6 +114,8 @@ def ordered_buildpacks
Timecop.return
end
+ let(:cnb_buildpacks) { Buildpack.list_admin_buildpacks(nil, 'cnb') }
+
subject(:all_buildpacks) { Buildpack.list_admin_buildpacks }
context 'with prioritized buildpacks' do
@@ -110,6 +123,9 @@ def ordered_buildpacks
buildpack_blobstore.cp_to_blobstore(buildpack_file_1.path, 'a key')
Buildpack.make(key: 'a key', position: 2)
+ buildpack_blobstore.cp_to_blobstore(buildpack_file_cnb.path, 'cnb key')
+ @cnb_buildpack = Buildpack.make(key: 'cnb key', position: 1, lifecycle: 'cnb')
+
buildpack_blobstore.cp_to_blobstore(buildpack_file_2.path, 'b key')
Buildpack.make(key: 'b key', position: 1)
@@ -131,6 +147,10 @@ def ordered_buildpacks
expect(all_buildpacks).to have(2).items
end
+ it 'returns the list of cnb buildpacks' do
+ expect(cnb_buildpacks.collect(&:key)).to eq(['cnb key'])
+ end
+
it 'randomly orders any buildpacks with the same position (for now we did not want to make clever logic of shifting stuff around: up to the user to get it all correct)' do
@another_buildpack.position = 1
@another_buildpack.save
@@ -138,6 +158,13 @@ def ordered_buildpacks
expect(all_buildpacks[2].key).to eq('a key')
end
+ it 'does not reorder cnb buildpacks when classical buildpacks change position)' do
+ @cnb_buildpack.position = 1
+ @cnb_buildpack.save
+
+ expect(cnb_buildpacks[0].key).to eq('cnb key')
+ end
+
context 'and there are buildpacks with null keys' do
let!(:null_buildpack) { Buildpack.create(name: 'nil_key_custom_buildpack', stack: Stack.make.name, position: 0) }
@@ -175,6 +202,7 @@ def ordered_buildpacks
end
context 'with a stack' do
+ let(:cnb_buildpacks) { Buildpack.list_admin_buildpacks('stack2', 'cnb') }
subject(:all_buildpacks) { Buildpack.list_admin_buildpacks('stack1') }
let!(:stack1) { Stack.make(name: 'stack1') }
let!(:stack2) { Stack.make(name: 'stack2') }
@@ -186,6 +214,9 @@ def ordered_buildpacks
buildpack_blobstore.cp_to_blobstore(buildpack_file_2.path, 'b key')
Buildpack.make(key: 'b key', position: 1, stack: 'stack2')
+ buildpack_blobstore.cp_to_blobstore(buildpack_file_cnb.path, 'cnb key')
+ Buildpack.make(key: 'cnb key', position: 1, stack: 'stack2', lifecycle: 'cnb')
+
buildpack_blobstore.cp_to_blobstore(buildpack_file_3.path, 'c key')
@another_buildpack = Buildpack.make(key: 'c key', position: 3, stack: nil)
end
@@ -195,6 +226,10 @@ def ordered_buildpacks
it 'returns the list in position order, including buildpacks with null stack or matching stacks' do
expect(all_buildpacks.collect(&:key)).to eq(['a key', 'c key'])
end
+
+ it 'returns the list of cnb buildpacks' do
+ expect(cnb_buildpacks.collect(&:key)).to eq(['cnb key'])
+ end
end
end
diff --git a/spec/unit/models/runtime/cnb_lifecycle_data_model_spec.rb b/spec/unit/models/runtime/cnb_lifecycle_data_model_spec.rb
index 8fea07fdf29..c7e5dd8ad23 100644
--- a/spec/unit/models/runtime/cnb_lifecycle_data_model_spec.rb
+++ b/spec/unit/models/runtime/cnb_lifecycle_data_model_spec.rb
@@ -199,7 +199,7 @@ module VCAP::CloudController
expect(lifecycle_data.valid?).to be(false)
expect(lifecycle_data.errors.full_messages.size).to eq(1)
- expect(lifecycle_data.errors.full_messages.first).to include('Specified invalid buildpack URL: "invalid_buildpack_name"')
+ expect(lifecycle_data.errors.full_messages.first).to include('Specified unknown buildpack name: "invalid_buildpack_name"')
end
it 'is valid' do