Skip to content
Closed
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
12 changes: 4 additions & 8 deletions e2e/backends/docker_compose.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
from typing import Literal, TextIO

import httpx
from nemo_platform_ext.client.tls import NMP_CLIENT_SSL_CERT_FILE_ENVVAR
from nemo_platform_plugin.client.tls import NMP_CLIENT_SSL_CERT_FILE_ENVVAR, httpx_tls_config_from_env

ComposeLifecycle = Literal["fresh", "reuse"]
_DIAGNOSTIC_COMMAND_TIMEOUT_SECONDS = 60
_CA_BUNDLE_ENVVARS = (NMP_CLIENT_SSL_CERT_FILE_ENVVAR, "REQUESTS_CA_BUNDLE", "SSL_CERT_FILE")


def _compose_env(env: dict[str, str] | None) -> dict[str, str]:
Expand Down Expand Up @@ -164,19 +165,14 @@ def start(self) -> None:
self._wait_ready()

def _wait_ready(self) -> None:
verify = (
self.env.get(NMP_CLIENT_SSL_CERT_FILE_ENVVAR)
or self.env.get("REQUESTS_CA_BUNDLE")
or self.env.get("SSL_CERT_FILE")
or True
)
tls_config = httpx_tls_config_from_env(self.env, cert_file_envvars=_CA_BUNDLE_ENVVARS)
deadline = time.monotonic() + self.wait_timeout_seconds
pending = list(dict.fromkeys(self.wait_urls))
last_results: dict[str, str] = {}
while time.monotonic() < deadline and pending:
for wait_url in list(pending):
try:
response = httpx.get(wait_url, timeout=5, verify=verify)
response = httpx.get(wait_url, timeout=5, **tls_config)
if response.status_code == 200:
pending.remove(wait_url)
else:
Expand Down
18 changes: 4 additions & 14 deletions e2e/services_pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import pytest
import yaml
from _pytest.nodes import Node
from nemo_platform_plugin.client.tls import NMP_CLIENT_SSL_CERT_FILE_ENVVAR, httpx_tls_config_from_env
from nmp.testing.e2e import Docker as DockerE2EBackend
from nmp.testing.e2e.config import deep_merge

Expand All @@ -42,6 +43,7 @@
# deployments orphan cleanup cannot delete peer platforms' docker containers.
_DEFAULT_E2E_DISABLE_DEPLOYMENTS_ORPHAN_CLEANUP = _E2E_REPO_ROOT / "e2e/configs/disable-deployments-orphan-cleanup.yaml"
_E2E_COMPOSE_LIFECYCLE_ENV = "NMP_E2E_COMPOSE_LIFECYCLE"
_CA_BUNDLE_ENVVARS = (NMP_CLIENT_SSL_CERT_FILE_ENVVAR, "REQUESTS_CA_BUNDLE", "SSL_CERT_FILE")


