-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Eren Atas <[email protected]> Reviewed-by: Alan dos Santos <[email protected]> Reviewed-by: Kaan Karakaya <[email protected]> Reviewed-by: Vinicius Gobbo <[email protected]>
- Loading branch information
Showing
46 changed files
with
6,505 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[submodule "crates/flagd/flagd-testbed"] | ||
path = crates/flagd/flagd-testbed | ||
url = https://github.com/open-feature/flagd-testbed.git | ||
[submodule "crates/flagd/schemas"] | ||
path = crates/flagd/schemas | ||
url = https://github.com/open-feature/flagd-schemas |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,45 @@ | ||
[package] | ||
name = "open-feature-flagd" | ||
version = "0.1.0" | ||
version = "0.0.1" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[build-dependencies] | ||
tonic-build = "0.12" | ||
prost = "0.13" | ||
prost-build = "0.13" | ||
|
||
[dev-dependencies] | ||
cucumber = "0.21" | ||
tokio-stream = "0.1" | ||
futures-core = "0.3" | ||
testcontainers = { version = "0.23.1", features = ["http_wait", "blocking"] } | ||
wiremock = "0.6.2" | ||
tempfile = "3.3.1" | ||
serial_test = "3.2" | ||
tracing-test = "0.2" | ||
test-log = { version = "0.2", features = ["trace"] } | ||
|
||
[dependencies] | ||
open-feature = "0.2" | ||
open-feature = "0.2" | ||
async-trait = "0.1" | ||
tonic = { version = "0.12", features = ["tls"] } | ||
prost = "0.13" | ||
prost-types = "0.13" | ||
tokio = { version = "1.0", features = ["full"] } | ||
serde_json = "1.0" | ||
serde = { version = "1.0", features = ["derive"] } | ||
lru = "0.13" | ||
futures = "0.3" | ||
reqwest = { version = "0.12", features = ["json", "stream"] } | ||
tracing = "0.1" | ||
tracing-subscriber = "0.3" | ||
anyhow = "1.0.95" | ||
regex = "1.11.1" | ||
semver = "1.0.25" | ||
murmurhash3 = "0.0.5" | ||
tower = "0.5" | ||
hyper-util = { version = "0.1", features = ["tokio"] } | ||
thiserror = "2.0" | ||
datalogic-rs = "2.0.16" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
[Generated by cargo-readme: `cargo readme --no-title > README.md`]:: | ||
# flagd Provider for OpenFeature | ||
|
||
A Rust implementation of the OpenFeature provider for flagd, enabling dynamic | ||
feature flag evaluation in your applications. | ||
|
||
This provider supports multiple evaluation modes, advanced targeting rules, caching strategies, | ||
and connection management. It is designed to work seamlessly with the OpenFeature SDK and the flagd service. | ||
|
||
### Core Features | ||
|
||
- **Multiple Evaluation Modes** | ||
- **RPC Resolver (Remote Evaluation):** Uses gRPC to perform flag evaluations remotely at a flagd instance. Supports bi-directional streaming, retry backoff, and custom name resolution (including Envoy support). | ||
- **REST Resolver:** Uses the OpenFeature Remote Evaluation Protocol (OFREP) over HTTP to evaluate flags. | ||
- **In-Process Resolver:** Performs evaluations locally using an embedded evaluation engine. Flag configurations can be retrieved via gRPC (sync mode). | ||
- **File Resolver:** Operates entirely from a flag definition file, updating on file changes in a best-effort manner. | ||
|
||
- **Advanced Targeting** | ||
- **Fractional Rollouts:** Uses consistent hashing (implemented via murmurhash3) to split traffic between flag variants in configurable proportions. | ||
- **Semantic Versioning:** Compare values using common operators such as '=', '!=', '<', '<=', '>', '>=', '^', and '~'. | ||
- **String Operations:** Custom operators for performing “starts_with” and “ends_with” comparisons. | ||
- **Complex Targeting Rules:** Leverages JSONLogic and custom operators to support nested conditions and dynamic evaluation. | ||
|
||
- **Caching Strategies** | ||
- Built-in support for LRU caching as well as an in-memory alternative. Flag evaluation results can be cached and later returned with a “CACHED” reason until the configuration updates. | ||
|
||
- **Connection Management** | ||
- Automatic connection establishment with configurable retries, timeout settings, and custom TLS or Unix-socket options. | ||
- Support for upstream name resolution including a custom resolver for Envoy proxy integration. | ||
|
||
### Installation | ||
Add the dependency in your `Cargo.toml`: | ||
```toml | ||
[dependencies] | ||
open-feature-flagd = "0.0.1" | ||
open-feature = "0.2" | ||
``` | ||
Then integrate it into your application: | ||
|
||
```rust | ||
use open_feature_flagd::{FlagdOptions, FlagdProvider, ResolverType}; | ||
use open_feature::provider::FeatureProvider; | ||
use open_feature::EvaluationContext; | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
// Example using the REST resolver mode. | ||
let provider = FlagdProvider::new(FlagdOptions { | ||
host: "localhost".to_string(), | ||
port: 8016, | ||
resolver_type: ResolverType::Rest, | ||
..Default::default() | ||
}).await.unwrap(); | ||
|
||
let context = EvaluationContext::default().with_targeting_key("user-123"); | ||
let result = provider.resolve_bool_value("bool-flag", &context).await.unwrap(); | ||
println!("Flag value: {}", result.value); | ||
} | ||
``` | ||
|
||
### Evaluation Modes | ||
#### Remote Resolver (RPC) | ||
In RPC mode, the provider communicates with flagd via gRPC. It supports features like streaming updates, retry mechanisms, and name resolution (including Envoy). | ||
|
||
```rust | ||
use open_feature_flagd::{FlagdOptions, FlagdProvider, ResolverType}; | ||
use open_feature::provider::FeatureProvider; | ||
use open_feature::EvaluationContext; | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
let provider = FlagdProvider::new(FlagdOptions { | ||
host: "localhost".to_string(), | ||
port: 8013, | ||
resolver_type: ResolverType::Rpc, | ||
..Default::default() | ||
}).await.unwrap(); | ||
|
||
let context = EvaluationContext::default().with_targeting_key("user-123"); | ||
let bool_result = provider.resolve_bool_value("feature-enabled", &context).await.unwrap(); | ||
println!("Feature enabled: {}", bool_result.value); | ||
} | ||
``` | ||
|
||
#### REST Resolver | ||
In REST mode the provider uses the OpenFeature Remote Evaluation Protocol (OFREP) over HTTP. | ||
It is useful when gRPC is not an option. | ||
```rust | ||
use open_feature_flagd::{FlagdOptions, FlagdProvider, ResolverType}; | ||
use open_feature::provider::FeatureProvider; | ||
use open_feature::EvaluationContext; | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
let provider = FlagdProvider::new(FlagdOptions { | ||
host: "localhost".to_string(), | ||
port: 8016, | ||
resolver_type: ResolverType::Rest, | ||
..Default::default() | ||
}).await.unwrap(); | ||
|
||
let context = EvaluationContext::default().with_targeting_key("user-456"); | ||
let result = provider.resolve_string_value("feature-variant", &context).await.unwrap(); | ||
println!("Variant: {}", result.value); | ||
} | ||
``` | ||
|
||
#### In-Process Resolver | ||
In-process evaluation is performed locally. Flag configurations are sourced via gRPC sync stream. | ||
This mode supports advanced targeting operators (fractional, semver, string comparisons) | ||
using the built-in evaluation engine. | ||
```rust | ||
use open_feature_flagd::{CacheSettings, FlagdOptions, FlagdProvider, ResolverType}; | ||
use open_feature::provider::FeatureProvider; | ||
use open_feature::EvaluationContext; | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
let provider = FlagdProvider::new(FlagdOptions { | ||
host: "localhost".to_string(), | ||
port: 8015, | ||
resolver_type: ResolverType::InProcess, | ||
selector: Some("my-service".to_string()), | ||
cache_settings: Some(CacheSettings::default()), | ||
..Default::default() | ||
}).await.unwrap(); | ||
|
||
let context = EvaluationContext::default() | ||
.with_targeting_key("user-abc") | ||
.with_custom_field("environment", "production") | ||
.with_custom_field("semver", "2.1.0"); | ||
|
||
let dark_mode = provider.resolve_bool_value("dark-mode", &context).await.unwrap(); | ||
println!("Dark mode enabled: {}", dark_mode.value); | ||
} | ||
``` | ||
|
||
#### File Mode | ||
File mode is an in-process variant where flag configurations are read from a file. | ||
This is useful for development or environments without network access. | ||
```rust | ||
use open_feature_flagd::{FlagdOptions, FlagdProvider, ResolverType}; | ||
use open_feature::provider::FeatureProvider; | ||
use open_feature::EvaluationContext; | ||
|
||
#[tokio::main] | ||
async fn main() { | ||
let file_path = "./path/to/flagd-config.json".to_string(); | ||
let provider = FlagdProvider::new(FlagdOptions { | ||
host: "localhost".to_string(), | ||
resolver_type: ResolverType::File, | ||
source_configuration: Some(file_path), | ||
..Default::default() | ||
}).await.unwrap(); | ||
|
||
let context = EvaluationContext::default(); | ||
let result = provider.resolve_int_value("rollout-percentage", &context).await.unwrap(); | ||
println!("Rollout percentage: {}", result.value); | ||
} | ||
``` | ||
|
||
### Configuration Options | ||
Configurations can be provided as constructor options or via environment variables (with constructor options taking priority). The following options are supported: | ||
|
||
| Option | Env Variable | Type / Supported Value | Default | Compatible Resolver | | ||
|-----------------------------------------|-----------------------------------------|-----------------------------------|-------------------------------------|--------------------------------| | ||
| Host | FLAGD_HOST | string | "localhost" | RPC, REST, In-Process, File | | ||
| Port | FLAGD_PORT | number | 8013 (RPC), 8016 (REST) | RPC, REST, In-Process, File | | ||
| Target URI | FLAGD_TARGET_URI | string | "" | RPC, In-Process | | ||
| TLS | FLAGD_TLS | boolean | false | RPC, In-Process | | ||
| Socket Path | FLAGD_SOCKET_PATH | string | "" | RPC | | ||
| Certificate Path | FLAGD_SERVER_CERT_PATH | string | "" | RPC, In-Process | | ||
| Cache Type (LRU / In-Memory / Disabled) | FLAGD_CACHE | string ("lru", "mem", "disabled") | lru | RPC, In-Process, File | | ||
| Cache TTL (Seconds) | FLAGD_CACHE_TTL | number | 60 | RPC, In-Process, File | | ||
| Max Cache Size | FLAGD_MAX_CACHE_SIZE | number | 1000 | RPC, In-Process, File | | ||
| Offline File Path | FLAGD_OFFLINE_FLAG_SOURCE_PATH | string | "" | File | | ||
| Retry Backoff (ms) | FLAGD_RETRY_BACKOFF_MS | number | 1000 | RPC, In-Process | | ||
| Retry Backoff Maximum (ms) | FLAGD_RETRY_BACKOFF_MAX_MS | number | 120000 | RPC, In-Process | | ||
| Retry Grace Period | FLAGD_RETRY_GRACE_PERIOD | number | 5 | RPC, In-Process | | ||
| Event Stream Deadline (ms) | FLAGD_STREAM_DEADLINE_MS | number | 600000 | RPC | | ||
| Offline Poll Interval (ms) | FLAGD_OFFLINE_POLL_MS | number | 5000 | File | | ||
| Source Selector | FLAGD_SOURCE_SELECTOR | string | "" | In-Process | | ||
|
||
### License | ||
Apache 2.0 - See [LICENSE](./../../LICENSE) for more information. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
Missing Features compared to Golang version: | ||
|
||
- Custom connector interface | ||
- Unix socket support for in-process evaluation (CANNOT FIND THIS IN FLAGD IMPLEMENTATION, SOCKET ONLY OPENS FOR GRPC EVALUATION, NOT SYNC) | ||
- Test coverage | ||
- E2E Testing selector |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
fn main() { | ||
let out_dir = std::env::var("OUT_DIR").unwrap(); | ||
|
||
tonic_build::configure() | ||
.build_server(true) | ||
.out_dir(&out_dir) | ||
.compile_protos( | ||
&[ | ||
"schemas/protobuf/flagd/evaluation/v1/evaluation.proto", | ||
"schemas/protobuf/flagd/sync/v1/sync.proto", | ||
], | ||
&["schemas/protobuf/"], | ||
) | ||
.unwrap(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Contributing | ||
## Development | ||
After cloning the repository, you first need to update git odules: | ||
```bash | ||
pushd rust-sdk-contrib/crates/flagd | ||
# Update and pull git submodules | ||
git submodule update --init | ||
``` | ||
Afterwards, you need to install `protoc`: | ||
- For MacOS: `brew install protobuf` | ||
- For Fedora: `dnf install protobuf protobuf-devel` | ||
|
||
Once steps mentioned above are done, `cargo build` will build crate. | ||
|
||
## Testing | ||
To run tests across a flagd server, `testcontainers-rs` crate been used to spin up containers. `Docker` is needed to be alled to run E2E tests. | ||
> At the time of writing, `podman` was tested and did not work. | ||
If it is not possible to access docker, unit tests can be run : | ||
```bash | ||
cargo test --lib | ||
``` | ||
|
||
open-feature-flagd uses `test-log` to have tracing logs table. To have full visibility on test logs, you can use: | ||
|
||
```bash | ||
RUST_LOG_SPAN_EVENTS=full RUST_LOG=debug cargo test -- --nocapture | ||
``` |
Submodule flagd-testbed
added at
9d35a0
Oops, something went wrong.