From f864ace32ca566a0fcec6cfc8f38d530135215dc Mon Sep 17 00:00:00 2001 From: pr-relay Date: Sun, 16 Aug 2026 12:48:45 +0000 Subject: [PATCH] fix: report TLS trust source in apm doctor --- src/apm_cli/commands/marketplace/doctor.py | 17 +++++++++++++ src/apm_cli/core/tls_trust.py | 20 +++++++++++++++ tests/conftest.py | 25 +++++++++++++++++++ .../unit/commands/test_marketplace_doctor.py | 21 ++++++++++++++++ 4 files changed, 83 insertions(+) diff --git a/src/apm_cli/commands/marketplace/doctor.py b/src/apm_cli/commands/marketplace/doctor.py index 014c742c7d..840e877bdc 100644 --- a/src/apm_cli/commands/marketplace/doctor.py +++ b/src/apm_cli/commands/marketplace/doctor.py @@ -195,6 +195,23 @@ def run_doctor(verbose: bool, *, logger_name: str = "doctor") -> int: ) ) + # Check 4: TLS trust source (informational) + try: + from ...core.tls_trust import describe_tls_trust + + tls_source, tls_precedence = describe_tls_trust() + tls_detail = f"{tls_source}; precedence: {tls_precedence}" + except Exception as exc: + tls_detail = f"Unable to determine TLS trust source: {str(exc)[:60]}" + checks.append( + _DoctorCheck( + name="TLS trust", + passed=True, + detail=tls_detail, + informational=True, + ) + ) + # Check 4: marketplace config presence + parsability project_root = Path.cwd() apm_path = project_root / "apm.yml" diff --git a/src/apm_cli/core/tls_trust.py b/src/apm_cli/core/tls_trust.py index ac77ef0ca6..95b80a1e1a 100644 --- a/src/apm_cli/core/tls_trust.py +++ b/src/apm_cli/core/tls_trust.py @@ -95,6 +95,26 @@ def log_tls_trust_status() -> None: logger.debug(message, *args) +def describe_tls_trust(env: Mapping[str, str] | None = None) -> tuple[str, str]: + """Return a user-facing trust source and precedence description.""" + if _env_flag(_DISABLE_ENV_VAR, env): + source = f"bundled CA (certifi); {_DISABLE_ENV_VAR}=1 disables OS trust-store injection" + elif has_explicit_ca_override(env): + source = f"explicit CA bundle: {_explicit_ca_path(env)}" + elif _LAST_TLS_STATUS is not None: + message, args = _LAST_TLS_STATUS + rendered = message % args if args else message + source = rendered.removeprefix("TLS: ") + else: + source = "not selected yet; OS trust store preferred with certifi fallback" + + precedence = ( + "APM_DISABLE_TRUSTSTORE > REQUESTS_CA_BUNDLE/CURL_CA_BUNDLE > " + "APM_EXTRA_CA_BUNDLE (when supported) > OS trust store > certifi fallback" + ) + return source, precedence + + def _env_flag(name: str, env: Mapping[str, str] | None = None) -> bool: environ = os.environ if env is None else env return environ.get(name, "").strip().lower() in _TRUTHY diff --git a/tests/conftest.py b/tests/conftest.py index 00b57bc8f2..f8031aa976 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -122,3 +122,28 @@ def _isolate_discovery_state(): yield clear_discovery_cache() perf_stats.reset() + + +@pytest.fixture(autouse=True) +def _isolate_ssl_state(): + """Restore process-global truststore injection between tests.""" + import contextlib + + try: + import truststore + except Exception: + truststore = None + + if truststore is not None: + with contextlib.suppress(Exception): + truststore.extract_from_ssl() + + from apm_cli.core.tls_trust import configure_process_tls_trust + + configure_process_tls_trust.cache_clear() + yield + configure_process_tls_trust.cache_clear() + + if truststore is not None: + with contextlib.suppress(Exception): + truststore.extract_from_ssl() diff --git a/tests/unit/commands/test_marketplace_doctor.py b/tests/unit/commands/test_marketplace_doctor.py index 91aeffb7d3..8c909d7aae 100644 --- a/tests/unit/commands/test_marketplace_doctor.py +++ b/tests/unit/commands/test_marketplace_doctor.py @@ -245,6 +245,27 @@ def test_no_token_informational(self, mock_run, runner, tmp_path, monkeypatch): assert result.exit_code == 0 # no token is informational, not a failure assert "unauthenticated" in result.output.lower() or "rate limit" in result.output.lower() + @patch("apm_cli.commands.marketplace.doctor.subprocess.run") + def test_tls_trust_source_is_reported(self, mock_run, runner, tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + mock_run.side_effect = [ + _make_run_result(0, stdout="git version 2.40.0"), + _make_run_result(0), + ] + + with patch( + "apm_cli.core.tls_trust.describe_tls_trust", + return_value=( + "OS trust store (truststore)", + "APM_DISABLE_TRUSTSTORE > explicit bundle > OS trust store > certifi fallback", + ), + ): + result = runner.invoke(cli, ["doctor"]) + + assert result.exit_code == 0 + assert "TLS trust" in result.output + assert "OS trust store" in result.output + # --------------------------------------------------------------------------- # Check 4: marketplace.yml