Skip to content

Commit 1716f4a

Browse files
committed
Merge branch 'feat/gapic-centralization-api-core-routing' into feat/gapic-centralization-api-core-mtls
2 parents 3158f86 + 1abba92 commit 1716f4a

2 files changed

Lines changed: 202 additions & 121 deletions

File tree

packages/google-api-core/google/api_core/gapic_v1/client_utils.py

Lines changed: 60 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,46 +14,87 @@
1414
# limitations under the License.
1515
#
1616

17-
"""Helpers for client setup and configuration."""
18-
1917
import os
20-
from typing import Any, Callable, Optional, Tuple
18+
from typing import Callable, Optional, Tuple
19+
from urllib.parse import urlparse, urlunparse
2120

21+
import google.auth.transport.mtls # type: ignore
2222
from google.auth.exceptions import MutualTLSChannelError # type: ignore
23-
from google.auth.transport import mtls # type: ignore
2423

2524

2625
def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]:
2726
"""Converts api endpoint to mTLS endpoint.
2827
2928
Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
3029
"*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
30+
Other URLs (including those that do not match these domain suffixes or
31+
already contain '.mtls.') are passed through as-is.
3132
3233
Args:
3334
api_endpoint (Optional[str]): the api endpoint to convert.
3435
3536
Returns:
3637
Optional[str]: converted mTLS api endpoint.
3738
"""
38-
if not api_endpoint or ".mtls." in api_endpoint:
39+
if not api_endpoint or ".mtls." in api_endpoint.lower():
40+
return api_endpoint
41+
42+
has_scheme = "://" in api_endpoint
43+
if not has_scheme:
44+
parsed = urlparse("//" + api_endpoint)
45+
else:
46+
parsed = urlparse(api_endpoint)
47+
48+
host = parsed.hostname
49+
if not host:
3950
return api_endpoint
4051

41-
if api_endpoint.endswith(".sandbox.googleapis.com"):
42-
# len(".sandbox.googleapis.com") == 23
43-
return api_endpoint[:-23] + ".mtls.sandbox.googleapis.com"
52+
port = f":{parsed.port}" if parsed.port else ""
4453

45-
if api_endpoint.endswith(".googleapis.com"):
46-
# len(".googleapis.com") == 15
47-
return api_endpoint[:-15] + ".mtls.googleapis.com"
54+
lowered_host = host.lower()
55+
if lowered_host.endswith(".sandbox.googleapis.com"):
56+
new_host = host[:-23] + ".mtls.sandbox.googleapis.com"
57+
elif lowered_host.endswith(".googleapis.com"):
58+
new_host = host[:-15] + ".mtls.googleapis.com"
59+
else:
60+
return api_endpoint
4861

49-
return api_endpoint
62+
netloc = new_host + port
63+
new_parsed = parsed._replace(netloc=netloc)
64+
65+
if not has_scheme:
66+
return urlunparse(new_parsed)[2:]
67+
else:
68+
return urlunparse(new_parsed)
69+
70+
71+
def should_use_mtls_endpoint(client_cert_available: bool) -> bool:
72+
"""Helper to determine whether to use mTLS endpoint.
73+
74+
Uses google.auth.transport.mtls.should_use_mtls_endpoint if available,
75+
otherwise falls back to evaluation of GOOGLE_API_USE_MTLS_ENDPOINT.
76+
"""
77+
if hasattr(google.auth.transport.mtls, "should_use_mtls_endpoint"):
78+
return google.auth.transport.mtls.should_use_mtls_endpoint(client_cert_available)
79+
80+
# Fallback logic for older google-auth versions
81+
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").strip().lower()
82+
if use_mtls_endpoint == "always":
83+
return True
84+
elif use_mtls_endpoint == "never":
85+
return False
86+
elif use_mtls_endpoint == "auto":
87+
return client_cert_available
88+
else:
89+
raise MutualTLSChannelError(
90+
f"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
91+
)
5092

5193

5294
def get_api_endpoint(
5395
api_override: Optional[str],
54-
client_cert_source: Optional[Any],
96+
client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]],
5597
universe_domain: str,
56-
use_mtls_endpoint: str,
5798
default_universe: str,
5899
default_mtls_endpoint: Optional[str],
59100
default_endpoint_template: str,
@@ -63,10 +104,9 @@ def get_api_endpoint(
63104
Args:
64105
api_override (Optional[str]): The API endpoint override. If specified,
65106
this is always returned.
66-
client_cert_source (Optional[Any]): The client certificate source used by the client.
107+
client_cert_source (Optional[Callable[[], Tuple[bytes, bytes]]]): The client
108+
certificate source used by the client.
67109
universe_domain (str): The universe domain used by the client.
68-
use_mtls_endpoint (str): How to use the mTLS endpoint. Possible values
69-
are "always", "auto", or "never".
70110
default_universe (str): The default universe domain.
71111
default_mtls_endpoint (Optional[str]): The default mTLS endpoint.
72112
default_endpoint_template (str): The default endpoint template containing
@@ -82,9 +122,9 @@ def get_api_endpoint(
82122
"""
83123
if api_override is not None:
84124
return api_override
85-
elif use_mtls_endpoint == "always" or (
86-
use_mtls_endpoint == "auto" and client_cert_source
87-
):
125+
126+
client_cert_available = client_cert_source is not None
127+
if should_use_mtls_endpoint(client_cert_available):
88128
if universe_domain.lower() != default_universe.lower():
89129
raise MutualTLSChannelError(
90130
f"mTLS is not supported in any universe other than {default_universe}."

0 commit comments

Comments
 (0)