Skip to content
Merged
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
17 changes: 17 additions & 0 deletions app/actions/app_create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ class AppCreate

class InvalidApp < StandardError; end

attr_reader :warnings

def initialize(user_audit_info)
@user_audit_info = user_audit_info
@logger = Steno.logger('cc.action.app_create')
Expand All @@ -24,6 +26,7 @@ def create(message, lifecycle)
)

lifecycle.create_lifecycle_data_model(app)
@warnings = validate_stack_state(app, lifecycle)
validate_buildpacks_are_ready(app)

MetadataUpdate.update(app, message)
Expand All @@ -47,6 +50,8 @@ def create(message, lifecycle)
rescue Sequel::ValidationFailed => e
v3_api_error!(:UniquenessError, e.message) if e.errors.on(%i[space_guid name])

raise InvalidApp.new(e.message)
rescue StackStateValidator::DisabledStackError, StackStateValidator::RestrictedStackError => e
raise InvalidApp.new(e.message)
end

Expand Down Expand Up @@ -74,5 +79,17 @@ def validate_buildpacks_are_ready(app)
end
end
end

def validate_stack_state(app, lifecycle)
return [] if lifecycle.type == Lifecycles::DOCKER

stack_name = app.lifecycle_data.try(:stack)
return [] unless stack_name

stack = Stack.find(name: stack_name)
return [] unless stack

StackStateValidator.validate_for_new_app!(stack)
end
end
end
22 changes: 21 additions & 1 deletion app/actions/app_update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ class AppUpdate
class DropletNotFound < StandardError; end
class InvalidApp < StandardError; end

attr_reader :warnings

def initialize(user_audit_info, manifest_triggered: false, runners: nil)
@user_audit_info = user_audit_info
@logger = Steno.logger('cc.action.app_update')
Expand Down Expand Up @@ -53,8 +55,12 @@ def update(app, message, lifecycle)
end
end

@warnings = validate_stack_state(app, message, lifecycle)

app
rescue Sequel::ValidationFailed => e
rescue Sequel::ValidationFailed,
StackStateValidator::DisabledStackError,
StackStateValidator::RestrictedStackError => e
raise InvalidApp.new(e.message)
end

Expand All @@ -81,5 +87,19 @@ def validate_not_changing_lifecycle_type!(app, lifecycle)
def existing_environment_variables_for(app)
app.environment_variables.nil? ? {} : app.environment_variables.symbolize_keys
end

def validate_stack_state(app, message, lifecycle)
return [] if lifecycle.type == Lifecycles::DOCKER
return [] unless message.requested?(:lifecycle) && message.buildpack_data.requested?(:stack)

stack = Stack.find(name: message.buildpack_data.stack)
return [] unless stack

if app.builds_dataset.count.zero?
StackStateValidator.validate_for_new_app!(stack)
else
StackStateValidator.validate_for_restaging!(stack)
end
end
end
end
27 changes: 26 additions & 1 deletion app/actions/build_create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class LogRateLimitOrgQuotaExceeded < BuildError
class StagingInProgress < BuildError
end

attr_reader :staging_response
attr_reader :staging_response, :warnings

