Skip to content

Commit 3b29b53

Browse files
committed
Stack state validation when creating and updating apps via V2 and V3 APIs
Sets warnings in the response hash when there are warnings Signed-off-by: Rashed Kamal <[email protected]>
1 parent 22da753 commit 3b29b53

File tree

19 files changed

+612
-14
lines changed

19 files changed

+612
-14
lines changed

app/actions/app_create.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ def initialize(user_audit_info)
1616

1717
def create(message, lifecycle)
1818
app = nil
19+
warnings = []
1920
AppModel.db.transaction do
2021
app = AppModel.create(
2122
name: message.name,
@@ -24,6 +25,7 @@ def create(message, lifecycle)
2425
)
2526

2627
lifecycle.create_lifecycle_data_model(app)
28+
warnings = validate_stack_state(app, lifecycle)
2729
validate_buildpacks_are_ready(app)
2830

2931
MetadataUpdate.update(app, message)
@@ -43,10 +45,14 @@ def create(message, lifecycle)
4345
)
4446
end
4547

48+
app.instance_variable_set(:@stack_warnings, warnings)
49+
4650
app
4751
rescue Sequel::ValidationFailed => e
4852
v3_api_error!(:UniquenessError, e.message) if e.errors.on(%i[space_guid name])
4953

54+
raise InvalidApp.new(e.message)
55+
rescue StackStateValidator::DisabledStackError, StackStateValidator::RestrictedStackError => e
5056
raise InvalidApp.new(e.message)
5157
end
5258

@@ -74,5 +80,17 @@ def validate_buildpacks_are_ready(app)
7480
end
7581
end
7682
end
83+
84+
def validate_stack_state(app, lifecycle)
85+
return [] if lifecycle.type == Lifecycles::DOCKER
86+
87+
stack_name = app.lifecycle_data.try(:stack)
88+
return [] unless stack_name
89+
90+
stack = Stack.find(name: stack_name)
91+
return [] unless stack
92+
93+
StackStateValidator.validate_for_new_app!(stack)
94+
end
7795
end
7896
end

app/actions/app_update.rb

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,13 @@ def update(app, message, lifecycle)
5353
end
5454
end
5555

56+
warnings = validate_stack_state(app, message, lifecycle)
57+
app.instance_variable_set(:@stack_warnings, warnings)
58+
5659
app
57-
rescue Sequel::ValidationFailed => e
60+
rescue Sequel::ValidationFailed,
61+
StackStateValidator::DisabledStackError,
62+
StackStateValidator::RestrictedStackError => e
5863
raise InvalidApp.new(e.message)
5964
end
6065

@@ -81,5 +86,19 @@ def validate_not_changing_lifecycle_type!(app, lifecycle)
8186
def existing_environment_variables_for(app)
8287
app.environment_variables.nil? ? {} : app.environment_variables.symbolize_keys
8388
end
89+
90+
def validate_stack_state(app, message, lifecycle)
91+
return [] if lifecycle.type == Lifecycles::DOCKER
92+
return [] unless message.requested?(:lifecycle) && message.buildpack_data.requested?(:stack)
93+
94+
stack = Stack.find(name: message.buildpack_data.stack)
95+
return [] unless stack
96+
97+
if app.builds_dataset.count.zero?
98+
StackStateValidator.validate_for_new_app!(stack)
99+
else
100+
StackStateValidator.validate_for_restaging!(stack)
101+
end
102+
end
84103
end
85104
end

app/actions/v2/app_create.rb

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ def create(request_attrs)
4646
@access_validator.validate_access(:create, process, request_attrs)
4747
end
4848

49+
warnings = validate_stack_state(request_attrs)
50+
process.instance_variable_set(:@stack_warnings, warnings)
51+
4952
process
5053
end
5154

@@ -106,6 +109,18 @@ def validate_package_exists!(process, request_attrs)
106109

107110
raise CloudController::Errors::ApiError.new_from_details('AppPackageInvalid', 'bits have not been uploaded')
108111
end
112+
113+
def validate_stack_state(request_attrs)
114+
return [] if request_attrs.key?('docker_image')
115+
116+
stack_name = get_stack_name(request_attrs['stack_guid'])
117+
stack = Stack.find(name: stack_name)
118+
return [] unless stack
119+
120+
StackStateValidator.validate_for_new_app!(stack)
121+
rescue StackStateValidator::DisabledStackError, StackStateValidator::RestrictedStackError => e
122+
raise CloudController::Errors::ApiError.new_from_details('StackValidationFailed', e.message)
123+
end
109124
end
110125
end
111126
end

app/actions/v2/app_update.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ def update(app, process, request_attrs)
3939
prepare_to_stage(app) if staging_necessary?(process, request_attrs)
4040
end
4141

