featurehub-sdk 2.0.1 → 2.1.0
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/.claude/CLAUDE.md +1 -0
- data/.dockerignore +12 -0
- data/.rubocop.yml +0 -2
- data/CHANGELOG.md +9 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +8 -4
- data/Makefile +12 -0
- data/README.md +66 -13
- data/examples/sinatra/Dockerfile +16 -16
- data/examples/sinatra/Gemfile +4 -0
- data/examples/sinatra/Gemfile.lock +31 -12
- data/examples/sinatra/README.adoc +18 -2
- data/examples/sinatra/app/application.rb +31 -19
- data/examples/sinatra/conf/nginx.conf +3 -11
- data/examples/sinatra/conf/webapp.conf +16 -0
- data/examples/sinatra/docker-compose.yaml +32 -4
- data/examples/sinatra/sinatra.iml +13 -5
- data/examples/sinatra/start.sh +19 -4
- data/featurehub-sdk.gemspec +2 -1
- data/lib/feature_hub/sdk/context.rb +3 -1
- data/lib/feature_hub/sdk/feature_hub_config.rb +32 -8
- data/lib/feature_hub/sdk/feature_state_holder.rb +6 -0
- data/lib/feature_hub/sdk/memcache_session_store.rb +221 -0
- data/lib/feature_hub/sdk/redis_session_store.rb +145 -49
- data/lib/feature_hub/sdk/session_store_helpers.rb +45 -0
- data/lib/feature_hub/sdk/version.rb +1 -1
- data/lib/featurehub-sdk.rb +2 -0
- data/sig/feature_hub/featurehub.rbs +2 -0
- metadata +24 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e641291e7daf8a2bb0209ecfd7b0af27fc46b4cd17645bbe7072171ee0c8eb79
|
|
4
|
+
data.tar.gz: c37349075e7d5ccdff4776d6980f612e0e8b3bd9d5964124aab104dcf06a991a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: efe9d777183431e8729c37aba09384998c82d8b52eddf2835e47aba7ea60546630a09587fe6f8639c1f4daddb637c68b915daf4dbcdd362ba59b8960ca68d321
|
|
7
|
+
data.tar.gz: c028d22c9f8e32077a69e2e0bbc2ba34ea96892cfd78431f480ea020bdf9d7baec15c3af8bb3c0cbe0497c112eec372cd5615f9393fbd5b9dc73a7bcbb08f7fc
|
data/.claude/CLAUDE.md
CHANGED
|
@@ -83,3 +83,4 @@ Type definitions live in [sig/feature_hub/featurehub.rbs](sig/feature_hub/featur
|
|
|
83
83
|
- RuboCop: max line length 120, metrics cops disabled, documentation disabled
|
|
84
84
|
- Tests mirror lib structure: `spec/feature_hub/sdk/**/*_spec.rb`
|
|
85
85
|
- Use `instance_double` for mocking, `aggregate_failures` for multiple assertions
|
|
86
|
+
- after making changes run `bundle exec rubocop -a` to autocorrect minor offences and understand what major offences may have been introduced and fix them
|
data/.dockerignore
ADDED
data/.rubocop.yml
CHANGED
data/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
## [2.1.0] - 2026-04-14
|
|
2
|
+
|
|
3
|
+
- Once the Config is closed it won't reopen
|
|
4
|
+
- Added Memcache cache that operates on the same general principles as Redis.
|
|
5
|
+
It requires Dalli to be available in your dependencies at least 4.x.
|
|
6
|
+
- The requirement for faraday 2+ has been relaxed, just faraday is now required in
|
|
7
|
+
the gemspec. It has been tested with 2 and 1.
|
|
8
|
+
- Redis session store has been updated so it only uses two keys
|
|
9
|
+
|
|
1
10
|
## [2.0.1] - 2026-03-27
|
|
2
11
|
|
|
3
12
|
- Remove `FeatureHub::Sdk.default_logger`; logger now defaults to `nil` instead of a stdout DEBUG logger
|
data/Gemfile
CHANGED
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
featurehub-sdk (2.0
|
|
4
|
+
featurehub-sdk (2.1.0)
|
|
5
5
|
concurrent-ruby (~> 1.3)
|
|
6
|
-
faraday
|
|
6
|
+
faraday
|
|
7
7
|
ld-eventsource (~> 2.5.1)
|
|
8
8
|
murmurhash3 (~> 0.1.7)
|
|
9
9
|
sem_version (~> 2.0.0)
|
|
@@ -16,6 +16,8 @@ GEM
|
|
|
16
16
|
ast (2.4.3)
|
|
17
17
|
concurrent-ruby (1.3.6)
|
|
18
18
|
connection_pool (3.0.2)
|
|
19
|
+
dalli (4.3.3)
|
|
20
|
+
logger
|
|
19
21
|
diff-lcs (1.6.2)
|
|
20
22
|
docile (1.4.1)
|
|
21
23
|
domain_name (0.6.20240107)
|
|
@@ -113,7 +115,8 @@ PLATFORMS
|
|
|
113
115
|
|
|
114
116
|
DEPENDENCIES
|
|
115
117
|
concurrent-ruby (~> 1.3)
|
|
116
|
-
|
|
118
|
+
dalli (~> 4)
|
|
119
|
+
faraday
|
|
117
120
|
featurehub-sdk!
|
|
118
121
|
ld-eventsource (~> 2.5.1)
|
|
119
122
|
murmurhash3 (~> 0.1.7)
|
|
@@ -129,12 +132,13 @@ CHECKSUMS
|
|
|
129
132
|
ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
|
|
130
133
|
concurrent-ruby (1.3.6) sha256=6b56837e1e7e5292f9864f34b69c5a2cbc75c0cf5338f1ce9903d10fa762d5ab
|
|
131
134
|
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
|
|
135
|
+
dalli (4.3.3) sha256=ae58aa3442b0d9e129898f56bc6e3a0f8b6149523e723b3eb124a05ae9a2da0c
|
|
132
136
|
diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
|
|
133
137
|
docile (1.4.1) sha256=96159be799bfa73cdb721b840e9802126e4e03dfc26863db73647204c727f21e
|
|
134
138
|
domain_name (0.6.20240107) sha256=5f693b2215708476517479bf2b3802e49068ad82167bcd2286f899536a17d933
|
|
135
139
|
faraday (2.14.0) sha256=8699cfe5d97e55268f2596f9a9d5a43736808a943714e3d9a53e6110593941cd
|
|
136
140
|
faraday-net_http (3.4.2) sha256=f147758260d3526939bf57ecf911682f94926a3666502e24c69992765875906c
|
|
137
|
-
featurehub-sdk (2.0
|
|
141
|
+
featurehub-sdk (2.1.0)
|
|
138
142
|
ffi (1.17.3-arm64-darwin) sha256=0c690555d4cee17a7f07c04d59df39b2fba74ec440b19da1f685c6579bb0717f
|
|
139
143
|
ffi (1.17.3-x86_64-darwin) sha256=1f211811eb5cfaa25998322cdd92ab104bfbd26d1c4c08471599c511f2c00bb5
|
|
140
144
|
ffi (1.17.3-x86_64-linux-gnu) sha256=3746b01f677aae7b16dc1acb7cb3cc17b3e35bdae7676a3f568153fb0e2c887f
|
data/Makefile
ADDED
data/README.md
CHANGED
|
@@ -295,34 +295,87 @@ The file path defaults to `featurehub-overrides.yaml` or the `FEATUREHUB_LOCAL_Y
|
|
|
295
295
|
|
|
296
296
|
## Caching feature state in Redis
|
|
297
297
|
|
|
298
|
-
`RedisSessionStore` persists feature values from a `FeatureHubRepository` to Redis. On startup it replays cached features into the repository, then listens for live updates and writes newer versions back. A background timer re-reads
|
|
298
|
+
`RedisSessionStore` persists feature values from a `FeatureHubRepository` to Redis. On startup it replays cached features into the repository, then listens for live updates and writes newer versions back. A background timer periodically re-reads a SHA key so that updates published by other processes are picked up automatically.
|
|
299
299
|
|
|
300
300
|
> **Warning:** Do not use `RedisSessionStore` with server-evaluated features. Each server-evaluated context resolves to different values; sharing a single Redis key across processes will cause them to overwrite each other's state.
|
|
301
301
|
|
|
302
|
+
Multi-process writes are safe: the store uses SHA256-based change detection and Redis `WATCH`/`MULTI`/`EXEC` to atomically update both keys and prevent races between concurrent writers.
|
|
303
|
+
|
|
304
|
+
Pass a `FeatureHubConfig` as the second argument — the store reads `repository` and `environment_id` from it and registers itself as a raw update listener automatically.
|
|
305
|
+
|
|
302
306
|
```ruby
|
|
303
307
|
# Requires the 'redis' gem: gem 'redis', '~> 5'
|
|
304
308
|
store = FeatureHub::Sdk::RedisSessionStore.new(
|
|
305
309
|
"redis://localhost:6379",
|
|
306
|
-
config.repository
|
|
310
|
+
config, # FeatureHubConfig — NOT config.repository
|
|
307
311
|
{
|
|
308
|
-
prefix:
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
312
|
+
prefix: "myapp", # Redis key prefix (default: "featurehub")
|
|
313
|
+
db: 0, # Redis DB index (default: 0)
|
|
314
|
+
refresh_timeout: 300, # Seconds between periodic SHA checks (default: 300)
|
|
315
|
+
backoff_timeout: 500, # Milliseconds to wait between WATCH retries (default: 500)
|
|
316
|
+
retry_update_count: 10, # Maximum WATCH retry attempts per write (default: 10)
|
|
317
|
+
logger: my_logger # Optional logger
|
|
313
318
|
}
|
|
314
319
|
)
|
|
315
320
|
|
|
316
|
-
# Register it so it also receives live updates
|
|
317
|
-
config.register_raw_update_listener(store)
|
|
318
|
-
|
|
319
321
|
# Shut down cleanly
|
|
320
322
|
store.close
|
|
321
323
|
```
|
|
322
324
|
|
|
325
|
+
You can also pass an existing Redis client instead of a connection string (e.g. a RedisCluster client or a pre-configured `Redis` instance):
|
|
326
|
+
|
|
327
|
+
```ruby
|
|
328
|
+
redis = Redis.new(url: "redis://localhost:6379", db: 1)
|
|
329
|
+
store = FeatureHub::Sdk::RedisSessionStore.new(redis, config)
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
You can also pass a `RedisSessionStoreOptions` object directly:
|
|
333
|
+
|
|
334
|
+
```ruby
|
|
335
|
+
opts = FeatureHub::Sdk::RedisSessionStoreOptions.new(prefix: "myapp", db: 2)
|
|
336
|
+
store = FeatureHub::Sdk::RedisSessionStore.new("redis://localhost:6379", config, opts)
|
|
337
|
+
```
|
|
338
|
+
|
|
323
339
|
Redis keys used:
|
|
324
|
-
- `{prefix}
|
|
325
|
-
- `{prefix}_{
|
|
340
|
+
- `{prefix}_{environment_id}` — JSON-encoded array of all feature states
|
|
341
|
+
- `{prefix}_{environment_id}_sha` — SHA256 fingerprint used for cross-process change detection
|
|
342
|
+
|
|
343
|
+
## Caching feature state in Memcache
|
|
344
|
+
|
|
345
|
+
`MemcacheSessionStore` persists feature values from a `FeatureHubRepository` to Memcache. On startup it reads any previously saved features from Memcache and replays them into the repository, then listens for live updates and writes newer versions back. A background timer periodically re-reads a SHA key so that updates published by other processes are picked up automatically.
|
|
346
|
+
|
|
347
|
+
> **Warning:** Do not use `MemcacheSessionStore` with server-evaluated features. Each server-evaluated context resolves to different values; sharing a single Memcache key across processes will cause them to overwrite each other's state.
|
|
348
|
+
|
|
349
|
+
Multi-process writes are safe: the store uses SHA256-based change detection and Dalli's compare-and-set (`cas`) to prevent races between concurrent writers.
|
|
350
|
+
|
|
351
|
+
```ruby
|
|
352
|
+
# Requires the 'dalli' gem: gem 'dalli', '~> 5'
|
|
353
|
+
store = FeatureHub::Sdk::MemcacheSessionStore.new(
|
|
354
|
+
"localhost:11211",
|
|
355
|
+
config,
|
|
356
|
+
{
|
|
357
|
+
prefix: "myapp", # Key prefix (default: "featurehub")
|
|
358
|
+
refresh_timeout: 300, # Seconds between periodic SHA checks (default: 300)
|
|
359
|
+
backoff_timeout: 500, # Milliseconds to wait between CAS retries (default: 500)
|
|
360
|
+
retry_update_count: 10, # Maximum CAS retry attempts per write (default: 10)
|
|
361
|
+
logger: my_logger # Optional logger (default: SDK default logger)
|
|
362
|
+
}
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Shut down cleanly
|
|
366
|
+
store.close
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
You can also pass an existing `Dalli::Client` instead of a connection string:
|
|
370
|
+
|
|
371
|
+
```ruby
|
|
372
|
+
dalli = Dalli::Client.new("localhost:11211", serializer: JSON)
|
|
373
|
+
store = FeatureHub::Sdk::MemcacheSessionStore.new(dalli, config)
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Memcache keys used:
|
|
377
|
+
- `{prefix}_{environment_id}` — JSON-encoded array of all feature states
|
|
378
|
+
- `{prefix}_{environment_id}_sha` — SHA256 fingerprint used for cross-process change detection
|
|
326
379
|
|
|
327
380
|
## Custom raw update listeners
|
|
328
381
|
|
|
@@ -346,7 +399,7 @@ end
|
|
|
346
399
|
config.register_raw_update_listener(MyAuditListener.new)
|
|
347
400
|
```
|
|
348
401
|
|
|
349
|
-
Callbacks are dispatched asynchronously via `Concurrent::Future`. The `source` parameter will be `"streaming"`, `"polling"`, `"local-yaml"`, `"redis-store"`, or `"unknown"`.
|
|
402
|
+
Callbacks are dispatched asynchronously via `Concurrent::Future`. The `source` parameter will be `"streaming"`, `"polling"`, `"local-yaml"`, `"redis-store"`, `"memcache-store"`, or `"unknown"`.
|
|
350
403
|
|
|
351
404
|
All listeners are closed automatically when `config.close` or `repository.close` is called.
|
|
352
405
|
|
data/examples/sinatra/Dockerfile
CHANGED
|
@@ -1,31 +1,31 @@
|
|
|
1
|
-
FROM
|
|
1
|
+
FROM phusion/passenger-ruby33:latest
|
|
2
2
|
|
|
3
3
|
MAINTAINER info@featurehub.io
|
|
4
|
-
ENV BUNDLER_VERSION
|
|
4
|
+
ENV BUNDLER_VERSION 4.0.3
|
|
5
5
|
ARG DEBIAN_FRONTEND=noninteractive
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
sh -c 'echo deb https://oss-binaries.phusionpassenger.com/apt/passenger bookworm main > /etc/apt/sources.list.d/passenger.list' && \
|
|
11
|
-
apt-get update && \
|
|
12
|
-
apt-get install -y nginx passenger
|
|
13
|
-
|
|
14
|
-
#ENV BUNDLE_PATH /bundle
|
|
15
|
-
RUN passenger-config build-native-support
|
|
7
|
+
# The purpose of this Dockerfile is to build a test to-do server that can be used against the
|
|
8
|
+
# e2e tests. It *MUST* test the SDK on the current HEAD, so the file is complicated and awkward
|
|
9
|
+
# and not like a typical deploy.
|
|
16
10
|
|
|
17
11
|
RUN echo 'gem: --no-document' >> ~/.gemrc && \
|
|
18
12
|
gem update --system && \
|
|
19
13
|
gem install bundler -v ${BUNDLER_VERSION} --force
|
|
20
14
|
|
|
15
|
+
RUN gem uninstall logger -v 1.6.0
|
|
16
|
+
|
|
21
17
|
# set up nsswitch
|
|
22
18
|
COPY examples/sinatra/conf/nsswitch.conf /etc/nsswitch.conf
|
|
23
19
|
|
|
24
|
-
RUN mkdir -p /app/featurehub
|
|
25
20
|
COPY examples/sinatra/conf/nginx.conf /etc/nginx/nginx.conf
|
|
26
|
-
|
|
21
|
+
ADD . /app/featurehub
|
|
27
22
|
WORKDIR /app/featurehub
|
|
28
|
-
RUN cd /
|
|
29
|
-
|
|
23
|
+
RUN rm Gemfile Gemfile.lock && cd examples/sinatra && bundle config set --local deployment 'true' --without 'development test' path 'vendor/bundle'
|
|
24
|
+
RUN cd /app/featurehub/examples/sinatra && cat .bundle/config && mkdir logs
|
|
25
|
+
RUN cd /app/featurehub/examples/sinatra && bundle install
|
|
26
|
+
RUN chown -R nobody /app
|
|
27
|
+
ENV BUNDLE_PATH=/app/featurehub/examples/sinatra/vendor/bundle
|
|
30
28
|
|
|
31
|
-
|
|
29
|
+
USER root
|
|
30
|
+
CMD /usr/sbin/nginx -g 'daemon off;'
|
|
31
|
+
#CMD /usr/bin/bash
|
data/examples/sinatra/Gemfile
CHANGED
|
@@ -4,7 +4,11 @@ source "https://rubygems.org"
|
|
|
4
4
|
|
|
5
5
|
ruby "3.3.10"
|
|
6
6
|
|
|
7
|
+
# 5 prevents us using ruby 3.2 which while EOL is still in our build
|
|
8
|
+
gem "dalli", "~> 4"
|
|
7
9
|
gem "featurehub-sdk", path: "../.."
|
|
10
|
+
# this is just to test 1.x works OK
|
|
11
|
+
gem "faraday", "~> 1"
|
|
8
12
|
gem "rack"
|
|
9
13
|
gem "redis"
|
|
10
14
|
gem "sinatra"
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: ../..
|
|
3
3
|
specs:
|
|
4
|
-
featurehub-sdk (2.
|
|
4
|
+
featurehub-sdk (2.1.0)
|
|
5
5
|
concurrent-ruby (~> 1.3)
|
|
6
|
-
faraday
|
|
6
|
+
faraday
|
|
7
7
|
ld-eventsource (~> 2.5.1)
|
|
8
8
|
murmurhash3 (~> 0.1.7)
|
|
9
9
|
sem_version (~> 2.0.0)
|
|
@@ -16,15 +16,35 @@ GEM
|
|
|
16
16
|
concurrent-ruby (1.3.6)
|
|
17
17
|
connection_pool (3.0.2)
|
|
18
18
|
daemons (1.4.1)
|
|
19
|
+
dalli (4.3.3)
|
|
20
|
+
logger
|
|
19
21
|
domain_name (0.6.20240107)
|
|
20
22
|
eventmachine (1.2.7)
|
|
21
|
-
faraday (
|
|
22
|
-
faraday-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
faraday (1.10.5)
|
|
24
|
+
faraday-em_http (~> 1.0)
|
|
25
|
+
faraday-em_synchrony (~> 1.0)
|
|
26
|
+
faraday-excon (~> 1.1)
|
|
27
|
+
faraday-httpclient (~> 1.0)
|
|
28
|
+
faraday-multipart (~> 1.0)
|
|
29
|
+
faraday-net_http (~> 1.0)
|
|
30
|
+
faraday-net_http_persistent (~> 1.0)
|
|
31
|
+
faraday-patron (~> 1.0)
|
|
32
|
+
faraday-rack (~> 1.0)
|
|
33
|
+
faraday-retry (~> 1.0)
|
|
34
|
+
ruby2_keywords (>= 0.0.4)
|
|
35
|
+
faraday-em_http (1.0.0)
|
|
36
|
+
faraday-em_synchrony (1.0.1)
|
|
37
|
+
faraday-excon (1.1.0)
|
|
38
|
+
faraday-httpclient (1.0.1)
|
|
39
|
+
faraday-multipart (1.2.0)
|
|
40
|
+
multipart-post (~> 2.0)
|
|
41
|
+
faraday-net_http (1.0.2)
|
|
42
|
+
faraday-net_http_persistent (1.2.0)
|
|
43
|
+
faraday-patron (1.0.0)
|
|
44
|
+
faraday-rack (1.0.0)
|
|
45
|
+
faraday-retry (1.0.4)
|
|
27
46
|
ffi (1.17.3-x86_64-darwin)
|
|
47
|
+
ffi (1.17.3-x86_64-linux-gnu)
|
|
28
48
|
ffi-compiler (1.3.2)
|
|
29
49
|
ffi (>= 1.15.5)
|
|
30
50
|
rake
|
|
@@ -36,7 +56,6 @@ GEM
|
|
|
36
56
|
http-cookie (1.1.0)
|
|
37
57
|
domain_name (~> 0.5)
|
|
38
58
|
http-form_data (2.3.0)
|
|
39
|
-
json (2.19.2)
|
|
40
59
|
ld-eventsource (2.5.1)
|
|
41
60
|
concurrent-ruby (~> 1.0)
|
|
42
61
|
http (>= 4.4.1, < 6.0.0)
|
|
@@ -44,11 +63,10 @@ GEM
|
|
|
44
63
|
ffi-compiler (~> 1.0)
|
|
45
64
|
rake (~> 13.0)
|
|
46
65
|
logger (1.7.0)
|
|
66
|
+
multipart-post (2.4.1)
|
|
47
67
|
murmurhash3 (0.1.7)
|
|
48
68
|
mustermann (2.0.2)
|
|
49
69
|
ruby2_keywords (~> 0.0.1)
|
|
50
|
-
net-http (0.9.1)
|
|
51
|
-
uri (>= 0.11.1)
|
|
52
70
|
public_suffix (7.0.5)
|
|
53
71
|
rack (2.2.6.4)
|
|
54
72
|
rack-protection (2.2.3)
|
|
@@ -72,7 +90,6 @@ GEM
|
|
|
72
90
|
eventmachine (~> 1.0, >= 1.0.4)
|
|
73
91
|
rack (>= 1, < 3)
|
|
74
92
|
tilt (2.1.0)
|
|
75
|
-
uri (1.1.1)
|
|
76
93
|
|
|
77
94
|
PLATFORMS
|
|
78
95
|
x86_64-darwin-21
|
|
@@ -80,6 +97,8 @@ PLATFORMS
|
|
|
80
97
|
x86_64-linux
|
|
81
98
|
|
|
82
99
|
DEPENDENCIES
|
|
100
|
+
dalli (~> 4)
|
|
101
|
+
faraday (~> 1)
|
|
83
102
|
featurehub-sdk!
|
|
84
103
|
rack
|
|
85
104
|
redis
|
|
@@ -4,7 +4,23 @@ This is a simple todo server example that we use to run our Cucumber tests again
|
|
|
4
4
|
to ensure that the SDK is operating as expected. Please read the `application.rb`
|
|
5
5
|
for details on how it works, it is fairly simple.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
using `docker_start.sh`
|
|
7
|
+
NOTE: The `Dockerfile` is intended for use in the e2e tests in the pipeline and is not a general use-case sample. It uses Passenger, Sinatra and the FeatureHub SDK on this specific branch. It is, as such, constructed from the root directory (using the `Makefile`).
|
|
9
8
|
|
|
9
|
+
We typically want to run one cache and then two copies of the server. We would normally disconnect the primary server (running on 8099) from edge, leaving the second server running (8100) and writing to the cache.
|
|
10
10
|
|
|
11
|
+
To disconnect from edge, call `curl localhost:8099/health/disconnect`
|
|
12
|
+
|
|
13
|
+
The tests should still pass as the primary server is picking up changes from the cache.
|
|
14
|
+
|
|
15
|
+
== Redis
|
|
16
|
+
|
|
17
|
+
----
|
|
18
|
+
docker run -d -p 6379:6379 --name redis redis
|
|
19
|
+
|
|
20
|
+
----
|
|
21
|
+
|
|
22
|
+
== Memcache
|
|
23
|
+
|
|
24
|
+
----
|
|
25
|
+
docker run -p 11211:11211 --name memcache -d memcached
|
|
26
|
+
----
|
|
@@ -5,14 +5,21 @@ require "sinatra"
|
|
|
5
5
|
require "featurehub-sdk"
|
|
6
6
|
require "json"
|
|
7
7
|
|
|
8
|
-
def configure_featurehub
|
|
8
|
+
def configure_featurehub(logger)
|
|
9
9
|
puts "FeatureHub SDK Version is #{FeatureHub::Sdk::VERSION}"
|
|
10
10
|
|
|
11
|
-
config = FeatureHub::Sdk::FeatureHubConfig.new
|
|
11
|
+
config = FeatureHub::Sdk::FeatureHubConfig.new(nil, nil, nil, nil, logger)
|
|
12
12
|
repo = config.repository
|
|
13
13
|
|
|
14
14
|
if ENV["FEATUREHUB_REDIS_STORE"]
|
|
15
|
-
|
|
15
|
+
FeatureHub::Sdk::RedisSessionStore.new(ENV["FEATUREHUB_REDIS_STORE"], config,
|
|
16
|
+
{ logger: logger, refresh_timeout: 3 })
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
if ENV["FEATUREHUB_MEMCACHE_STORE"]
|
|
20
|
+
FeatureHub::Sdk::MemcacheSessionStore.new(ENV["FEATUREHUB_MEMCACHE_STORE"],
|
|
21
|
+
config, { logger: logger,
|
|
22
|
+
refresh_timeout: 3 })
|
|
16
23
|
end
|
|
17
24
|
|
|
18
25
|
if ENV["FEATUREHUB_LOCAL_YAML"]
|
|
@@ -20,6 +27,7 @@ def configure_featurehub
|
|
|
20
27
|
config.register_interceptor(FeatureHub::Sdk::LocalYamlValueInterceptor.new(watch: true))
|
|
21
28
|
end
|
|
22
29
|
|
|
30
|
+
puts "connecting to FeatureHub"
|
|
23
31
|
# connect to edge service
|
|
24
32
|
config.init
|
|
25
33
|
end
|
|
@@ -34,7 +42,9 @@ class App < Sinatra::Base
|
|
|
34
42
|
rack = File.new("logs/rack.log", "a+")
|
|
35
43
|
use Rack::CommonLogger, rack
|
|
36
44
|
|
|
37
|
-
|
|
45
|
+
logger = Logger.new($stdout)
|
|
46
|
+
set :logger, logger
|
|
47
|
+
set :fh_config, configure_featurehub(logger)
|
|
38
48
|
set :users, {}
|
|
39
49
|
end
|
|
40
50
|
|
|
@@ -42,10 +52,6 @@ class App < Sinatra::Base
|
|
|
42
52
|
content_type "application/json"
|
|
43
53
|
end
|
|
44
54
|
|
|
45
|
-
get("/config/disconnect-edge") do
|
|
46
|
-
settings.fh_config.close_edge
|
|
47
|
-
end
|
|
48
|
-
|
|
49
55
|
# Routes
|
|
50
56
|
# "resolve" a specific todo for this user
|
|
51
57
|
put("/todo/:user/:id/resolve") do
|
|
@@ -83,6 +89,7 @@ class App < Sinatra::Base
|
|
|
83
89
|
if new_todo["title"].nil?
|
|
84
90
|
status(400)
|
|
85
91
|
else
|
|
92
|
+
settings.logger.debug("todo is #{new_todo}")
|
|
86
93
|
todos.push(Todo.new(new_todo["id"] || 1, new_todo["title"], new_todo["resolved"] || false))
|
|
87
94
|
todo_list(user)
|
|
88
95
|
end
|
|
@@ -94,13 +101,26 @@ class App < Sinatra::Base
|
|
|
94
101
|
end
|
|
95
102
|
|
|
96
103
|
get("/health/readiness") do
|
|
97
|
-
if
|
|
104
|
+
if settings.fh_config.repository.ready?
|
|
105
|
+
"ok"
|
|
106
|
+
else
|
|
107
|
+
status(500)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
get("/health/liveness") do
|
|
112
|
+
if settings.fh_config.repository.ready?
|
|
98
113
|
"ok"
|
|
99
114
|
else
|
|
100
115
|
status(500)
|
|
101
116
|
end
|
|
102
117
|
end
|
|
103
118
|
|
|
119
|
+
get("/health/disconnect") do
|
|
120
|
+
settings.fh_config.close_edge
|
|
121
|
+
status 200
|
|
122
|
+
end
|
|
123
|
+
|
|
104
124
|
private
|
|
105
125
|
|
|
106
126
|
def todo_list(user)
|
|
@@ -113,6 +133,7 @@ class App < Sinatra::Base
|
|
|
113
133
|
new_todos.push(Todo.new(todo.id, process_title(ctx, todo.title), todo.resolved).to_h)
|
|
114
134
|
end
|
|
115
135
|
|
|
136
|
+
settings.logger.debug("todos #{new_todos}")
|
|
116
137
|
new_todos.to_json
|
|
117
138
|
end
|
|
118
139
|
|
|
@@ -139,15 +160,6 @@ class App < Sinatra::Base
|
|
|
139
160
|
|
|
140
161
|
new_title = new_title.upcase if ctx.enabled?("FEATURE_TITLE_TO_UPPERCASE")
|
|
141
162
|
|
|
142
|
-
|
|
143
|
-
# puts("features via edge service: #{settings.fh_config.get_or_create_edge_service.repository}")
|
|
144
|
-
|
|
145
|
-
# puts("enabled? #{ctx.repo.features}")
|
|
146
|
-
puts(ctx.enabled?("FEATURE_TITLE_TO_UPPERCASE"))
|
|
147
|
-
puts(ctx.flag("FEATURE_TITLE_TO_UPPERCASE"))
|
|
148
|
-
puts(settings.fh_config.repository.feature("FEATURE_TITLE_TO_UPPERCASE").feature_type)
|
|
149
|
-
puts(settings.fh_config.repository.feature("FEATURE_TITLE_TO_UPPERCASE").flag)
|
|
150
|
-
|
|
151
|
-
new_title&.strip
|
|
163
|
+
new_title
|
|
152
164
|
end
|
|
153
165
|
end
|
|
@@ -11,15 +11,6 @@ events {
|
|
|
11
11
|
http {
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
passenger_user_switching on;
|
|
15
|
-
passenger_user root;
|
|
16
|
-
passenger_default_user root;
|
|
17
|
-
|
|
18
|
-
passenger_max_pool_size 6;
|
|
19
|
-
|
|
20
|
-
passenger_disable_security_update_check on;
|
|
21
|
-
passenger_disable_anonymous_telemetry on;
|
|
22
|
-
|
|
23
14
|
include /etc/nginx/conf/vhosts/*.conf;
|
|
24
15
|
include /etc/nginx/conf.d/*.conf;
|
|
25
16
|
|
|
@@ -33,8 +24,9 @@ http {
|
|
|
33
24
|
access_log /dev/stdout;
|
|
34
25
|
listen 8099 default_server;
|
|
35
26
|
server_name localhost;
|
|
36
|
-
root /app/featurehub;
|
|
37
|
-
passenger_app_root /app/featurehub;
|
|
27
|
+
root /app/featurehub/examples/sinatra;
|
|
28
|
+
passenger_app_root /app/featurehub/examples/sinatra;
|
|
29
|
+
passenger_preload_bundler on;
|
|
38
30
|
passenger_enabled on;
|
|
39
31
|
passenger_startup_file config.ru;
|
|
40
32
|
passenger_app_type rack;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
server {
|
|
2
|
+
listen 80;
|
|
3
|
+
server_name localhost;
|
|
4
|
+
root /app/featurehub/examples/sinatra;
|
|
5
|
+
|
|
6
|
+
passenger_enabled on;
|
|
7
|
+
passenger_user root;
|
|
8
|
+
|
|
9
|
+
# If this is a Ruby app, specify a Ruby version:
|
|
10
|
+
# For Ruby 3.3
|
|
11
|
+
passenger_ruby /usr/bin/ruby3.3;
|
|
12
|
+
|
|
13
|
+
# Nginx has a default limit of 1 MB for request bodies, which also applies
|
|
14
|
+
# to file uploads. The following line enables uploads of up to 50 MB:
|
|
15
|
+
#client_max_body_size 50M;
|
|
16
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
services:
|
|
2
2
|
party-server:
|
|
3
|
-
image: featurehub/party-server:
|
|
3
|
+
image: featurehub/party-server:latest
|
|
4
4
|
ports:
|
|
5
5
|
- 8085:8085
|
|
6
6
|
user: 999:999
|
|
@@ -12,13 +12,41 @@ services:
|
|
|
12
12
|
image: redis:latest
|
|
13
13
|
ports:
|
|
14
14
|
- 6379:6379
|
|
15
|
-
|
|
16
|
-
image:
|
|
15
|
+
memcache:
|
|
16
|
+
image: memcached:1.6-alpine
|
|
17
|
+
ports:
|
|
18
|
+
- 11211:11211
|
|
19
|
+
sinatra:
|
|
20
|
+
image: todo-server:e2e
|
|
21
|
+
ports:
|
|
22
|
+
- 8099:8099
|
|
23
|
+
environment:
|
|
24
|
+
RAILS_ENV: production
|
|
25
|
+
FEATUREHUB_CLIENT_API_KEY: "a1286d73-a23b-4f5b-8fdf-04dd03668fb9/xPlhDYnWlKjjZ3maUPquhOUOmk50gn*Hh8Q5qY6JEURrxtEEjzk"
|
|
26
|
+
FEATUREHUB_EDGE_URL: http://party-server:8085
|
|
27
|
+
FEATUREHUB_BASE_URL: http://party-server:8085
|
|
28
|
+
sinatra-redis:
|
|
29
|
+
image: todo-server:e2e
|
|
30
|
+
ports:
|
|
31
|
+
- 8099:8099
|
|
32
|
+
environment:
|
|
33
|
+
FEATUREHUB_POLLING_INTERVAL: "3"
|
|
34
|
+
FEATUREHUB_CLIENT_API_KEY: "a1286d73-a23b-4f5b-8fdf-04dd03668fb9/xPlhDYnWlKjjZ3maUPquhOUOmk50gn*Hh8Q5qY6JEURrxtEEjzk"
|
|
35
|
+
FEATUREHUB_EDGE_URL: http://party-server:8085
|
|
36
|
+
FEATUREHUB_BASE_URL: http://party-server:8085
|
|
37
|
+
FEATUREHUB_REDIS_STORE: redis://redis:6379
|
|
38
|
+
sinatra-memcache:
|
|
39
|
+
image: todo-server:e2e
|
|
17
40
|
ports:
|
|
18
41
|
- 8099:8099
|
|
19
42
|
environment:
|
|
43
|
+
RAILS_ENV: production
|
|
44
|
+
FEATUREHUB_POLLING_INTERVAL: "3"
|
|
45
|
+
FEATUREHUB_CLIENT_API_KEY: "a1286d73-a23b-4f5b-8fdf-04dd03668fb9/xPlhDYnWlKjjZ3maUPquhOUOmk50gn*Hh8Q5qY6JEURrxtEEjzk"
|
|
20
46
|
FEATUREHUB_EDGE_URL: http://party-server:8085
|
|
21
47
|
FEATUREHUB_BASE_URL: http://party-server:8085
|
|
22
|
-
|
|
48
|
+
FEATUREHUB_MEMCACHE_STORE: memcache
|
|
49
|
+
depends_on:
|
|
50
|
+
- memcache
|
|
23
51
|
volumes:
|
|
24
52
|
featurehub-h2-data:
|