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

Commit 04864b1

Browse files
author
Derik Evangelista
authored
v3(route bindings): fetch parameters (cloudfoundry#1908)
[#174272953](https://www.pivotaltracker.com/story/show/174272953)
1 parent 38a5419 commit 04864b1

8 files changed

Lines changed: 253 additions & 13 deletions

File tree

app/controllers/v3/service_route_bindings_controller.rb

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
require 'cloud_controller/paging/sequel_paginator'
1414

1515
class ServiceRouteBindingsController < ApplicationController
16+
before_action :set_route_binding, only: [:show, :parameters, :destroy]
17+
1618
def create
1719
route_services_disabled! unless route_services_enabled?
1820
message = parse_create_request
@@ -40,10 +42,9 @@ def create
4042

4143
def show
4244
message = show_message
43-
route_binding = RouteBinding.first(guid: hashed_params[:guid])
44-
route_binding_not_found! unless route_binding && can_read_space?(route_binding.route.space)
45+
route_binding_not_found! unless @route_binding && can_read_space?(@route_binding.route.space)
4546
presenter = Presenters::V3::ServiceRouteBindingPresenter.new(
46-
route_binding,
47+
@route_binding,
4748
decorators: decorators(message)
4849
)
4950
render status: :ok, json: presenter
@@ -62,20 +63,34 @@ def index
6263
end
6364

6465
def destroy
65-
route_binding = RouteBinding.first(guid: hashed_params[:guid])
66-
route_binding_not_found! unless route_binding && can_read_space?(route_binding.route.space)
66+
route_binding_not_found! unless @route_binding && can_read_space?(@route_binding.route.space)
6767

6868
action = V3::ServiceRouteBindingDelete.new(service_event_repository)
69-
result = action.delete(route_binding, async_allowed: false)
69+
result = action.delete(@route_binding, async_allowed: false)
7070

7171
if result.is_a? V3::ServiceRouteBindingDelete::RequiresAsync
72-
pollable_job_guid = enqueue_unbind_job(route_binding.guid)
72+
pollable_job_guid = enqueue_unbind_job(@route_binding.guid)
7373
head :accepted, 'Location' => url_builder.build_url(path: "/v3/jobs/#{pollable_job_guid}")
7474
else
7575
head :no_content
7676
end
7777
end
7878

79+
def parameters
80+
route_binding_not_found! unless @route_binding && can_read_space?(@route_binding.route.space)
81+
unauthorized! unless can_write_space?(@route_binding.route.space)
82+
83+
fetcher = ServiceBindingRead.new
84+
parameters = fetcher.fetch_parameters(@route_binding)
85+
86+
render status: :ok, json: parameters
87+
rescue ServiceBindingRead::NotSupportedError
88+
bad_request!('user provided service instances do not support fetching route bindings parameters.') if @route_binding.service_instance.user_provided_instance?
89+
bad_request!('this service does not support fetching route bindings parameters.')
90+
rescue LockCheck::ServiceBindingLockedError
91+
unprocessable!('There is an operation in progress for the service route binding.')
92+
end
93+
7994
private
8095

8196
AVAILABLE_DECORATORS = [
@@ -206,4 +221,8 @@ def parameters_not_supported!
206221
def already_exists!
207222
raise CloudController::Errors::ApiError.new_from_details('ServiceInstanceAlreadyBoundToSameRoute').with_response_code(422)
208223
end
224+
225+
def set_route_binding
226+
@route_binding = RouteBinding.first(guid: hashed_params[:guid])
227+
end
209228
end

app/presenters/v3/service_route_binding_presenter.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,11 @@ def links
4545
route: {
4646
href: url_builder.build_url(path: "/v3/routes/#{binding.route.guid}")
4747
}
48-
}
48+
}.tap do |l|
49+
if binding.service_instance.managed_instance?
50+
l[:parameters] = { href: url_builder.build_url(path: "/v3/service_route_bindings/#{binding.guid}/parameters") }
51+
end
52+
end
4953
end
5054

5155
def relationships

config/routes.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,11 @@
197197
# service_route_bindings
198198
resources :service_route_bindings,
199199
param: :guid,
200-
only: [:show, :create, :index, :destroy]
200+
only: [:show, :create, :index, :destroy] do
201+
member do
202+
get :parameters
203+
end
204+
end
201205

202206
# service_brokers
203207
get '/service_brokers', to: 'service_brokers#index'
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
### Get parameters for a service credential binding
2+
3+
```
4+
Example Request
5+
```
6+
7+
```shell
8+
curl "https://api.example.org/v3/service_credential_bindings/[guid]/parameters" \
9+
-X GET \
10+
-H "Authorization: bearer [token]"
11+
```
12+
13+
```
14+
Example Response
15+
```
16+
17+
```http
18+
HTTP/1.1 200 OK
19+
Content-Type: application/json
20+
21+
{
22+
"foo": "bar",
23+
"foz": "baz"
24+
}
25+
```
26+
27+
Queries the Service Broker for the parameters associated with this service credential binding.
28+
The broker catalog must have enabled the `bindings_retrievable` feature for the Service Offering.
29+
Check the [Service Offering object](#the-service-offering-object) for the value of this feature flag.
30+
This endpoint is not available for User-Provided Service Instances.
31+
32+
#### Definition
33+
`GET /v3/service_credential_bindings/:guid/parameters`
34+
35+
#### Permitted roles
36+
|
37+
--- | ---
38+
Admin |
39+
Admin Read-Only |
40+
Space Developer |
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
### Get parameters for a route binding
2+
3+
```
4+
Example Request
5+
```
6+
7+
```shell
8+
curl "https://api.example.org/v3/service_route_bindings/[guid]/parameters" \
9+
-X GET \
10+
-H "Authorization: bearer [token]"
11+
```
12+
13+
```
14+
Example Response
15+
```
16+
17+
```http
18+
HTTP/1.1 200 OK
19+
Content-Type: application/json
20+
21+
{
22+
"foo": "bar",
23+
"foz": "baz"
24+
}
25+
```
26+
27+
Queries the Service Broker for the parameters associated with this service route binding.
28+
The broker catalog must have enabled the `bindings_retrievable` feature for the Service Offering.
29+
Check the [Service Offering object](#the-service-offering-object) for the value of this feature flag.
30+
This endpoint is not available for User-Provided Service Instances.
31+
32+
#### Definition
33+
`GET /v3/service_route_bindings/:guid/parameters`
34+
35+
#### Permitted roles
36+
|
37+
--- | ---
38+
Admin |
39+
Admin Read-Only |
40+
Space Developer |

docs/v3/source/index.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,12 +367,14 @@ includes:
367367
- experimental_resources/service_credential_bindings/list
368368
- experimental_resources/service_credential_bindings/delete
369369
- experimental_resources/service_credential_bindings/details
370+
- experimental_resources/service_credential_bindings/parameters
370371
- experimental_resources/service_route_bindings/header
371372
- experimental_resources/service_route_bindings/object
372373
- experimental_resources/service_route_bindings/create
373374
- experimental_resources/service_route_bindings/list
374375
- experimental_resources/service_route_bindings/get
375376
- experimental_resources/service_route_bindings/delete
377+
- experimental_resources/service_route_bindings/parameters
376378
- experimental_resources/service_instances/header
377379
- experimental_resources/service_instances/create
378380
- experimental_resources/service_instances/get

spec/request/service_route_bindings_spec.rb

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@
558558
route_guid: route.guid,
559559
last_operation_type: 'create',
560560
last_operation_state: 'succeeded',
561+
include_params_link: service_instance.managed_instance?
561562
)
562563
)
563564
end
@@ -670,6 +671,7 @@
670671
route_guid: route.guid,
671672
last_operation_type: 'create',
672673
last_operation_state: 'successful',
674+
include_params_link: service_instance_1.managed_instance?
673675
),
674676
expected_json(
675677
binding_guid: route_binding_2.guid,
@@ -678,6 +680,7 @@
678680
route_guid: route.guid,
679681
last_operation_type: 'create',
680682
last_operation_state: 'successful',
683+
include_params_link: service_instance_2.managed_instance?
681684
)
682685
]
683686
end
@@ -816,6 +819,7 @@
816819
route_guid: route.guid,
817820
last_operation_type: 'create',
818821
last_operation_state: 'successful',
822+
include_params_link: service_instance.managed_instance?
819823
)
820824
end
821825

