Skip to content

Commit

Permalink
Add manifest source option (#88)
Browse files Browse the repository at this point in the history
* configuration-adjustments

* add-option-storage-only

* add-option-with-yaml-file

* add-option-yaml-with-backup-to-storage

* add-necessary-adjustments-for-redis

* add-spec-option-storage-only

* add-spec-option-with-yaml-file

* add-spec-option-yaml-with-backup-to-storage

* add-require

* tests-adjustments

* version and readme update

* Update README.md

Co-authored-by: Nando Sousa <nandosousafr@users.noreply.github.com>

* Update README.md

Co-authored-by: Nando Sousa <nandosousafr@users.noreply.github.com>

* Update README.md

Co-authored-by: Nando Sousa <nandosousafr@users.noreply.github.com>

* Update README.md

Co-authored-by: Nando Sousa <nandosousafr@users.noreply.github.com>

* Update spec/feature_flagger/feature_spec.rb

Co-authored-by: Nando Sousa <nandosousafr@users.noreply.github.com>

* Update spec/feature_flagger/manager_spec.rb

Co-authored-by: Nando Sousa <nandosousafr@users.noreply.github.com>

* Update spec/feature_flagger/manager_spec.rb

Co-authored-by: Nando Sousa <nandosousafr@users.noreply.github.com>

* Update spec/feature_flagger/manager_spec.rb

Co-authored-by: Nando Sousa <nandosousafr@users.noreply.github.com>

* Update spec/feature_flagger/model_spec.rb

Co-authored-by: Nando Sousa <nandosousafr@users.noreply.github.com>

* Update spec/feature_flagger/model_spec.rb

Co-authored-by: Nando Sousa <nandosousafr@users.noreply.github.com>

* Update spec/feature_flagger/model_spec.rb

Co-authored-by: Nando Sousa <nandosousafr@users.noreply.github.com>

* Update spec/feature_flagger/storage/feature_keys_migration_spec.rb

Co-authored-by: Nando Sousa <nandosousafr@users.noreply.github.com>

* Update yaml_with_backup_to_storage_spec.rb

* Update with_yaml_file_spec.rb

* Update storage_only_spec.rb

* Update feature_spec.rb

Co-authored-by: Nando Sousa <nandosousafr@users.noreply.github.com>
  • Loading branch information
miclain and Nando Sousa authored Dec 16, 2021
1 parent 79ceb6c commit 520b620
Show file tree
Hide file tree
Showing 16 changed files with 177 additions and 29 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,35 @@ to ensure the data stored in Redis storage is right. Check [#67](https://github.

$ bundle exec rake feature_flagger:migrate_to_resource_keys

## Extra options

There are a few options to store/retrieve your rollout manifest (a.k.a rollout.yml):

If you have a rollout.yml file and want to use Redis to keep a backup, add the follow code to the configuration block:

```ruby
require 'feature_flagger/manifest_sources/yaml_with_backup_to_storage'
FeatureFlagger.configure do |config|
...
config.manifest_source = FeatureFlagger::ManifestSources::YAMLWithBackupToStorage.new(config.storage)
...
end
```

If you already have your manifest on Redis and prefer not to keep a copy in your application, add the following code to the configuration block:

```ruby
require 'feature_flagger/manifest_sources/storage_only'

FeatureFlagger.configure do |config|
...
config.manifest_source = FeatureFlagger::ManifestSources::StorageOnly.new(config.storage)
...
end
```

If you have the YAML file and don't need a backup, it is unnecessary to do any different configuration.

## Contributing

Bug reports and pull requests are welcome!
Expand Down
1 change: 1 addition & 0 deletions lib/feature_flagger.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
require 'feature_flagger/manager'
require 'feature_flagger/railtie'
require 'feature_flagger/notifier'
require 'feature_flagger/manifest_sources/with_yaml_file'

module FeatureFlagger
class << self
Expand Down
10 changes: 3 additions & 7 deletions lib/feature_flagger/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module FeatureFlagger
class Configuration
attr_accessor :storage, :cache_store, :yaml_filepath, :notifier_callback
attr_accessor :storage, :cache_store, :manifest_source, :notifier_callback

def initialize
@storage ||= Storage::Redis.default_client
@yaml_filepath ||= default_yaml_filepath
@manifest_source ||= FeatureFlagger::ManifestSources::WithYamlFile.new
@notifier_callback = nil
@cache_store = nil
end
Expand All @@ -17,7 +17,7 @@ def cache_store=(cache_store)
end

def info
@info ||= YAML.load_file(yaml_filepath) if yaml_filepath
@manifest_source.resolved_info
end

def mapped_feature_keys(resource_name = nil)
Expand All @@ -29,10 +29,6 @@ def mapped_feature_keys(resource_name = nil)

private

def default_yaml_filepath
"#{Rails.root}/config/rollout.yml" if defined?(Rails)
end

def make_keys_recursively(hash, keys = [], composed_key = [])
unless hash.values[0].is_a?(Hash)
keys.push(composed_key)
Expand Down
13 changes: 13 additions & 0 deletions lib/feature_flagger/manifest_sources/storage_only.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
module FeatureFlagger
module ManifestSources
class StorageOnly
def initialize(storage)
@storage = storage
end

def resolved_info
YAML.load(@storage.read_manifest_backup)
end
end
end
end
14 changes: 14 additions & 0 deletions lib/feature_flagger/manifest_sources/with_yaml_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module FeatureFlagger
module ManifestSources
class WithYamlFile
def initialize(yaml_path = nil)
@yaml_path = yaml_path
@yaml_path ||= "#{Rails.root}/config/rollout.yml" if defined?(Rails)
end

def resolved_info
@resolved_info ||= ::YAML.load_file(@yaml_path) if @yaml_path
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
module FeatureFlagger
module ManifestSources
class YAMLWithBackupToStorage
def initialize(storage, yaml_path = nil)
@yaml_path = yaml_path || ("#{Rails.root}/config/rollout.yml" if defined?(Rails))
@storage = storage
end

def resolved_info
@resolved_info ||= begin
yaml_data = YAML.load_file(@yaml_path) if @yaml_path
@storage.write_manifest_backup(YAML.dump(yaml_data))
yaml_data
end
end
end
end
end
15 changes: 13 additions & 2 deletions lib/feature_flagger/storage/redis.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
module FeatureFlagger
module Storage
class Redis
DEFAULT_NAMESPACE = :feature_flagger
RESOURCE_PREFIX = "_r".freeze
DEFAULT_NAMESPACE = :feature_flagger
RESOURCE_PREFIX = "_r".freeze
MANIFEST_PREFIX = "_m".freeze
MANIFEST_KEY = "manifest_file".freeze
SCAN_EACH_BATCH_SIZE = 1000.freeze

def initialize(redis)
Expand Down Expand Up @@ -75,6 +77,7 @@ def feature_keys
# Reject keys related to feature responsible for return
# released features for a given account.
next if key.start_with?("#{RESOURCE_PREFIX}:")
next if key.start_with?("#{MANIFEST_PREFIX}:")

feature_keys << key
end
Expand All @@ -89,6 +92,14 @@ def synchronize_feature_and_resource
).call
end

def read_manifest_backup
@redis.get("#{MANIFEST_PREFIX}:#{MANIFEST_KEY}")
end

def write_manifest_backup(yaml_as_string)
@redis.set("#{MANIFEST_PREFIX}:#{MANIFEST_KEY}", yaml_as_string)
end

private

def resource_key(resource_name, resource_id)
Expand Down
2 changes: 1 addition & 1 deletion lib/feature_flagger/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module FeatureFlagger
VERSION = "2.1.1"
VERSION = "2.2.0"
end
4 changes: 2 additions & 2 deletions spec/feature_flagger/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ module FeatureFlagger
let(:configuration) { described_class.new }

before do
filepath = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
configuration.yaml_filepath = filepath
yaml_path = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
allow(configuration).to receive(:info).and_return(YAML.load_file(yaml_path))
end

context 'without resource name' do
Expand Down
5 changes: 3 additions & 2 deletions spec/feature_flagger/feature_spec.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
require 'spec_helper'
require 'feature_flagger/manifest_sources/with_yaml_file'

module FeatureFlagger
RSpec.describe Feature do
subject { Feature.new(key, :feature_flagger_dummy_class) }

before do
filepath = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
FeatureFlagger.config.yaml_filepath = filepath
yaml_path = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
FeatureFlagger.config.manifest_source = FeatureFlagger::ManifestSources::WithYamlFile.new(yaml_path)
end

describe '#initialize' do
Expand Down
12 changes: 6 additions & 6 deletions spec/feature_flagger/manager_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ module FeatureFlagger
config.storage = storage
end

filepath = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
FeatureFlagger.config.yaml_filepath = filepath
yaml_path = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
FeatureFlagger.config.manifest_source = FeatureFlagger::ManifestSources::WithYamlFile.new(yaml_path)

# All good here
FeatureFlagger.control.release_to_all('feature_flagger_dummy_class:email_marketing:behavior_score')
Expand Down Expand Up @@ -45,8 +45,8 @@ module FeatureFlagger
end
FeatureFlagger.control.release(feature_key, 0)

filepath = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
FeatureFlagger.config.yaml_filepath = filepath
yaml_path = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
FeatureFlagger.config.manifest_source = FeatureFlagger::ManifestSources::WithYamlFile.new(yaml_path)
end

it 'cleanup key' do
Expand All @@ -59,8 +59,8 @@ module FeatureFlagger

context "mapped feature key" do
before do
filepath = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
FeatureFlagger.config.yaml_filepath = filepath
yaml_path = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
FeatureFlagger.config.manifest_source = FeatureFlagger::ManifestSources::WithYamlFile.new(yaml_path)
end

it 'do not cleanup key' do
Expand Down
13 changes: 6 additions & 7 deletions spec/feature_flagger/model_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ def id; 14 end
let(:control) { FeatureFlagger.control }

before do
filepath = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
FeatureFlagger.config.yaml_filepath = filepath
yaml_path = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
FeatureFlagger.config.manifest_source = FeatureFlagger::ManifestSources::WithYamlFile.new(yaml_path)
end

describe '#release' do
Expand Down Expand Up @@ -128,8 +128,8 @@ def id; 14 end
FeatureFlagger.control.release('feature_flagger_dummy_class:feature_a', 0)
FeatureFlagger.control.release('feature_flagger_dummy_class:feature_b', 0)

filepath = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
FeatureFlagger.config.yaml_filepath = filepath
yaml_path = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
FeatureFlagger.config.manifest_source = FeatureFlagger::ManifestSources::WithYamlFile.new(yaml_path)
end

it 'returns all detached feature keys' do
Expand All @@ -149,8 +149,8 @@ def id; 14 end
end
FeatureFlagger.control.release(feature_key, 0)

filepath = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
FeatureFlagger.config.yaml_filepath = filepath
yaml_path = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
FeatureFlagger.config.manifest_source = FeatureFlagger::ManifestSources::WithYamlFile.new(yaml_path)
end

it 'cleanup key' do
Expand Down Expand Up @@ -190,6 +190,5 @@ def uuid
)
end
end

