Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
20 changes: 20 additions & 0 deletions app/models/runtime/feature_flag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,26 @@ def self.admin_read_only?
VCAP::CloudController::SecurityContext.admin_read_only?
end

def self.override_default_flags(feature_flag_overrides)
invalid_keys = feature_flag_overrides.keys.to_set - FeatureFlag::DEFAULT_FLAGS.keys.to_set
raise "Invalid feature flag name(s): #{invalid_keys.to_a}" if invalid_keys.any?

invalid_values = feature_flag_overrides.reject { |_, v| v.is_a?(TrueClass) || v.is_a?(FalseClass) }
raise "Invalid feature flag value(s): #{invalid_values}" if invalid_values.any?

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
107 changes: 107 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,112 @@ module VCAP::CloudController
end
end
end

describe 'default flag override in config' do
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

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

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

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 '.override_default_flags' do
context 'with invalid flags' do
it 'raises an error for the one and only invalid name' do
feature_flag_overrides = { an_invalid_name: true }
expect do
FeatureFlag.override_default_flags(feature_flag_overrides)
end.to raise_error('Invalid feature flag name(s): [:an_invalid_name]')
end

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

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

it 'raises an error for invalid values' do
feature_flag_overrides = { diego_docker: 'an invalid value', user_org_creation: false }
expect do
FeatureFlag.override_default_flags(feature_flag_overrides)
end.to raise_error('Invalid feature flag value(s): {:diego_docker=>"an invalid value"}')
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

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