Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions providers/openfeature-provider-flagd/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,12 @@ api.set_provider(FlagdProvider(
The default options can be defined in the FlagdProvider constructor.

| Option name | Environment variable name | Type & Values | Default | Compatible resolver |
| ------------------------ | ------------------------------ | -------------------------- | ----------------------------- | ------------------- |
|--------------------------|--------------------------------|----------------------------|-------------------------------|---------------------|
| resolver_type | FLAGD_RESOLVER | enum - `rpc`, `in-process` | rpc | |
| host | FLAGD_HOST | str | localhost | rpc & in-process |
| port | FLAGD_PORT | int | 8013 (rpc), 8015 (in-process) | rpc & in-process |
| tls | FLAGD_TLS | bool | false | rpc & in-process |
| cert_path | FLAGD_SERVER_CERT_PATH | String | null | rpc & in-process |
| deadline | FLAGD_DEADLINE_MS | int | 500 | rpc & in-process |
| stream_deadline_ms | FLAGD_STREAM_DEADLINE_MS | int | 600000 | rpc & in-process |
| keep_alive_time | FLAGD_KEEP_ALIVE_TIME_MS | int | 0 | rpc & in-process |
Expand All @@ -64,8 +65,6 @@ The default options can be defined in the FlagdProvider constructor.
<!-- not implemented
| target_uri | FLAGD_TARGET_URI | alternative to host/port, supporting custom name resolution | string | null | rpc & in-process |
| socket_path | FLAGD_SOCKET_PATH | alternative to host port, unix socket | String | null | rpc & in-process |
| cert_path | FLAGD_SERVER_CERT_PATH | tls cert path | String | null | rpc & in-process |
| max_event_stream_retries | FLAGD_MAX_EVENT_STREAM_RETRIES | int | 5 | rpc |
| context_enricher | - | sync-metadata to evaluation context mapping function | function | identity function | in-process |
| offline_pollIntervalMs | FLAGD_OFFLINE_POLL_MS | poll interval for reading offlineFlagSourcePath | int | 5000 | in-process |
-->
Expand Down Expand Up @@ -100,17 +99,18 @@ and the evaluation will default.

TLS is available in situations where flagd is running on another host.

<!--

You may optionally supply an X.509 certificate in PEM format. Otherwise, the default certificate store will be used.
```java
FlagdProvider flagdProvider = new FlagdProvider(
FlagdOptions.builder()
.host("myflagdhost")
.tls(true) // use TLS
.certPath("etc/cert/ca.crt") // PEM cert
.build());

```python
from openfeature import api
from openfeature.contrib.provider.flagd import FlagdProvider

api.set_provider(FlagdProvider(
tls=True, # use TLS
cert_path="etc/cert/ca.crt" # PEM cert
))
```
-->

## License

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ class CacheType(Enum):
DEFAULT_RETRY_GRACE_PERIOD_SECONDS = 5
DEFAULT_STREAM_DEADLINE = 600000
DEFAULT_TLS = False
DEFAULT_TLS_CERT: typing.Optional[str] = None

ENV_VAR_CACHE_SIZE = "FLAGD_MAX_CACHE_SIZE"
ENV_VAR_CACHE_TYPE = "FLAGD_CACHE"
Expand All @@ -44,6 +45,7 @@ class CacheType(Enum):
ENV_VAR_RETRY_GRACE_PERIOD_SECONDS = "FLAGD_RETRY_GRACE_PERIOD"
ENV_VAR_STREAM_DEADLINE_MS = "FLAGD_STREAM_DEADLINE_MS"
ENV_VAR_TLS = "FLAGD_TLS"
ENV_VAR_TLS_CERT = "FLAGD_SERVER_CERT_PATH"

T = typing.TypeVar("T")

Expand Down Expand Up @@ -87,6 +89,7 @@ def __init__( # noqa: PLR0913
keep_alive_time: typing.Optional[int] = None,
cache: typing.Optional[CacheType] = None,
max_cache_size: typing.Optional[int] = None,
cert_path: typing.Optional[str] = None,
):
self.host = env_or_default(ENV_VAR_HOST, DEFAULT_HOST) if host is None else host

Expand Down Expand Up @@ -200,3 +203,9 @@ def __init__( # noqa: PLR0913
if max_cache_size is None
else max_cache_size
)

self.cert_path = (
env_or_default(ENV_VAR_TLS_CERT, DEFAULT_TLS_CERT)
if cert_path is None
else cert_path
)
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ def __init__( # noqa: PLR0913
max_cache_size: typing.Optional[int] = None,
retry_backoff_max_ms: typing.Optional[int] = None,
retry_grace_period: typing.Optional[int] = None,
cert_path: typing.Optional[str] = None,
):
"""
Create an instance of the FlagdProvider
Expand Down Expand Up @@ -91,6 +92,7 @@ def __init__( # noqa: PLR0913
keep_alive_time=keep_alive_time,
cache=cache_type,
max_cache_size=max_cache_size,
cert_path=cert_path,
)

self.resolver = self.setup_resolver()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,25 +64,41 @@ def __init__(
self.streamline_deadline_seconds = config.stream_deadline_ms * 0.001
self.deadline = config.deadline_ms * 0.001
self.connected = False
channel_factory = grpc.secure_channel if config.tls else grpc.insecure_channel
self.channel = self._generate_channel(config)
self.stub = evaluation_pb2_grpc.ServiceStub(self.channel)

self.thread: typing.Optional[threading.Thread] = None
self.timer: typing.Optional[threading.Timer] = None

self.start_time = time.time()

def _generate_channel(self, config: Config) -> grpc.Channel:
target = f"{config.host}:{config.port}"
# Create the channel with the service config
options = [
("grpc.keepalive_time_ms", config.keep_alive_time),
("grpc.initial_reconnect_backoff_ms", config.retry_backoff_ms),
("grpc.max_reconnect_backoff_ms", config.retry_backoff_max_ms),
("grpc.min_reconnect_backoff_ms", config.deadline_ms),
]
self.channel = channel_factory(
f"{config.host}:{config.port}",
options=options,
)
self.stub = evaluation_pb2_grpc.ServiceStub(self.channel)

self.thread: typing.Optional[threading.Thread] = None
self.timer: typing.Optional[threading.Timer] = None
if config.tls:
channel_args = {
"options": options,
"credentials": grpc.ssl_channel_credentials(),
}
if config.cert_path:
with open(config.cert_path, "rb") as f:
channel_args["credentials"] = grpc.ssl_channel_credentials(f.read())

channel = grpc.secure_channel(target, **channel_args)

else:
channel = grpc.insecure_channel(
target,
options=options,
)

self.start_time = time.time()
return channel

def initialize(self, evaluation_context: EvaluationContext) -> None:
self.connect()
Expand Down
68 changes: 68 additions & 0 deletions providers/openfeature-provider-flagd/tests/e2e/test_rpc_ssl.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
from pathlib import Path

import pytest
from pytest_bdd import given, scenarios
from tests.e2e.conftest import SPEC_PATH
from tests.e2e.flagd_container import FlagdContainer
from tests.e2e.steps import wait_for

from openfeature import api
from openfeature.client import OpenFeatureClient
from openfeature.contrib.provider.flagd import FlagdProvider
from openfeature.contrib.provider.flagd.config import ResolverType
from openfeature.provider import ProviderStatus


@pytest.fixture(autouse=True, scope="module")
def client_name() -> str:
return "rpc"


@pytest.fixture(autouse=True, scope="module")
def resolver_type() -> ResolverType:
return ResolverType.RPC


@pytest.fixture(autouse=True, scope="module")
def port():
return 8013


@pytest.fixture(autouse=True, scope="module")
def image():
return "ghcr.io/open-feature/flagd-testbed-ssl"


@given("a flagd provider is set", target_fixture="client")
@given("a provider is registered", target_fixture="client")
def setup_provider(
container: FlagdContainer, resolver_type, client_name, port
) -> OpenFeatureClient:
try:
container.get_exposed_port(port)
except: # noqa: E722
container.start()

path = (
Path(__file__).parents[2] / "openfeature/test-harness/ssl/custom-root-cert.crt"
)

api.set_provider(
FlagdProvider(
resolver_type=resolver_type,
port=int(container.get_exposed_port(port)),
timeout=1,
retry_grace_period=3,
tls=True,
cert_path=str(path.absolute()),
),
client_name,
)
client = api.get_client(client_name)
wait_for(lambda: client.get_provider_status() == ProviderStatus.READY)
return client


scenarios(
f"{SPEC_PATH}/specification/assets/gherkin/evaluation.feature",
)
Loading