feature_flagger 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b54bc71d733eb04b2e52156763557a45f94d9722918b12b09e89a07553b88ee4
4
- data.tar.gz: 5984d522ae2d19f324de342db5810cd9bf351c43a9e60e159f8442209f38575f
3
+ metadata.gz: de6dae8c2c1631e95a47c0a6bb99f5e42c3ca2716f7ac4feb045fbd72a279133
4
+ data.tar.gz: 8619614ec4718f56ca20e1617d398ddb69bd1a81f0412568ed95c2215f7e7c2c
5
5
  SHA512:
6
- metadata.gz: ece7587ed9528ca583e61645bc3e5e9fcb710f37308ebbc8bb278390966e8edec6e8dc0a282fbb5864893623da631a1d4f3bffc0876dd484d3523e8008c90c11
7
- data.tar.gz: 9ccdf791f1c7b6c5f10674753ecfbbfb81b4fa69546057e8061b8c0c269c347999503cb4fc868a215073e70cd6b8cb1382c39bcccfb8c11a0fcd2a346601f82d
6
+ metadata.gz: e930f492fd481b66cd8924b603a4e067872ab2b2d864de8402bfb28e24504d81c5b3149596f0c08c255eaf84b74ca98a82657d3ac2f1ef54c9c538facfcf3428
7
+ data.tar.gz: 851c0d1b9eab08442232ee9c3681b3e90b559ed0fc01e4e631c7e3ec26915731682b393ceb1d5d447d1565c20896fbd138fcd18c7678aabfd2047a5158867250
data/README.md CHANGED
@@ -111,6 +111,13 @@ To clean it up, execute or schedule the rake:
111
111
 
112
112
  $ bundle exec rake feature_flagger:cleanup_removed_rollouts
113
113
 
