diff --git a/providers/openfeature-provider-flagd/README.md b/providers/openfeature-provider-flagd/README.md index 9cbb865e..fbade51f 100644 --- a/providers/openfeature-provider-flagd/README.md +++ b/providers/openfeature-provider-flagd/README.md @@ -81,7 +81,7 @@ The default options can be defined in the FlagdProvider constructor. |--------------------------|--------------------------------|----------------------------|-------------------------------|---------------------| | 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 | +| port | FLAGD_SYNC_PORT (in-process), FLAGD_PORT (rpc or fallback) | 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 | diff --git a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py index 2a21ba36..f2feb648 100644 --- a/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py +++ b/providers/openfeature-provider-flagd/src/openfeature/contrib/provider/flagd/config.py @@ -42,6 +42,7 @@ class CacheType(Enum): ENV_VAR_OFFLINE_FLAG_SOURCE_PATH = "FLAGD_OFFLINE_FLAG_SOURCE_PATH" ENV_VAR_OFFLINE_POLL_MS = "FLAGD_OFFLINE_POLL_MS" ENV_VAR_PORT = "FLAGD_PORT" +ENV_VAR_SYNC_PORT = "FLAGD_SYNC_PORT" ENV_VAR_RESOLVER_TYPE = "FLAGD_RESOLVER" ENV_VAR_RETRY_BACKOFF_MS = "FLAGD_RETRY_BACKOFF_MS" ENV_VAR_RETRY_BACKOFF_MAX_MS = "FLAGD_RETRY_BACKOFF_MAX_MS" @@ -79,7 +80,7 @@ def env_or_default( @dataclasses.dataclass class Config: - def __init__( # noqa: PLR0913 + def __init__( # noqa: PLR0913, PLR0915 self, host: typing.Optional[str] = None, port: typing.Optional[int] = None, @@ -149,17 +150,25 @@ def __init__( # noqa: PLR0913 else resolver ) - default_port = ( - DEFAULT_PORT_RPC - if self.resolver is ResolverType.RPC - else DEFAULT_PORT_IN_PROCESS - ) - - self.port: int = ( - int(env_or_default(ENV_VAR_PORT, default_port, cast=int)) - if port is None - else port - ) + # Port configuration with FLAGD_SYNC_PORT support for in-process mode + if port is None: + if self.resolver is ResolverType.IN_PROCESS: + # For in-process: try FLAGD_SYNC_PORT, then FLAGD_PORT (backwards compatibility), then default + port_from_env = os.environ.get(ENV_VAR_SYNC_PORT) or os.environ.get( + ENV_VAR_PORT + ) + self.port = ( + int(port_from_env) + if port_from_env is not None + else DEFAULT_PORT_IN_PROCESS + ) + else: + # For RPC: use FLAGD_PORT only + self.port = int( + env_or_default(ENV_VAR_PORT, DEFAULT_PORT_RPC, cast=int) + ) + else: + self.port = port self.offline_flag_source_path = ( env_or_default( diff --git a/providers/openfeature-provider-flagd/tests/test_config.py b/providers/openfeature-provider-flagd/tests/test_config.py index 510009d0..a671f8c1 100644 --- a/providers/openfeature-provider-flagd/tests/test_config.py +++ b/providers/openfeature-provider-flagd/tests/test_config.py @@ -23,6 +23,7 @@ ENV_VAR_PORT, ENV_VAR_RETRY_BACKOFF_MS, ENV_VAR_STREAM_DEADLINE_MS, + ENV_VAR_SYNC_PORT, ENV_VAR_TLS, CacheType, Config, @@ -147,3 +148,56 @@ def test_uses_arguments_over_environments_and_defaults(monkeypatch, resolver_typ assert config.retry_backoff_ms == retry_backoff assert config.stream_deadline_ms == stream_deadline assert config.tls is tls + + +def test_in_process_uses_sync_port(monkeypatch): + """Test that FLAGD_SYNC_PORT is used for in-process mode when set.""" + sync_port = 9999 + monkeypatch.setenv(ENV_VAR_SYNC_PORT, str(sync_port)) + + config = Config(resolver=ResolverType.IN_PROCESS) + assert config.port == sync_port + assert config.resolver == ResolverType.IN_PROCESS + + +def test_in_process_fallback_to_port(monkeypatch): + """Test that FLAGD_PORT is used as fallback when FLAGD_SYNC_PORT is not set.""" + port = 7777 + monkeypatch.setenv(ENV_VAR_PORT, str(port)) + + config = Config(resolver=ResolverType.IN_PROCESS) + assert config.port == port + assert config.resolver == ResolverType.IN_PROCESS + + +def test_in_process_sync_port_priority(monkeypatch): + """Test that FLAGD_SYNC_PORT takes priority over FLAGD_PORT when both are set.""" + sync_port = 9999 + port = 7777 + monkeypatch.setenv(ENV_VAR_SYNC_PORT, str(sync_port)) + monkeypatch.setenv(ENV_VAR_PORT, str(port)) + + config = Config(resolver=ResolverType.IN_PROCESS) + assert config.port == sync_port # FLAGD_SYNC_PORT should win + assert config.resolver == ResolverType.IN_PROCESS + + +def test_rpc_ignores_sync_port(monkeypatch): + """Test that RPC mode uses FLAGD_PORT and ignores FLAGD_SYNC_PORT.""" + sync_port = 9999 + port = 7777 + monkeypatch.setenv(ENV_VAR_SYNC_PORT, str(sync_port)) + monkeypatch.setenv(ENV_VAR_PORT, str(port)) + + config = Config(resolver=ResolverType.RPC) + assert config.port == port # RPC should use FLAGD_PORT + assert config.resolver == ResolverType.RPC + + +def test_in_process_default_port_when_no_env_var(monkeypatch): + """Test that in-process mode uses default port when neither env var is set.""" + monkeypatch.delenv(ENV_VAR_SYNC_PORT, raising=False) + monkeypatch.delenv(ENV_VAR_PORT, raising=False) + config = Config(resolver=ResolverType.IN_PROCESS) + assert config.port == DEFAULT_PORT_IN_PROCESS + assert config.resolver == ResolverType.IN_PROCESS