Skip to content
Open
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
14 changes: 14 additions & 0 deletions src/flyte/remote/_client/auth/_session.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
import os
import socket
import typing
from dataclasses import dataclass
Expand All @@ -18,6 +19,9 @@
get_async_proxy_authenticator,
)

_USE_PYQWEST_DNS_RESOLVER_ENV = "_FLYTE_USE_PYQWEST_DNS_RESOLVER"
_TRUE_ENV_VALUES = frozenset({"1", "true", "yes", "on"})


@dataclass(frozen=True)
class SessionConfig:
Expand Down Expand Up @@ -142,15 +146,25 @@ async def _resolve_tls_ca_cert(
return None


def _use_system_dns() -> bool:
return os.environ.get(_USE_PYQWEST_DNS_RESOLVER_ENV, "").lower() not in _TRUE_ENV_VALUES


def _build_pyqwest_client(tls_ca_cert: bytes | None = None) -> pyqwest.Client:
"""Build a pyqwest Client with sensible transport defaults."""
use_system_dns = _use_system_dns()
transport = pyqwest.HTTPTransport(
tls_ca_cert=tls_ca_cert,
timeout=None,
connect_timeout=30.0,
read_timeout=None,
pool_idle_timeout=90.0,
tcp_keepalive_interval=30.0, # was grpc.keepalive_time_ms = 30000
# Use the OS resolver by default so Flyte matches curl/browser behavior
# on VPNs, split-DNS setups, captive portals, and broken IPv6 networks.
# Server deployments can set _FLYTE_USE_PYQWEST_DNS_RESOLVER=true to opt
# back into pyqwest's bundled resolver for app-owned DNS behavior.
use_system_dns=use_system_dns,
)
return pyqwest.Client(transport=transport)

Expand Down
30 changes: 30 additions & 0 deletions tests/flyte/remote/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,36 @@ def test_with_valid_cert_returns_pyqwest_client(self):
client = _build_pyqwest_client(cert_pem)
assert isinstance(client, pyqwest.Client)

@patch("flyte.remote._client.auth._session.pyqwest.Client")
@patch("flyte.remote._client.auth._session.pyqwest.HTTPTransport")
def test_uses_system_dns_by_default(self, mock_transport, mock_client, monkeypatch):
monkeypatch.delenv("_FLYTE_USE_PYQWEST_DNS_RESOLVER", raising=False)

_build_pyqwest_client(None)

mock_transport.assert_called_once()
assert mock_transport.call_args.kwargs["use_system_dns"] is True
mock_client.assert_called_once_with(transport=mock_transport.return_value)

@pytest.mark.parametrize("value", ["1", "true", "TRUE", "yes", "on"])
@patch("flyte.remote._client.auth._session.pyqwest.Client")
@patch("flyte.remote._client.auth._session.pyqwest.HTTPTransport")
def test_can_opt_into_pyqwest_dns_resolver(self, mock_transport, mock_client, monkeypatch, value):
monkeypatch.setenv("_FLYTE_USE_PYQWEST_DNS_RESOLVER", value)

_build_pyqwest_client(None)

assert mock_transport.call_args.kwargs["use_system_dns"] is False

@patch("flyte.remote._client.auth._session.pyqwest.Client")
@patch("flyte.remote._client.auth._session.pyqwest.HTTPTransport")
def test_ignores_falsey_pyqwest_dns_resolver_env(self, mock_transport, mock_client, monkeypatch):
monkeypatch.setenv("_FLYTE_USE_PYQWEST_DNS_RESOLVER", "false")

_build_pyqwest_client(None)

assert mock_transport.call_args.kwargs["use_system_dns"] is True


_SESSION_MOD = "flyte.remote._client.auth._session"

Expand Down
Loading