114
+ ## Upgrading
115
+
116
+ When upgrading from `1.1.x` to `1.2.x` the following command must be executed
117
+ to ensure the data stored in Redis storage is right. Check [#67](https://github.com/ResultadosDigitais/feature_flagger/pull/67) and [#68](https://github.com/ResultadosDigitais/feature_flagger/pull/68) for more info.
118
+
119
+ $ bundle exec rake feature_flagger:migrate_to_resource_keys
120
+
114
121
  ## Contributing
115
122
 
116
123
  Bug reports and pull requests are welcome!
@@ -13,7 +13,15 @@ module FeatureFlagger
13
13
  end
14
14
 
15
15
  def release(feature_key, resource_id)
16
- @storage.add(feature_key, resource_id)
16
+ resource_name = Storage::Keys.extract_resource_name_from_feature_key(
17
+ feature_key
18
+ )
19
+
20
+ @storage.add(feature_key, resource_name, resource_id)
21
+ end
22
+
23
+ def releases(resource_name, resource_id)
24
+ @storage.fetch_releases(resource_name, resource_id, RELEASED_FEATURES)
17
25
  end
18
26
 
19
27
  def release_to_all(feature_key)
@@ -21,7 +29,11 @@ module FeatureFlagger
21
29
  end
22
30
 
23
31
  def unrelease(feature_key, resource_id)
24
- @storage.remove(feature_key, resource_id)
32
+ resource_name = Storage::Keys.extract_resource_name_from_feature_key(
33
+ feature_key
34
+ )
35
+
36
+ @storage.remove(feature_key, resource_name, resource_id)
25
37
  end
26
38
 
27
39
  def unrelease_to_all(feature_key)
@@ -40,8 +52,14 @@ module FeatureFlagger
40
52
  @storage.has_value?(RELEASED_FEATURES, feature_key)
41
53
  end
42
54
 
55
+ # DEPRECATED: this method will be removed from public api on v2.0 version.
56
+ # use instead the feature_keys method.
43
57
  def search_keys(query)
44
58
  @storage.search_keys(query)
45
59
  end
60
+
61
+ def feature_keys
62
+ @storage.feature_keys
63
+ end
46
64
  end
47
65
  end
@@ -0,0 +1,18 @@
1
+ begin
2
+ require 'active_support/core_ext/string/inflections'
3
+ rescue LoadError
4
+ unless ''.respond_to?(:constantize)
5
+ class String
6
+ def constantize
7
+ names = split('::')
8
+ names.shift if names.empty? || names.first.empty?
9
+
10
+ constant = Object
11
+ names.each do |name|
12
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
13
+ end
14
+ constant
15
+ end
16
+ end
17
+ end
18
+ end
@@ -2,8 +2,9 @@ module FeatureFlagger
2
2
  class Manager
3
3
 
4
4
  def self.detached_feature_keys
5
- persisted_features = FeatureFlagger.control.search_keys("*").to_a
5
+ persisted_features = FeatureFlagger.control.feature_keys
6
6
  mapped_feature_keys = FeatureFlagger.config.mapped_feature_keys
7
+
7
8
  persisted_features - mapped_feature_keys
8
9
  end
9
10
 
@@ -26,6 +26,11 @@ module FeatureFlagger
26
26
  FeatureFlagger.control.unrelease(feature.key, id)
27
27
  end
28
28
 
29
+ def releases
30
+ resource_name = self.class.feature_flagger_model_settings.entity_name
31
+ FeatureFlagger.control.releases(resource_name, id)
32
+ end
33
+
29
34
  private
30
35
 
31
36
  def feature_flagger_identifier
@@ -48,6 +53,11 @@ module FeatureFlagger
48
53
  FeatureFlagger.control.release(feature.key, resource_id)
49
54
  end
50
55
 
56
+ def unrelease_id(resource_id, *feature_key)
57
+ feature = Feature.new(feature_key, feature_flagger_model_settings.entity_name)
58
+ FeatureFlagger.control.unrelease(feature.key, resource_id)
59
+ end
60
+
51
61
  def all_released_ids_for(*feature_key)
52
62
  feature_key.flatten!
53
63
  feature = Feature.new(feature_key, feature_flagger_model_settings.entity_name)
@@ -2,8 +2,8 @@ if defined?(Rails)
2
2
  module FeatureFlagger
3
3
  class Railtie < Rails::Railtie
4
4
  rake_tasks do
5
- load 'tasks/cleanup.rake'
5
+ load 'tasks/feature_flagger.rake'
6
6
  end
7
7
  end
8
8
  end
9
- end
9
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ module FeatureFlagger
4
+ module Storage
5
+ class FeatureKeysMigration
6
+
7
+ def initialize(from_redis, to_control)
8
+ @from_redis = from_redis
9
+ @to_control = to_control
10
+ end
11
+
12
+ # call migrates features key from the old fashioned to the new
13
+ # format.
14
+ #
15
+ # It must replicate feature keys with changes:
16
+ #
17
+ # from "avenue:traffic_lights" => 42
18
+ # to "avenue:42" => traffic_lights
19
+ def call
20
+ @from_redis.scan_each(match: "*", count: FeatureFlagger::Storage::Redis::SCAN_EACH_BATCH_SIZE) do |redis_key|
21
+ # filter out resource_keys
22
+ next if redis_key.start_with?("#{FeatureFlagger::Storage::Redis::RESOURCE_PREFIX}:")
23
+
24
+ migrate_key(redis_key)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def migrate_key(key)
31
+ return migrate_release_to_all(key) if feature_released_to_all?(key)
32
+
33
+ migrate_release(key)
34
+ end
35
+
36
+ def migrate_release_to_all(key)
37
+ features = @from_redis.smembers(key)
38
+
39
+ features.each do |feature_key|
40
+ @to_control.release_to_all(feature_key)
41
+ end
42
+ end
43
+
44
+ def feature_released_to_all?(key)
45
+ FeatureFlagger::Control::RELEASED_FEATURES == key
46
+ end
47
+
48
+ def migrate_release(key)
49
+ resource_ids = @from_redis.smembers(key)
50
+
51
+ @to_control.release(key, resource_ids)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,21 @@
1
+ module FeatureFlagger
2
+ module Storage
3
+ module Keys
4
+ MINIMUM_VALID_FEATURE_PATH = 2.freeze
5
+
6
+ def self.resource_key(prefix, resource_name, resource_id)
7
+ "#{prefix}:#{resource_name}:#{resource_id}"
8
+ end
9
+
10
+ def self.extract_resource_name_from_feature_key(feature_key)
11
+ feature_paths = feature_key.split(':')
12
+
13
+ raise InvalidResourceNameError if feature_paths.size < MINIMUM_VALID_FEATURE_PATH
14
+
15
+ feature_paths.first
16
+ end
17
+
18
+ class InvalidResourceNameError < StandardError; end
19
+ end
20
+ end
21
+ end
@@ -1,11 +1,13 @@
1
1
  require 'redis'
2
2
  require 'redis-namespace'
3
+ require_relative './keys'
3
4
 
4
5
  module FeatureFlagger
5
6
  module Storage
6
7
  class Redis
7
-
8
8
  DEFAULT_NAMESPACE = :feature_flagger
9
+ RESOURCE_PREFIX = "_r".freeze
10
+ SCAN_EACH_BATCH_SIZE = 1000.freeze
9
11
 
10
12
  def initialize(redis)
11
13
  @redis = redis
@@ -17,39 +19,102 @@ module FeatureFlagger
17
19
  new(ns)
18
20
  end
19
21
 
22
+ def fetch_releases(resource_name, resource_id, global_key)
23
+ resource_key = resource_key(resource_name, resource_id)
24
+ @redis.sunion(resource_key, global_key)
25
+ end
26
+
20
27
  def has_value?(key, value)
21
28
  @redis.sismember(key, value)
22
29
  end
23
30
 
24
- def add(key, value)
25
- @redis.sadd(key, value)
26
- end
31
+ def add(feature_key, resource_name, resource_id)
32
+ resource_key = resource_key(resource_name, resource_id)
27
33
 
28
- def remove(key, value)
29
- @redis.srem(key, value)
34
+ @redis.multi do |redis|
35
+ redis.sadd(feature_key, resource_id)
36
+ redis.sadd(resource_key, feature_key)
37
+ end
30
38
  end
31
39
 
32
- def remove_all(global_key, key)
40
+ def remove(feature_key, resource_name, resource_id)
41
+ resource_key = resource_key(resource_name, resource_id)
42
+
33
43
  @redis.multi do |redis|
34
- redis.srem(global_key, key)
35
- redis.del(key)
44
+ redis.srem(feature_key, resource_id)
45
+ redis.srem(resource_key, feature_key)
36
46
  end
37
47
  end
38
48
 
49
+ def remove_all(global_key, feature_key)
50
+ @redis.srem(global_key, feature_key)
51
+ remove_feature_key_from_resources(feature_key)
52
+ end
53
+
39
54
  def add_all(global_key, key)
40
- @redis.multi do |redis|
41
- redis.sadd(global_key, key)
42
- redis.del(key)
43
- end
55
+ @redis.sadd(global_key, key)
56
+ remove_feature_key_from_resources(key)
44
57
  end
45
58
 
46
59
  def all_values(key)
47
60
  @redis.smembers(key)
48
61
  end
49
62
 
63
+ # DEPRECATED: this method will be removed from public api on v2.0 version.
64
+ # use instead the feature_keys method.
50
65
  def search_keys(query)
51
66
  @redis.scan_each(match: query)
52
67
  end
68
+
69
+ def feature_keys
70
+ feature_keys = []
71
+
72
+ @redis.scan_each(match: "*") do |key|
73
+ # Reject keys related to feature responsible for return
74
+ # released features for a given account.
75
+ next if key.start_with?("#{RESOURCE_PREFIX}:")
76
+
77
+ feature_keys << key
78
+ end
79
+
80
+ feature_keys
81
+ end
82
+
83
+ def synchronize_feature_and_resource
84
+ FeatureFlagger::Storage::FeatureKeysMigration.new(
85
+ @redis,
86
+ FeatureFlagger.control,
87
+ ).call
88
+ end
89
+
90
+ private
91
+
92
+ def resource_key(resource_name, resource_id)
93
+ FeatureFlagger::Storage::Keys.resource_key(
94
+ RESOURCE_PREFIX,
95
+ resource_name,
96
+ resource_id,
97
+ )
98
+ end
99
+
100
+ def remove_feature_key_from_resources(feature_key)
101
+ cursor = 0
102
+ resource_name = feature_key.split(":").first
103
+
104
+ loop do
105
+ cursor, resource_ids = @redis.sscan(feature_key, cursor, count: SCAN_EACH_BATCH_SIZE)
106
+
107
+ @redis.multi do |redis|
108
+ resource_ids.each do |resource_id|
109
+ key = resource_key(resource_name, resource_id)
110
+ redis.srem(key, feature_key)
111
+ redis.srem(feature_key, resource_id)
112
+ end
113
+ end
114
+
115
+ break if cursor == "0"
116
+ end
117
+ end
53
118
  end
54
119
  end
55
120
  end
@@ -1,3 +1,3 @@
1
1
  module FeatureFlagger
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
@@ -0,0 +1,41 @@
1
+ namespace :feature_flagger do
2
+ desc "cleaning up keys from storage that are no longer in the rollout.yml file"
3
+ task :cleanup_removed_rollouts => :environment do
4
+ keys = FeatureFlagger::Manager.detached_feature_keys
5
+ puts "Found keys to remove: #{keys}"
6
+ keys.each do |key|
7
+ FeatureFlagger::Manager.cleanup_detached key
8
+ end
9
+ end
10
+
11
+ desc "Synchronizes resource_keys with feature_keys, recommended to apps that installed feature flagger before v.1.2.0"
12
+ task :migrate_to_resource_keys => :environment do
13
+ storage = FeatureFlagger.config.storage
14
+ storage.synchronize_feature_and_resource
15
+ end
16
+
17
+ desc "Release feature to given identifiers, Usage: `$ bundle exec rake feature_flagger:release\[Account,email_marketing:whitelabel,1,2,3,4\]`"
18
+ task :release, [:entity_name, :feature_key] => :environment do |_, args|
19
+ entity = args.entity_name.constantize
20
+ entity_ids = args.extras
21
+ entity.release_id(entity_ids, *args.feature_key.split(':'))
22
+ end
23
+
24
+ desc "Unrelease feature to given identifiers, Usage: `$ bundle exec rake feature_flagger:unrelease\[Account,email_marketing:whitelabel,1,2,3,4\]`"
25
+ task :unrelease, [:entity_name, :feature_key] => :environment do |_, args|
26
+ entity, entity_ids = args.entity_name.constantize, args.extras
27
+ entity.unrelease_id(entity_ids, *args.feature_key.split(':'))
28
+ end
29
+
30
+ desc "Release one feature to all entity ids, Usage: `$ bundle exec rake feature_flagger:release_to_all\[Account,email_marketing:whitelabel\]`"
31
+ task :release_to_all, [:entity_name, :feature_key] => :environment do |_, args|
32
+ entity = args.entity_name.constantize
33
+ entity.release_to_all(*args.feature_key.split(':'))
34
+ end
35
+
36
+ desc "Unrelease one feature to all entity ids, Usage: `$ bundle exec rake feature_flagger:unrelease_to_all\[Account,email_marketing:whitelabel\]`"
37
+ task :unrelease_to_all, [:entity_name, :feature_key] => :environment do |_, args|
38
+ entity = args.entity_name.constantize
39
+ entity.unrelease_to_all(*args.feature_key.split(':'))
40
+ end
41
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: feature_flagger
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nando Sousa
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-10-14 00:00:00.000000000 Z
12
+ date: 2020-08-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
@@ -43,30 +43,30 @@ dependencies:
43
43
  name: bundler
44
44
  requirement: !ruby/object:Gem::Requirement
45
45
  requirements:
46
- - - "~>"
46
+ - - ">="
47
47
  - !ruby/object:Gem::Version
48
- version: '2.0'
48
+ version: '0'
49
49
  type: :development
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
- - - "~>"
53
+ - - ">="
54
54
  - !ruby/object:Gem::Version
55
- version: '2.0'
55
+ version: '0'
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: rake
58
58
  requirement: !ruby/object:Gem::Requirement
59
59
  requirements:
60
60
  - - "~>"
61
61
  - !ruby/object:Gem::Version
62
- version: '10.0'
62
+ version: '13.0'
63
63
  type: :development
64
64
  prerelease: false
65
65
  version_requirements: !ruby/object:Gem::Requirement
66
66
  requirements:
67
67
  - - "~>"
68
68
  - !ruby/object:Gem::Version
69
- version: '10.0'
69
+ version: '13.0'
70
70
  - !ruby/object:Gem::Dependency
71
71
  name: rspec
72
72
  requirement: !ruby/object:Gem::Requirement
@@ -81,20 +81,34 @@ dependencies:
81
81
  - - "~>"
82
82
  - !ruby/object:Gem::Version
83
83
  version: '3.0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: simplecov
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - '='
89
+ - !ruby/object:Gem::Version
90
+ version: '0.17'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - '='
96
+ - !ruby/object:Gem::Version
97
+ version: '0.17'
84
98
  - !ruby/object:Gem::Dependency
85
99
  name: fakeredis
86
100
  requirement: !ruby/object:Gem::Requirement
87
101
  requirements:
88
102
  - - '='
89
103
  - !ruby/object:Gem::Version
90
- version: 0.7.0
104
+ version: 0.8.0
91
105
  type: :development
92
106
  prerelease: false
93
107
  version_requirements: !ruby/object:Gem::Requirement
94
108
  requirements:
95
109
  - - '='
96
110
  - !ruby/object:Gem::Version
97
- version: 0.7.0
111
+ version: 0.8.0
98
112
  description: Management tool to make it easier rollouting features to customers.
99
113
  email:
100
114
  - nandosousafr@gmail.com
@@ -108,14 +122,17 @@ files:
108
122
  - lib/feature_flagger.rb
109
123
  - lib/feature_flagger/configuration.rb
110
124
  - lib/feature_flagger/control.rb
125
+ - lib/feature_flagger/core_ext.rb
111
126
  - lib/feature_flagger/feature.rb
112
127
  - lib/feature_flagger/manager.rb
113
128
  - lib/feature_flagger/model.rb
114
129
  - lib/feature_flagger/model_settings.rb
115
130
  - lib/feature_flagger/railtie.rb
131
+ - lib/feature_flagger/storage/feature_keys_migration.rb
132
+ - lib/feature_flagger/storage/keys.rb
116
133
  - lib/feature_flagger/storage/redis.rb
117
134
  - lib/feature_flagger/version.rb
118
- - lib/tasks/cleanup.rake
135
+ - lib/tasks/feature_flagger.rake
119
136
  homepage: http://github.com/ResultadosDigitais/feature_flagger
120
137
  licenses:
121
138
  - MIT
@@ -135,8 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
135
152
  - !ruby/object:Gem::Version
136
153
  version: 2.0.0
137
154
  requirements: []
138
- rubyforge_project:
139
- rubygems_version: 2.7.9
155
+ rubygems_version: 3.1.4
140
156
  signing_key:
141
157
  specification_version: 4
142
158
  summary: Partial release your features.
@@ -1,10 +0,0 @@
1
- namespace :feature_flagger do
2
- desc "cleaning up keys from storage that are no longer in the rollout.yml file"
3
- task :cleanup_removed_rollouts => :environment do
4
- keys = FeatureFlagger::Manager.detached_feature_keys
5
- puts "Found keys to remove: #{keys}"
6
- keys.each do |key|
7
- FeatureFlagger::Manager.cleanup_detached key
8
- end
9
- end
10
- end