def admin_headers() -> dict[str, str]:
Expand Down Expand Up @@ -780,18 +782,6 @@ def _wait_for_auth_ready(url: str, proc: subprocess.Popen[Any] | None, timeout:
return False


def _request_verify_from_env(env: Mapping[str, str] | None = None) -> str | bool:
source = dict(os.environ)
if env:
source.update(env)
return (
source.get("NMP_CLIENT_SSL_CERT_FILE")
or source.get("REQUESTS_CA_BUNDLE")
or source.get("SSL_CERT_FILE")
or True
)


def _wait_for_auth_ready_url(
url: str,
proc: subprocess.Popen[Any] | None,
Expand All @@ -800,12 +790,12 @@ def _wait_for_auth_ready_url(
timeout: float = _AUTH_READY_TIMEOUT,
) -> bool:
deadline = time.monotonic() + timeout
verify = _request_verify_from_env(env)
tls_config = httpx_tls_config_from_env(env, cert_file_envvars=_CA_BUNDLE_ENVVARS)
while time.monotonic() < deadline:
if proc is not None and _process_exited(proc):
return False
try:
response = httpx.get(url, timeout=5.0, verify=verify)
response = httpx.get(url, timeout=5.0, **tls_config)
if response.status_code == 200:
return True
except httpx.RequestError as exc:
Expand Down
4 changes: 0 additions & 4 deletions packages/nemo_platform/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -665,10 +665,6 @@ customization = "nemo_customizer.cli:CustomizationCLI"
experimentalist = "nemo_experimentalist_plugin.cli:ExperimentalistCLI"
analyst = "nemo_insights_plugin.analyst.cli:AnalystCLI"

# Generated from [tool.bundle-package]; do not edit this table by hand.
[project.entry-points."nemo.client_provider"]
platform = "nmp.common.client_factory:PlatformNemoClientProvider"

# Generated from [tool.bundle-package]; do not edit this table by hand.
[project.entry-points."nemo.controllers"]
agents-deployment = "nemo_agents_plugin.runner.controller:AgentDeploymentController"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@
from dataclasses import dataclass

import httpx
from nemo_platform_plugin.client.tls import httpx_tls_config_from_env
from rich.console import Console
from rich.panel import Panel

from nemo_platform_ext.auth.token_provider import refresh_token_grant
from nemo_platform_ext.client.tls import client_verify_from_env

console = Console()

Expand Down Expand Up @@ -74,7 +74,7 @@ def __init__(

async def start_device_authorization(self) -> DeviceCodeResponse:
"""Start the device authorization flow."""
async with httpx.AsyncClient(verify=client_verify_from_env()) as client:
async with httpx.AsyncClient(**httpx_tls_config_from_env()) as client:
response = await client.post(
self.device_authorization_endpoint,
data={
Expand Down Expand Up @@ -104,7 +104,7 @@ async def poll_for_token(
"""Poll the token endpoint until authorization is complete."""
start_time = time.time()

async with httpx.AsyncClient(verify=client_verify_from_env()) as client:
async with httpx.AsyncClient(**httpx_tls_config_from_env()) as client:
while time.time() - start_time < expires_in:
await _async_pause(interval)

Expand Down Expand Up @@ -284,7 +284,7 @@ def authenticate_with_password_grant(
"password": password,
"scope": scope,
}
with httpx.Client(verify=client_verify_from_env()) as client:
with httpx.Client(**httpx_tls_config_from_env()) as client:
response = client.post(token_endpoint, data=data, timeout=30.0)

if response.status_code != 200:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@
from typing import Any

import httpx

from nemo_platform_ext.client.tls import client_verify_from_env
from nemo_platform_plugin.client.tls import httpx_tls_config_from_env

DEFAULT_OAUTH_SCOPES = "openid profile email offline_access"

Expand Down Expand Up @@ -157,7 +156,7 @@ def discover_nmp_config(base_url: str, timeout: float = 10.0) -> NMPOIDCConfig:
response = httpx.get(
f"{base_url.rstrip('/')}/apis/auth/discovery",
timeout=timeout,
verify=client_verify_from_env(),
**httpx_tls_config_from_env(),
)
response.raise_for_status()
data = response.json()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
from dataclasses import dataclass, field

import httpx
from nemo_platform_plugin.client.tls import httpx_tls_config_from_env
from typing_extensions import Self

from nemo_platform_ext.auth.helpers import decode_jwt_claims
from nemo_platform_ext.client.tls import client_verify_from_env

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -56,7 +56,7 @@ def refresh_token_grant(
if scope:
data["scope"] = scope

response = httpx.post(token_endpoint, data=data, timeout=timeout, verify=client_verify_from_env())
response = httpx.post(token_endpoint, data=data, timeout=timeout, **httpx_tls_config_from_env())

if response.status_code != 200:
error_data: dict[str, str] = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@
WORKLOAD_IDENTITY_TOKEN_FILE_ENVVAR,
subject_token_type_for_exchange,
)
from nemo_platform_plugin.client.tls import httpx_tls_config_from_env

from nemo_platform_ext.auth.token_provider import DEFAULT_REFRESH_MARGIN_SECONDS, TokenSet
from nemo_platform_ext.client.tls import client_verify_from_env

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -103,7 +103,7 @@ def token_exchange_grant(
if scope:
data["scope"] = scope

response = httpx.post(token_endpoint, data=data, timeout=timeout, verify=client_verify_from_env())
response = httpx.post(token_endpoint, data=data, timeout=timeout, **httpx_tls_config_from_env())

if response.status_code != 200:
error_data: dict[str, object] = {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from nemo_platform import NeMoPlatform
from nemo_platform_plugin.capabilities import probe_docker
from nemo_platform_plugin.client.adapter import client_from_platform
from nemo_platform_plugin.client.tls import HttpxTLSConfig, httpx_tls_config_from_env
from nemo_platform_plugin.secrets.client import SecretsClient
from nemo_platform_plugin.secrets.types import PlatformSecretCreateRequest, PlatformSecretUpdateRequest
from nemo_platform_plugin.workspaces.client import WorkspacesClient
Expand All @@ -49,7 +50,6 @@
from nemo_platform_ext.cli.docker_preflight import DOCKER_PREFLIGHT_MESSAGE, require_docker_for_default_local
from nemo_platform_ext.cli.telemetry import emit
from nemo_platform_ext.cli.telemetry.events import OnboardingStepEvent, TaskStatusEnum
from nemo_platform_ext.client.tls import client_verify_from_env
from nemo_platform_ext.config.config import Config
from nemo_platform_ext.config.models import DEFAULT_BASE_URL, ConfigFile, ConfigParams, LocalServicesConfig, NoAuthUser
from nemo_platform_ext.local.install import services_extra_install_command
Expand Down Expand Up @@ -323,11 +323,11 @@ def _check_platform_reachable(base_url: str, timeout: float = 5.0) -> bool:
Local ``nemo services run`` publishes ``/status``. Hosted deployments may
only expose ``/cluster-info`` on ingress, so try both.
"""
verify = client_verify_from_env()
tls_config = httpx_tls_config_from_env()
root = base_url.rstrip("/")
for path in _PLATFORM_REACHABILITY_PATHS:
try:
resp = httpx.get(f"{root}{path}", timeout=timeout, verify=verify)
resp = httpx.get(f"{root}{path}", timeout=timeout, **tls_config)
if resp.status_code == 200:
return True
except Exception:
Expand Down Expand Up @@ -466,10 +466,10 @@ def _platform_request_headers(cli_context: CLIContext) -> dict[str, str] | None:
return {key: value for key, value in headers.items() if isinstance(key, str) and isinstance(value, str)}


def _hosted_platform_without_status(base_url: str, *, timeout: float, verify: str | bool) -> bool:
def _hosted_platform_without_status(base_url: str, *, timeout: float, tls_config: HttpxTLSConfig) -> bool:
"""Return True when ``/cluster-info`` confirms a hosted platform that omits ``/status``."""
try:
resp = httpx.get(f"{base_url.rstrip('/')}/cluster-info", timeout=timeout, verify=verify)
resp = httpx.get(f"{base_url.rstrip('/')}/cluster-info", timeout=timeout, **tls_config)
except Exception:
return False
return resp.status_code == 200
Expand All @@ -485,13 +485,13 @@ def _check_controller_health(base_url: str, timeout: float = 5.0) -> tuple[bool,
If ``controllers.status`` is empty on the first call (startup timing race),
waits ``_CONTROLLER_HEALTH_RETRY_DELAY`` seconds and retries once.
"""
verify = client_verify_from_env()
tls_config = httpx_tls_config_from_env()
root = base_url.rstrip("/")
for attempt in range(2):
try:
resp = httpx.get(f"{root}/status", timeout=timeout, verify=verify)
resp = httpx.get(f"{root}/status", timeout=timeout, **tls_config)
if resp.status_code == 404:
if _hosted_platform_without_status(root, timeout=timeout, verify=verify):
if _hosted_platform_without_status(root, timeout=timeout, tls_config=tls_config):
return True, "Hosted deployment does not publish /status."
return False, "Unexpected status 404 from /status endpoint."
if resp.status_code != 200:
Expand Down
Loading
Loading