Skip to content

Commit 0f991b6

Browse files
committed
feat(generator): add _compat.py fallback for mTLS helpers
1 parent e9f6a99 commit 0f991b6

3 files changed

Lines changed: 68 additions & 58 deletions

File tree

packages/gapic-generator/gapic/generator/generator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ def get_response(self, api_schema: api.API, opts: Options) -> CodeGeneratorRespo
120120
for template_name in client_templates:
121121
# Quick check: Skip "private" templates.
122122
filename = template_name.split("/")[-1]
123-
if filename.startswith("_") and filename != "__init__.py.j2":
123+
if filename.startswith("_") and filename not in ("__init__.py.j2", "_compat.py.j2"):
124124
continue
125125

126126
# Append to the output files dictionary.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# {% include '_license.j2' %}
2+
3+
import os
4+
from typing import Optional, Callable, Tuple, Union
5+
from google.auth.exceptions import MutualTLSChannelError
6+
7+
try:
8+
from google.api_core.gapic_v1.client_utils import (
9+
use_client_cert_effective,
10+
get_client_cert_source,
11+
read_environment_variables,
12+
)
13+
except ImportError:
14+
from google.auth.transport import mtls # type: ignore
15+
16+
# TODO: Remove these fallbacks when google-api-core >= 2.18.0 is the minimum required version.
17+
18+
def use_client_cert_effective() -> bool:
19+
"""Returns whether client certificate should be used for mTLS."""
20+
if hasattr(mtls, "should_use_client_cert"):
21+
return mtls.should_use_client_cert()
22+
else:
23+
use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower()
24+
if use_client_cert_str not in ("true", "false"):
25+
raise ValueError(
26+
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
27+
" either `true` or `false`"
28+
)
29+
return use_client_cert_str == "true"
30+
31+
def get_client_cert_source(
32+
provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]],
33+
use_cert_flag: bool,
34+
) -> Optional[Callable[[], Tuple[bytes, bytes]]]:
35+
"""Return the client cert source to be used by the client."""
36+
client_cert_source = None
37+
if use_cert_flag:
38+
if provided_cert_source:
39+
client_cert_source = provided_cert_source
40+
elif (
41+
hasattr(mtls, "has_default_client_cert_source")
42+
and mtls.has_default_client_cert_source()
43+
):
44+
client_cert_source = mtls.default_client_cert_source()
45+
else:
46+
raise ValueError(
47+
"Client certificate is required for mTLS, but no client certificate source was provided or found."
48+
)
49+
return client_cert_source
50+
51+
def read_environment_variables() -> Tuple[bool, str, Optional[str]]:
52+
"""Returns the environment variables used by the client."""
53+
use_client_cert = use_client_cert_effective()
54+
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
55+
universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
56+
if use_mtls_endpoint not in ("auto", "never", "always"):
57+
raise MutualTLSChannelError(
58+
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` "
59+
"must be `never`, `auto` or `always`"
60+
)
61+
return use_client_cert, use_mtls_endpoint, universe_domain_env

packages/gapic-generator/gapic/templates/%namespace/%name_%version/%sub/services/%service/client.py.j2

Lines changed: 6 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ from google.api_core import exceptions as core_exceptions
3030
from google.api_core import extended_operation
3131
{% endif %}
3232
from google.api_core import gapic_v1
33+
from {{package_path}} import _compat as client_utils
3334
from google.api_core import retry as retries
3435
from google.auth import credentials as ga_credentials # type: ignore
3536
from google.auth.transport import mtls # type: ignore
@@ -190,30 +191,8 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
190191

191192
@staticmethod
192193
def _use_client_cert_effective() -> bool:
193-
"""Returns whether client certificate should be used for mTLS if the
194-
google-auth version supports should_use_client_cert automatic mTLS enablement.
195-
196-
Alternatively, read from the GOOGLE_API_USE_CLIENT_CERTIFICATE env var.
197-
198-
Returns:
199-
bool: whether client certificate should be used for mTLS
200-
Raises:
201-
ValueError: (If using a version of google-auth without should_use_client_cert and
202-
GOOGLE_API_USE_CLIENT_CERTIFICATE is set to an unexpected value.)
203-
"""
204-
# TODO: Remove this fallback when google-auth >= 2.43.0 is the minimum required version
205-
# check if google-auth version supports should_use_client_cert for automatic mTLS enablement
206-
if hasattr(mtls, "should_use_client_cert"): # pragma: NO COVER
207-
return mtls.should_use_client_cert()
208-
else: # pragma: NO COVER
209-
# if unsupported, fallback to reading from env var
210-
use_client_cert_str = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false").lower()
211-
if use_client_cert_str not in ("true", "false"):
212-
raise ValueError(
213-
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be"
214-
" either `true` or `false`"
215-
)
216-
return use_client_cert_str == "true"
194+
"""Returns whether client certificate should be used for mTLS."""
195+
return client_utils.use_client_cert_effective()
217196

218197
@classmethod
219198
def from_service_account_info(cls, info: dict, *args, **kwargs):
@@ -357,30 +336,8 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
357336
provided_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]],
358337
use_cert_flag: bool,
359338
) -> Optional[Callable[[], Tuple[bytes, bytes]]]:
360-
"""Return the client cert source to be used by the client.
361-
362-
Args:
363-
provided_cert_source (Callable[[], Tuple[bytes, bytes]]): The client certificate source provided.
364-
use_cert_flag (bool): A flag indicating whether to use the client certificate.
365-
366-
Returns:
367-
Callable[[], Tuple[bytes, bytes]] or None: The client cert source to be used by the client.
368-
"""
369-
# TODO: Remove this fallback when google-auth >= 2.43.0 is the minimum required version
370-
client_cert_source = None
371-
if use_cert_flag:
372-
if provided_cert_source:
373-
client_cert_source = provided_cert_source
374-
elif (
375-
hasattr(mtls, "has_default_client_cert_source")
376-
and mtls.has_default_client_cert_source()
377-
):
378-
client_cert_source = mtls.default_client_cert_source()
379-
else:
380-
raise ValueError(
381-
"Client certificate is required for mTLS, but no client certificate source was provided or found."
382-
)
383-
return client_cert_source
339+
"""Return the client cert source to be used by the client."""
340+
return client_utils.get_client_cert_source(provided_cert_source, use_cert_flag)
384341

385342
@staticmethod
386343
def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str:
@@ -588,15 +545,7 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
588545

589546
universe_domain_opt = getattr(self._client_options, 'universe_domain', None)
590547

591-
# TODO: Remove the use_client_cert fallback when google-auth >= 2.43.0 is the minimum required version
592-
self._use_client_cert = {{ service.client_name }}._use_client_cert_effective()
593-
self._use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto").lower()
594-
if self._use_mtls_endpoint not in ("auto", "never", "always"):
595-
raise MutualTLSChannelError(
596-
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` "
597-
"must be `never`, `auto` or `always`"
598-
)
599-
self._universe_domain_env = os.getenv("GOOGLE_CLOUD_UNIVERSE_DOMAIN")
548+
self._use_client_cert, self._use_mtls_endpoint, self._universe_domain_env = client_utils.read_environment_variables()
600549
self._client_cert_source = {{ service.client_name }}._get_client_cert_source(self._client_options.client_cert_source, self._use_client_cert)
601550
self._universe_domain = {{ service.client_name }}._get_universe_domain(universe_domain_opt, self._universe_domain_env)
602551
self._api_endpoint: str = "" # updated below, depending on `transport`

0 commit comments

Comments
 (0)