Skip to content

Commit b7db2eb

Browse files
committed
feat: Platform Engineer can configure CF feature flags via bosh deployment
- Added the code to override default feature flags upon initialization of new deployment. - TNZ-48301
1 parent 275e080 commit b7db2eb

File tree

7 files changed

+158
-4
lines changed

7 files changed

+158
-4
lines changed

app/models/runtime/feature_flag.rb

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,23 @@ def self.admin_read_only?
8686
VCAP::CloudController::SecurityContext.admin_read_only?
8787
end
8888

89+
def self.override_default_flags(feature_flag_overrides)
90+
raise "Invalid feature flag name(s): #{feature_flag_overrides}" unless feature_flag_overrides.keys.to_set <= DEFAULT_FLAGS.keys.to_set
91+
raise "Invalid feature flag value(s): #{feature_flag_overrides}" unless feature_flag_overrides.values.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) }
92+
93+
feature_flag_overrides.each do |flag_name, flag_value|
94+
feature_flag = FeatureFlag.find(name: flag_name.to_s)
95+
if feature_flag
96+
next if feature_flag.enabled == flag_value
97+
else
98+
feature_flag = FeatureFlag.new(name: flag_name.to_s)
99+
end
100+
101+
feature_flag.enabled = flag_value
102+
feature_flag.save
103+
end
104+
end
105+
89106
private_class_method :admin?
90107
end
91108
end
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module CCInitializers
2+
def self.feature_flag_overrides(cc_config)
3+
@logger ||= Steno.logger('cc.feature_flag_overrides')
4+
@logger.info("Initializing feature_flag_overrides: #{cc_config[:feature_flag_overrides]}")
5+
return unless cc_config[:feature_flag_overrides]
6+
7+
VCAP::CloudController::FeatureFlag.override_default_flags(cc_config[:feature_flag_overrides])
8+
end
9+
end

lib/cloud_controller/config_schemas/api_schema.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -426,7 +426,8 @@ class ApiSchema < VCAP::Config
426426
update_metric_tags_on_rename: bool,
427427
app_instance_stopping_state: bool,
428428

429-
optional(:enable_ipv6) => bool
429+
optional(:enable_ipv6) => bool,
430+
optional(:feature_flag_overrides) => Hash
430431
}
431432
end
432433
# rubocop:enable Metrics/BlockLength

lib/cloud_controller/config_schemas/clock_schema.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,9 @@ class ClockSchema < VCAP::Config
218218

219219
max_labels_per_resource: Integer,
220220
max_annotations_per_resource: Integer,
221-
custom_metric_tag_prefix_list: Array
221+
custom_metric_tag_prefix_list: Array,
222+
223+
optional(:feature_flag_overrides) => Hash
222224
}
223225
end
224226
# rubocop:enable Metrics/BlockLength

lib/cloud_controller/config_schemas/deployment_updater_schema.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,9 @@ class DeploymentUpdaterSchema < VCAP::Config
164164

165165
max_labels_per_resource: Integer,
166166
max_annotations_per_resource: Integer,
167-
custom_metric_tag_prefix_list: Array
167+
custom_metric_tag_prefix_list: Array,
168+
169+
optional(:feature_flag_overrides) => Hash
168170
}
169171
end
170172
# rubocop:enable Metrics/BlockLength

lib/cloud_controller/config_schemas/worker_schema.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -223,7 +223,9 @@ class WorkerSchema < VCAP::Config
223223
max_labels_per_resource: Integer,
224224
max_annotations_per_resource: Integer,
225225
custom_metric_tag_prefix_list: Array,
226-
default_app_lifecycle: enum('buildpack', 'cnb')
226+
default_app_lifecycle: enum('buildpack', 'cnb'),
227+
228+
optional(:feature_flag_overrides) => Hash
227229
}
228230
end
229231
# rubocop:enable Metrics/BlockLength

