Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,148 @@ Add conditional logic to check if static code exists in google-api-core and use
falling back to the local implementation if not present. #}
{# TODO(https://github.com/googleapis/google-cloud-python/issues/17883):
Backfill compatibility functions being removed from the client layer. #}
"""A compatibility module for older versions of google-api-core."""

from typing import Optional
from urllib.parse import urlparse, urlunparse

from google.auth.exceptions import MutualTLSChannelError


def get_default_mtls_endpoint(api_endpoint: Optional[str]) -> Optional[str]:
"""Converts api endpoint to mTLS endpoint.

Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
"*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
Other URLs (including those that do not match these domain suffixes or
already contain '.mtls.') are passed through as-is.

Args:
api_endpoint (Optional[str]): the api endpoint to convert.

Returns:
Optional[str]: converted mTLS api endpoint.
"""
if not api_endpoint or ".mtls." in api_endpoint.lower():
return api_endpoint

has_scheme = "://" in api_endpoint
if not has_scheme:
parsed = urlparse("//" + api_endpoint)
else:
parsed = urlparse(api_endpoint)

host = parsed.hostname
if not host:
return api_endpoint

port = f":{parsed.port}" if parsed.port else ""

lowered_host = host.lower()
suffix_sandbox = ".sandbox.googleapis.com"
suffix_google = ".googleapis.com"
if lowered_host.endswith(suffix_sandbox):
new_host = host[: -len(suffix_sandbox)] + ".mtls.sandbox.googleapis.com"
elif lowered_host.endswith(suffix_google):
new_host = host[: -len(suffix_google)] + ".mtls.googleapis.com"
else:
return api_endpoint

netloc = new_host + port
new_parsed = parsed._replace(netloc=netloc)

if not has_scheme:
return urlunparse(new_parsed)[2:]
else:
return urlunparse(new_parsed)

def get_api_endpoint(
api_override: Optional[str],
universe_domain: str,
default_universe: str,
default_mtls_endpoint: Optional[str],
default_endpoint_template: str,
use_mtls: bool,
) -> str:
"""Return the API endpoint used by the client.

Args:
api_override (Optional[str]): The API endpoint override. If specified,
this is always returned.
universe_domain (str): The universe domain used by the client.
default_universe (str): The default universe domain.
default_mtls_endpoint (Optional[str]): The default mTLS endpoint.
default_endpoint_template (str): The default endpoint template containing
a placeholder `{UNIVERSE_DOMAIN}`.
use_mtls (bool): Whether to use the mTLS endpoint.

Returns:
str: The API endpoint to be used by the client.

Raises:
google.auth.exceptions.MutualTLSChannelError: If mTLS is requested but
not supported in the configured universe domain.
ValueError: If mTLS is requested but no mTLS endpoint is available.
"""
if api_override is not None:
return api_override

if use_mtls:
if universe_domain.lower() != default_universe.lower():
raise MutualTLSChannelError(
f"mTLS is not supported in any universe other than {default_universe}."
)
if not default_mtls_endpoint:
raise ValueError("mTLS endpoint is not available.")
return default_mtls_endpoint
else:
return default_endpoint_template.format(UNIVERSE_DOMAIN=universe_domain)

def get_universe_domain(
*potential_universes: Optional[str],
default_universe: str,
) -> str:
"""Return the universe domain used by the client.

Args:
*potential_universes (Optional[str]): Potential universe domains in order of preference.
default_universe (str): The default universe domain.

Returns:
str: The universe domain to be used by the client.

Raises:
EmptyUniverseError: If the resolved universe domain is an empty string.
"""
resolved = next(
(x.strip() for x in potential_universes if x is not None),
default_universe,
)

if not resolved:
raise EmptyUniverseError()
return resolved

def determine_domain(
client_universe_domain: Optional[str], universe_domain_env: Optional[str]
) -> str:
"""Return the universe domain used by the client.

Args:
client_universe_domain (Optional[str]): The universe domain configured via the client options.
universe_domain_env (Optional[str]): The universe domain configured via the
"GOOGLE_CLOUD_UNIVERSE_DOMAIN" environment variable.

Returns:
str: The universe domain to be used by the client.

Raises:
ValueError: If the universe domain is an empty string.
"""
return get_universe_domain(
client_universe_domain,
universe_domain_env,
default_universe=DEFAULT_UNIVERSE,
)

{% endblock %}
{% endblock %}
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ from google.api_core import exceptions as core_exceptions
from google.api_core import extended_operation
{% endif %}
from google.api_core import gapic_v1
from {{package_path}} import _compat as client_utils
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials # type: ignore
from google.auth.transport import mtls # type: ignore
Expand Down Expand Up @@ -143,44 +144,10 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
"""{{ service.meta.doc|rst(width=72, indent=4) }}{% if service.version|length %}
This class implements API version {{ service.version }}.{% endif %}"""

@staticmethod
def _get_default_mtls_endpoint(api_endpoint) -> Optional[str]:
"""Converts api endpoint to mTLS endpoint.

Convert "*.sandbox.googleapis.com" and "*.googleapis.com" to
"*.mtls.sandbox.googleapis.com" and "*.mtls.googleapis.com" respectively.
Args:
api_endpoint (Optional[str]): the api endpoint to convert.
Returns:
Optional[str]: converted mTLS api endpoint.
"""
if not api_endpoint:
return api_endpoint

mtls_endpoint_re = re.compile(
r"(?P<name>[^.]+)(?P<mtls>\.mtls)?(?P<sandbox>\.sandbox)?(?P<googledomain>\.googleapis\.com)?"
)

m = mtls_endpoint_re.match(api_endpoint)
if m is None:
# Could not parse api_endpoint; return as-is.
return api_endpoint

name, mtls, sandbox, googledomain = m.groups()
if mtls or not googledomain:
return api_endpoint

if sandbox:
return api_endpoint.replace(
"sandbox.googleapis.com", "mtls.sandbox.googleapis.com"
)

return api_endpoint.replace(".googleapis.com", ".mtls.googleapis.com")

# Note: DEFAULT_ENDPOINT is deprecated. Use _DEFAULT_ENDPOINT_TEMPLATE instead.
DEFAULT_ENDPOINT = {% if service.host %}"{{ service.host }}"{% else %}None{% endif %}

DEFAULT_MTLS_ENDPOINT = _get_default_mtls_endpoint.__func__( # type: ignore
DEFAULT_MTLS_ENDPOINT = client_utils.get_default_mtls_endpoint(
DEFAULT_ENDPOINT
)

Expand Down Expand Up @@ -392,30 +359,34 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
return client_cert_source

@staticmethod
def _get_api_endpoint(api_override, client_cert_source, universe_domain, use_mtls_endpoint) -> str:
def _get_api_endpoint(
api_override: Optional[str],
client_cert_source: Optional[Callable[[], Tuple[bytes, bytes]]],
universe_domain: str,
use_mtls_endpoint: str,
) -> Optional[str]:
"""Return the API endpoint used by the client.

Args:
api_override (str): The API endpoint override. If specified, this is always
api_override (Optional[str]): The API endpoint override. If specified, this is always
the return value of this function and the other arguments are not used.
client_cert_source (bytes): The client certificate source used by the client.
client_cert_source (Union[Callable[[], Tuple[bytes, bytes]], None]): The client certificate source used by the client.
universe_domain (str): The universe domain used by the client.
use_mtls_endpoint (str): How to use the mTLS endpoint, which depends also on the other parameters.
Possible values are "always", "auto", or "never".

Returns:
str: The API endpoint to be used by the client.
Optional[str]: The API endpoint to be used by the client.
"""
if api_override is not None:
api_endpoint = api_override
elif use_mtls_endpoint == "always" or (use_mtls_endpoint == "auto" and client_cert_source):
_default_universe = {{ service.client_name }}._DEFAULT_UNIVERSE
if universe_domain != _default_universe:
raise MutualTLSChannelError(f"mTLS is not supported in any universe other than {_default_universe}.")
api_endpoint = {{ service.client_name }}.DEFAULT_MTLS_ENDPOINT
else:
api_endpoint = {{ service.client_name }}._DEFAULT_ENDPOINT_TEMPLATE.format(UNIVERSE_DOMAIN=universe_domain)
return api_endpoint
return client_utils.get_api_endpoint(
api_override,
client_cert_source,
universe_domain,
use_mtls_endpoint,
{{ service.client_name }}._DEFAULT_UNIVERSE,
{{ service.client_name }}.DEFAULT_MTLS_ENDPOINT,
{{ service.client_name }}._DEFAULT_ENDPOINT_TEMPLATE,
)

@staticmethod
def _get_universe_domain(client_universe_domain: Optional[str], universe_domain_env: Optional[str]) -> str:
Expand All @@ -431,14 +402,11 @@ class {{ service.client_name }}(metaclass={{ service.client_name }}Meta):
Raises:
ValueError: If the universe domain is an empty string.
"""
universe_domain = {{ service.client_name }}._DEFAULT_UNIVERSE
if client_universe_domain is not None:
universe_domain = client_universe_domain
elif universe_domain_env is not None:
universe_domain = universe_domain_env
if len(universe_domain.strip()) == 0:
raise ValueError("Universe Domain cannot be an empty string.")
return universe_domain
return client_utils.get_universe_domain(
client_universe_domain,
universe_domain_env,
default_universe={{ service.client_name }}._DEFAULT_UNIVERSE,
)

def _validate_universe_domain(self):
"""Validates client's and credentials' universe domains are consistent.
Expand Down
Loading
Loading