def initialize(user_audit_info: UserAuditInfo.from_context(SecurityContext),
memory_limit_calculator: QuotaValidatingStagingMemoryCalculator.new,
Expand All @@ -41,6 +41,7 @@ def initialize(user_audit_info: UserAuditInfo.from_context(SecurityContext),

def create_and_stage(package:, lifecycle:, metadata: nil, start_after_staging: false)
logger.info("creating build for package #{package.guid}")
@warnings = validate_stack_state!(lifecycle, package.app)
staging_in_progress! if package.app.staging_in_progress?
raise InvalidPackage.new('Cannot stage package whose state is not ready.') if package.state != PackageModel::READY_STATE

Expand Down Expand Up @@ -179,5 +180,29 @@ def stagers
def staging_in_progress!
raise StagingInProgress
end

def validate_stack_state!(lifecycle, app)
return [] if lifecycle.type == Lifecycles::DOCKER

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

warnings = if first_build_for_app?(app)
StackStateValidator.validate_for_new_app!(stack)
else
StackStateValidator.validate_for_restaging!(stack)
end
warnings.each { |warning| logger.warn(warning) }
warnings
rescue StackStateValidator::DisabledStackError, StackStateValidator::RestrictedStackError => e
raise CloudController::Errors::ApiError.new_from_details(
'StackValidationFailed',
e.message
)
end

def first_build_for_app?(app)
app.builds_dataset.count.zero?
end
end
end
3 changes: 2 additions & 1 deletion app/actions/stack_create.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ def initialize(user_audit_info)
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: 2 additions & 1 deletion app/actions/stack_update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@ def initialize(user_audit_info)

def update(stack, message)
stack.db.transaction do
stack.update(state: message.state) if message.requested?(:state)
MetadataUpdate.update(stack, message)
Repositories::StackEventRepository.new.record_stack_update(stack, @user_audit_info, message.audit_hash)
end
@logger.info("Finished updating metadata on stack #{stack.guid}")
@logger.info("Finished updating stack #{stack.guid}")

stack
rescue Sequel::ValidationFailed => e
Expand Down
16 changes: 16 additions & 0 deletions app/actions/v2/app_create.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
module VCAP::CloudController
module V2
class AppCreate
attr_reader :warnings

def initialize(access_validator:)
@access_validator = access_validator
end
Expand Down Expand Up @@ -46,6 +48,8 @@ def create(request_attrs)
@access_validator.validate_access(:create, process, request_attrs)
end

@warnings = validate_stack_state(request_attrs)

process
end

Expand Down Expand Up @@ -106,6 +110,18 @@ def validate_package_exists!(process, request_attrs)

raise CloudController::Errors::ApiError.new_from_details('AppPackageInvalid', 'bits have not been uploaded')
end

def validate_stack_state(request_attrs)
return [] if request_attrs.key?('docker_image')

stack_name = get_stack_name(request_attrs['stack_guid'])
stack = Stack.find(name: stack_name)
return [] unless stack

StackStateValidator.validate_for_new_app!(stack)
rescue StackStateValidator::DisabledStackError, StackStateValidator::RestrictedStackError => e
raise CloudController::Errors::ApiError.new_from_details('StackValidationFailed', e.message)
end
end
end
end
6 changes: 6 additions & 0 deletions app/actions/v2/app_stage.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
module VCAP::CloudController
module V2
class AppStage
attr_reader :warnings

def initialize(stagers:)
@stagers = stagers
@warnings = []
end

def stage(process)
Expand All @@ -25,6 +28,9 @@ def stage(process)
lifecycle: lifecycle,
start_after_staging: true
)

@warnings = build_creator.warnings || []

TelemetryLogger.v2_emit(
'create-build',
{
Expand Down
25 changes: 24 additions & 1 deletion app/actions/v2/app_update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
module VCAP::CloudController
module V2
class AppUpdate
attr_reader :warnings

def initialize(access_validator:, stagers:)
@access_validator = access_validator
@stagers = stagers
@warnings = []
end

def update(app, process, request_attrs)
Expand Down Expand Up @@ -36,6 +39,7 @@ def update(app, process, request_attrs)
prepare_to_stage(app) if staging_necessary?(process, request_attrs)
end

validate_stack_state(app, request_attrs)
stage(process) if staging_necessary?(process, request_attrs)
end

Expand Down Expand Up @@ -116,7 +120,9 @@ def prepare_to_stage(app)
end

def stage(process)
V2::AppStage.new(stagers: @stagers).stage(process)
app_stage = V2::AppStage.new(stagers: @stagers)
app_stage.stage(process)
@warnings = app_stage.warnings
end

def start_or_stop(app, request_attrs)
Expand Down Expand Up @@ -179,6 +185,23 @@ def staging_necessary?(process, request_attrs)
def v2_api_staging_disabled?
!!VCAP::CloudController::Config.config.get(:temporary_disable_v2_staging)
end

def validate_stack_state(app, request_attrs)
return unless request_attrs.key?('stack_guid')
return if request_attrs.key?('docker_image')

stack = Stack.find(guid: request_attrs['stack_guid'])
return unless stack

stack_warnings = if app.builds_dataset.count.zero?
StackStateValidator.validate_for_new_app!(stack)
else
StackStateValidator.validate_for_restaging!(stack)
end
@warnings.concat(stack_warnings)
rescue StackStateValidator::DisabledStackError, StackStateValidator::RestrictedStackError => e
raise CloudController::Errors::ApiError.new_from_details('StackValidationFailed', e.message)
end
end
end
end
3 changes: 3 additions & 0 deletions app/controllers/runtime/apps_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,7 @@ def update(guid)

updater = V2::AppUpdate.new(access_validator: self, stagers: @stagers)
updater.update(app, process, request_attrs)
updater.warnings.each { |warning| add_warning(warning) }

after_update(process)

Expand Down Expand Up @@ -357,6 +358,8 @@ def create
creator = V2::AppCreate.new(access_validator: self)
process = creator.create(request_attrs)

creator.warnings&.each { |warning| add_warning(warning) }

@app_event_repository.record_app_create(
process,
process.space,
Expand Down
5 changes: 4 additions & 1 deletion app/controllers/runtime/restages_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ def restage(guid)
process.app.update(droplet_guid: nil)
AppStart.start_without_event(process.app, create_revision: false)
end
V2::AppStage.new(stagers: @stagers).stage(process)

app_stage = V2::AppStage.new(stagers: @stagers)
app_stage.stage(process)
app_stage.warnings.each { |warning| add_warning(warning) }

@app_event_repository.record_app_restage(process, UserAuditInfo.from_context(SecurityContext))

Expand Down
10 changes: 8 additions & 2 deletions app/controllers/v3/apps_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ def create
FeatureFlag.raise_unless_enabled!(:diego_cnb) if lifecycle.type == VCAP::CloudController::Lifecycles::CNB
unprocessable!(lifecycle.errors.full_messages) unless lifecycle.valid?

app = AppCreate.new(user_audit_info).create(message, lifecycle)
app_creator = AppCreate.new(user_audit_info)
app = app_creator.create(message, lifecycle)
TelemetryLogger.v3_emit(
'create-app',
{
Expand All @@ -107,6 +108,8 @@ def create
}
)

add_warning_headers(app_creator.warnings) if app_creator.warnings&.any?

render status: :created, json: Presenters::V3::AppPresenter.new(app)
rescue AppCreate::InvalidApp => e
unprocessable!(e.message)
Expand All @@ -125,7 +128,8 @@ def update
lifecycle = AppLifecycleProvider.provide_for_update(message, app)
unprocessable!(lifecycle.errors.full_messages) unless lifecycle.valid?

app = AppUpdate.new(user_audit_info).update(app, message, lifecycle)
app_updater = AppUpdate.new(user_audit_info)
app = app_updater.update(app, message, lifecycle)
TelemetryLogger.v3_emit(
'update-app',
{
Expand All @@ -134,6 +138,8 @@ def update
}
)

add_warning_headers(app_updater.warnings) if app_updater.warnings&.any?

render status: :ok, json: Presenters::V3::AppPresenter.new(app)
rescue AppUpdate::DropletNotFound
droplet_not_found!
Expand Down
5 changes: 4 additions & 1 deletion app/controllers/v3/builds_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ def create
FeatureFlag.raise_unless_enabled!(:diego_cnb) if lifecycle.type == VCAP::CloudController::Lifecycles::CNB
unprocessable!(lifecycle.errors.full_messages) unless lifecycle.valid?

build = BuildCreate.new.create_and_stage(package: package, lifecycle: lifecycle, metadata: message.metadata)
build_creator = BuildCreate.new
build = build_creator.create_and_stage(package: package, lifecycle: lifecycle, metadata: message.metadata)

TelemetryLogger.v3_emit(
'create-build',
Expand All @@ -65,6 +66,8 @@ def create
}
)

add_warning_headers(build_creator.warnings) if build_creator.warnings&.any?

render status: :created, json: Presenters::V3::BuildPresenter.new(build)
rescue BuildCreate::InvalidPackage => e
bad_request!(e.message)
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 @@ -52,7 +52,9 @@ def update
message = StackUpdateMessage.new(hashed_params[:body])
unprocessable!(message.errors.full_messages) unless message.valid?

changing_default_stack_state = stack.default? && message.requested?(:state)
stack = StackUpdate.new(user_audit_info).update(stack, message)
add_warning_headers(["Changing state on the default stack '#{stack.name}' may affect new application deployments."]) if changing_default_stack_state

render status: :ok, json: Presenters::V3::StackPresenter.new(stack)
end
Expand Down
14 changes: 13 additions & 1 deletion app/messages/stack_create_message.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
require 'messages/metadata_base_message'
require 'models/helpers/stack_states'

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: StackStates::VALID_STATES, message: "must be one of #{StackStates::VALID_STATES.join(', ')}" }, allow_nil: false, if: :state_requested?

def state_requested?
requested?(:state)
end

def state
return @state if defined?(@state)

@state = requested?(:state) ? super : StackStates::DEFAULT_STATE
end
end
end
Loading