Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions app/models/runtime/feature_flag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,23 @@ def self.admin_read_only?
VCAP::CloudController::SecurityContext.admin_read_only?
end

def self.override_default_flags(feature_flag_overrides)
raise "Invalid feature flag name(s): #{feature_flag_overrides}" unless feature_flag_overrides.keys.to_set <= DEFAULT_FLAGS.keys.to_set
Comment thread
Gerg marked this conversation as resolved.
Outdated
raise "Invalid feature flag value(s): #{feature_flag_overrides}" unless feature_flag_overrides.values.all? { |item| item.is_a?(TrueClass) || item.is_a?(FalseClass) }
Comment thread
Gerg marked this conversation as resolved.
Outdated

feature_flag_overrides.each do |flag_name, flag_value|
feature_flag = FeatureFlag.find(name: flag_name.to_s)
if feature_flag
next if feature_flag.enabled == flag_value
else
feature_flag = FeatureFlag.new(name: flag_name.to_s)
end

feature_flag.enabled = flag_value
feature_flag.save
end
end

private_class_method :admin?
end
end
9 changes: 9 additions & 0 deletions config/initializers/feature_flag_overrides.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module CCInitializers
def self.feature_flag_overrides(cc_config)
@logger ||= Steno.logger('cc.feature_flag_overrides')
@logger.info("Initializing feature_flag_overrides: #{cc_config[:feature_flag_overrides]}")
return unless cc_config[:feature_flag_overrides]

VCAP::CloudController::FeatureFlag.override_default_flags(cc_config[:feature_flag_overrides])
end
end
3 changes: 2 additions & 1 deletion lib/cloud_controller/config_schemas/api_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,8 @@ class ApiSchema < VCAP::Config
update_metric_tags_on_rename: bool,
app_instance_stopping_state: bool,

optional(:enable_ipv6) => bool
optional(:enable_ipv6) => bool,
optional(:feature_flag_overrides) => Hash
}
end
# rubocop:enable Metrics/BlockLength
Expand Down
4 changes: 3 additions & 1 deletion lib/cloud_controller/config_schemas/clock_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,9 @@ class ClockSchema < VCAP::Config

max_labels_per_resource: Integer,
max_annotations_per_resource: Integer,
custom_metric_tag_prefix_list: Array
custom_metric_tag_prefix_list: Array,

optional(:feature_flag_overrides) => Hash
}
end
# rubocop:enable Metrics/BlockLength
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,9 @@ class DeploymentUpdaterSchema < VCAP::Config

max_labels_per_resource: Integer,
max_annotations_per_resource: Integer,
custom_metric_tag_prefix_list: Array
custom_metric_tag_prefix_list: Array,

optional(:feature_flag_overrides) => Hash
}
end
# rubocop:enable Metrics/BlockLength
Expand Down
4 changes: 3 additions & 1 deletion lib/cloud_controller/config_schemas/worker_schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,9 @@ class WorkerSchema < VCAP::Config
max_labels_per_resource: Integer,
max_annotations_per_resource: Integer,
custom_metric_tag_prefix_list: Array,
default_app_lifecycle: enum('buildpack', 'cnb')
default_app_lifecycle: enum('buildpack', 'cnb'),

optional(:feature_flag_overrides) => Hash
}
end
# rubocop:enable Metrics/BlockLength
Expand Down
121 changes: 121 additions & 0 deletions spec/unit/models/runtime/feature_flag_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -225,5 +225,126 @@ module VCAP::CloudController
end
end
end

describe 'default flag override in config' do
Comment thread
Gerg marked this conversation as resolved.
let(:key) { :diego_docker }
let(:default_value) { FeatureFlag::DEFAULT_FLAGS[key] }

context 'when there was no previously set conflicting value' do
let(:config_value) { !default_value }

before do
FeatureFlag.override_default_flags({ key => config_value })
end

after do
FeatureFlag.find(name: key.to_s)&.destroy
Comment thread
Gerg marked this conversation as resolved.
Outdated
end

context 'and the value is not changed by admin' do
it 'returns the config-set value' do
expect(FeatureFlag.enabled?(key)).to be config_value
end
end

context 'and the value is changed by admin' do
let(:admin_value) { !config_value }
let(:admin_override) do
flag = FeatureFlag.find(name: key.to_s)
flag.enabled = admin_value
flag.save
end

before do
admin_override
end

after do
admin_override.destroy
end

it 'returns the admin-set value' do
expect(FeatureFlag.enabled?(key)).to be admin_value
end
end
end

context 'when there was previously set conflicting value' do
let(:admin_value) { !default_value }

before do
FeatureFlag.make(name: key.to_s, enabled: admin_value)
end

after do
FeatureFlag.find(name: key.to_s)&.destroy
end

it 'overwrites the existing admin-set value' do
expect(FeatureFlag.enabled?(key)).to be admin_value
FeatureFlag.override_default_flags({ key => !admin_value })
expect(FeatureFlag.enabled?(key)).to be !admin_value
end
end
end

describe '.update_default_flags' do
Comment thread
Gerg marked this conversation as resolved.
Outdated
context 'with invalid flags' do
it 'raises an error for the one and only invalid name' do
expect do
FeatureFlag.override_default_flags({ an_invalid_name: true })
end.to raise_error(/Invalid feature flag name\(s\)/)
Comment thread
Gerg marked this conversation as resolved.
Outdated
end

it 'raises an error for a mix of valid and invalid names' do
expect do
FeatureFlag.override_default_flags({ diego_docker: true, an_invalid_name: true })
end.to raise_error(/Invalid feature flag name\(s\)/)
end

it 'raises an error for all invalid names' do
expect do
FeatureFlag.override_default_flags({ invalid_name1: true, invalid_name2: false })
end.to raise_error(/Invalid feature flag name\(s\)/)
end

it 'raises an error for invalid values' do
expect do
FeatureFlag.override_default_flags({ diego_docker: 'an invalid value', user_org_creation: false })
end.to raise_error(/Invalid feature flag value\(s\)/)
end
end

context 'with valid flags' do
let(:default_diego_docker_value) { FeatureFlag::DEFAULT_FLAGS[:diego_docker] }
let(:default_user_org_creation_value) { FeatureFlag::DEFAULT_FLAGS[:user_org_creation] }

before do
expect do
FeatureFlag.override_default_flags({ diego_docker: !default_diego_docker_value, user_org_creation: !default_user_org_creation_value })
end.not_to raise_error
end

after do
%i[diego_docker user_org_creation].each do |flag_key|
FeatureFlag.find(name: flag_key.to_s)&.destroy
end
end

it 'updates values' do
expect(FeatureFlag.enabled?(:diego_docker)).to be !default_diego_docker_value
expect(FeatureFlag.enabled?(:user_org_creation)).to be !default_user_org_creation_value
end
end

context 'with empty flags' do
it 'no effect' do
FeatureFlag.override_default_flags({})
FeatureFlag::DEFAULT_FLAGS.each do |key, value|
expect(FeatureFlag.enabled?(key)).to eq value
end
end
end
end
end
end