spec/unit/models/runtime/feature_flag_spec.rb

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,5 +225,126 @@ module VCAP::CloudController
225225
end
226226
end
227227
end
228+
229+
describe 'default flag override in config' do
230+
let(:key) { :diego_docker }
231+
let(:default_value) { FeatureFlag::DEFAULT_FLAGS[key] }
232+
233+
context 'when there was no previously set conflicting value' do
234+
let(:config_value) { !default_value }
235+
236+
before do
237+
FeatureFlag.override_default_flags({ key => config_value })
238+
end
239+
240+
after do
241+
FeatureFlag.find(name: key.to_s)&.destroy
242+
end
243+
244+
context 'and the value is not changed by admin' do
245+
it 'returns the config-set value' do
246+
expect(FeatureFlag.enabled?(key)).to be config_value
247+
end
248+
end
249+
250+
context 'and the value is changed by admin' do
251+
let(:admin_value) { !config_value }
252+
let(:admin_override) do
253+
flag = FeatureFlag.find(name: key.to_s)
254+
flag.enabled = admin_value
255+
flag.save
256+
end
257+
258+
before do
259+
admin_override
260+
end
261+
262+
after do
263+
admin_override.destroy
264+
end
265+
266+
it 'returns the admin-set value' do
267+
expect(FeatureFlag.enabled?(key)).to be admin_value
268+
end
269+
end
270+
end
271+
272+
context 'when there was previously set conflicting value' do
273+
let(:admin_value) { !default_value }
274+
275+
before do
276+
FeatureFlag.make(name: key.to_s, enabled: admin_value)
277+
end
278+
279+
after do
280+
FeatureFlag.find(name: key.to_s)&.destroy
281+
end
282+
283+
it 'overwrites the existing admin-set value' do
284+
expect(FeatureFlag.enabled?(key)).to be admin_value
285+
FeatureFlag.override_default_flags({ key => !admin_value })
286+
expect(FeatureFlag.enabled?(key)).to be !admin_value
287+
end
288+
end
289+
end
290+
291+
describe '.update_default_flags' do
292+
context 'with invalid flags' do
293+
it 'raises an error for the one and only invalid name' do
294+
expect do
295+
FeatureFlag.override_default_flags({ an_invalid_name: true })
296+
end.to raise_error(/Invalid feature flag name\(s\)/)
297+
end
298+
299+
it 'raises an error for a mix of valid and invalid names' do
300+
expect do
301+
FeatureFlag.override_default_flags({ diego_docker: true, an_invalid_name: true })
302+
end.to raise_error(/Invalid feature flag name\(s\)/)
303+
end
304+
305+
it 'raises an error for all invalid names' do
306+
expect do
307+
FeatureFlag.override_default_flags({ invalid_name1: true, invalid_name2: false })
308+
end.to raise_error(/Invalid feature flag name\(s\)/)
309+
end
310+
311+
it 'raises an error for invalid values' do
312+
expect do
313+
FeatureFlag.override_default_flags({ diego_docker: 'an invalid value', user_org_creation: false })
314+
end.to raise_error(/Invalid feature flag value\(s\)/)
315+
end
316+
end
317+
318+
context 'with valid flags' do
319+
let(:default_diego_docker_value) { FeatureFlag::DEFAULT_FLAGS[:diego_docker] }
320+
let(:default_user_org_creation_value) { FeatureFlag::DEFAULT_FLAGS[:user_org_creation] }
321+
322+
before do
323+
expect do
324+
FeatureFlag.override_default_flags({ diego_docker: !default_diego_docker_value, user_org_creation: !default_user_org_creation_value })
325+
end.not_to raise_error
326+
end
327+
328+
after do
329+
%i[diego_docker user_org_creation].each do |flag_key|
330+
FeatureFlag.find(name: flag_key.to_s)&.destroy
331+
end
332+
end
333+
334+
it 'updates values' do
335+
expect(FeatureFlag.enabled?(:diego_docker)).to be !default_diego_docker_value
336+
expect(FeatureFlag.enabled?(:user_org_creation)).to be !default_user_org_creation_value
337+
end
338+
end
339+
340+
context 'with empty flags' do
341+
it 'no effect' do
342+
FeatureFlag.override_default_flags({})
343+
FeatureFlag::DEFAULT_FLAGS.each do |key, value|
344+
expect(FeatureFlag.enabled?(key)).to eq value
345+
end
346+
end
347+
end
348+
end
228349
end
229350
end

0 commit comments

Comments
 (0)