Skip to content
This repository was archived by the owner on Jun 2, 2021. It is now read-only.

Commit 805220c

Browse files
committed
v3 🐞: default route in manifest no longer errors when meant to be ignored
According to the [docs](http://v3-apidocs.cloudfoundry.org/version/3.86.0/index.html#the-manifest-schema), the default-route flag should be ignored if routes are also provided in the manifest. However if the app name was an invalid route host, the request would error out early in the message class. We pushed validation back into the action right before a default route would actually be created. [finishes #173550835]
1 parent 669600b commit 805220c

7 files changed

Lines changed: 84 additions & 90 deletions

app/actions/app_apply_manifest.rb

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

1010
module VCAP::CloudController
1111
class AppApplyManifest
12+
class Error < StandardError; end
1213
class NoDefaultDomain < StandardError; end
1314
class ServiceBindingError < StandardError; end
1415

@@ -105,6 +106,7 @@ def update_routes(app, message)
105106
end
106107

107108
if update_message.default_route && existing_routes.empty?
109+
validate_name_dns_compliant!(app.name)
108110
domain_name = get_default_domain_name(app)
109111

110112
route = "#{app.name}.#{domain_name}"
@@ -121,6 +123,18 @@ def get_default_domain_name(app)
121123
domain_name
122124
end
123125

126+
def validate_name_dns_compliant!(name)
127+
prefix = 'Failed to create default route from app name:'
128+
129+
if name.present? && name.length > 63
130+
error!(prefix + ' Host cannot exceed 63 characters')
131+
end
132+
133+
unless name&.match(/\A[\w\-]+\z/)
134+
error!(prefix + ' Host must be either "*" or contain only alphanumeric characters, "_", or "-"')
135+
end
136+
end
137+
124138
def create_service_bindings(manifest_service_bindings_message, app)
125139
action = ServiceBindingCreate.new(@user_audit_info, manifest_triggered: true)
126140
manifest_service_bindings_message.manifest_service_bindings.each do |manifest_service_binding|
@@ -162,5 +176,9 @@ def volume_services_enabled?
162176
def logger
163177
@logger ||= Steno.logger('cc.action.app_apply_manifest')
164178
end
179+
180+
def error!(message)
181+
raise Error.new(message)
182+
end
165183
end
166184
end

app/jobs/app_apply_manifest_action_job.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ def perform
2323
SidecarUpdate::InvalidSidecar,
2424
ManifestRouteUpdate::InvalidRoute,
2525
Route::InvalidOrganizationRelation,
26+
AppApplyManifest::Error,
2627
AppApplyManifest::ServiceBindingError => e
2728

2829
error = CloudController::Errors::ApiError.new_from_details('UnprocessableEntity', e.message)

app/jobs/space_apply_manifest_action_job.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ def perform
2222
ProcessUpdate::InvalidProcess,
2323
ManifestRouteUpdate::InvalidRoute,
2424
Route::InvalidOrganizationRelation,
25+
AppApplyManifest::Error,
2526
AppApplyManifest::ServiceBindingError => e
2627

2728
app_name = AppModel.find(guid: app_guid)&.name

app/messages/named_app_manifest_message.rb

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,5 @@ class NamedAppManifestMessage < AppManifestMessage
66
register_allowed_keys [:name]
77

88
validates :name, presence: { message: 'must not be empty' }, string: true
9-
validate :validate_name_dns_compliant!, if: -> { default_route }
10-
11-
def validate_name_dns_compliant!
12-
prefix = 'Failed to create default route from app name:'
13-
14-
if name.present? && name.length > 63
15-
errors.add(prefix, 'Host cannot exceed 63 characters')
16-
end
17-
18-
unless name&.match(/\A[\w\-]+\z/)
19-
errors.add(prefix, 'Host must be either "*" or contain only alphanumeric characters, "_", or "-"')
20-
end
21-
end
229
end
2310
end

spec/request/space_manifests_spec.rb

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@
309309
end
310310

311311
context 'when the app name is not a valid host name and the default-route flag is set to true' do
312-
let(:app1_model) { VCAP::CloudController::AppModel.make(name: '!' * 64, space: space) }
312+
let(:app1_model) { VCAP::CloudController::AppModel.make(name: 'a' * 64, space: space) }
313313
let(:yml_manifest) do
314314
{
315315
'applications' => [
@@ -319,16 +319,51 @@
319319
}.to_yaml
320320
end
321321

322-
it 'returns a 422 with an informative message' do
322+
it 'returns a 202 but fails on the job' do
323323
expect {
324324
post "/v3/spaces/#{space.guid}/actions/apply_manifest", yml_manifest, yml_headers(user_header)
325325
}.to change { VCAP::CloudController::AppModel.count }.by(0)
326326

327-
expect(last_response).to have_status_code(422)
328-
expect(last_response).to include_error_message(
329-
/Failed to create default route from app name: Host cannot exceed 63 characters/)
330-
expect(last_response).to include_error_message(
331-
/Failed to create default route from app name: Host must be either "\*" or contain only alphanumeric characters, "_", or "-"/)
327+
expect(last_response).to have_status_code(202)
328+
expect(last_response.status).to eq(202), last_response.body
329+
330+
job_guid = VCAP::CloudController::PollableJobModel.last.guid
331+
expect(last_response.headers['Location']).to match(%r(/v3/jobs/#{job_guid}))
332+
333+
Delayed::Worker.new.work_off
334+
job = VCAP::CloudController::PollableJobModel.find(guid: job_guid)
335+
errors = YAML.safe_load(job.cf_api_error)['errors']
336+
expect(errors.length).to eq 1
337+
expect(errors[0]['detail']).to include('Host cannot exceed 63 characters')
338+
end
339+
340+
context 'and routes are provided in the manifest' do
341+
let(:yml_manifest) do
342+
{
343+
'applications' => [
344+
{ 'name' => app1_model.name,
345+
'default-route' => true,
346+
'routes' => [{ 'route' => "http://#{route.host}.#{shared_domain.name}" }] },
347+
]
348+
}.to_yaml
349+
end
350+
351+
it 'returns a 202 and succeeds' do
352+
expect {
353+
post "/v3/spaces/#{space.guid}/actions/apply_manifest", yml_manifest, yml_headers(user_header)
354+
}.to change { VCAP::CloudController::AppModel.count }.by(0)
355+
356+
expect(last_response).to have_status_code(202)
357+
expect(last_response.status).to eq(202), last_response.body
358+
359+
job_guid = VCAP::CloudController::PollableJobModel.last.guid
360+
expect(last_response.headers['Location']).to match(%r(/v3/jobs/#{job_guid}))
361+
362+
Delayed::Worker.new.work_off
363+
job = VCAP::CloudController::PollableJobModel.find(guid: job_guid)
364+
expect(job.complete?).to be true
365+
expect(job.cf_api_error).to be nil
366+
end
332367
end
333368
end
334369

spec/unit/actions/app_apply_manifest_spec.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,28 @@ module VCAP::CloudController
553553
end
554554
end
555555

556+
context 'when the app name has special characters' do
557+
let(:message) { AppManifestMessage.create_from_yml({ name: 'blah!@#', default_route: true }) }
558+
let(:app_model) { AppModel.make(name: 'blah!@#') }
559+
it 'fails with a useful error message' do
560+
expect {
561+
app_apply_manifest.apply(app_model.guid, message)
562+
}.to raise_error(AppApplyManifest::Error,
563+
/Failed to create default route from app name: Host must be either "\*" or contain only alphanumeric characters, "_", or "-"/)
564+
end
565+
end
566+
567+
context 'when the app name is too long' do
568+
let(:app_name) { 'a' * 100 }
569+
let(:message) { AppManifestMessage.create_from_yml({ name: app_name, default_route: true }) }
570+
let(:app_model) { AppModel.make(name: app_name * 100) }
571+
it 'fails with a useful error' do
572+
expect {
573+
app_apply_manifest.apply(app_model.guid, message)
574+
}.to raise_error(AppApplyManifest::Error, 'Failed to create default route from app name: Host cannot exceed 63 characters')
575+
end
576+
end
577+
556578
context 'when there is no shared domain' do
557579
let(:domain) { PrivateDomain.make(owning_organization: app.organization) }
558580

spec/unit/messages/named_app_manifest_message_spec.rb

Lines changed: 0 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -24,76 +24,6 @@ module VCAP::CloudController
2424
expect(message.errors.full_messages[0]).to match(/^Name must not be empty/)
2525
end
2626
end
27-
28-
context 'when the name is too long' do
29-
let(:params) { { name: 'x' * 64, 'default-route': default_route } }
30-
31-
context 'when default-route is true' do
32-
let(:default_route) { true }
33-
34-
it 'is not valid' do
35-
message = NamedAppManifestMessage.create_from_yml(params)
36-
37-
expect(message).to_not be_valid
38-
expect(message.errors.full_messages[0]).to match(/Host cannot exceed 63 characters/)
39-
end
40-
end
41-
42-
context 'when default-route is false' do
43-
let(:default_route) { false }
44-
45-
it 'is valid' do
46-
message = NamedAppManifestMessage.create_from_yml(params)
47-
48-
expect(message).to be_valid
49-
end
50-
end
51-
52-
context 'when default-route is not set' do
53-
let(:default_route) { nil }
54-
55-
it 'is valid' do
56-
message = NamedAppManifestMessage.create_from_yml(params)
57-
58-
expect(message).to be_valid
59-
end
60-
end
61-
end
62-
63-
context 'when the name contains special characters' do
64-
let(:params) { { name: '%%%', 'default-route': default_route } }
65-
66-
context 'when default-route is true' do
67-
let(:default_route) { true }
68-
69-
it 'is not valid' do
70-
message = NamedAppManifestMessage.create_from_yml(params)
71-
72-
expect(message).to_not be_valid
73-
expect(message.errors.full_messages[0]).to match(/Host must be either "\*" or contain only alphanumeric characters, "_", or "-"/)
74-
end
75-
end
76-
77-
context 'when default-route is false' do
78-
let(:default_route) { false }
79-
80-
it 'is valid' do
81-
message = NamedAppManifestMessage.create_from_yml(params)
82-
83-
expect(message).to be_valid
84-
end
85-
end
86-
87-
context 'when default-route is not set' do
88-
let(:default_route) { nil }
89-
90-
it 'is valid' do
91-
message = NamedAppManifestMessage.create_from_yml(params)
92-
93-
expect(message).to be_valid
94-
end
95-
end
96-
end
9727
end
9828
end
9929
end

0 commit comments

Comments
 (0)