Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds possibility to migrate feature_keys to resource_keys #68

Merged
merged 10 commits into from
Aug 3, 2020
1 change: 1 addition & 0 deletions lib/feature_flagger/manager.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class Manager
def self.detached_feature_keys
persisted_features = FeatureFlagger.control.feature_keys
mapped_feature_keys = FeatureFlagger.config.mapped_feature_keys

persisted_features - mapped_feature_keys
end

Expand Down
59 changes: 59 additions & 0 deletions lib/feature_flagger/storage/feature_keys_migration.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# frozen_string_literal: true

module FeatureFlagger
module Storage
class FeatureKeysMigration

def initialize(from_redis, to_control)
@from_redis = from_redis
@to_control = to_control
end

# call migrates features key from the old fashioned to the new
# format.
#
# It must replicate feature keys with changes:
#
# from "avenue:traffic_lights" => 42
# to "avenue:42" => traffic_lights
def call
@from_redis.scan_each(match: "*", count: FeatureFlagger::Storage::Redis::SCAN_EACH_BATCH_SIZE) do |redis_key|
# filter out resource_keys
next if redis_key.start_with?("#{FeatureFlagger::Storage::Redis::RESOURCE_PREFIX}:")
henrich-m marked this conversation as resolved.
Show resolved Hide resolved

migrate_key(redis_key)
end
end

private

def migrate_key(key)
return migrate_release_to_all(key) if feature_released_to_all?(key)

migrate_release(key)
end

def migrate_release_to_all(key)
features = @from_redis.smembers(key)

features.each do |feature_key|
@to_control.release_to_all(feature_key)
rescue KeyNotFoundError => _e
next
end
end

def feature_released_to_all?(key)
FeatureFlagger::Control::RELEASED_FEATURES == key
end

def migrate_release(key)
resource_ids = @from_redis.smembers(key)

@to_control.release(key, resource_ids)
rescue KeyNotFoundError => _e
return
andrehjr marked this conversation as resolved.
Show resolved Hide resolved
end
end
end
end
7 changes: 7 additions & 0 deletions lib/feature_flagger/storage/redis.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,13 @@ def feature_keys
feature_keys
end

def synchronize_feature_and_resource
FeatureFlagger::Storage::FeatureKeysMigration.new(
@redis,
FeatureFlagger.control,
).call
end

private

def resource_key(resource_name, resource_id)
Expand Down
6 changes: 6 additions & 0 deletions lib/tasks/feature_flagger.rake
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ namespace :feature_flagger do
end
end

desc "Synchronizes resource_keys with feature_keys, recommended to apps that installed feature flagger before v.1.2.0"
task :migrate_to_resource_keys => :environment do
storage = FeatureFlagger.config.storage
storage.synchronize_feature_and_resource
end

desc "Release feature to given identifiers, Usage: `$ bundle exec rake feature_flagger:release\[Account,email_marketing:whitelabel,1,2,3,4\]`"
task :release, [:entity_name, :feature_key] => :environment do |_, args|
entity = args.entity_name.constantize
Expand Down
73 changes: 73 additions & 0 deletions spec/feature_flagger/storage/feature_keys_migration_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# frozen_string_literal: true

require 'spec_helper'
require 'feature_flagger/storage/feature_keys_migration'

RSpec.describe FeatureFlagger::Storage::FeatureKeysMigration do
subject(:migrator) { described_class.new(redis, control) }

let(:redis) { FakeRedis::Redis.new }
let(:control) { FeatureFlagger::Control.new(FeatureFlagger::Storage::Redis.new(redis)) }
let(:global_key) { FeatureFlagger::Control::RELEASED_FEATURES }

before do
filepath = File.expand_path('../fixtures/rollout_example.yml', __dir__)
FeatureFlagger.config.yaml_filepath = filepath
end

describe '.call' do
context 'when there are keys in the old format' do
before do
redis.sadd('feature_flagger_dummy_class:email_marketing:behavior_score', 42)
redis.sadd('feature_flagger_dummy_class:email_marketing:whitelabel', 42)
redis.sadd('feature_flagger_dummy_class:email_marketing:whitelabel', 1)
redis.sadd(global_key, 'other_feature_flagger_dummy_class:feature_c:feature_c_1')
redis.sadd(global_key, 'other_feature_flagger_dummy_class:feature_c:feature_c_2')
redis.sadd(global_key, 'account')

migrator.call
end

it 'migrates feature keys to the new format' do
expect(control.released?('feature_flagger_dummy_class:email_marketing:behavior_score', 42)).to be_truthy
expect(control.released?('feature_flagger_dummy_class:email_marketing:whitelabel', 42)).to be_truthy
expect(control.released?('feature_flagger_dummy_class:email_marketing:whitelabel', 1)).to be_truthy
end

it 'migrates all released feature keys to the new format' do
expect(control.released_to_all?('other_feature_flagger_dummy_class:feature_c:feature_c_2')).to be_truthy
expect(control.released_to_all?('other_feature_flagger_dummy_class:feature_c:feature_c_1')).to be_truthy
end
end

context 'when there are keys in both formats' do
before do
redis.sadd('feature_flagger_dummy_class:email_marketing:behavior_score', 42)
redis.sadd('feature_flagger_dummy_class:email_marketing:whitelabel', 42)
redis.sadd('feature_flagger_dummy_class:email_marketing:whitelabel', 1)
redis.sadd(global_key, 'feature_flagger_dummy_class:email_marketing:whitelabel')

control.release('other_feature_flagger_dummy_class:feature_b', 42)
control.release_to_all('other_feature_flagger_dummy_class:feature_c:feature_c_1')

migrator.call
end

it 'migrates feature keys to the new format' do
expect(control.released?('feature_flagger_dummy_class:email_marketing:behavior_score', 42)).to be_truthy
expect(control.released?('feature_flagger_dummy_class:email_marketing:whitelabel', 42)).to be_truthy
expect(control.released?('feature_flagger_dummy_class:email_marketing:whitelabel', 1)).to be_truthy
expect(control.released?('other_feature_flagger_dummy_class:feature_b', 42)).to be_truthy
end

it 'does not migrate internal keys' do
expect(redis.keys.count).to eq(7)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

end

it 'migrates all released feature keys to the new format ' do
expect(control.released_to_all?('feature_flagger_dummy_class:email_marketing:whitelabel')).to be_truthy
expect(control.released_to_all?('other_feature_flagger_dummy_class:feature_c:feature_c_1')).to be_truthy
end
end
end
end