flipper 0.28.3 → 1.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/FUNDING.yml +1 -0
- data/.github/workflows/ci.yml +56 -10
- data/.github/workflows/examples.yml +54 -11
- data/.github/workflows/release.yml +54 -0
- data/.superset/config.json +4 -0
- data/CLAUDE.md +93 -0
- data/Changelog.md +1 -559
- data/Gemfile +17 -8
- data/README.md +34 -29
- data/Rakefile +2 -2
- data/benchmark/typecast_ips.rb +8 -0
- data/docs/images/banner.jpg +0 -0
- data/docs/images/flipper_cloud.png +0 -0
- data/examples/cloud/app.ru +12 -0
- data/examples/cloud/backoff_policy.rb +13 -0
- data/examples/cloud/basic.rb +22 -0
- data/examples/cloud/cloud_setup.rb +20 -0
- data/examples/cloud/forked.rb +36 -0
- data/examples/cloud/import.rb +17 -0
- data/examples/cloud/poll_interval/README.md +111 -0
- data/examples/cloud/poll_interval/client.rb +108 -0
- data/examples/cloud/poll_interval/server.rb +98 -0
- data/examples/cloud/threaded.rb +33 -0
- data/examples/dsl.rb +0 -14
- data/examples/expressions.rb +237 -0
- data/examples/strict.rb +18 -0
- data/exe/flipper +5 -0
- data/flipper-cloud.gemspec +19 -0
- data/flipper.gemspec +8 -4
- data/lib/flipper/actor.rb +6 -3
- data/lib/flipper/adapter.rb +27 -1
- data/lib/flipper/adapter_builder.rb +44 -0
- data/lib/flipper/adapters/actor_limit.rb +54 -0
- data/lib/flipper/adapters/cache_base.rb +161 -0
- data/lib/flipper/adapters/dual_write.rb +7 -5
- data/lib/flipper/adapters/failover.rb +9 -7
- data/lib/flipper/adapters/failsafe.rb +2 -6
- data/lib/flipper/adapters/http/client.rb +53 -14
- data/lib/flipper/adapters/http/error.rb +2 -2
- data/lib/flipper/adapters/http.rb +56 -16
- data/lib/flipper/adapters/instrumented.rb +2 -6
- data/lib/flipper/adapters/memoizable.rb +17 -22
- data/lib/flipper/adapters/memory.rb +5 -7
- data/lib/flipper/adapters/operation_logger.rb +18 -92
- data/lib/flipper/adapters/poll.rb +16 -3
- data/lib/flipper/adapters/pstore.rb +18 -12
- data/lib/flipper/adapters/read_only.rb +8 -41
- data/lib/flipper/adapters/strict.rb +75 -0
- data/lib/flipper/adapters/sync/feature_synchronizer.rb +14 -1
- data/lib/flipper/adapters/sync/synchronizer.rb +13 -5
- data/lib/flipper/adapters/sync.rb +7 -7
- data/lib/flipper/adapters/wrapper.rb +54 -0
- data/lib/flipper/cli.rb +314 -0
- data/lib/flipper/cloud/configuration.rb +267 -0
- data/lib/flipper/cloud/dsl.rb +27 -0
- data/lib/flipper/cloud/message_verifier.rb +95 -0
- data/lib/flipper/cloud/middleware.rb +63 -0
- data/lib/flipper/cloud/migrate.rb +71 -0
- data/lib/flipper/cloud/routes.rb +14 -0
- data/lib/flipper/cloud/telemetry/backoff_policy.rb +96 -0
- data/lib/flipper/cloud/telemetry/instrumenter.rb +22 -0
- data/lib/flipper/cloud/telemetry/metric.rb +39 -0
- data/lib/flipper/cloud/telemetry/metric_storage.rb +30 -0
- data/lib/flipper/cloud/telemetry/submitter.rb +100 -0
- data/lib/flipper/cloud/telemetry.rb +191 -0
- data/lib/flipper/cloud.rb +54 -0
- data/lib/flipper/configuration.rb +25 -4
- data/lib/flipper/dsl.rb +48 -43
- data/lib/flipper/engine.rb +102 -0
- data/lib/flipper/export.rb +0 -2
- data/lib/flipper/exporters/json/export.rb +1 -1
- data/lib/flipper/exporters/json/v1.rb +1 -1
- data/lib/flipper/expression/builder.rb +73 -0
- data/lib/flipper/expression/constant.rb +25 -0
- data/lib/flipper/expression.rb +71 -0
- data/lib/flipper/expressions/all.rb +9 -0
- data/lib/flipper/expressions/any.rb +9 -0
- data/lib/flipper/expressions/boolean.rb +9 -0
- data/lib/flipper/expressions/comparable.rb +13 -0
- data/lib/flipper/expressions/equal.rb +9 -0
- data/lib/flipper/expressions/feature_enabled.rb +34 -0
- data/lib/flipper/expressions/greater_than.rb +9 -0
- data/lib/flipper/expressions/greater_than_or_equal_to.rb +9 -0
- data/lib/flipper/expressions/less_than.rb +9 -0
- data/lib/flipper/expressions/less_than_or_equal_to.rb +9 -0
- data/lib/flipper/expressions/not_equal.rb +9 -0
- data/lib/flipper/expressions/now.rb +9 -0
- data/lib/flipper/expressions/number.rb +9 -0
- data/lib/flipper/expressions/percentage.rb +9 -0
- data/lib/flipper/expressions/percentage_of_actors.rb +12 -0
- data/lib/flipper/expressions/property.rb +9 -0
- data/lib/flipper/expressions/random.rb +9 -0
- data/lib/flipper/expressions/string.rb +9 -0
- data/lib/flipper/expressions/time.rb +16 -0
- data/lib/flipper/feature.rb +63 -1
- data/lib/flipper/gate.rb +2 -1
- data/lib/flipper/gate_values.rb +5 -2
- data/lib/flipper/gates/expression.rb +75 -0
- data/lib/flipper/instrumentation/log_subscriber.rb +13 -5
- data/lib/flipper/instrumentation/statsd.rb +4 -2
- data/lib/flipper/instrumentation/statsd_subscriber.rb +2 -4
- data/lib/flipper/instrumentation/subscriber.rb +0 -4
- data/lib/flipper/metadata.rb +8 -1
- data/lib/flipper/middleware/memoizer.rb +30 -14
- data/lib/flipper/model/active_record.rb +23 -0
- data/lib/flipper/poller.rb +54 -12
- data/lib/flipper/serializers/gzip.rb +22 -0
- data/lib/flipper/serializers/json.rb +17 -0
- data/lib/flipper/spec/shared_adapter_specs.rb +82 -63
- data/lib/flipper/test/shared_adapter_test.rb +77 -58
- data/lib/flipper/test_help.rb +43 -0
- data/lib/flipper/typecast.rb +37 -9
- data/lib/flipper/types/percentage.rb +1 -1
- data/lib/flipper/version.rb +11 -1
- data/lib/flipper.rb +61 -8
- data/lib/generators/flipper/setup_generator.rb +68 -0
- data/lib/generators/flipper/templates/initializer.rb +45 -0
- data/lib/generators/flipper/templates/update/migrations/01_create_flipper_tables.rb.erb +22 -0
- data/lib/generators/flipper/templates/update/migrations/02_change_flipper_gates_value_to_text.rb.erb +18 -0
- data/lib/generators/flipper/update_generator.rb +35 -0
- data/package-lock.json +41 -0
- data/package.json +10 -0
- data/spec/fixtures/environment.rb +1 -0
- data/spec/flipper/adapter_builder_spec.rb +72 -0
- data/spec/flipper/adapter_spec.rb +21 -0
- data/spec/flipper/adapters/actor_limit_spec.rb +75 -0
- data/spec/flipper/adapters/dual_write_spec.rb +15 -2
- data/spec/flipper/adapters/failover_spec.rb +12 -0
- data/spec/flipper/adapters/http/client_spec.rb +61 -0
- data/spec/flipper/adapters/http_spec.rb +375 -74
- data/spec/flipper/adapters/instrumented_spec.rb +1 -1
- data/spec/flipper/adapters/memoizable_spec.rb +21 -21
- data/spec/flipper/adapters/operation_logger_spec.rb +2 -2
- data/spec/flipper/adapters/poll_spec.rb +41 -0
- data/spec/flipper/adapters/read_only_spec.rb +32 -17
- data/spec/flipper/adapters/strict_spec.rb +122 -0
- data/spec/flipper/adapters/sync/feature_synchronizer_spec.rb +39 -0
- data/spec/flipper/adapters/sync/synchronizer_spec.rb +87 -0
- data/spec/flipper/adapters/sync_spec.rb +13 -0
- data/spec/flipper/cli_spec.rb +217 -0
- data/spec/flipper/cloud/configuration_spec.rb +251 -0
- data/spec/flipper/cloud/dsl_spec.rb +82 -0
- data/spec/flipper/cloud/message_verifier_spec.rb +104 -0
- data/spec/flipper/cloud/middleware_spec.rb +289 -0
- data/spec/flipper/cloud/migrate_spec.rb +160 -0
- data/spec/flipper/cloud/telemetry/backoff_policy_spec.rb +107 -0
- data/spec/flipper/cloud/telemetry/metric_spec.rb +87 -0
- data/spec/flipper/cloud/telemetry/metric_storage_spec.rb +58 -0
- data/spec/flipper/cloud/telemetry/submitter_spec.rb +145 -0
- data/spec/flipper/cloud/telemetry_spec.rb +208 -0
- data/spec/flipper/cloud_spec.rb +186 -0
- data/spec/flipper/configuration_spec.rb +17 -0
- data/spec/flipper/dsl_spec.rb +34 -73
- data/spec/flipper/engine_spec.rb +374 -0
- data/spec/flipper/exporters/json/v1_spec.rb +3 -3
- data/spec/flipper/expression/builder_spec.rb +248 -0
- data/spec/flipper/expression_spec.rb +188 -0
- data/spec/flipper/expressions/all_spec.rb +15 -0
- data/spec/flipper/expressions/any_spec.rb +15 -0
- data/spec/flipper/expressions/boolean_spec.rb +15 -0
- data/spec/flipper/expressions/equal_spec.rb +24 -0
- data/spec/flipper/expressions/greater_than_or_equal_to_spec.rb +28 -0
- data/spec/flipper/expressions/greater_than_spec.rb +28 -0
- data/spec/flipper/expressions/less_than_or_equal_to_spec.rb +28 -0
- data/spec/flipper/expressions/less_than_spec.rb +32 -0
- data/spec/flipper/expressions/not_equal_spec.rb +15 -0
- data/spec/flipper/expressions/now_spec.rb +11 -0
- data/spec/flipper/expressions/number_spec.rb +21 -0
- data/spec/flipper/expressions/percentage_of_actors_spec.rb +20 -0
- data/spec/flipper/expressions/percentage_spec.rb +15 -0
- data/spec/flipper/expressions/property_spec.rb +13 -0
- data/spec/flipper/expressions/random_spec.rb +9 -0
- data/spec/flipper/expressions/string_spec.rb +11 -0
- data/spec/flipper/expressions/time_spec.rb +29 -0
- data/spec/flipper/feature_spec.rb +380 -10
- data/spec/flipper/gate_values_spec.rb +2 -2
- data/spec/flipper/gates/expression_spec.rb +190 -0
- data/spec/flipper/identifier_spec.rb +4 -5
- data/spec/flipper/instrumentation/log_subscriber_spec.rb +10 -2
- data/spec/flipper/instrumentation/statsd_subscriber_spec.rb +16 -2
- data/spec/flipper/middleware/memoizer_spec.rb +107 -7
- data/spec/flipper/model/active_record_spec.rb +72 -0
- data/spec/flipper/poller_spec.rb +347 -4
- data/spec/flipper/serializers/gzip_spec.rb +13 -0
- data/spec/flipper/serializers/json_spec.rb +13 -0
- data/spec/flipper/typecast_spec.rb +43 -7
- data/spec/flipper/types/actor_spec.rb +18 -1
- data/spec/flipper_integration_spec.rb +247 -16
- data/spec/flipper_spec.rb +92 -29
- data/spec/spec_helper.rb +24 -17
- data/spec/support/actor_names.yml +1 -0
- data/spec/support/fail_on_output.rb +8 -0
- data/spec/support/fake_backoff_policy.rb +15 -0
- data/spec/support/spec_helpers.rb +34 -8
- data/test/adapters/actor_limit_test.rb +20 -0
- data/test_rails/generators/flipper/setup_generator_test.rb +69 -0
- data/test_rails/generators/flipper/update_generator_test.rb +96 -0
- data/test_rails/helper.rb +22 -2
- data/test_rails/system/test_help_test.rb +52 -0
- metadata +139 -75
- data/.tool-versions +0 -1
- data/lib/flipper/railtie.rb +0 -47
- data/spec/flipper/railtie_spec.rb +0 -109
data/README.md
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
[](https://www.flippercloud.io)
|
|
2
2
|
|
|
3
|
-
[Website](https://flippercloud.io) | [Documentation](https://flippercloud.io/docs) | [Examples](examples) | [Twitter](https://twitter.com/flipper_cloud)
|
|
3
|
+
[Website](https://flippercloud.io?utm_source=oss&utm_medium=readme&utm_campaign=website_link) | [Documentation](https://flippercloud.io/docs?utm_source=oss&utm_medium=readme&utm_campaign=docs_link) | [Examples](examples) | [Chat](https://chat.flippercloud.io/join/xjHq-aJsA-BeZH) | [Twitter](https://twitter.com/flipper_cloud) | [Ruby.social](https://ruby.social/@flipper)
|
|
4
4
|
|
|
5
5
|
# Flipper
|
|
6
6
|
|
|
7
|
-
> Beautiful, performant feature flags for Ruby.
|
|
7
|
+
> Beautiful, performant feature flags for Ruby and Rails.
|
|
8
8
|
|
|
9
9
|
Flipper gives you control over who has access to features in your app.
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
- Enable or disable features for everyone, specific actors, groups of actors, a percentage of actors, or a percentage of time.
|
|
12
|
+
- Configure your feature flags from the console or a web UI.
|
|
13
|
+
- Regardless of what data store you are using, Flipper can performantly store your feature flags.
|
|
14
|
+
- Use [Flipper Cloud](#flipper-cloud) to cascade features from multiple environments, share settings with your team, control permissions, keep an audit history, and rollback.
|
|
15
15
|
|
|
16
16
|
Control your software — don't let it control you.
|
|
17
17
|
|
|
@@ -35,7 +35,7 @@ Or install it yourself with:
|
|
|
35
35
|
|
|
36
36
|
## Subscribe & Ship
|
|
37
37
|
|
|
38
|
-
[💌 Subscribe](https://
|
|
38
|
+
[💌 Subscribe](https://blog.flippercloud.io/#/portal/signup) - we'll send you short and sweet emails when we release new versions ([examples](https://blog.flippercloud.io/tag/releases/)).
|
|
39
39
|
|
|
40
40
|
## Getting Started
|
|
41
41
|
|
|
@@ -43,7 +43,7 @@ Use `Flipper#enabled?` in your app to check if a feature is enabled.
|
|
|
43
43
|
|
|
44
44
|
```ruby
|
|
45
45
|
# check if search is enabled
|
|
46
|
-
if Flipper.enabled?
|
|
46
|
+
if Flipper.enabled?(:search, current_user)
|
|
47
47
|
puts 'Search away!'
|
|
48
48
|
else
|
|
49
49
|
puts 'No search for you!'
|
|
@@ -66,24 +66,26 @@ Flipper.enable_group :search, :admin
|
|
|
66
66
|
Flipper.enable_percentage_of_actors :search, 2
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
Read more about [getting started with Flipper](https://flippercloud.io/docs) and [enabling features](https://flippercloud.io/docs/features).
|
|
69
|
+
Read more about [getting started with Flipper](https://flippercloud.io/docs?utm_source=oss&utm_medium=readme&utm_campaign=getting_started) and [enabling features](https://flippercloud.io/docs/features?utm_source=oss&utm_medium=readme&utm_campaign=enabling_features).
|
|
70
70
|
|
|
71
71
|
## Flipper Cloud
|
|
72
72
|
|
|
73
|
-
Like Flipper and want more? Check out [Flipper Cloud](https://www.flippercloud.io), which comes with:
|
|
73
|
+
Like Flipper and want more? Check out [Flipper Cloud](https://www.flippercloud.io?utm_source=oss&utm_medium=readme&utm_campaign=check_out), which comes with:
|
|
74
74
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
75
|
+
- **multiple environments** — production, staging, per continent, whatever you need. Every environment inherits from production by default and every project comes with a [project overview page](https://blog.flippercloud.io/project-overview/) that shows each feature and its status in each environment.
|
|
76
|
+
- **personal environments** — everyone on your team gets a personal environment (that inherits from production) which they can modify however they want without stepping on anyone else's toes.
|
|
77
|
+
- **permissions** — grant access to everyone in your organization or lockdown each project to particular people. You can even limit access to a particular environment (like production) to specific people.
|
|
78
|
+
- **audit history** — every feature change and who made it.
|
|
79
|
+
- **rollbacks** — enable or disable a feature accidentally? No problem. You can roll back to any point in the audit history with a single click.
|
|
80
|
+
- **maintenance** — we'll keep the lights on for you. We also have handy webhooks and background polling for keeping your app in sync with Cloud, so **our availability won't affect yours**. All your feature flag reads are local to your app.
|
|
81
|
+
- **everything in one place** — no need to bounce around from different application UIs or IRB consoles.
|
|
82
82
|
|
|
83
|
-
[](https://www.flippercloud.io)
|
|
83
|
+
[](https://www.flippercloud.io?utm_source=oss&utm_medium=readme&utm_campaign=screenshot)
|
|
84
84
|
|
|
85
85
|
Cloud is super simple to integrate with Rails ([demo app](https://github.com/fewerandfaster/flipper-rails-demo)), Sinatra or any other framework.
|
|
86
86
|
|
|
87
|
+
We also have a [free plan](https://www.flippercloud.io?utm_source=oss&utm_medium=readme&utm_campaign=free_plan) that you can use forever.
|
|
88
|
+
|
|
87
89
|
## Contributing
|
|
88
90
|
|
|
89
91
|
1. Fork it
|
|
@@ -95,17 +97,20 @@ Cloud is super simple to integrate with Rails ([demo app](https://github.com/few
|
|
|
95
97
|
|
|
96
98
|
## Releasing
|
|
97
99
|
|
|
98
|
-
1. Update the version
|
|
99
|
-
2. `
|
|
100
|
-
3.
|
|
100
|
+
1. Update the version in `lib/flipper/version.rb` and commit.
|
|
101
|
+
2. Tag and push: `git tag v1.x.x && git push origin v1.x.x`
|
|
102
|
+
3. GitHub Actions builds and publishes all gems to RubyGems automatically.
|
|
103
|
+
4. Edit and publish the draft [GitHub Release](https://github.com/flippercloud/flipper/releases).
|
|
101
104
|
|
|
102
105
|
## Brought To You By
|
|
103
106
|
|
|
104
|
-
| pic
|
|
105
|
-
|
|
106
|
-
| 
|
|
107
|
-
| 
|
|
108
|
-
| 
|
|
109
|
-
|  | [@alexwheeler](https://github.com/alexwheeler) | api
|
|
110
|
-
| 
|
|
111
|
-
| 
|
|
107
|
+
| pic | @mention | area |
|
|
108
|
+
| ---------------------------------------------------------------------- | ---------------------------------------------- | ----------- |
|
|
109
|
+
|  | [@jnunemaker](https://github.com/jnunemaker) | most things |
|
|
110
|
+
|  | [@bkeepers](https://github.com/bkeepers) | most things |
|
|
111
|
+
|  | [@dpep](https://github.com/dpep) | tbd |
|
|
112
|
+
|  | [@alexwheeler](https://github.com/alexwheeler) | api |
|
|
113
|
+
|  | [@thetimbanks](https://github.com/thetimbanks) | ui |
|
|
114
|
+
|  | [@lazebny](https://github.com/lazebny) | docker |
|
|
115
|
+
|  | [@pagertree](https://github.com/pagertree) | sponsor |
|
|
116
|
+
|  | [@kdaigle](https://github.com/kdaigle) | sponsor |
|
data/Rakefile
CHANGED
|
@@ -27,7 +27,8 @@ end
|
|
|
27
27
|
|
|
28
28
|
require 'rspec/core/rake_task'
|
|
29
29
|
RSpec::Core::RakeTask.new(:spec) do |t|
|
|
30
|
-
t.rspec_opts = %w(--color
|
|
30
|
+
t.rspec_opts = %w(--color)
|
|
31
|
+
t.verbose = false
|
|
31
32
|
end
|
|
32
33
|
|
|
33
34
|
namespace :spec do
|
|
@@ -41,7 +42,6 @@ end
|
|
|
41
42
|
Rake::TestTask.new do |t|
|
|
42
43
|
t.libs = %w(lib test)
|
|
43
44
|
t.pattern = 'test/**/*_test.rb'
|
|
44
|
-
t.options = '--documentation'
|
|
45
45
|
t.warning = false
|
|
46
46
|
end
|
|
47
47
|
|
data/benchmark/typecast_ips.rb
CHANGED
|
@@ -16,4 +16,12 @@ Benchmark.ips do |x|
|
|
|
16
16
|
x.report("Typecast.to_float '1'") { Flipper::Typecast.to_float('1'.freeze) }
|
|
17
17
|
x.report("Typecast.to_float 1.01") { Flipper::Typecast.to_float(1) }
|
|
18
18
|
x.report("Typecast.to_float '1.01'") { Flipper::Typecast.to_float('1'.freeze) }
|
|
19
|
+
|
|
20
|
+
x.report("Typecast.to_number 1") { Flipper::Typecast.to_number(1) }
|
|
21
|
+
x.report("Typecast.to_number 1.1") { Flipper::Typecast.to_number(1.1) }
|
|
22
|
+
x.report("Typecast.to_number '1'") { Flipper::Typecast.to_number('1'.freeze) }
|
|
23
|
+
x.report("Typecast.to_number '1.1'") { Flipper::Typecast.to_number('1.1'.freeze) }
|
|
24
|
+
x.report("Typecast.to_number nil") { Flipper::Typecast.to_number(nil) }
|
|
25
|
+
time = Time.now
|
|
26
|
+
x.report("Typecast.to_number Time.now") { Flipper::Typecast.to_number(time) }
|
|
19
27
|
end
|
data/docs/images/banner.jpg
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Usage (from the repo root):
|
|
2
|
+
# env FLIPPER_CLOUD_TOKEN=<token> FLIPPER_CLOUD_SYNC_SECRET=<secret> bundle exec rackup examples/cloud/app.ru -p 9999
|
|
3
|
+
# http://localhost:9999/
|
|
4
|
+
|
|
5
|
+
require 'bundler/setup'
|
|
6
|
+
require 'flipper/cloud'
|
|
7
|
+
|
|
8
|
+
Flipper.configure do |config|
|
|
9
|
+
config.default { Flipper::Cloud.new }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
run Flipper::Cloud.app
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Just a simple example that shows how the backoff policy works.
|
|
2
|
+
require 'bundler/setup'
|
|
3
|
+
require 'flipper/cloud/telemetry/backoff_policy'
|
|
4
|
+
|
|
5
|
+
intervals = []
|
|
6
|
+
policy = Flipper::Cloud::Telemetry::BackoffPolicy.new
|
|
7
|
+
|
|
8
|
+
5.times do |n|
|
|
9
|
+
intervals << policy.next_interval
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
pp intervals.map { |i| i.round(2) }
|
|
13
|
+
puts "Total: #{intervals.sum.round(2)}ms (#{(intervals.sum/1_000.0).round(2)} sec)"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# Usage (from the repo root):
|
|
2
|
+
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/basic.rb
|
|
3
|
+
|
|
4
|
+
require_relative "./cloud_setup"
|
|
5
|
+
require 'bundler/setup'
|
|
6
|
+
require 'flipper/cloud'
|
|
7
|
+
|
|
8
|
+
Flipper[:stats].enable
|
|
9
|
+
|
|
10
|
+
if Flipper[:stats].enabled?
|
|
11
|
+
puts 'Enabled!'
|
|
12
|
+
else
|
|
13
|
+
puts 'Disabled!'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Flipper[:stats].disable
|
|
17
|
+
|
|
18
|
+
if Flipper[:stats].enabled?
|
|
19
|
+
puts 'Enabled!'
|
|
20
|
+
else
|
|
21
|
+
puts 'Disabled!'
|
|
22
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
if ENV["FLIPPER_CLOUD_TOKEN"].nil? || ENV["FLIPPER_CLOUD_TOKEN"].empty?
|
|
2
|
+
warn "FLIPPER_CLOUD_TOKEN missing so skipping cloud example."
|
|
3
|
+
exit
|
|
4
|
+
end
|
|
5
|
+
|
|
6
|
+
matrix_key = if ENV["CI"]
|
|
7
|
+
suffix_rails = ENV["RAILS_VERSION"].split(".").take(2).join
|
|
8
|
+
suffix_ruby = RUBY_VERSION.split(".").take(2).join
|
|
9
|
+
"FLIPPER_CLOUD_TOKEN_#{suffix_ruby}_#{suffix_rails}"
|
|
10
|
+
else
|
|
11
|
+
"FLIPPER_CLOUD_TOKEN"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
if matrix_token = ENV[matrix_key]
|
|
15
|
+
puts "Using #{matrix_key} for FLIPPER_CLOUD_TOKEN"
|
|
16
|
+
ENV["FLIPPER_CLOUD_TOKEN"] = matrix_token
|
|
17
|
+
else
|
|
18
|
+
warn "Missing #{matrix_key}. Go create an environment in flipper cloud and set #{matrix_key} to the adapter token for that environment in github actions secrets."
|
|
19
|
+
exit 1
|
|
20
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Usage (from the repo root):
|
|
2
|
+
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/threaded.rb
|
|
3
|
+
|
|
4
|
+
require_relative "./cloud_setup"
|
|
5
|
+
require 'bundler/setup'
|
|
6
|
+
require 'flipper/cloud'
|
|
7
|
+
|
|
8
|
+
puts Process.pid
|
|
9
|
+
|
|
10
|
+
# Make a call in the parent process so we can detect forking.
|
|
11
|
+
Flipper.enabled?(:stats)
|
|
12
|
+
|
|
13
|
+
pids = 2.times.map do |n|
|
|
14
|
+
fork {
|
|
15
|
+
# Check every second to see if the feature is enabled
|
|
16
|
+
threads = []
|
|
17
|
+
2.times do
|
|
18
|
+
threads << Thread.new do
|
|
19
|
+
loop do
|
|
20
|
+
sleep rand
|
|
21
|
+
|
|
22
|
+
if Flipper[:stats].enabled?
|
|
23
|
+
puts "#{Process.pid} #{Time.now.to_i} Enabled!"
|
|
24
|
+
else
|
|
25
|
+
puts "#{Process.pid} #{Time.now.to_i} Disabled!"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
threads.map(&:join)
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
pids.each do |pid|
|
|
35
|
+
Process.waitpid pid, 0
|
|
36
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Usage (from the repo root):
|
|
2
|
+
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/import.rb
|
|
3
|
+
|
|
4
|
+
require_relative "./cloud_setup"
|
|
5
|
+
require 'bundler/setup'
|
|
6
|
+
require 'flipper'
|
|
7
|
+
require 'flipper/cloud'
|
|
8
|
+
|
|
9
|
+
Flipper.enable(:test)
|
|
10
|
+
Flipper.enable(:search)
|
|
11
|
+
Flipper.enable_actor(:stats, Flipper::Actor.new("jnunemaker"))
|
|
12
|
+
Flipper.enable_percentage_of_time(:logging, 5)
|
|
13
|
+
|
|
14
|
+
cloud = Flipper::Cloud.new
|
|
15
|
+
|
|
16
|
+
# makes cloud identical to memory flipper
|
|
17
|
+
cloud.import(Flipper)
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Poll Interval Dynamic Adjustment Demo
|
|
2
|
+
|
|
3
|
+
This demo shows how the Flipper poller dynamically adjusts its polling interval based on the `poll-interval` header from the server, and how it responds to the `poll-shutdown` header.
|
|
4
|
+
|
|
5
|
+
## Files
|
|
6
|
+
|
|
7
|
+
- `server.rb` - Test server that responds with configurable headers
|
|
8
|
+
- `client.rb` - Client that polls the server and logs interval changes
|
|
9
|
+
- `README.md` - This file
|
|
10
|
+
|
|
11
|
+
## How to Run
|
|
12
|
+
|
|
13
|
+
### Terminal 1: Start the Server
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bundle exec ruby examples/cloud/poll_interval/server.rb
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The server will start on http://localhost:3000 and show a prompt where you can control what headers to send.
|
|
20
|
+
|
|
21
|
+
### Terminal 2: Start the Client
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bundle exec ruby examples/cloud/poll_interval/client.rb
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The client will start polling the server every 10 seconds (the minimum) and log all activity.
|
|
28
|
+
|
|
29
|
+
## Testing Scenarios
|
|
30
|
+
|
|
31
|
+
### 1. Change Poll Interval
|
|
32
|
+
|
|
33
|
+
In the **server terminal**, type a number to set the poll interval:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
> 20
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
In the **client terminal**, you'll see:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
[HH:MM:SS] WARN: ⚠️ INTERVAL CHANGED: 10.0s → 20.0s
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The client will now poll every 20 seconds instead of 10.
|
|
46
|
+
|
|
47
|
+
### 2. Try an Invalid Interval (Below Minimum)
|
|
48
|
+
|
|
49
|
+
In the **server terminal**:
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
> 5
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
In the **client terminal**, you'll see a warning:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
Flipper::Cloud poll interval must be greater than or equal to 10 but was 5.0. Setting interval to 10.
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The interval will remain at 10 seconds (the minimum).
|
|
62
|
+
|
|
63
|
+
### 3. Trigger Shutdown
|
|
64
|
+
|
|
65
|
+
In the **server terminal**:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
> shutdown
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
In the **client terminal**, you'll see:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
[HH:MM:SS] WARN: Shutdown requested by server via poll-shutdown header
|
|
75
|
+
[HH:MM:SS] WARN: Poller stopped
|
|
76
|
+
[HH:MM:SS] WARN: Poller thread is no longer running
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The poller will stop gracefully.
|
|
80
|
+
|
|
81
|
+
### 4. Reset Headers
|
|
82
|
+
|
|
83
|
+
In the **server terminal**:
|
|
84
|
+
|
|
85
|
+
```
|
|
86
|
+
> reset
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The server will stop sending special headers. The client will continue with its current interval.
|
|
90
|
+
|
|
91
|
+
## What You'll Learn
|
|
92
|
+
|
|
93
|
+
- How `poll-interval` header dynamically adjusts polling frequency
|
|
94
|
+
- How `poll-shutdown` header gracefully stops the poller
|
|
95
|
+
- How minimum interval enforcement works (10 seconds minimum)
|
|
96
|
+
- How the poller continues working even if the server returns errors
|
|
97
|
+
- Real-time logging of poller events via instrumentation
|
|
98
|
+
|
|
99
|
+
## Implementation Details
|
|
100
|
+
|
|
101
|
+
The poller checks response headers in the `ensure` block of the `sync` method, which means:
|
|
102
|
+
|
|
103
|
+
- Interval adjustments happen even if the sync fails with an error
|
|
104
|
+
- Shutdown signals are never missed, even during failures
|
|
105
|
+
- The poller is resilient to network issues
|
|
106
|
+
|
|
107
|
+
The `interval=` setter handles all validation:
|
|
108
|
+
|
|
109
|
+
- Type conversion via `Flipper::Typecast.to_float`
|
|
110
|
+
- Minimum enforcement (10 seconds)
|
|
111
|
+
- Warning messages for invalid values
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Example showing poll interval being dynamically adjusted via poll-interval header
|
|
2
|
+
#
|
|
3
|
+
# Usage:
|
|
4
|
+
# 1. Terminal 1: bundle exec ruby examples/cloud/poll_interval/server.rb
|
|
5
|
+
# 2. Terminal 2: bundle exec ruby examples/cloud/poll_interval/client.rb
|
|
6
|
+
|
|
7
|
+
require 'bundler/setup'
|
|
8
|
+
require 'flipper'
|
|
9
|
+
require 'flipper/adapters/http'
|
|
10
|
+
require 'flipper/poller'
|
|
11
|
+
require 'logger'
|
|
12
|
+
|
|
13
|
+
# Setup logging to show what's happening
|
|
14
|
+
logger = Logger.new(STDOUT)
|
|
15
|
+
logger.level = Logger::INFO
|
|
16
|
+
logger.formatter = proc do |severity, datetime, progname, msg|
|
|
17
|
+
"[#{datetime.strftime('%H:%M:%S')}] #{severity}: #{msg}\n"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Create HTTP adapter pointing to localhost:3000
|
|
21
|
+
http_adapter = Flipper::Adapters::Http.new(url: 'http://localhost:3000/flipper')
|
|
22
|
+
|
|
23
|
+
# Create instrumenter to log poller events
|
|
24
|
+
instrumenter = Module.new do
|
|
25
|
+
def self.instrument(name, payload = {})
|
|
26
|
+
case payload[:operation]
|
|
27
|
+
when :poll
|
|
28
|
+
logger.info "Polling remote adapter..."
|
|
29
|
+
when :shutdown_requested
|
|
30
|
+
logger.warn "Shutdown requested by server via poll-shutdown header"
|
|
31
|
+
when :stop
|
|
32
|
+
logger.warn "Poller stopped"
|
|
33
|
+
when :thread_start
|
|
34
|
+
logger.info "Poller thread started"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
result = yield if block_given?
|
|
38
|
+
|
|
39
|
+
if payload[:operation] == :poll && result
|
|
40
|
+
logger.info "Poll completed successfully"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
result
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.logger=(l)
|
|
47
|
+
@logger = l
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.logger
|
|
51
|
+
@logger
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
instrumenter.logger = logger
|
|
55
|
+
|
|
56
|
+
# Create poller with custom instrumenter and short initial interval
|
|
57
|
+
poller = Flipper::Poller.new(
|
|
58
|
+
remote_adapter: http_adapter,
|
|
59
|
+
interval: 5, # Start with 5 second interval (will be enforced to 10 minimum)
|
|
60
|
+
instrumenter: instrumenter,
|
|
61
|
+
start_automatically: false,
|
|
62
|
+
shutdown_automatically: false
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
logger.info "Starting poller with interval: #{poller.interval} seconds"
|
|
66
|
+
logger.info "Minimum allowed interval: #{Flipper::Poller::MINIMUM_POLL_INTERVAL} seconds"
|
|
67
|
+
logger.info ""
|
|
68
|
+
logger.info "Server can control polling via response headers:"
|
|
69
|
+
logger.info " - poll-interval: <seconds> (adjust poll frequency)"
|
|
70
|
+
logger.info " - poll-shutdown: true (stop polling)"
|
|
71
|
+
logger.info ""
|
|
72
|
+
|
|
73
|
+
# Track interval changes
|
|
74
|
+
last_interval = poller.interval
|
|
75
|
+
|
|
76
|
+
# Start the poller
|
|
77
|
+
poller.start
|
|
78
|
+
|
|
79
|
+
# Monitor for interval changes and log them
|
|
80
|
+
logger.info "Monitoring poller... (Ctrl+C to exit)"
|
|
81
|
+
logger.info ""
|
|
82
|
+
|
|
83
|
+
begin
|
|
84
|
+
loop do
|
|
85
|
+
sleep 2
|
|
86
|
+
|
|
87
|
+
current_interval = poller.interval
|
|
88
|
+
|
|
89
|
+
# Highlight when it changes
|
|
90
|
+
if current_interval != last_interval
|
|
91
|
+
logger.warn "⚠️ INTERVAL CHANGED: #{last_interval}s → #{current_interval}s"
|
|
92
|
+
last_interval = current_interval
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Check if poller thread is still alive
|
|
96
|
+
unless poller.thread&.alive?
|
|
97
|
+
logger.warn "Poller thread is no longer running"
|
|
98
|
+
break
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
rescue Interrupt
|
|
102
|
+
logger.info ""
|
|
103
|
+
logger.info "Interrupted by user"
|
|
104
|
+
ensure
|
|
105
|
+
logger.info "Stopping poller..."
|
|
106
|
+
poller.stop
|
|
107
|
+
logger.info "Final interval: #{poller.interval} seconds"
|
|
108
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Simple test server for demonstrating poll interval changes
|
|
2
|
+
#
|
|
3
|
+
# Usage:
|
|
4
|
+
# 1. Terminal 1: bundle exec ruby examples/cloud/poll_interval/server.rb
|
|
5
|
+
# 2. Terminal 2: bundle exec ruby examples/cloud/poll_interval/client.rb
|
|
6
|
+
#
|
|
7
|
+
# Commands in server terminal:
|
|
8
|
+
# - Type a number (e.g., "15") to set poll-interval header to that value
|
|
9
|
+
# - Type "shutdown" to send poll-shutdown: true header
|
|
10
|
+
# - Type "reset" to stop sending special headers
|
|
11
|
+
# - Ctrl+C to exit
|
|
12
|
+
|
|
13
|
+
require 'bundler/setup'
|
|
14
|
+
require 'webrick'
|
|
15
|
+
require 'json'
|
|
16
|
+
|
|
17
|
+
# State for what headers to send
|
|
18
|
+
$poll_interval = nil
|
|
19
|
+
$poll_shutdown = false
|
|
20
|
+
|
|
21
|
+
# Thread to handle user input for changing headers
|
|
22
|
+
input_thread = Thread.new do
|
|
23
|
+
puts ""
|
|
24
|
+
puts "=" * 60
|
|
25
|
+
puts "Server Controls:"
|
|
26
|
+
puts " Type a number (e.g., '15') to set poll-interval"
|
|
27
|
+
puts " Type 'shutdown' to trigger poll shutdown"
|
|
28
|
+
puts " Type 'reset' to clear all special headers"
|
|
29
|
+
puts "=" * 60
|
|
30
|
+
puts ""
|
|
31
|
+
|
|
32
|
+
loop do
|
|
33
|
+
print "> "
|
|
34
|
+
input = gets&.chomp
|
|
35
|
+
break if input.nil?
|
|
36
|
+
|
|
37
|
+
case input
|
|
38
|
+
when /^\d+$/
|
|
39
|
+
$poll_interval = input.to_i
|
|
40
|
+
puts "✓ Will send poll-interval: #{$poll_interval}"
|
|
41
|
+
when "shutdown"
|
|
42
|
+
$poll_shutdown = true
|
|
43
|
+
puts "✓ Will send poll-shutdown: true"
|
|
44
|
+
when "reset"
|
|
45
|
+
$poll_interval = nil
|
|
46
|
+
$poll_shutdown = false
|
|
47
|
+
puts "✓ Cleared all special headers"
|
|
48
|
+
else
|
|
49
|
+
puts "Unknown command. Use a number, 'shutdown', or 'reset'"
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Setup WEBrick server
|
|
55
|
+
server = WEBrick::HTTPServer.new(
|
|
56
|
+
Port: 3000,
|
|
57
|
+
Logger: WEBrick::Log.new($stdout, WEBrick::Log::INFO),
|
|
58
|
+
AccessLog: [[
|
|
59
|
+
$stdout,
|
|
60
|
+
WEBrick::AccessLog::COMMON_LOG_FORMAT
|
|
61
|
+
]]
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Handle GET /flipper/features
|
|
65
|
+
server.mount_proc '/flipper/features' do |req, res|
|
|
66
|
+
# Build response
|
|
67
|
+
response_body = {
|
|
68
|
+
features: []
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
res.status = 200
|
|
72
|
+
res['Content-Type'] = 'application/json'
|
|
73
|
+
res.body = JSON.generate(response_body)
|
|
74
|
+
|
|
75
|
+
# Add special headers if configured
|
|
76
|
+
if $poll_interval
|
|
77
|
+
res['poll-interval'] = $poll_interval.to_s
|
|
78
|
+
puts "→ Sent poll-interval: #{$poll_interval}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
if $poll_shutdown
|
|
82
|
+
res['poll-shutdown'] = 'true'
|
|
83
|
+
puts "→ Sent poll-shutdown: true"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Trap interrupt and shutdown gracefully
|
|
88
|
+
trap('INT') do
|
|
89
|
+
puts "\nShutting down server..."
|
|
90
|
+
server.shutdown
|
|
91
|
+
input_thread.kill
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
puts "Server starting on http://localhost:3000"
|
|
95
|
+
puts "Endpoint: GET http://localhost:3000/flipper/features"
|
|
96
|
+
puts ""
|
|
97
|
+
|
|
98
|
+
server.start
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Usage (from the repo root):
|
|
2
|
+
# env FLIPPER_CLOUD_TOKEN=<token> bundle exec ruby examples/cloud/threaded.rb
|
|
3
|
+
|
|
4
|
+
require_relative "./cloud_setup"
|
|
5
|
+
require 'bundler/setup'
|
|
6
|
+
require 'flipper/cloud'
|
|
7
|
+
|
|
8
|
+
puts Process.pid
|
|
9
|
+
|
|
10
|
+
Flipper.configure do |config|
|
|
11
|
+
config.default {
|
|
12
|
+
Flipper::Cloud.new(
|
|
13
|
+
local_adapter: config.adapter,
|
|
14
|
+
debug_output: STDOUT,
|
|
15
|
+
)
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# You might want to do this at some point to see different results:
|
|
20
|
+
# Flipper.enable(:search)
|
|
21
|
+
# Flipper.disable(:stats)
|
|
22
|
+
|
|
23
|
+
# Check every second to see if the feature is enabled
|
|
24
|
+
5.times.map { |i|
|
|
25
|
+
Thread.new {
|
|
26
|
+
loop do
|
|
27
|
+
sleep rand
|
|
28
|
+
|
|
29
|
+
Flipper.enabled?(:stats)
|
|
30
|
+
Flipper.enabled?(:search)
|
|
31
|
+
end
|
|
32
|
+
}
|
|
33
|
+
}.each(&:join)
|
data/examples/dsl.rb
CHANGED
|
@@ -47,20 +47,6 @@ puts "stats.enabled?: #{stats.enabled?}"
|
|
|
47
47
|
puts "stats.enabled? person: #{stats.enabled? person}"
|
|
48
48
|
puts
|
|
49
49
|
|
|
50
|
-
# get an instance of the percentage of time type set to 5
|
|
51
|
-
puts Flipper.time(5).inspect
|
|
52
|
-
|
|
53
|
-
# get an instance of the percentage of actors type set to 15
|
|
54
|
-
puts Flipper.actors(15).inspect
|
|
55
|
-
|
|
56
|
-
# get an instance of an actor using an object that responds to flipper_id
|
|
57
|
-
responds_to_flipper_id = Struct.new(:flipper_id).new(10)
|
|
58
|
-
puts Flipper.actor(responds_to_flipper_id).inspect
|
|
59
|
-
|
|
60
|
-
# get an instance of an actor using an object
|
|
61
|
-
actor = Struct.new(:flipper_id).new(22)
|
|
62
|
-
puts Flipper.actor(actor).inspect
|
|
63
|
-
|
|
64
50
|
# register a top level group
|
|
65
51
|
admins = Flipper.register(:admins) { |actor|
|
|
66
52
|
actor.respond_to?(:admin?) && actor.admin?
|