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

Commit c5865d9

Browse files
v3 🐞: default route in manifest no longer errors when meant to be ignored
Originally reverted (07b60b1) because unit tests were failing when running with mysql. Error message: ``` Error: expected VCAP::CloudController::AppApplyManifest::Error with "Failed to create default route from app name: Host cannot exceed 63 characters", got #<Sequel::DatabaseError: Mysql2::Error: Data too long for column 'name' at row 1> ``` The original commit (805220c) used an app name which was 10,000 characters long, well exceeding the 63 character limit. Reducing the name to 100 characters gave us the expected error message rather than the Sequel error. [finishes #173550835] Co-authored-by: Sarah Weinstein <sweinstein@pivotal.io> Co-authored-by: Merric De Launey <mdelauney@pivotal.io>
1 parent 526bbf8 commit c5865d9

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) }
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)