@@ -1115,6 +1119,113 @@
11151119
end
11161120
end
11171121

1122+
describe 'GET /v3/service_route_bindings/:guid/parameters' do
1123+
let(:offering) { VCAP::CloudController::Service.make(requires: ['route_forwarding'], bindings_retrievable: true) }
1124+
let(:plan) { VCAP::CloudController::ServicePlan.make(service: offering) }
1125+
let(:service_instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space, service_plan: plan) }
1126+
let(:route) { VCAP::CloudController::Route.make(space: space) }
1127+
let(:app_model) { VCAP::CloudController::AppModel.make(space: space) }
1128+
let(:binding) { bind_service_to_route(service_instance, route) }
1129+
1130+
let(:api_call) { ->(user_headers) { get "/v3/service_route_bindings/#{binding.guid}/parameters", nil, user_headers } }
1131+
1132+
context 'managed service instances' do
1133+
let(:broker_base_url) { service_instance.service_broker.broker_url }
1134+
let(:broker_fetch_binding_url) { "#{broker_base_url}/v2/service_instances/#{service_instance.guid}/service_bindings/#{binding.guid}" }
1135+
let(:broker_status_code) { 200 }
1136+
let(:broker_response) { { parameters: { abra: 'kadabra', kadabra: 'alakazan' } } }
1137+
let(:parameters_response) { { code: 200, response_object: broker_response[:parameters] } }
1138+
1139+
let(:expected_codes_and_responses) do
1140+
Hash.new(code: 403).tap do |h|
1141+
h['admin'] = parameters_response
1142+
h['admin_readonly'] = parameters_response
1143+
h['space_developer'] = parameters_response
1144+
h['org_auditor'] = { code: 404 }
1145+
h['org_billing_manager'] = { code: 404 }
1146+
h['no_role'] = { code: 404 }
1147+
end
1148+
end
1149+
1150+
before do
1151+
stub_request(:get, broker_fetch_binding_url).
1152+
to_return(status: broker_status_code, body: broker_response.to_json, headers: {})
1153+
end
1154+
1155+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
1156+
1157+
context 'when bindings are not retrievable' do
1158+
let(:offering) { VCAP::CloudController::Service.make(requires: ['route_forwarding']) }
1159+
1160+
it 'returns the appropriate error' do
1161+
api_call.call(admin_headers)
1162+
expect(last_response).to have_status_code(400)
1163+
expect(parsed_response['errors']).to include(
1164+
include({
1165+
'detail' => 'Bad request: this service does not support fetching route bindings parameters.',
1166+
'title' => 'CF-BadRequest',
1167+
'code' => 1004,
1168+
})
1169+
)
1170+
end
1171+
end
1172+
1173+
context 'when there is an operation in progress' do
1174+
before do
1175+
binding.save_with_new_operation({}, {
1176+
type: 'create',
1177+
state: 'in progress'
1178+
})
1179+
end
1180+
1181+
it 'returns the appropriate error' do
1182+
api_call.call(admin_headers)
1183+
expect(last_response).to have_status_code(422)
1184+
expect(parsed_response['errors']).to include(
1185+
include({
1186+
'detail' => 'There is an operation in progress for the service route binding.',
1187+
'title' => 'CF-UnprocessableEntity',
1188+
'code' => 10008,
1189+
})
1190+
)
1191+
end
1192+
end
1193+
end
1194+
1195+
context 'user provided service instances' do
1196+
let(:service_instance) { VCAP::CloudController::UserProvidedServiceInstance.make(space: space, route_service_url: 'https://route.example.com') }
1197+
let(:error_response) { {
1198+
code: 'CF-BadRequest',
1199+
error: 'User provided service instances do not support fetching service binding parameters.'
1200+
}
1201+
}
1202+
let(:expected_codes_and_responses) do
1203+
Hash.new(code: 403).tap do |h|
1204+
h['admin'] = { code: 400, response_object: error_response }
1205+
h['admin_readonly'] = { code: 400, response_object: error_response }
1206+
h['space_developer'] = { code: 400, response_object: error_response }
1207+
h['org_auditor'] = { code: 404 }
1208+
h['org_billing_manager'] = { code: 404 }
1209+
h['no_role'] = { code: 404 }
1210+
end
1211+
end
1212+
1213+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
1214+
1215+
it 'returns the appropriate error' do
1216+
api_call.call(admin_headers)
1217+
expect(last_response).to have_status_code(400)
1218+
expect(parsed_response['errors']).to include(
1219+
include({
1220+
'detail' => 'Bad request: user provided service instances do not support fetching route bindings parameters.',
1221+
'title' => 'CF-BadRequest',
1222+
'code' => 1004,
1223+
})
1224+
)
1225+
end
1226+
end
1227+
end
1228+
11181229
let(:user) { VCAP::CloudController::User.make }
11191230
let(:org) { VCAP::CloudController::Organization.make }
11201231
let(:space) { VCAP::CloudController::Space.make(organization: org) }
@@ -1126,7 +1237,7 @@
11261237
headers_for(user)
11271238
end
11281239

