Skip to content

Commit c00ebc1

Browse files
authored
feat: Platform Engineer can configure CF feature flags via bosh deplo… (#4523)
* 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 * fix: A method name being tested * Improvements suggested in the PR review - Changed the error message for invalid flag names and values to be more specific. - Changed to do stronger test assertions for error message validations. * fix: Rubocop offenses * Remove unnecessary test clean-up code.
1 parent b487f35 commit c00ebc1

File tree

7 files changed

+147
-4
lines changed

7 files changed

+147
-4
lines changed

app/models/runtime/feature_flag.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,26 @@ 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+
invalid_keys = feature_flag_overrides.keys.to_set - FeatureFlag::DEFAULT_FLAGS.keys.to_set
91+
raise "Invalid feature flag name(s): #{invalid_keys.to_a}" if invalid_keys.any?
92+
93+
invalid_values = feature_flag_overrides.reject { |_, v| v.is_a?(TrueClass) || v.is_a?(FalseClass) }
94+
raise "Invalid feature flag value(s): #{invalid_values}" if invalid_values.any?
95+
96+
feature_flag_overrides.each do |flag_name, flag_value|
97+
feature_flag = FeatureFlag.find(name: flag_name.to_s)
98+
if feature_flag
99+
next if feature_flag.enabled == flag_value
100+
else
101+
feature_flag = FeatureFlag.new(name: flag_name.to_s)
102+
end
103+
104+
feature_flag.enabled = flag_value
105+
feature_flag.save
106+
end
107+
end
108+
89109
private_class_method :admin?
90110
end
91111
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
@@ -427,7 +427,8 @@ class ApiSchema < VCAP::Config
427427
update_metric_tags_on_rename: bool,
428428
app_instance_stopping_state: bool,
429429

430-
optional(:enable_ipv6) => bool
430+
optional(:enable_ipv6) => bool,
431+
optional(:feature_flag_overrides) => Hash
431432
}
432433
end
433434
# 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
@@ -219,7 +219,9 @@ class ClockSchema < VCAP::Config
219219

220220
max_labels_per_resource: Integer,
221221
max_annotations_per_resource: Integer,
222-
custom_metric_tag_prefix_list: Array
222+
custom_metric_tag_prefix_list: Array,
223+
224+
optional(:feature_flag_overrides) => Hash
223225
}
224226
end
225227
# 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
@@ -165,7 +165,9 @@ class DeploymentUpdaterSchema < VCAP::Config
165165

166166
max_labels_per_resource: Integer,
167167
max_annotations_per_resource: Integer,
168-
custom_metric_tag_prefix_list: Array
168+
custom_metric_tag_prefix_list: Array,
169+
170+
optional(:feature_flag_overrides) => Hash
169171
}
170172
end
171173
# 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
@@ -224,7 +224,9 @@ class WorkerSchema < VCAP::Config
224224
max_labels_per_resource: Integer,
225225
max_annotations_per_resource: Integer,
226226
custom_metric_tag_prefix_list: Array,
227-
default_app_lifecycle: enum('buildpack', 'cnb')
227+
default_app_lifecycle: enum('buildpack', 'cnb'),
228+
229+
optional(:feature_flag_overrides) => Hash
228230
}
229231
end
230232
# rubocop:enable Metrics/BlockLength

spec/unit/models/runtime/feature_flag_spec.rb

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,5 +225,112 @@ 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+
context 'and the value is not changed by admin' do
241+
it 'returns the config-set value' do
242+
expect(FeatureFlag.enabled?(key)).to be config_value
243+
end
244+
end
245+
246+
context 'and the value is changed by admin' do
247+
let(:admin_value) { !config_value }
248+
let(:admin_override) do
249+
flag = FeatureFlag.find(name: key.to_s)
250+
flag.enabled = admin_value
251+
flag.save
252+
end
253+
254+
before do
255+
admin_override
256+
end
257+
258+
it 'returns the admin-set value' do
259+
expect(FeatureFlag.enabled?(key)).to be admin_value
260+
end
261+
end
262+
end
263+
264+
context 'when there was previously set conflicting value' do
265+
let(:admin_value) { !default_value }
266+
267+
before do
268+
FeatureFlag.make(name: key.to_s, enabled: admin_value)
269+
end
270+
271+
it 'overwrites the existing admin-set value' do
272+
expect(FeatureFlag.enabled?(key)).to be admin_value
273+
FeatureFlag.override_default_flags({ key => !admin_value })
274+
expect(FeatureFlag.enabled?(key)).to be !admin_value
275+
end
276+
end
277+
end
278+
279+
describe '.override_default_flags' do
280+
context 'with invalid flags' do
281+
it 'raises an error for the one and only invalid name' do
282+
feature_flag_overrides = { an_invalid_name: true }
283+
expect do
284+
FeatureFlag.override_default_flags(feature_flag_overrides)
285+
end.to raise_error('Invalid feature flag name(s): [:an_invalid_name]')
286+
end
287+
288+
it 'raises an error for a mix of valid and invalid names' do
289+
feature_flag_overrides = { diego_docker: true, an_invalid_name: true }
290+
expect do
291+
FeatureFlag.override_default_flags(feature_flag_overrides)
292+
end.to raise_error('Invalid feature flag name(s): [:an_invalid_name]')
293+
end
294+
295+
it 'raises an error for all invalid names' do
296+
feature_flag_overrides = { invalid_name1: true, invalid_name2: false }
297+
expect do
298+
FeatureFlag.override_default_flags(feature_flag_overrides)
299+
end.to raise_error('Invalid feature flag name(s): [:invalid_name1, :invalid_name2]')
300+
end
301+
302+
it 'raises an error for invalid values' do
303+
feature_flag_overrides = { diego_docker: 'an invalid value', user_org_creation: false }
304+
expect do
305+
FeatureFlag.override_default_flags(feature_flag_overrides)
306+
end.to raise_error('Invalid feature flag value(s): {:diego_docker=>"an invalid value"}')
307+
end
308+
end
309+
310+
context 'with valid flags' do
311+
let(:default_diego_docker_value) { FeatureFlag::DEFAULT_FLAGS[:diego_docker] }
312+
let(:default_user_org_creation_value) { FeatureFlag::DEFAULT_FLAGS[:user_org_creation] }
313+
314+
before do
315+
expect do
316+
FeatureFlag.override_default_flags({ diego_docker: !default_diego_docker_value, user_org_creation: !default_user_org_creation_value })
317+
end.not_to raise_error
318+
end
319+
320+
it 'updates values' do
321+
expect(FeatureFlag.enabled?(:diego_docker)).to be !default_diego_docker_value
322+
expect(FeatureFlag.enabled?(:user_org_creation)).to be !default_user_org_creation_value
323+
end
324+
end
325+
326+
context 'with empty flags' do
327+
it 'no effect' do
328+
FeatureFlag.override_default_flags({})
329+
FeatureFlag::DEFAULT_FLAGS.each do |key, value|
330+
expect(FeatureFlag.enabled?(key)).to eq value
331+
end
332+
end
333+
end
334+
end
228335
end
229336
end

0 commit comments

Comments
 (0)