Marco Ricci commited on 2025-08-02 14:26:22
Zeige 1 geänderte Dateien mit 30 Einfügungen und 3 Löschungen.
Change the feature detection system to actually test whether it can successfully construct an SSH agent client, and then report that the feature is available (or not) based on the success of this test. Previously, `derivepassphrase` would determine whether it supported connecting to an SSH agent or not by checking for Python support for UNIX domain sockets. This was the same criteria as used by the actual SSH agent client constructor to attempt, well, constructing an SSH agent client. However, since the SSH agent client now delegates the construction to the socket provider mechanism, which includes fallbacks and "available, but not sensibly callable" providers, determining whether SSH agent support is available is not quite as straight-forward anymore as testing for `socket.AF_UNIX` was. I could attempt to replicate the registry entry resolving steps to see if we have a working socket provider, but even that entails actually running the provider and checking for the absence of certain kinds of exceptions (but not other kinds, which indicate a manner of success!). Plus, a separate, indepedent replication of the search logic runs the risk of becoming desynchronized with the original implementation: `derivepassphrase vault --version` thinks that support is or is not available, and the SSH agent client constructor thinks the opposite. A *far* more robust solution is to actually test the functionality we are hoping to use directly, i.e., to test constructing an SSH agent client: it obviously doesn't work if we don't have support, and if it does work, we have support. (This technique would of course generalize to other feature flags, if we had any such.) So that's what we'll do.
... | ... |
@@ -18,17 +18,21 @@ import collections |
18 | 18 |
import importlib.metadata |
19 | 19 |
import inspect |
20 | 20 |
import logging |
21 |
-import socket |
|
21 |
+import sys |
|
22 | 22 |
import warnings |
23 | 23 |
from typing import TYPE_CHECKING, Callable, Literal, TextIO, TypeVar |
24 | 24 |
|
25 | 25 |
import click |
26 | 26 |
import click.shell_completion |
27 |
+import exceptiongroup |
|
27 | 28 |
from typing_extensions import Any, ParamSpec, override |
28 | 29 |
|
29 |
-from derivepassphrase import _internals, _types |
|
30 |
+from derivepassphrase import _internals, _types, ssh_agent |
|
30 | 31 |
from derivepassphrase._internals import cli_messages as _msg |
31 | 32 |
|
33 |
+if sys.version_info < (3, 11): |
|
34 |
+ from exceptiongroup import BaseExceptionGroup |
|
35 |
+ |
|
32 | 36 |
if TYPE_CHECKING: |
33 | 37 |
import types |
34 | 38 |
from collections.abc import ( |
... | ... |
@@ -1140,6 +1144,29 @@ def export_vault_version_option_callback( |
1140 | 1144 |
ctx.exit() |
1141 | 1145 |
|
1142 | 1146 |
|
1147 |
+def _test_for_ssh_key_feature() -> bool: |
|
1148 |
+ """Return true if we support SSH keys. |
|
1149 |
+ |
|
1150 |
+ This is the feature test for [`_types.Feature.SSH_KEY`][]. We test |
|
1151 |
+ this by attempting to construct an SSH agent client, reporting |
|
1152 |
+ whether this can principally work, or not. |
|
1153 |
+ |
|
1154 |
+ """ |
|
1155 |
+ ret = True |
|
1156 |
+ |
|
1157 |
+ def handle_notimplementederror(_exc: BaseExceptionGroup) -> None: |
|
1158 |
+ nonlocal ret |
|
1159 |
+ ret = False |
|
1160 |
+ |
|
1161 |
+ with exceptiongroup.catch({ # noqa: SIM117 |
|
1162 |
+ NotImplementedError: handle_notimplementederror, |
|
1163 |
+ Exception: lambda _exc: None, |
|
1164 |
+ }): |
|
1165 |
+ with ssh_agent.SSHAgentClient.ensure_agent_subcontext(): |
|
1166 |
+ pass |
|
1167 |
+ return ret |
|
1168 |
+ |
|
1169 |
+ |
|
1143 | 1170 |
def vault_version_option_callback( |
1144 | 1171 |
ctx: click.Context, |
1145 | 1172 |
param: click.Parameter, |
... | ... |
@@ -1148,7 +1175,7 @@ def vault_version_option_callback( |
1148 | 1175 |
if value and not ctx.resilient_parsing: |
1149 | 1176 |
common_version_output(ctx, param, value) |
1150 | 1177 |
features = { |
1151 |
- _types.Feature.SSH_KEY: hasattr(socket, 'AF_UNIX'), |
|
1178 |
+ _types.Feature.SSH_KEY: _test_for_ssh_key_feature(), |
|
1152 | 1179 |
} |
1153 | 1180 |
click.echo() |
1154 | 1181 |
version_info_types: dict[_msg.Label, list[str]] = { |
1155 | 1182 |