Skip to content

Commit 2e66684

Browse files
committed
Introduce 'file-based-vcap-services' app feature
1 parent 089f380 commit 2e66684

File tree

10 files changed

+253
-29
lines changed

10 files changed

+253
-29
lines changed

app/actions/app_feature_update.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ def self.update(feature_name, app, message)
88
app.update(revisions_enabled: message.enabled)
99
when 'service-binding-k8s'
1010
app.update(service_binding_k8s_enabled: message.enabled)
11+
when 'file-based-vcap-services'
12+
app.update(file_based_vcap_services_enabled: message.enabled)
1113
end
1214
end
1315
end

app/controllers/v3/app_features_controller.rb

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require 'presenters/v3/app_ssh_feature_presenter'
44
require 'presenters/v3/app_revisions_feature_presenter'
55
require 'presenters/v3/app_service_binding_k8s_feature_presenter'
6+
require 'presenters/v3/app_file_based_vcap_services_feature_presenter'
67
require 'presenters/v3/app_ssh_status_presenter'
78
require 'actions/app_feature_update'
89

@@ -12,8 +13,9 @@ class AppFeaturesController < ApplicationController
1213
SSH_FEATURE = 'ssh'.freeze
1314
REVISIONS_FEATURE = 'revisions'.freeze
1415
SERVICE_BINDING_K8S_FEATURE = 'service-binding-k8s'.freeze
16+
FILE_BASED_VCAP_SERVICES_FEATURE = 'file-based-vcap-services'.freeze
1517

16-
TRUSTED_APP_FEATURES = [SSH_FEATURE, SERVICE_BINDING_K8S_FEATURE].freeze
18+
TRUSTED_APP_FEATURES = [SSH_FEATURE, SERVICE_BINDING_K8S_FEATURE, FILE_BASED_VCAP_SERVICES_FEATURE].freeze
1719
UNTRUSTED_APP_FEATURES = [REVISIONS_FEATURE].freeze
1820
APP_FEATURES = (TRUSTED_APP_FEATURES + UNTRUSTED_APP_FEATURES).freeze
1921

@@ -53,6 +55,10 @@ def update
5355
message = VCAP::CloudController::AppFeatureUpdateMessage.new(hashed_params['body'])
5456
unprocessable!(message.errors.full_messages) unless message.valid?
5557

58+
if message.enabled && both_service_binding_features_enabled?(app, name)
59+
unprocessable!("'file-based-vcap-services' and 'service-binding-k8s' features cannot be enabled at the same time.")
60+
end
61+
5662
AppFeatureUpdate.update(hashed_params[:name], app, message)
5763
render status: :ok, json: feature_presenter_for(hashed_params[:name], app)
5864
end
@@ -83,7 +89,8 @@ def feature_presenter_for(feature_name, app)
8389
presenters = {
8490
SSH_FEATURE => Presenters::V3::AppSshFeaturePresenter,
8591
REVISIONS_FEATURE => Presenters::V3::AppRevisionsFeaturePresenter,
86-
SERVICE_BINDING_K8S_FEATURE => Presenters::V3::AppServiceBindingK8sFeaturePresenter
92+
SERVICE_BINDING_K8S_FEATURE => Presenters::V3::AppServiceBindingK8sFeaturePresenter,
93+
FILE_BASED_VCAP_SERVICES_FEATURE => Presenters::V3::AppFileBasedVcapServicesFeaturePresenter
8794
}
8895
presenters[feature_name].new(app)
8996
end
@@ -92,7 +99,16 @@ def presented_app_features(app)
9299
[
93100
Presenters::V3::AppSshFeaturePresenter.new(app),
94101
Presenters::V3::AppRevisionsFeaturePresenter.new(app),
95-
Presenters::V3::AppServiceBindingK8sFeaturePresenter.new(app)
102+
Presenters::V3::AppServiceBindingK8sFeaturePresenter.new(app),
103+
Presenters::V3::AppFileBasedVcapServicesFeaturePresenter.new(app)
96104
]
97105
end
106+
107+
def both_service_binding_features_enabled?(app, feature_name)
108+
if feature_name == 'file-based-vcap-services'
109+
app.service_binding_k8s_enabled
110+
elsif feature_name == 'service-binding-k8s'
111+
app.file_based_vcap_services_enabled
112+
end
113+
end
98114
end
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
require 'presenters/v3/base_presenter'
2+
3+
module VCAP::CloudController::Presenters::V3
4+
class AppFileBasedVcapServicesFeaturePresenter < BasePresenter
5+
def to_hash
6+
{
7+
name: 'file-based-vcap-services',
8+
description: 'Enable file-based VCAP service bindings for the app',
9+
enabled: app.file_based_vcap_services_enabled
10+
}
11+
end
12+
13+
private
14+
15+
def app
16+
@resource
17+
end
18+
end
19+
end
Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
Sequel.migration do
2-
change do
3-
add_column :apps, :service_binding_k8s_enabled, :boolean, default: false, null: false
2+
up do
3+
alter_table :apps do
4+
add_column :service_binding_k8s_enabled, :boolean, default: false, null: false
5+
add_column :file_based_vcap_services_enabled, :boolean, default: false, null: false
6+
add_constraint(name: :only_one_sb_feature_enabled) do
7+
Sequel.lit('NOT (service_binding_k8s_enabled AND file_based_vcap_services_enabled)')
8+
end
9+
end
10+
end
11+
12+
down do
13+
alter_table :apps do
14+
drop_column :service_binding_k8s_enabled
15+
drop_column :file_based_vcap_services_enabled
16+
drop_constraint(name: :only_one_sb_feature_enabled)
17+
end
418
end
519
end

