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

Commit 258eb69

Browse files
feat: add api key support (#146)
* 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 754e2f7 commit 258eb69

File tree

3 files changed

+254
-44
lines changed

3 files changed

+254
-44
lines changed

google/cloud/functions_v1/services/cloud_functions_service/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
@@ -128,6 +128,42 @@ def from_service_account_file(cls, filename: str, *args, **kwargs):
128128

129129
from_service_account_json = from_service_account_file
130130

131+
@classmethod
132+
def get_mtls_endpoint_and_cert_source(
133+
cls, client_options: Optional[ClientOptions] = None
134+
):
135+
"""Return the API endpoint and client cert source for mutual TLS.
136+
137+
The client cert source is determined in the following order:
138+
(1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
139+
client cert source is None.
140+
(2) if `client_options.client_cert_source` is provided, use the provided one; if the
141+
default client cert source exists, use the default one; otherwise the client cert
142+
source is None.
143+
144+
The API endpoint is determined in the following order:
145+
(1) if `client_options.api_endpoint` if provided, use the provided one.
146+
(2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
147+
default mTLS endpoint; if the environment variabel is "never", use the default API
148+
endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
149+
use the default API endpoint.
150+
151+
More details can be found at https://google.aip.dev/auth/4114.
152+
153+
Args:
154+
client_options (google.api_core.client_options.ClientOptions): Custom options for the
155+
client. Only the `api_endpoint` and `client_cert_source` properties may be used
156+
in this method.
157+
158+
Returns:
159+
Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
160+
client cert source to use.
161+
162+
Raises:
163+
google.auth.exceptions.MutualTLSChannelError: If any errors happen.
164+
"""
165+
return CloudFunctionsServiceClient.get_mtls_endpoint_and_cert_source(client_options) # type: ignore
166+
131167
@property
132168
def transport(self) -> CloudFunctionsServiceTransport:
133169
"""Returns the transport used by the client instance.

google/cloud/functions_v1/services/cloud_functions_service/client.py

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

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

336-
# Create SSL credentials for mutual TLS if needed.
337-
if os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") not in (
338-
"true",
339-
"false",
340-
):
341-
raise ValueError(
342-
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
343-
)
344-
use_client_cert = (
345-
os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true"
403+
api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source(
404+
client_options
346405
)
347406

348-
client_cert_source_func = None
349-
is_mtls = False
350-
if use_client_cert:
351-
if client_options.client_cert_source:
352-
is_mtls = True
353-
client_cert_source_func = client_options.client_cert_source
354-
else:
355-
is_mtls = mtls.has_default_client_cert_source()
356-
if is_mtls:
357-
client_cert_source_func = mtls.default_client_cert_source()
358-
else:
359-
client_cert_source_func = None
360-
361-
# Figure out which api endpoint to use.
362-
if client_options.api_endpoint is not None:
363-
api_endpoint = client_options.api_endpoint
364-
else:
365-
use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
366-
if use_mtls_env == "never":
367-
api_endpoint = self.DEFAULT_ENDPOINT
368-
elif use_mtls_env == "always":
369-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
370-
elif use_mtls_env == "auto":
371-
if is_mtls:
372-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
373-
else:
374-
api_endpoint = self.DEFAULT_ENDPOINT
375-
else:
376-
raise MutualTLSChannelError(
377-
"Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted "
378-
"values: never, auto, always"
379-
)
407+
api_key_value = getattr(client_options, "api_key", None)
408+
if api_key_value and credentials:
409+
raise ValueError(
410+
"client_options.api_key and credentials are mutually exclusive"
411+
)
380412

381413
# Save or instantiate the transport.
382414
# Ordinarily, we provide the transport, but allowing a custom transport
383415
# instance provides an extensibility point for unusual situations.
384416
if isinstance(transport, CloudFunctionsServiceTransport):
385417
# transport is a CloudFunctionsServiceTransport instance.
386-
if credentials or client_options.credentials_file:
418+
if credentials or client_options.credentials_file or api_key_value:
387419
raise ValueError(
388420
"When providing a transport instance, "
389421
"provide its credentials directly."
@@ -395,6 +427,15 @@ def __init__(
395427
)
396428
self._transport = transport
397429
else:
430+
import google.auth._default # type: ignore
431+
432+
if api_key_value and hasattr(
433+
google.auth._default, "get_api_key_credentials"
434+
):
435+
credentials = google.auth._default.get_api_key_credentials(
436+
api_key_value
437+
)
438+
398439
Transport = type(self).get_transport_class(transport)
399440
self._transport = Transport(
400441
credentials=credentials,

tests/unit/gapic/functions_v1/test_cloud_functions_service.py

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,87 @@ def test_cloud_functions_service_client_mtls_env_auto(
426426
)
427427

428428

429+
@pytest.mark.parametrize(
430+
"client_class", [CloudFunctionsServiceClient, CloudFunctionsServiceAsyncClient]
431+
)
432+
@mock.patch.object(
433+
CloudFunctionsServiceClient,
434+
"DEFAULT_ENDPOINT",
435+
modify_default_endpoint(CloudFunctionsServiceClient),
436+
)
437+
@mock.patch.object(
438+
CloudFunctionsServiceAsyncClient,
439+
"DEFAULT_ENDPOINT",
440+
modify_default_endpoint(CloudFunctionsServiceAsyncClient),
441+
)
442+
def test_cloud_functions_service_client_get_mtls_endpoint_and_cert_source(client_class):
443+
mock_client_cert_source = mock.Mock()
444+
445+
# Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "true".
446+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
447+
mock_api_endpoint = "foo"
448+
options = client_options.ClientOptions(
449+
client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint
450+
)
451+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source(
452+
options
453+
)
454+
assert api_endpoint == mock_api_endpoint
455+
assert cert_source == mock_client_cert_source
456+
457+
# Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "false".
458+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}):
459+
mock_client_cert_source = mock.Mock()
460+
mock_api_endpoint = "foo"
461+
options = client_options.ClientOptions(
462+
client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint
463+
)
464+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source(
465+
options
466+
)
467+
assert api_endpoint == mock_api_endpoint
468+
assert cert_source is None
469+
470+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "never".
471+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}):
472+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
473+
assert api_endpoint == client_class.DEFAULT_ENDPOINT
474+
assert cert_source is None
475+
476+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "always".
477+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}):
478+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
479+
assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT
480+
assert cert_source is None
481+
482+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert doesn't exist.
483+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
484+
with mock.patch(
485+
"google.auth.transport.mtls.has_default_client_cert_source",
486+
return_value=False,
487+
):
488+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
489+
assert api_endpoint == client_class.DEFAULT_ENDPOINT
490+
assert cert_source is None
491+
492+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert exists.
493+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
494+
with mock.patch(
495+
"google.auth.transport.mtls.has_default_client_cert_source",
496+
return_value=True,
497+
):
498+
with mock.patch(
499+
"google.auth.transport.mtls.default_client_cert_source",
500+
return_value=mock_client_cert_source,
501+
):
502+
(
503+
api_endpoint,
504+
cert_source,
505+
) = client_class.get_mtls_endpoint_and_cert_source()
506+
assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT
507+
assert cert_source == mock_client_cert_source
508+
509+
429510
@pytest.mark.parametrize(
430511
"client_class,transport_class,transport_name",
431512
[
@@ -2734,6 +2815,25 @@ def test_credentials_transport_error():
27342815
transport=transport,
27352816
)
27362817

2818+
# It is an error to provide an api_key and a transport instance.
2819+
transport = transports.CloudFunctionsServiceGrpcTransport(
2820+
credentials=ga_credentials.AnonymousCredentials(),
2821+
)
2822+
options = client_options.ClientOptions()
2823+
options.api_key = "api_key"
2824+
with pytest.raises(ValueError):
2825+
client = CloudFunctionsServiceClient(
2826+
client_options=options, transport=transport,
2827+
)
2828+
2829+
# It is an error to provide an api_key and a credential.
2830+
options = mock.Mock()
2831+
options.api_key = "api_key"
2832+
with pytest.raises(ValueError):
2833+
client = CloudFunctionsServiceClient(
2834+
client_options=options, credentials=ga_credentials.AnonymousCredentials()
2835+
)
2836+
27372837
# It is an error to provide scopes and a transport instance.
27382838
transport = transports.CloudFunctionsServiceGrpcTransport(
27392839
credentials=ga_credentials.AnonymousCredentials(),
@@ -3394,3 +3494,36 @@ def test_client_ctx():
33943494
with client:
33953495
pass
33963496
close.assert_called()
3497+
3498+
3499+
@pytest.mark.parametrize(
3500+
"client_class,transport_class",
3501+
[
3502+
(CloudFunctionsServiceClient, transports.CloudFunctionsServiceGrpcTransport),
3503+
(
3504+
CloudFunctionsServiceAsyncClient,
3505+
transports.CloudFunctionsServiceGrpcAsyncIOTransport,
3506+
),
3507+
],
3508+
)
3509+
def test_api_key_credentials(client_class, transport_class):
3510+
with mock.patch.object(
3511+
google.auth._default, "get_api_key_credentials", create=True
3512+
) as get_api_key_credentials:
3513+
mock_cred = mock.Mock()
3514+
get_api_key_credentials.return_value = mock_cred
3515+
options = client_options.ClientOptions()
3516+
options.api_key = "api_key"
3517+
with mock.patch.object(transport_class, "__init__") as patched:
3518+
patched.return_value = None
3519+
client = client_class(client_options=options)
3520+
patched.assert_called_once_with(
3521+
credentials=mock_cred,
3522+
credentials_file=None,
3523+
host=client.DEFAULT_ENDPOINT,
3524+
scopes=None,
3525+
client_cert_source_for_mtls=None,
3526+
quota_project_id=None,
3527+
client_info=transports.base.DEFAULT_CLIENT_INFO,
3528+
always_use_jwt_access=True,
3529+
)

0 commit comments

Comments
 (0)