1129-
def expected_json(binding_guid:, route_service_url:, route_guid:, service_instance_guid:, last_operation_state:, last_operation_type:)
1240+
def expected_json(binding_guid:, route_service_url:, route_guid:, service_instance_guid:, last_operation_state:, last_operation_type:, include_params_link:)
11301241
{
11311242
guid: binding_guid,
11321243
created_at: iso8601,
@@ -1160,8 +1271,14 @@ def expected_json(binding_guid:, route_service_url:, route_guid:, service_instan
11601271
},
11611272
route: {
11621273
href: "#{link_prefix}/v3/routes/#{route_guid}"
1163-
}
1164-
}
1274+
},
1275+
}.tap do |ls|
1276+
if include_params_link
1277+
ls[:parameters] = {
1278+
href: "#{link_prefix}/v3/service_route_bindings/#{binding_guid}/parameters"
1279+
}
1280+
end
1281+
end
11651282
}
11661283
end
11671284

spec/unit/presenters/v3/service_route_binding_presenter_spec.rb

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ module CloudController
6161
},
6262
service_instance: {
6363
href: %r{.*/v3/service_instances/#{service_instance.guid}}
64-
}
64+
},
65+
6566
}
6667
}
6768
)
@@ -108,6 +109,19 @@ module CloudController
108109
expect(decorator2).to have_received(:decorate).with({ foo: 'bar' }, [binding])
109110
end
110111
end
112+
113+
describe 'links' do
114+
let(:offering) { VCAP::CloudController::Service.make(requires: ['route_forwarding']) }
115+
let(:plan) { VCAP::CloudController::ServicePlan.make(service: offering) }
116+
let(:service_instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space, service_plan: plan) }
117+
118+
it 'include parameters for managed service instance bindings' do
119+
presenter = described_class.new(binding)
120+
expect(presenter.to_hash.dig(:links, :parameters)).to match({
121+
href: %r{.*/v3/service_route_bindings/#{guid}/parameters}
122+
})
123+
end
124+
end
111125
end
112126
end
113127
end

0 commit comments

Comments
 (0)