end
end
4 changes: 2 additions & 2 deletions spec/feature_flagger/storage/feature_keys_migration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
let(:global_key) { FeatureFlagger::Control::RELEASED_FEATURES }

before do
filepath = File.expand_path('../fixtures/rollout_example.yml', __dir__)
FeatureFlagger.config.yaml_filepath = filepath
yaml_path = File.expand_path('../fixtures/rollout_example.yml', __dir__)
FeatureFlagger.config.manifest_source = FeatureFlagger::ManifestSources::WithYamlFile.new(yaml_path)
end

describe '.call' do
Expand Down
24 changes: 24 additions & 0 deletions spec/manifests/storage_only_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require 'spec_helper'
require 'feature_flagger/manifest_sources/storage_only'

module FeatureFlagger
module ManifestSources
RSpec.describe StorageOnly do
describe '.resolved_info' do
context 'without local yaml file' do
it 'returns a yaml file from storage' do

storage = spy('Storage')
yaml_path = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
yaml_data = YAML.load_file(yaml_path)
yaml_as_string = YAML.dump(yaml_data)
allow(storage).to receive(:read_manifest_backup).and_return(yaml_as_string)

manifest_source = StorageOnly.new(storage)
expect(manifest_source.resolved_info).to eq(yaml_data)
end
end
end
end
end
end
18 changes: 18 additions & 0 deletions spec/manifests/with_yaml_file_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
require 'spec_helper'
require 'feature_flagger/manifest_sources/with_yaml_file'

