Skip to content

Commit 5d5b59b

Browse files
feat: add api key support (#74)
* chore: upgrade gapic-generator-java, gax-java and gapic-generator-python PiperOrigin-RevId: 423842556 Source-Link: googleapis/googleapis@a616ca0 Source-Link: googleapis/googleapis-gen@29b938c 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 cedd432 commit 5d5b59b

File tree

3 files changed

+245
-44
lines changed

3 files changed

+245
-44
lines changed

packages/google-cloud-service-usage/google/cloud/service_usage_v1/services/service_usage/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
@@ -109,6 +109,42 @@ def from_service_account_file(cls, filename: str, *args, **kwargs):
109109

110110
from_service_account_json = from_service_account_file
111111

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

packages/google-cloud-service-usage/google/cloud/service_usage_v1/services/service_usage/client.py

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

228+
@classmethod
229+
def get_mtls_endpoint_and_cert_source(
230+
cls, client_options: Optional[client_options_lib.ClientOptions] = None
231+
):
232+
"""Return the API endpoint and client cert source for mutual TLS.
233+
234+
The client cert source is determined in the following order:
235+
(1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
236+
client cert source is None.
237+
(2) if `client_options.client_cert_source` is provided, use the provided one; if the
238+
default client cert source exists, use the default one; otherwise the client cert
239+
source is None.
240+
241+
The API endpoint is determined in the following order:
242+
(1) if `client_options.api_endpoint` if provided, use the provided one.
243+
(2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
244+
default mTLS endpoint; if the environment variabel is "never", use the default API
245+
endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
246+
use the default API endpoint.
247+
248+
More details can be found at https://google.aip.dev/auth/4114.
249+
250+
Args:
251+
client_options (google.api_core.client_options.ClientOptions): Custom options for the
252+
client. Only the `api_endpoint` and `client_cert_source` properties may be used
253+
in this method.
254+
255+
Returns:
256+
Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
257+
client cert source to use.
258+
259+
Raises:
260+
google.auth.exceptions.MutualTLSChannelError: If any errors happen.
261+
"""
262+
if client_options is None:
263+
client_options = client_options_lib.ClientOptions()
264+
use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")
265+
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
266+
if use_client_cert not in ("true", "false"):
267+
raise ValueError(
268+
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
269+
)
270+
if use_mtls_endpoint not in ("auto", "never", "always"):
271+
raise MutualTLSChannelError(
272+
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
273+
)
274+
275+
# Figure out the client cert source to use.
276+
client_cert_source = None
277+
if use_client_cert == "true":
278+
if client_options.client_cert_source:
279+
client_cert_source = client_options.client_cert_source
280+
elif mtls.has_default_client_cert_source():
281+
client_cert_source = mtls.default_client_cert_source()
282+
283+
# Figure out which api endpoint to use.
284+
if client_options.api_endpoint is not None:
285+
api_endpoint = client_options.api_endpoint
286+
elif use_mtls_endpoint == "always" or (
287+
use_mtls_endpoint == "auto" and client_cert_source
288+
):
289+
api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
290+
else:
291+
api_endpoint = cls.DEFAULT_ENDPOINT
292+
293+
return api_endpoint, client_cert_source
294+
228295
def __init__(
229296
self,
230297
*,
@@ -275,57 +342,22 @@ def __init__(
275342
if client_options is None:
276343
client_options = client_options_lib.ClientOptions()
277344

278-
# Create SSL credentials for mutual TLS if needed.
279-
if os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") not in (
280-
"true",
281-
"false",
282-
):
283-
raise ValueError(
284-
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
285-
)
286-
use_client_cert = (
287-
os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true"
345+
api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source(
346+
client_options
288347
)
289348

290-
client_cert_source_func = None
291-
is_mtls = False
292-
if use_client_cert:
293-
if client_options.client_cert_source:
294-
is_mtls = True
295-
client_cert_source_func = client_options.client_cert_source
296-
else:
297-
is_mtls = mtls.has_default_client_cert_source()
298-
if is_mtls:
299-
client_cert_source_func = mtls.default_client_cert_source()
300-
else:
301-
client_cert_source_func = None
302-
303-
# Figure out which api endpoint to use.
304-
if client_options.api_endpoint is not None:
305-
api_endpoint = client_options.api_endpoint
306-
else:
307-
use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
308-
if use_mtls_env == "never":
309-
api_endpoint = self.DEFAULT_ENDPOINT
310-
elif use_mtls_env == "always":
311-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
312-
elif use_mtls_env == "auto":
313-
if is_mtls:
314-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
315-
else:
316-
api_endpoint = self.DEFAULT_ENDPOINT
317-
else:
318-
raise MutualTLSChannelError(
319-
"Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted "
320-
"values: never, auto, always"
321-
)
349+
api_key_value = getattr(client_options, "api_key", None)
350+
if api_key_value and credentials:
351+
raise ValueError(
352+
"client_options.api_key and credentials are mutually exclusive"
353+
)
322354

323355
# Save or instantiate the transport.
324356
# Ordinarily, we provide the transport, but allowing a custom transport
325357
# instance provides an extensibility point for unusual situations.
326358
if isinstance(transport, ServiceUsageTransport):
327359
# transport is a ServiceUsageTransport instance.
328-
if credentials or client_options.credentials_file:
360+
if credentials or client_options.credentials_file or api_key_value:
329361
raise ValueError(
330362
"When providing a transport instance, "
331363
"provide its credentials directly."
@@ -337,6 +369,15 @@ def __init__(
337369
)
338370
self._transport = transport
339371
else:
372+
import google.auth._default # type: ignore
373+
374+
if api_key_value and hasattr(
375+
google.auth._default, "get_api_key_credentials"
376+
):
377+
credentials = google.auth._default.get_api_key_credentials(
378+
api_key_value
379+
)
380+
340381
Transport = type(self).get_transport_class(transport)
341382
self._transport = Transport(
342383
credentials=credentials,

packages/google-cloud-service-usage/tests/unit/gapic/service_usage_v1/test_service_usage.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,83 @@ def test_service_usage_client_mtls_env_auto(
389389
)
390390

391391

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

1498+
# It is an error to provide an api_key and a transport instance.
1499+
transport = transports.ServiceUsageGrpcTransport(
1500+
credentials=ga_credentials.AnonymousCredentials(),
1501+
)
1502+
options = client_options.ClientOptions()
1503+
options.api_key = "api_key"
1504+
with pytest.raises(ValueError):
1505+
client = ServiceUsageClient(client_options=options, transport=transport,)
1506+
1507+
# It is an error to provide an api_key and a credential.
1508+
options = mock.Mock()
1509+
options.api_key = "api_key"
1510+
with pytest.raises(ValueError):
1511+
client = ServiceUsageClient(
1512+
client_options=options, credentials=ga_credentials.AnonymousCredentials()
1513+
)
1514+
14211515
# It is an error to provide scopes and a transport instance.
14221516
transport = transports.ServiceUsageGrpcTransport(
14231517
credentials=ga_credentials.AnonymousCredentials(),
@@ -1994,3 +2088,33 @@ def test_client_ctx():
19942088
with client:
19952089
pass
19962090
close.assert_called()
2091+
2092+
2093+
@pytest.mark.parametrize(
2094+
"client_class,transport_class",
2095+
[
2096+
(ServiceUsageClient, transports.ServiceUsageGrpcTransport),
2097+
(ServiceUsageAsyncClient, transports.ServiceUsageGrpcAsyncIOTransport),
2098+
],
2099+
)
2100+
def test_api_key_credentials(client_class, transport_class):
2101+
with mock.patch.object(
2102+
google.auth._default, "get_api_key_credentials", create=True
2103+
) as get_api_key_credentials:
2104+
mock_cred = mock.Mock()
2105+
get_api_key_credentials.return_value = mock_cred
2106+
options = client_options.ClientOptions()
2107+
options.api_key = "api_key"
2108+
with mock.patch.object(transport_class, "__init__") as patched:
2109+
patched.return_value = None
2110+
client = client_class(client_options=options)
2111+
patched.assert_called_once_with(
2112+
credentials=mock_cred,
2113+
credentials_file=None,
2114+
host=client.DEFAULT_ENDPOINT,
2115+
scopes=None,
2116+
client_cert_source_for_mtls=None,
2117+
quota_project_id=None,
2118+
client_info=transports.base.DEFAULT_CLIENT_INFO,
2119+
always_use_jwt_access=True,
2120+
)

0 commit comments

Comments
 (0)