Skip to content

Commit 14d540e

Browse files
committed
[PoC] Implement RFC Improve Stack management
https://github.com/cloudfoundry/community/pull/1220/files
1 parent bb92b9e commit 14d540e

File tree

15 files changed

+220
-20
lines changed

15 files changed

+220
-20
lines changed

app/actions/build_create.rb

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ def create_and_stage(package:, lifecycle:, metadata: nil, start_after_staging: f
4545
raise InvalidPackage.new('Cannot stage package whose state is not ready.') if package.state != PackageModel::READY_STATE
4646

4747
requested_buildpacks_disabled!(lifecycle)
48+
validate_stack!(lifecycle, package)
4849

4950
staging_details = get_staging_details(package, lifecycle)
5051
staging_details.start_after_staging = start_after_staging
@@ -74,11 +75,13 @@ def create_and_stage(package:, lifecycle:, metadata: nil, start_after_staging: f
7475

7576
Repositories::AppUsageEventRepository.new.create_from_build(build, 'STAGING_STARTED')
7677
app = package.app
77-
Repositories::BuildEventRepository.record_build_create(build,
78-
@user_audit_info,
79-
app.name,
80-
app.space_guid,
81-
app.organization_guid)
78+
Repositories::BuildEventRepository.record_build_create(
79+
build,
80+
@user_audit_info,
81+
app.name,
82+
app.space_guid,
83+
app.organization_guid
84+
)
8285
end
8386

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

9497
private
9598

99+
def validate_stack!(lifecycle, package)
100+
return unless lifecycle.type == Lifecycles::BUILDPACK
101+
102+
stack = Stack.find(name: lifecycle.staging_stack)
103+
return unless stack
104+
105+
if stack.disabled?
106+
raise CloudController::Errors::ApiError.new_from_details(
107+
'StackDisabled',
108+
"Cannot stage app, stack '#{stack.name}' is disabled."
109+
)
110+
end
111+
112+
if stack.locked? && package.app.processes.empty?
113+
raise CloudController::Errors::ApiError.new_from_details(
114+
'StackLocked',
115+
"Cannot stage new app, stack '#{stack.name}' is locked."
116+
)
117+
end
118+
119+
# This is a warning, so we just log it.
120+
logger.warn("Stack '#{stack.name}' is deprecated.") if stack.deprecated?
121+
end
122+
96123
def requested_buildpacks_disabled!(lifecycle)
97124
return if lifecycle.type == Lifecycles::DOCKER
98125

app/actions/stack_create.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ class Error < ::StandardError
66
def create(message)
77
stack = VCAP::CloudController::Stack.create(
88
name: message.name,
9-
description: message.description
9+
description: message.description,
10+
deprecated_at: message.deprecated_at,
11+
locked_at: message.locked_at,
12+
disabled_at: message.disabled_at
1013
)
1114

1215
MetadataUpdate.update(stack, message)

app/actions/stack_update.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,13 @@ def initialize
99

1010
def update(stack, message)
1111
stack.db.transaction do
12+
# Update stack attributes (excluding metadata which is handled separately)
13+
stack_attributes = {}
14+
%i[deprecated_at locked_at disabled_at].each do |attr|
15+
stack_attributes[attr] = message.public_send(attr) if message.requested?(attr)
16+
end
17+
stack.set(stack_attributes) if stack_attributes.any?
18+
stack.save
1219
MetadataUpdate.update(stack, message)
1320
end
1421
@logger.info("Finished updating metadata on stack #{stack.guid}")

app/messages/stack_create_message.rb

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22

33
module VCAP::CloudController
44
class StackCreateMessage < MetadataBaseMessage
5-
register_allowed_keys %i[name description]
5+
register_allowed_keys %i[name description deprecated_at locked_at disabled_at]
66

77
validates :name, presence: true, length: { maximum: 250 }
88
validates :description, length: { maximum: 250 }
9+
validates :deprecated_at, timestamp: true, allow_nil: true
10+
validates :locked_at, timestamp: true, allow_nil: true
11+
validates :disabled_at, timestamp: true, allow_nil: true
912
end
1013
end

app/messages/stack_update_message.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,12 @@
22

33
module VCAP::CloudController
44
class StackUpdateMessage < MetadataBaseMessage
5-
register_allowed_keys []
5+
register_allowed_keys %i[deprecated_at locked_at disabled_at]
66

77
validates_with NoAdditionalKeysValidator
8+
9+
validates :deprecated_at, timestamp: true, allow_nil: true
10+
validates :locked_at, timestamp: true, allow_nil: true
11+
validates :disabled_at, timestamp: true, allow_nil: true
812
end
913
end

app/models/runtime/stack.rb

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ class AppsStillPresentError < StandardError
2626

2727
plugin :serialization
2828

29-
export_attributes :name, :description, :build_rootfs_image, :run_rootfs_image
30-
import_attributes :name, :description, :build_rootfs_image, :run_rootfs_image
29+
export_attributes :name, :description, :build_rootfs_image, :run_rootfs_image, :deprecated_at, :locked_at, :disabled_at
30+
import_attributes :name, :description, :build_rootfs_image, :run_rootfs_image, :deprecated_at, :locked_at, :disabled_at
3131

3232
strip_attributes :name
3333

@@ -43,6 +43,19 @@ def around_save
4343
def validate
4444
validates_presence :name
4545
validates_unique :name
46+
validate_timestamp_ordering
47+
end
48+
49+
def deprecated?
50+
deprecated_at && deprecated_at <= Time.now
51+
end
52+
53+
def locked?
54+
locked_at && locked_at <= Time.now
55+
end
56+
57+
def disabled?
58+
disabled_at && disabled_at <= Time.now
4659
end
4760

4861
def before_destroy
@@ -95,8 +108,20 @@ def self.populate_from_hash(hash)
95108
stack.set(hash)
96109
Steno.logger('cc.stack').warn('stack.populate.collision', hash) if stack.modified?
97110
else
98-
create(hash.slice('name', 'description', 'build_rootfs_image', 'run_rootfs_image'))
111+
create(hash.slice('name', 'description', 'build_rootfs_image', 'run_rootfs_image', 'deprecated_at', 'locked_at', 'disabled_at'))
99112
end
100113
end
114+
115+
private
116+
117+
def validate_timestamp_ordering
118+
return unless [deprecated_at, locked_at, disabled_at].any?
119+
120+
errors.add(:deprecated_at, 'must be before locked_at') if deprecated_at && locked_at && deprecated_at > locked_at
121+
122+
return unless locked_at && disabled_at && locked_at > disabled_at
123+
124+
errors.add(:locked_at, 'must be before disabled_at')
125+
end
101126
end
102127
end

app/presenters/v3/stack_presenter.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ def to_hash
1515
run_rootfs_image: stack.run_rootfs_image,
1616
build_rootfs_image: stack.build_rootfs_image,
1717
default: stack.default?,
18+
deprecated_at: stack.deprecated_at,
19+
locked_at: stack.locked_at,
20+
disabled_at: stack.disabled_at,
1821
metadata: {
1922
labels: hashified_labels(stack.labels),
2023
annotations: hashified_annotations(stack.annotations)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Sequel.migration do
2+
change do
3+
alter_table(:stacks) do
4+
add_column :deprecated_at, DateTime, null: true
5+
add_column :locked_at, DateTime, null: true
6+
add_column :disabled_at, DateTime, null: true
7+
end
8+
end
9+
end

errors/v2.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,6 +863,16 @@
863863
http_code: 404
864864
message: "The stack could not be found: %s"
865865

866+
250004:
867+
name: StackDisabled
868+
http_code: 422
869+
message: "Cannot stage app, stack '%s' is disabled."
870+
871+
250005:
872+
name: StackLocked
873+
http_code: 422
874+
message: "Cannot stage new app, stack '%s' is locked."
875+
866876
260001:
867877
name: ServicePlanVisibilityInvalid
868878
http_code: 400

lib/cloud_controller/diego/buildpack/staging_action_builder.rb

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,33 @@ def task_environment_variables
1717

1818
private
1919

20+
def lifecycle
21+
staging_details.lifecycle
22+
end
23+
2024
def stage_action
2125
staging_details_env = BbsEnvironmentBuilder.build(staging_details.environment_variables)
2226

27+
# Stack deprecation warning will be handled by the build_create action
28+
buildpack_keys = if lifecycle.respond_to?(:buildpack_infos)
29+
lifecycle.buildpack_infos.map(&:key)
30+
else
31+
lifecycle_data[:buildpacks]&.map { |bp| bp[:key] } || [] # rubocop:disable Rails/Pluck
32+
end
33+
34+
skip_detect = if lifecycle.respond_to?(:skip_detect?)
35+
lifecycle.skip_detect?
36+
else
37+
lifecycle_data[:buildpacks]&.any? { |bp| bp[:skip_detect] } || false
38+
end
39+
2340
::Diego::Bbs::Models::RunAction.new(
2441
path: '/tmp/lifecycle/builder',
2542
user: 'vcap',
2643
args: [
27-
"-buildpackOrder=#{lifecycle_data[:buildpacks].pluck(:key).join(',')}",
44+
"-buildpackOrder=#{buildpack_keys.join(',')}",
2845
"-skipCertVerify=#{config.get(:skip_cert_verify)}",
29-
"-skipDetect=#{skip_detect?}",
46+
"-skipDetect=#{skip_detect}",
3047
'-buildDir=/tmp/app',
3148
'-outputDroplet=/tmp/droplet',
3249
'-outputMetadata=/tmp/result.json',

0 commit comments

Comments
 (0)