docs/v3/source/includes/api_resources/_app_features.erb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,15 @@
2323
"name": "service-binding-k8s",
2424
"description": "Enable k8s service bindings for the app",
2525
"enabled": false
26+
},
27+
{
28+
"name": "file-based-vcap-services",
29+
"description": "Enable file-based VCAP service bindings for the app",
30+
"enabled": false
2631
}
2732
],
2833
"pagination": {
29-
"total_results": 3,
34+
"total_results": 4,
3035
"total_pages": 1,
3136
"first": { "href": "/v3/apps/05d39de4-2c9e-4c76-8fd6-10417da07e42/features" },
3237
"last": { "href": "/v3/apps/05d39de4-2c9e-4c76-8fd6-10417da07e42/features" },

docs/v3/source/includes/resources/app_features/_supported_features.md.erb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ Name | Description
77
**ssh** | Enable SSHing into the app
88
**revisions** | Enable [versioning](#revisions) of an application
99
**service-binding-k8s** | Enable k8s service bindings for the app (experimental)
10+
**file-based-vcap-services** | Enable file-based VCAP service bindings for the app (experimental)

spec/request/app_features_spec.rb

Lines changed: 157 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,14 @@
77
let(:admin_header) { admin_headers_for(user) }
88
let(:org) { VCAP::CloudController::Organization.make(created_at: 3.days.ago) }
99
let(:space) { VCAP::CloudController::Space.make(organization: org) }
10-
let(:app_model) { VCAP::CloudController::AppModel.make(space: space, enable_ssh: true, service_binding_k8s_enabled: true) }
10+
let(:service_binding_k8s_enabled) { true }
11+
let(:file_based_vcap_services_enabled) { false }
12+
let(:request_body_enabled) { { body: { enabled: true } } }
13+
let(:app_model) { VCAP::CloudController::AppModel.make(
14+
space: space,
15+
enable_ssh: true,
16+
service_binding_k8s_enabled: service_binding_k8s_enabled,
17+
file_based_vcap_services_enabled: file_based_vcap_services_enabled) }
1118

1219
describe 'GET /v3/apps/:guid/features' do
1320
context 'getting a list of available features for the app' do
@@ -29,11 +36,16 @@
2936
'name' => 'service-binding-k8s',
3037
'description' => 'Enable k8s service bindings for the app',
3138
'enabled' => true
39+
},
40+
{
41+
'name' => 'file-based-vcap-services',
42+
'description' => 'Enable file-based VCAP service bindings for the app',
43+
'enabled' => false
3244
}
3345
],
3446
'pagination' =>
3547
{
36-
'total_results' => 3,
48+
'total_results' => 4,
3749
'total_pages' => 1,
3850
'first' => { 'href' => "/v3/apps/#{app_model.guid}/features" },
3951
'last' => { 'href' => "/v3/apps/#{app_model.guid}/features" },
@@ -112,6 +124,19 @@
112124

113125
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
114126
end
127+
128+
context 'file-based-vcap-services app feature' do
129+
let(:api_call) { ->(user_headers) { get "/v3/apps/#{app_model.guid}/features/file-based-vcap-services", nil, user_headers } }
130+
let(:feature_response_object) do
131+
{
132+
'name' => 'file-based-vcap-services',
133+
'description' => 'Enable file-based VCAP service bindings for the app',
134+
'enabled' => false
135+
}
136+
end
137+
138+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
139+
end
115140
end
116141

117142
describe 'PATCH /v3/apps/:guid/features/:name' do
@@ -192,36 +217,148 @@
192217
end
193218

194219
context 'service-binding-k8s app feature' do
195-
let(:api_call) { ->(user_headers) { patch "/v3/apps/#{app_model.guid}/features/service-binding-k8s", request_body.to_json, user_headers } }
196-
let(:feature_response_object) do
197-
{
198-
'name' => 'service-binding-k8s',
199-
'description' => 'Enable k8s service bindings for the app',
200-
'enabled' => false
201-
}
220+
context 'when feature is enabled' do
221+
let(:service_binding_k8s_enabled) { true }
222+
let(:api_call) { ->(user_headers) { patch "/v3/apps/#{app_model.guid}/features/service-binding-k8s", request_body.to_json, user_headers } }
223+
let(:feature_response_object) do
224+
{
225+
'name' => 'service-binding-k8s',
226+
'description' => 'Enable k8s service bindings for the app',
227+
'enabled' => false
228+
}
229+
end
230+
231+
let(:expected_codes_and_responses) do
232+
h = Hash.new({ code: 403, errors: CF_NOT_AUTHORIZED }.freeze)
233+
%w[no_role org_auditor org_billing_manager].each { |r| h[r] = { code: 404 } }
234+
%w[admin space_developer].each { |r| h[r] = { code: 200, response_object: feature_response_object } }
235+
h
236+
end
237+
238+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
239+
240+
context 'when organization is suspended' do
241+
let(:expected_codes_and_responses) do
242+
h = super()
243+
h['space_developer'] = { code: 403, errors: CF_ORG_SUSPENDED }
244+
h
245+
end
246+
247+
before do
248+
org.update(status: VCAP::CloudController::Organization::SUSPENDED)
249+
end
250+
251+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
252+
end
202253
end
203254

204-
let(:expected_codes_and_responses) do
205-
h = Hash.new({ code: 403, errors: CF_NOT_AUTHORIZED }.freeze)
206-
%w[no_role org_auditor org_billing_manager].each { |r| h[r] = { code: 404 } }
207-
%w[admin space_developer].each { |r| h[r] = { code: 200, response_object: feature_response_object } }
208-
h
255+
context 'when feature is disabled' do
256+
let(:service_binding_k8s_enabled) { false }
257+
let(:api_call) { ->(user_headers) { patch "/v3/apps/#{app_model.guid}/features/service-binding-k8s", request_body_enabled.to_json, user_headers } }
258+
259+
let(:feature_response_object) do
260+
{
261+
'name' => 'service-binding-k8s',
262+
'description' => 'Enable k8s service bindings for the app',
263+
'enabled' => true
264+
}
265+
end
266+
267+
let(:expected_codes_and_responses) do
268+
h = Hash.new({ code: 403, errors: CF_NOT_AUTHORIZED }.freeze)
269+
%w[no_role org_auditor org_billing_manager].each { |r| h[r] = { code: 404 } }
270+
%w[admin space_developer].each { |r| h[r] = { code: 200, response_object: feature_response_object } }
271+
h
272+
end
273+
274+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
275+
276+
context 'when file-based-vcap-services is enabled' do
277+
before do
278+
patch "/v3/apps/#{app_model.guid}/features/file-based-vcap-services", request_body_enabled.to_json, admin_header
279+
end
280+
281+
it 'returns an error which states that both features cannot be enabled at the same time' do
282+
patch "/v3/apps/#{app_model.guid}/features/service-binding-k8s", request_body_enabled.to_json, admin_header
283+
284+
expect(last_response.status).to eq(422)
285+
expect(parsed_response['errors'][0]['detail']).to eq("'file-based-vcap-services' and 'service-binding-k8s' features cannot be enabled at the same time.")
286+
end
287+
end
209288
end
289+
end
210290

211-
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
291+
context 'file-based-vcap-services app feature' do
292+
context 'when feature is enabled' do
293+
let(:service_binding_k8s_enabled) { false }
294+
let(:file_based_vcap_services_enabled) { true }
295+
let(:api_call) { ->(user_headers) { patch "/v3/apps/#{app_model.guid}/features/file-based-vcap-services", request_body.to_json, user_headers } }
296+
let(:feature_response_object) do
297+
{
298+
'name' => 'file-based-vcap-services',
299+
'description' => 'Enable file-based VCAP service bindings for the app',
300+
'enabled' => false
301+
}
302+
end
212303

213-
context 'when organization is suspended' do
214304
let(:expected_codes_and_responses) do
215-
h = super()
216-
h['space_developer'] = { code: 403, errors: CF_ORG_SUSPENDED }
305+
h = Hash.new({ code: 403, errors: CF_NOT_AUTHORIZED }.freeze)
306+
%w[no_role org_auditor org_billing_manager].each { |r| h[r] = { code: 404 } }
307+
%w[admin space_developer].each { |r| h[r] = { code: 200, response_object: feature_response_object } }
217308
h
218309
end
219310

220-
before do
221-
org.update(status: VCAP::CloudController::Organization::SUSPENDED)
311+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
312+
313+
context 'when organization is suspended' do
314+
let(:expected_codes_and_responses) do
315+
h = super()
316+
h['space_developer'] = { code: 403, errors: CF_ORG_SUSPENDED }
317+
h
318+
end
319+
320+
before do
321+
org.update(status: VCAP::CloudController::Organization::SUSPENDED)
322+
end
323+
324+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
325+
end
326+
end
327+
328+
context 'when feature is disabled' do
329+
let(:service_binding_k8s_enabled) { false }
330+
let(:file_based_vcap_services_enabled) { false }
331+
let(:api_call) { ->(user_headers) { patch "/v3/apps/#{app_model.guid}/features/file-based-vcap-services", request_body_enabled.to_json, user_headers } }
332+
333+
let(:feature_response_object) do
334+
{
335+
'name' => 'file-based-vcap-services',
336+
'description' => 'Enable file-based VCAP service bindings for the app',
337+
'enabled' => true
338+
}
339+
end
340+
341+
let(:expected_codes_and_responses) do
342+
h = Hash.new({ code: 403, errors: CF_NOT_AUTHORIZED }.freeze)
343+
%w[no_role org_auditor org_billing_manager].each { |r| h[r] = { code: 404 } }
344+
%w[admin space_developer].each { |r| h[r] = { code: 200, response_object: feature_response_object } }
345+
h
222346
end
223347

224348
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
349+
350+
context 'when service-binding-k8s is enabled' do
351+
before do
352+
patch "/v3/apps/#{app_model.guid}/features/service-binding-k8s", request_body_enabled.to_json, admin_header
353+
end
354+
355+
it 'returns an error which states that both features cannot be enabled at the same time' do
356+
patch "/v3/apps/#{app_model.guid}/features/file-based-vcap-services", request_body_enabled.to_json, admin_header
357+
358+
expect(last_response.status).to eq(422)
359+
expect(parsed_response['errors'][0]['detail']).to eq("'file-based-vcap-services' and 'service-binding-k8s' features cannot be enabled at the same time.")
360+
end
361+
end
225362
end
226363
end
227364
end

spec/unit/actions/app_feature_update_spec.rb

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
module VCAP::CloudController
66
RSpec.describe AppFeatureUpdate do
77
subject(:app_feature_update) { AppFeatureUpdate }
8-
let(:app) { AppModel.make(enable_ssh: false, revisions_enabled: false, file_based_service_bindings_enabled: false) }
8+
let(:app) { AppModel.make(enable_ssh: false, revisions_enabled: false) }
99
let(:message) { AppFeatureUpdateMessage.new(enabled: true) }
1010

1111
describe '.update' do
@@ -32,6 +32,14 @@ module VCAP::CloudController
3232
end.to change { app.reload.service_binding_k8s_enabled }.to(true)
3333
end
3434
end
35+
36+
context 'when the feature name is file-based-vcap-services' do
37+
it 'updates the file_based_vcap_services_enabled column on the app' do
38+
expect do
39+
AppFeatureUpdate.update('file-based-vcap-services', app, message)
40+
end.to change { app.reload.file_based_vcap_services_enabled }.to(true)
41+
end
42+
end
3543
end
3644
end
3745
end

0 commit comments

Comments
 (0)