42+
validate_stack_state(app, request_attrs)
4243
stage(process) if staging_necessary?(process, request_attrs)
4344
end
4445

@@ -184,6 +185,23 @@ def staging_necessary?(process, request_attrs)
184185
def v2_api_staging_disabled?
185186
!!VCAP::CloudController::Config.config.get(:temporary_disable_v2_staging)
186187
end
188+
189+
def validate_stack_state(app, request_attrs)
190+
return unless request_attrs.key?('stack_guid')
191+
return if request_attrs.key?('docker_image')
192+
193+
stack = Stack.find(guid: request_attrs['stack_guid'])
194+
return unless stack
195+
196+
stack_warnings = if app.builds_dataset.count.zero?
197+
StackStateValidator.validate_for_new_app!(stack)
198+
else
199+
StackStateValidator.validate_for_restaging!(stack)
200+
end
201+
@warnings.concat(stack_warnings)
202+
rescue StackStateValidator::DisabledStackError, StackStateValidator::RestrictedStackError => e
203+
raise CloudController::Errors::ApiError.new_from_details('StackValidationFailed', e.message)
204+
end
187205
end
188206
end
189207
end

app/controllers/runtime/apps_controller.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,8 @@ def create
358358
creator = V2::AppCreate.new(access_validator: self)
359359
process = creator.create(request_attrs)
360360

361+
process.stack_warnings&.each { |warning| add_warning(warning) }
362+
361363
@app_event_repository.record_app_create(
362364
process,
363365
process.space,

app/controllers/v3/apps_controller.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ def create
107107
}
108108
)
109109

110+
add_warning_headers(app.stack_warnings) if app.stack_warnings&.any?
111+
110112
render status: :created, json: Presenters::V3::AppPresenter.new(app)
111113
rescue AppCreate::InvalidApp => e
112114
unprocessable!(e.message)
@@ -134,6 +136,8 @@ def update
134136
}
135137
)
136138

139+
add_warning_headers(app.stack_warnings) if app.stack_warnings&.any?
140+
137141
render status: :ok, json: Presenters::V3::AppPresenter.new(app)
138142
rescue AppUpdate::DropletNotFound
139143
droplet_not_found!

app/models/runtime/app_model.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ class AppModel < Sequel::Model(:apps)
1111
DEFAULT_CONTAINER_USER = 'vcap'.freeze
1212
DEFAULT_DOCKER_CONTAINER_USER = 'root'.freeze
1313

14+
attr_reader :stack_warnings
15+
1416
many_to_many :routes, join_table: :route_mappings, left_key: :app_guid, left_primary_key: :guid, right_primary_key: :guid, right_key: :route_guid
1517
one_to_many :route_mappings, class: 'VCAP::CloudController::RouteMappingModel', key: :app_guid, primary_key: :guid
1618
one_to_many :service_bindings, key: :app_guid, primary_key: :guid

app/models/runtime/process_model.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ class ProcessModel < Sequel::Model(:processes) # rubocop:disable Metrics/ClassLe
2222

2323
extend IntegerArraySerializer
2424

25+
attr_reader :stack_warnings
26+
2527
def after_initialize
2628
self.instances ||= db_schema[:instances][:default].to_i
2729
self.memory ||= Config.config.get(:default_app_memory)

app/presenters/v3/app_presenter.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ def to_hash
4545
links: build_links
4646
}
4747

48+
hash[:warnings] = app_warnings if app_warnings
49+
4850
@decorators.reduce(hash) { |memo, d| d.decorate(memo, [app]) }
4951
end
5052

@@ -74,6 +76,12 @@ def build_links
7476

7577
links.delete_if { |_, v| v.nil? }
7678
end
79+
80+
def app_warnings
81+
return nil unless app.stack_warnings&.any?
82+
83+
app.stack_warnings.map { |warning| { detail: warning } }
84+
end
7785
end
7886
end
7987
end

app/presenters/v3/build_presenter.rb

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def associated_resources
1515
end
1616

1717
def to_hash
18-
{
18+
hash = {
1919
guid: build.guid,
2020
created_at: build.created_at,
2121
updated_at: build.updated_at,
@@ -30,7 +30,6 @@ def to_hash
3030
},
3131
package: { guid: build.package_guid },
3232
droplet: droplet,
33-
warnings: build_warnings,
3433
created_by: {
3534
guid: build.created_by_user_guid,
3635
name: build.created_by_user_name,
@@ -43,6 +42,10 @@ def to_hash
4342
},
4443
links: build_links
4544
}
45+
46+
hash[:warnings] = build_warnings if build_warnings
47+
48+
hash
4649
end
4750

4851
private

0 commit comments

Comments
 (0)