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 @@ -35,17 +35,16 @@
)

import google.protobuf
from google.api_core import _feature_gating_helpers, _otel_helpers, gapic_v1
from google.api_core import client_options as client_options_lib
from google.api_core import exceptions as core_exceptions
from google.api_core import gapic_v1
from google.api_core import retry as retries
from google.auth import credentials as ga_credentials # type: ignore
from google.auth.exceptions import MutualTLSChannelError # type: ignore
from google.auth.transport import mtls # type: ignore
from google.auth.transport.grpc import SslCredentials # type: ignore
from google.oauth2 import service_account # type: ignore

from google.cloud.secretmanager_v1 import gapic_version as package_version
from google.oauth2 import service_account # type: ignore

try:
OptionalRetry = Union[retries.Retry, gapic_v1.method._MethodDefault, None]
Expand All @@ -68,7 +67,6 @@
import google.protobuf.field_mask_pb2 as field_mask_pb2 # type: ignore
import google.protobuf.timestamp_pb2 as timestamp_pb2 # type: ignore
from google.cloud.location import locations_pb2 # type: ignore

from google.cloud.secretmanager_v1.services.secret_manager_service import pagers
from google.cloud.secretmanager_v1.types import resources, service

Expand Down Expand Up @@ -746,17 +744,45 @@ def __init__(
else cast(Callable[..., SecretManagerServiceTransport], transport)
)
# initialize with the provided callable or the passed in class
self._transport = transport_init(
credentials=credentials,
credentials_file=self._client_options.credentials_file,
host=self._api_endpoint,
scopes=self._client_options.scopes,
client_cert_source_for_mtls=self._client_cert_source,
quota_project_id=self._client_options.quota_project_id,
client_info=client_info,
always_use_jwt_access=True,
api_audience=self._client_options.api_audience,
)
transport_kwargs = {
"credentials": credentials,
"credentials_file": self._client_options.credentials_file,
"host": self._api_endpoint,
"scopes": self._client_options.scopes,
"client_cert_source_for_mtls": self._client_cert_source,
"quota_project_id": self._client_options.quota_project_id,
"client_info": client_info,
"always_use_jwt_access": True,
"api_audience": self._client_options.api_audience,
}

# NOTE: Eager channel wrapping is only required to accommodate standard gRPC.
# OTel gRPC interceptors use a custom protocol (grpcext) that crashes standard
# grpc.intercept_channel() inside the Transport. Other wrappers (like Asyncio/REST)
# do not suffer from this specific type incompatibility.
# We create a raw_channel, apply otel capabilities to the channel and
# pass the channel to transport_init. When the Transport finds an existing
# channel it will use that, otherwise it will create one lazily.
if transport_init is SecretManagerServiceGrpcTransport:
if _otel_helpers.is_otel_capabilities_enabled(self._client_options):
# Eagerly create the channel using the Transport's classmethod
raw_channel = transport_init.create_channel(
self._api_endpoint,
credentials=credentials,
credentials_file=self._client_options.credentials_file,
scopes=self._client_options.scopes,
quota_project_id=self._client_options.quota_project_id,
)

# Apply OTel capabilities to the channel
wrapped_channel = _otel_helpers.apply_otel_capabilities_to_channel(
raw_channel, self._client_options
)

# Inject the wrapped channel into transport kwargs
transport_kwargs["channel"] = wrapped_channel

self._transport = transport_init(**transport_kwargs)

if "async" not in str(self._transport):
if CLIENT_LOGGING_SUPPORTED and _LOGGER.isEnabledFor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,8 @@
from google.auth import credentials as ga_credentials # type: ignore
from google.auth.transport.grpc import SslCredentials # type: ignore
from google.cloud.location import locations_pb2 # type: ignore
from google.protobuf.json_format import MessageToJson

from google.cloud.secretmanager_v1.types import resources, service
from google.protobuf.json_format import MessageToJson

from .base import DEFAULT_CLIENT_INFO, SecretManagerServiceTransport