module FeatureFlagger
module ManifestSources
RSpec.describe WithYamlFile do
describe '.resolved_info' do
context 'whit local yaml file' do
it 'returns a yaml file from yaml_path' do
yaml_path = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
manifest_source = WithYamlFile.new(yaml_path)
expect(manifest_source.resolved_info).to eq(YAML.load_file(yaml_path))
end
end
end
end
end
end
24 changes: 24 additions & 0 deletions spec/manifests/yaml_with_backup_to_storage_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require 'spec_helper'
require 'feature_flagger/manifest_sources/yaml_with_backup_to_storage'

module FeatureFlagger
module ManifestSources
RSpec.describe YAMLWithBackupToStorage do
describe '.resolved_info' do
context 'with local yaml file to storage' do
it 'returns a yaml file from yaml_path with backup to storage' do
storage = spy('Storage')
yaml_path = File.expand_path('../../fixtures/rollout_example.yml', __FILE__)
yaml_data = YAML.load_file(yaml_path)
yaml_as_string = YAML.dump(yaml_data)

manifest_source = YAMLWithBackupToStorage.new(storage, yaml_path)
expect(manifest_source.resolved_info).to eq(yaml_data)

expect(storage).to have_received(:write_manifest_backup).with(yaml_as_string)
end
end
end
end
end
end

0 comments on commit 520b620

Please sign in to comment.