Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 26 additions & 5 deletions app/actions/build_create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def create_and_stage(package:, lifecycle:, metadata: nil, start_after_staging: f
raise InvalidPackage.new('Cannot stage package whose state is not ready.') if package.state != PackageModel::READY_STATE

requested_buildpacks_disabled!(lifecycle)
validate_stack!(lifecycle, package)

staging_details = get_staging_details(package, lifecycle)
staging_details.start_after_staging = start_after_staging
Expand Down Expand Up @@ -74,11 +75,13 @@ def create_and_stage(package:, lifecycle:, metadata: nil, start_after_staging: f

Repositories::AppUsageEventRepository.new.create_from_build(build, 'STAGING_STARTED')
app = package.app
Repositories::BuildEventRepository.record_build_create(build,
@user_audit_info,
app.name,
app.space_guid,
app.organization_guid)
Repositories::BuildEventRepository.record_build_create(
build,
@user_audit_info,
app.name,
app.space_guid,
app.organization_guid
)
end

logger.info("build created: #{build.guid}")
Expand All @@ -93,6 +96,24 @@ def create_and_stage(package:, lifecycle:, metadata: nil, start_after_staging: f

private

def validate_stack!(lifecycle, package)
return unless lifecycle.type == Lifecycles::BUILDPACK

stack = Stack.find(name: lifecycle.staging_stack)
return unless stack

case stack.state
when 'DEPRECATED'
logger.warn("Stack '#{stack.name}' is deprecated. #{stack.description}")
when 'LOCKED'
# Check if this is a new app (no existing processes) vs an existing app being updated
has_existing_processes = ProcessModel.where(app_guid: package.app.guid).count > 0
raise CloudController::Errors::ApiError.new_from_details('StackLocked', stack.name, stack.description) unless has_existing_processes
when 'DISABLED'
raise CloudController::Errors::ApiError.new_from_details('StackDisabled', stack.name, stack.description)
end
end

def requested_buildpacks_disabled!(lifecycle)
return if lifecycle.type == Lifecycles::DOCKER

Expand Down
3 changes: 2 additions & 1 deletion app/actions/stack_create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ class Error < ::StandardError
def create(message)
stack = VCAP::CloudController::Stack.create(
name: message.name,
description: message.description
description: message.description,
state: message.state
)

MetadataUpdate.update(stack, message)
Expand Down
3 changes: 3 additions & 0 deletions app/actions/stack_update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ def initialize

def update(stack, message)
stack.db.transaction do
stack.state = message.state if message.requested?(:state)
stack.description = message.description if message.requested?(:description)
stack.save
MetadataUpdate.update(stack, message)
end
@logger.info("Finished updating metadata on stack #{stack.guid}")
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/v3/stacks_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ def update
stack = StackUpdate.new.update(stack, message)

render status: :ok, json: Presenters::V3::StackPresenter.new(stack)
rescue StackUpdate::InvalidStack => e
unprocessable! e
end

def show_apps
Expand Down
4 changes: 3 additions & 1 deletion app/messages/stack_create_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@

module VCAP::CloudController
class StackCreateMessage < MetadataBaseMessage
register_allowed_keys %i[name description]
register_allowed_keys %i[name description state]

validates :name, presence: true, length: { maximum: 250 }
validates :description, length: { maximum: 250 }
validates :state, inclusion: { in: %w[ACTIVE DEPRECATED LOCKED DISABLED],
message: 'must be one of [ACTIVE, DEPRECATED, LOCKED, DISABLED]' }, allow_nil: true
end
end
6 changes: 5 additions & 1 deletion app/messages/stack_update_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

module VCAP::CloudController
class StackUpdateMessage < MetadataBaseMessage
register_allowed_keys []
register_allowed_keys [:state, :description]

validates_with NoAdditionalKeysValidator

validates :state, inclusion: { in: %w[ACTIVE DEPRECATED LOCKED DISABLED],
message: 'must be one of [ACTIVE, DEPRECATED, LOCKED, DISABLED]' }, allow_nil: true
validates :description, length: { maximum: 250 }
end
end
7 changes: 4 additions & 3 deletions app/models/runtime/stack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class AppsStillPresentError < StandardError

plugin :serialization

export_attributes :name, :description, :build_rootfs_image, :run_rootfs_image
import_attributes :name, :description, :build_rootfs_image, :run_rootfs_image
export_attributes :name, :description, :build_rootfs_image, :run_rootfs_image, :state
import_attributes :name, :description, :build_rootfs_image, :run_rootfs_image, :state

strip_attributes :name

Expand All @@ -43,6 +43,7 @@ def around_save
def validate
validates_presence :name
validates_unique :name
validates_includes %w[ACTIVE DEPRECATED LOCKED DISABLED], :state, allow_missing: true
end

def before_destroy
Expand Down Expand Up @@ -95,7 +96,7 @@ def self.populate_from_hash(hash)
stack.set(hash)
Steno.logger('cc.stack').warn('stack.populate.collision', hash) if stack.modified?
else
create(hash.slice('name', 'description', 'build_rootfs_image', 'run_rootfs_image'))
create(hash.slice('name', 'description', 'build_rootfs_image', 'run_rootfs_image', 'state'))
end
end
end
Expand Down
1 change: 1 addition & 0 deletions app/presenters/v3/stack_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ def to_hash
description: stack.description,
run_rootfs_image: stack.run_rootfs_image,
build_rootfs_image: stack.build_rootfs_image,
state: stack.state,
default: stack.default?,
metadata: {
labels: hashified_labels(stack.labels),
Expand Down
7 changes: 7 additions & 0 deletions db/migrations/20250724135100_add_state_to_stacks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Sequel.migration do
change do
alter_table(:stacks) do
add_column :state, String, null: false, default: 'ACTIVE', size: 255
end
end
end
14 changes: 7 additions & 7 deletions devenv.sh
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,27 @@ help_command() {

# Create a clean development environment
create_command(){
docker-compose -p "" down
docker compose -p "" down
docker buildx bake -f docker-compose.yml &
docker-compose -p "" pull &
docker compose -p "" pull &
wait $(jobs -p)
docker-compose -p "" up -d --build
docker compose -p "" up -d --build
./.devcontainer/scripts/setupDevelopmentEnvironment.sh
}

# Start containers
start_command(){
docker-compose -p "" start
docker compose -p "" start
}

# Stop containers
stop_command(){
docker-compose -p "" stop
docker compose -p "" stop
}

# Remove containers
destroy_command(){
docker-compose -p "" down
docker compose -p "" down
}

# Call Setup IDEs Script
Expand Down Expand Up @@ -72,7 +72,7 @@ fi
# Check Prerequisites
export should_exit=0
# Check Path Exists
for p in docker docker-compose ruby bundle mysql psql yq; do
for p in docker ruby bundle mysql psql yq; do
if ! command -v "${p}" >/dev/null 2>&1; then
echo "Error: Dependency \"$p\" is not installed" && export should_exit=1
fi
Expand Down
10 changes: 10 additions & 0 deletions errors/v2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -863,6 +863,16 @@
http_code: 404
message: "The stack could not be found: %s"

250004:
name: StackDisabled
http_code: 422
message: "Cannot stage app, stack '%s' is disabled. %s"

250005:
name: StackLocked
http_code: 422
message: "Cannot stage new app, stack '%s' is locked. %s"

260001:
name: ServicePlanVisibilityInvalid
http_code: 400
Expand Down
38 changes: 35 additions & 3 deletions lib/cloud_controller/diego/buildpack/staging_action_builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,20 @@ def task_environment_variables

private

def lifecycle
staging_details.lifecycle
end

def stage_action
staging_details_env = BbsEnvironmentBuilder.build(staging_details.environment_variables)

::Diego::Bbs::Models::RunAction.new(
main_staging_action = ::Diego::Bbs::Models::RunAction.new(
path: '/tmp/lifecycle/builder',
user: 'vcap',
args: [
"-buildpackOrder=#{lifecycle_data[:buildpacks].pluck(:key).join(',')}",
"-buildpackOrder=#{lifecycle.buildpack_infos.map(&:key).join(',')}",
"-skipCertVerify=#{config.get(:skip_cert_verify)}",
"-skipDetect=#{skip_detect?}",
"-skipDetect=#{lifecycle.skip_detect?}",
'-buildDir=/tmp/app',
'-outputDroplet=/tmp/droplet',
'-outputMetadata=/tmp/result.json',
Expand All @@ -37,6 +41,34 @@ def stage_action
resource_limits: ::Diego::Bbs::Models::ResourceLimits.new(nofile: config.get(:staging, :minimum_staging_file_descriptor_limit)),
env: staging_details_env + platform_options_env
)

# Check if stack has warnings and add them if needed
stack = Stack.find(name: lifecycle.staging_stack)
warning_actions = []

if stack&.state == 'DEPRECATED'
warning_message = "\033[1;33mWARNING: Stack '#{stack.name}' is deprecated. #{stack.description}\033[0m"
warning_actions << ::Diego::Bbs::Models::RunAction.new(
path: '/bin/echo',
user: 'vcap',
args: ['-e', warning_message],
env: staging_details_env + platform_options_env
)
elsif stack&.state == 'LOCKED'
warning_message = "\033[1;33mNOTICE: Stack '#{stack.name}' is locked and can only be used to update existing applications. #{stack.description}\033[0m"
warning_actions << ::Diego::Bbs::Models::RunAction.new(
path: '/bin/echo',
user: 'vcap',
args: ['-e', warning_message],
env: staging_details_env + platform_options_env
)
end

if warning_actions.any?
serial(warning_actions + [main_staging_action])
else
main_staging_action
end
end

def platform_options_env
Expand Down
4 changes: 4 additions & 0 deletions lib/cloud_controller/diego/lifecycles/buildpack_info.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,9 @@ def to_s
buildpack_record.nil? ? nil : buildpack_record.name
end
end

def key
buildpack_record.try(:key) || buildpack_url
end
end
end
4 changes: 4 additions & 0 deletions lib/cloud_controller/diego/lifecycles/buildpack_lifecycle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ def staging_environment_variables
}
end

def skip_detect?
!buildpack_infos.empty?
end

private

def app_stack
Expand Down
2 changes: 1 addition & 1 deletion lib/tasks/db.rake
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ namespace :db do
host = "-h #{uri.host}"
port = "-P #{uri.port}" if uri.port
user = "-u #{uri.user}" if uri.user
pass = "--password=#{uri.password}" if uri.password
pass = "--password=#{uri.password || ''}"
end
end
[host, port, user, pass, passenv]
Expand Down
Loading
Loading