Expand Down Expand Up @@ -148,6 +147,7 @@ def __init__(
client_info: gapic_v1.client_info.ClientInfo = DEFAULT_CLIENT_INFO,
always_use_jwt_access: Optional[bool] = False,
api_audience: Optional[str] = None,
interceptors: Optional[Sequence[grpc.ClientInterceptor]] = None,
) -> None:
"""Instantiate the transport.

Expand Down Expand Up @@ -198,6 +198,9 @@ def __init__(
to the service that will be set when using certain 3rd party
authentication flows. Audience is typically a resource identifier.
If not set, the host value will be used as a default.
interceptors (Optional[Sequence[grpc.ClientInterceptor]]):
Additional interceptors to be injected into the gRPC channel pipeline.
These are executed in order.

Raises:
google.auth.exceptions.MutualTLSChannelError: If mutual TLS transport
Expand Down Expand Up @@ -274,6 +277,10 @@ def __init__(
],
)

if interceptors:
for i in interceptors:
self._grpc_channel = grpc.intercept_channel(self._grpc_channel, i)

self._interceptor = _LoggingClientInterceptor()
self._logged_channel = grpc.intercept_channel(
self._grpc_channel, self._interceptor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,14 @@
from google.auth import credentials as ga_credentials
from google.auth.exceptions import MutualTLSChannelError
from google.cloud.location import locations_pb2
from google.oauth2 import service_account

from google.cloud.secretmanager_v1.services.secret_manager_service import (
SecretManagerServiceAsyncClient,
SecretManagerServiceClient,
pagers,
transports,
)
from google.cloud.secretmanager_v1.types import resources, service
from google.oauth2 import service_account

CRED_INFO_JSON = {
"credential_source": "/path/to/file",
Expand Down Expand Up @@ -770,6 +769,63 @@ def test_secret_manager_service_client_client_options(
)


def test_secret_manager_service_client_otel_eager_channel_injection():
# Mock the helpers to simulate OTel being enabled
mock_raw_channel = mock.Mock()
mock_wrapped_channel = mock.Mock()

with mock.patch(
"google.cloud.secretmanager_v1.services.secret_manager_service.client._otel_helpers.is_otel_capabilities_enabled",
return_value=True,
) as mock_is_enabled:
with mock.patch(
"google.cloud.secretmanager_v1.services.secret_manager_service.client._otel_helpers.apply_otel_capabilities_to_channel",
return_value=mock_wrapped_channel,
) as mock_apply_otel:
with mock.patch.object(
transports.SecretManagerServiceGrpcTransport,
"create_channel",
return_value=mock_raw_channel,
) as mock_create_channel:
with mock.patch.object(
transports.SecretManagerServiceGrpcTransport, "__init__"
) as patched_transport_init:
patched_transport_init.return_value = None

client = SecretManagerServiceClient(transport="grpc")

mock_is_enabled.assert_called_once()
mock_create_channel.assert_called_once()
mock_apply_otel.assert_called_once_with(mock_raw_channel, mock.ANY)

called_kwargs = patched_transport_init.call_args.kwargs
assert "channel" in called_kwargs
assert called_kwargs["channel"] == mock_wrapped_channel


def test_secret_manager_service_client_otel_eager_channel_injection_disabled():
# Simulate OTel being disabled
with mock.patch(
"google.cloud.secretmanager_v1.services.secret_manager_service.client._otel_helpers.is_otel_capabilities_enabled",
return_value=False,
) as mock_is_enabled:
with mock.patch.object(
transports.SecretManagerServiceGrpcTransport, "create_channel"
) as mock_create_channel:
with mock.patch.object(
transports.SecretManagerServiceGrpcTransport, "__init__"
) as patched_transport_init:
patched_transport_init.return_value = None

client = SecretManagerServiceClient(transport="grpc")

mock_is_enabled.assert_called_once()
mock_create_channel.assert_not_called()

called_kwargs = patched_transport_init.call_args.kwargs
assert "channel" not in called_kwargs


@pytest.mark.parametrize(
"client_class,transport_class,transport_name,use_client_cert_env",
[
Expand Down
Loading