Skip to content
This repository was archived by the owner on Sep 5, 2023. It is now read-only.

Commit 88cf10a

Browse files
feat: add api key support (#58)
* chore: upgrade gapic-generator-java, gax-java and gapic-generator-python PiperOrigin-RevId: 423842556 Source-Link: googleapis/googleapis@a616ca0 Source-Link: https://github.com/googleapis/googleapis-gen/commit/29b938c58c1e51d019f2ee539d55dc0a3c86a905 Copy-Tag: eyJwIjoiLmdpdGh1Yi8uT3dsQm90LnlhbWwiLCJoIjoiMjliOTM4YzU4YzFlNTFkMDE5ZjJlZTUzOWQ1NWRjMGEzYzg2YTkwNSJ9 * 🦉 Updates from OwlBot See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md Co-authored-by: Owl Bot <gcf-owl-bot[bot]@users.noreply.github.com>
1 parent 9fe18f7 commit 88cf10a

File tree

3 files changed

+245
-44
lines changed

3 files changed

+245
-44
lines changed

google/cloud/datastream_v1alpha1/services/datastream/async_client.py

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from collections import OrderedDict
1717
import functools
1818
import re
19-
from typing import Dict, Sequence, Tuple, Type, Union
19+
from typing import Dict, Optional, Sequence, Tuple, Type, Union
2020
import pkg_resources
2121

2222
from google.api_core.client_options import ClientOptions
@@ -116,6 +116,42 @@ def from_service_account_file(cls, filename: str, *args, **kwargs):
116116

117117
from_service_account_json = from_service_account_file
118118

119+
@classmethod
120+
def get_mtls_endpoint_and_cert_source(
121+
cls, client_options: Optional[ClientOptions] = None
122+
):
123+
"""Return the API endpoint and client cert source for mutual TLS.
124+
125+
The client cert source is determined in the following order:
126+
(1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
127+
client cert source is None.
128+
(2) if `client_options.client_cert_source` is provided, use the provided one; if the
129+
default client cert source exists, use the default one; otherwise the client cert
130+
source is None.
131+
132+
The API endpoint is determined in the following order:
133+
(1) if `client_options.api_endpoint` if provided, use the provided one.
134+
(2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
135+
default mTLS endpoint; if the environment variabel is "never", use the default API
136+
endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
137+
use the default API endpoint.
138+
139+
More details can be found at https://google.aip.dev/auth/4114.
140+
141+
Args:
142+
client_options (google.api_core.client_options.ClientOptions): Custom options for the
143+
client. Only the `api_endpoint` and `client_cert_source` properties may be used
144+
in this method.
145+
146+
Returns:
147+
Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
148+
client cert source to use.
149+
150+
Raises:
151+
google.auth.exceptions.MutualTLSChannelError: If any errors happen.
152+
"""
153+
return DatastreamClient.get_mtls_endpoint_and_cert_source(client_options) # type: ignore
154+
119155
@property
120156
def transport(self) -> DatastreamTransport:
121157
"""Returns the transport used by the client instance.

google/cloud/datastream_v1alpha1/services/datastream/client.py

Lines changed: 84 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,73 @@ def parse_common_location_path(path: str) -> Dict[str, str]:
295295
m = re.match(r"^projects/(?P<project>.+?)/locations/(?P<location>.+?)$", path)
296296
return m.groupdict() if m else {}
297297

298+
@classmethod
299+
def get_mtls_endpoint_and_cert_source(
300+
cls, client_options: Optional[client_options_lib.ClientOptions] = None
301+
):
302+
"""Return the API endpoint and client cert source for mutual TLS.
303+
304+
The client cert source is determined in the following order:
305+
(1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
306+
client cert source is None.
307+
(2) if `client_options.client_cert_source` is provided, use the provided one; if the
308+
default client cert source exists, use the default one; otherwise the client cert
309+
source is None.
310+
311+
The API endpoint is determined in the following order:
312+
(1) if `client_options.api_endpoint` if provided, use the provided one.
313+
(2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
314+
default mTLS endpoint; if the environment variabel is "never", use the default API
315+
endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
316+
use the default API endpoint.
317+
318+
More details can be found at https://google.aip.dev/auth/4114.
319+
320+
Args:
321+
client_options (google.api_core.client_options.ClientOptions): Custom options for the
322+
client. Only the `api_endpoint` and `client_cert_source` properties may be used
323+
in this method.
324+
325+
Returns:
326+
Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
327+
client cert source to use.
328+
329+
Raises:
330+
google.auth.exceptions.MutualTLSChannelError: If any errors happen.
331+
"""
332+
if client_options is None:
333+
client_options = client_options_lib.ClientOptions()
334+
use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")
335+
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
336+
if use_client_cert not in ("true", "false"):
337+
raise ValueError(
338+
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
339+
)
340+
if use_mtls_endpoint not in ("auto", "never", "always"):
341+
raise MutualTLSChannelError(
342+
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
343+
)
344+
345+
# Figure out the client cert source to use.
346+
client_cert_source = None
347+
if use_client_cert == "true":
348+
if client_options.client_cert_source:
349+
client_cert_source = client_options.client_cert_source
350+
elif mtls.has_default_client_cert_source():
351+
client_cert_source = mtls.default_client_cert_source()
352+
353+
# Figure out which api endpoint to use.
354+
if client_options.api_endpoint is not None:
355+
api_endpoint = client_options.api_endpoint
356+
elif use_mtls_endpoint == "always" or (
357+
use_mtls_endpoint == "auto" and client_cert_source
358+
):
359+
api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
360+
else:
361+
api_endpoint = cls.DEFAULT_ENDPOINT
362+
363+
return api_endpoint, client_cert_source
364+
298365
def __init__(
299366
self,
300367
*,
@@ -345,57 +412,22 @@ def __init__(
345412
if client_options is None:
346413
client_options = client_options_lib.ClientOptions()
347414

348-
# Create SSL credentials for mutual TLS if needed.
349-
if os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") not in (
350-
"true",
351-
"false",
352-
):
353-
raise ValueError(
354-
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
355-
)
356-
use_client_cert = (
357-
os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true"
415+
api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source(
416+
client_options
358417
)
359418

360-
client_cert_source_func = None
361-
is_mtls = False
362-
if use_client_cert:
363-
if client_options.client_cert_source:
364-
is_mtls = True
365-
client_cert_source_func = client_options.client_cert_source
366-
else:
367-
is_mtls = mtls.has_default_client_cert_source()
368-
if is_mtls:
369-
client_cert_source_func = mtls.default_client_cert_source()
370-
else:
371-
client_cert_source_func = None
372-
373-
# Figure out which api endpoint to use.
374-
if client_options.api_endpoint is not None:
375-
api_endpoint = client_options.api_endpoint
376-
else:
377-
use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
378-
if use_mtls_env == "never":
379-
api_endpoint = self.DEFAULT_ENDPOINT
380-
elif use_mtls_env == "always":
381-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
382-
elif use_mtls_env == "auto":
383-
if is_mtls:
384-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
385-
else:
386-
api_endpoint = self.DEFAULT_ENDPOINT
387-
else:
388-
raise MutualTLSChannelError(
389-
"Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted "
390-
"values: never, auto, always"
391-
)
419+
api_key_value = getattr(client_options, "api_key", None)
420+
if api_key_value and credentials:
421+
raise ValueError(
422+
"client_options.api_key and credentials are mutually exclusive"
423+
)
392424

393425
# Save or instantiate the transport.
394426
# Ordinarily, we provide the transport, but allowing a custom transport
395427
# instance provides an extensibility point for unusual situations.
396428
if isinstance(transport, DatastreamTransport):
397429
# transport is a DatastreamTransport instance.
398-
if credentials or client_options.credentials_file:
430+
if credentials or client_options.credentials_file or api_key_value:
399431
raise ValueError(
400432
"When providing a transport instance, "
401433
"provide its credentials directly."
@@ -407,6 +439,15 @@ def __init__(
407439
)
408440
self._transport = transport
409441
else:
442+
import google.auth._default # type: ignore
443+
444+
if api_key_value and hasattr(
445+
google.auth._default, "get_api_key_credentials"
446+
):
447+
credentials = google.auth._default.get_api_key_credentials(
448+
api_key_value
449+
)
450+
410451
Transport = type(self).get_transport_class(transport)
411452
self._transport = Transport(
412453
credentials=credentials,

tests/unit/gapic/datastream_v1alpha1/test_datastream.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -392,6 +392,83 @@ def test_datastream_client_mtls_env_auto(
392392
)
393393

394394

395+
@pytest.mark.parametrize("client_class", [DatastreamClient, DatastreamAsyncClient])
396+
@mock.patch.object(
397+
DatastreamClient, "DEFAULT_ENDPOINT", modify_default_endpoint(DatastreamClient)
398+
)
399+
@mock.patch.object(
400+
DatastreamAsyncClient,
401+
"DEFAULT_ENDPOINT",
402+
modify_default_endpoint(DatastreamAsyncClient),
403+
)
404+
def test_datastream_client_get_mtls_endpoint_and_cert_source(client_class):
405+
mock_client_cert_source = mock.Mock()
406+
407+
# Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "true".
408+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
409+
mock_api_endpoint = "foo"
410+
options = client_options.ClientOptions(
411+
client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint
412+
)
413+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source(
414+
options
415+
)
416+
assert api_endpoint == mock_api_endpoint
417+
assert cert_source == mock_client_cert_source
418+
419+
# Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "false".
420+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}):
421+
mock_client_cert_source = mock.Mock()
422+
mock_api_endpoint = "foo"
423+
options = client_options.ClientOptions(
424+
client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint
425+
)
426+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source(
427+
options
428+
)
429+
assert api_endpoint == mock_api_endpoint
430+
assert cert_source is None
431+
432+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "never".
433+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}):
434+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
435+
assert api_endpoint == client_class.DEFAULT_ENDPOINT
436+
assert cert_source is None
437+
438+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "always".
439+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}):
440+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
441+
assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT
442+
assert cert_source is None
443+
444+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert doesn't exist.
445+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
446+
with mock.patch(
447+
"google.auth.transport.mtls.has_default_client_cert_source",
448+
return_value=False,
449+
):
450+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
451+
assert api_endpoint == client_class.DEFAULT_ENDPOINT
452+
assert cert_source is None
453+
454+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert exists.
455+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
456+
with mock.patch(
457+
"google.auth.transport.mtls.has_default_client_cert_source",
458+
return_value=True,
459+
):
460+
with mock.patch(
461+
"google.auth.transport.mtls.default_client_cert_source",
462+
return_value=mock_client_cert_source,
463+
):
464+
(
465+
api_endpoint,
466+
cert_source,
467+
) = client_class.get_mtls_endpoint_and_cert_source()
468+
assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT
469+
assert cert_source == mock_client_cert_source
470+
471+
395472
@pytest.mark.parametrize(
396473
"client_class,transport_class,transport_name",
397474
[
@@ -5612,6 +5689,23 @@ def test_credentials_transport_error():
56125689
transport=transport,
56135690
)
56145691

5692+
# It is an error to provide an api_key and a transport instance.
5693+
transport = transports.DatastreamGrpcTransport(
5694+
credentials=ga_credentials.AnonymousCredentials(),
5695+
)
5696+
options = client_options.ClientOptions()
5697+
options.api_key = "api_key"
5698+
with pytest.raises(ValueError):
5699+
client = DatastreamClient(client_options=options, transport=transport,)
5700+
5701+
# It is an error to provide an api_key and a credential.
5702+
options = mock.Mock()
5703+
options.api_key = "api_key"
5704+
with pytest.raises(ValueError):
5705+
client = DatastreamClient(
5706+
client_options=options, credentials=ga_credentials.AnonymousCredentials()
5707+
)
5708+
56155709
# It is an error to provide scopes and a transport instance.
56165710
transport = transports.DatastreamGrpcTransport(
56175711
credentials=ga_credentials.AnonymousCredentials(),
@@ -6286,3 +6380,33 @@ def test_client_ctx():
62866380
with client:
62876381
pass
62886382
close.assert_called()
6383+
6384+
6385+
@pytest.mark.parametrize(
6386+
"client_class,transport_class",
6387+
[
6388+
(DatastreamClient, transports.DatastreamGrpcTransport),
6389+
(DatastreamAsyncClient, transports.DatastreamGrpcAsyncIOTransport),
6390+
],
6391+
)
6392+
def test_api_key_credentials(client_class, transport_class):
6393+
with mock.patch.object(
6394+
google.auth._default, "get_api_key_credentials", create=True
6395+
) as get_api_key_credentials:
6396+
mock_cred = mock.Mock()
6397+
get_api_key_credentials.return_value = mock_cred
6398+
options = client_options.ClientOptions()
6399+
options.api_key = "api_key"
6400+
with mock.patch.object(transport_class, "__init__") as patched:
6401+
patched.return_value = None
6402+
client = client_class(client_options=options)
6403+
patched.assert_called_once_with(
6404+
credentials=mock_cred,
6405+
credentials_file=None,
6406+
host=client.DEFAULT_ENDPOINT,
6407+
scopes=None,
6408+
client_cert_source_for_mtls=None,
6409+
quota_project_id=None,
6410+
client_info=transports.base.DEFAULT_CLIENT_INFO,
6411+
always_use_jwt_access=True,
6412+
)

0 commit comments

Comments
 (0)