8000 feat: add api key support (#222) · googleapis/google-cloud-python@d8aa4b7 · GitHub
[go: up one dir, main page]

Skip to content

Commit d8aa4b7

Browse files
feat: add api key support (#222)
* 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 3eaf89c commit d8aa4b7

File tree

3 files changed

+245
-44
lines changed

3 files changed

+245
-44
lines changed

packages/google-cloud-build/google/cloud/devtools/cloudbuild_v1/services/cloud_build/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
@@ -132,6 +132,42 @@ def from_service_account_file(cls, filename: str, *args, **kwargs):
132132

133133
from_service_account_json = from_service_account_file
134134

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

packages/google-cloud-build/google/cloud/devtools/cloudbuild_v1/services/cloud_build/client.py

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

362+
@classmethod
363+
def get_mtls_endpoint_and_cert_source(
364+
cls, client_options: Optional[client_options_lib.ClientOptions] = None
365+
):
366+
"""Return the API endpoint and client cert source for mutual TLS.
367+
368+
The client cert source is determined in the following order:
369+
(1) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is not "true", the
370+
client cert source is None.
371+
(2) if `client_options.client_cert_source` is provided, use the provided one; if the
372+
default client cert source exists, use the default one; otherwise the client cert
373+
source is None.
374+
375+
The API endpoint is determined in the following order:
376+
(1) if `client_options.api_endpoint` if provided, use the provided one.
377+
(2) if `GOOGLE_API_USE_CLIENT_CERTIFICATE` environment variable is "always", use the
378+
default mTLS endpoint; if the environment variabel is "never", use the default API
379+
endpoint; otherwise if client cert source exists, use the default mTLS endpoint, otherwise
380+
use the default API endpoint.
381+
382+
More details can be found at https://google.aip.dev/auth/4114.
383+
384+
Args:
385+
client_options (google.api_core.client_options.ClientOptions): Custom options for the
386+
client. Only the `api_endpoint` and `client_cert_source` properties may be used
387+
in this method.
388+
389+
Returns:
390+
Tuple[str, Callable[[], Tuple[bytes, bytes]]]: returns the API endpoint and the
391+
client cert source to use.
392+
393+
Raises:
394+
google.auth.exceptions.MutualTLSChannelError: If any errors happen.
395+
"""
396+
if client_options is None:
397+
client_options = client_options_lib.ClientOptions()
398+
use_client_cert = os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false")
399+
use_mtls_endpoint = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
400+
if use_client_cert not in ("true", "false"):
401+
raise ValueError(
402+
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
403+
)
404+
if use_mtls_endpoint not in ("auto", "never", "always"):
405+
raise MutualTLSChannelError(
406+
"Environment variable `GOOGLE_API_USE_MTLS_ENDPOINT` must be `never`, `auto` or `always`"
407+
)
408+
409+
# Figure out the client cert source to use.
410+
client_cert_source = None
411+
if use_client_cert == "true":
412+
if client_options.client_cert_source:
413+
client_cert_source = client_options.client_cert_source
414+
elif mtls.has_default_client_cert_source():
415+
client_cert_source = mtls.default_client_cert_source()
416+
417+
# Figure out which api endpoint to use.
418+
if client_options.api_endpoint is not None:
419+
api_endpoint = client_options.api_endpoint
420+
elif use_mtls_endpoint == "always" or (
421+
use_mtls_endpoint == "auto" and client_cert_source
422+
):
423+
api_endpoint = cls.DEFAULT_MTLS_ENDPOINT
424+
else:
425+
api_endpoint = cls.DEFAULT_ENDPOINT
426+
427+
return api_endpoint, client_cert_source
428+
362429
def __init__(
363430
self,
364431
*,
@@ -409,57 +476,22 @@ def __init__(
409476
if client_options is None:
410477
client_options = client_options_lib.ClientOptions()
411478

412-
# Create SSL credentials for mutual TLS if needed.
413-
if os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") not in (
414-
"true",
415-
"false",
416-
):
417-
raise ValueError(
418-
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
419-
)
420-
use_client_cert = (
421-
os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true"
479+
api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source(
480+
client_options
422481
)
423482

424-
client_cert_source_func = None
425-
is_mtls = False
426-
if use_client_cert:
427-
if client_options.client_cert_source:
428-
is_mtls = True
429-
client_cert_source_func = client_options.client_cert_source
430-
else:
431-
is_mtls = mtls.has_default_client_cert_source()
432-
if is_mtls:
433-
client_cert_source_func = mtls.default_client_cert_source()
434-
else:
435-
client_cert_source_func = None
436-
437-
# Figure out which api endpoint to use.
438-
if client_options.api_endpoint is not None:
439-
api_endpoint = client_options.api_endpoint
440-
else:
441-
use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
442-
if use_mtls_env == "never":
443-
api_endpoint = self.DEFAULT_ENDPOINT
444-
elif use_mtls_env == "always":
445-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
446-
elif use_mtls_env == "auto":
447-
if is_mtls:
448-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
449-
else:
450-
api_endpoint = self.DEFAULT_ENDPOINT
451-
else:
452-
raise MutualTLSChannelError(
453-
"Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted "
454-
"values: never, auto, always"
455-
)
483+
api_key_value = getattr(client_options, "api_key", None)
484+
if api_key_value and credentials:
485+
raise ValueError(
486+
"client_options.api_key and credentials are mutually exclusive"
487+
)
456488

457489
# Save or instantiate the transport.
458490
# Ordinarily, we provide the transport, but allowing a custom transport
459491
# instance provides an extensibility point for unusual situations.
460492
if isinstance(transport, CloudBuildTransport):
461493
# transport is a CloudBuildTransport instance.
462-
if credentials or client_options.credentials_file:
494+
if credentials or client_options.credentials_file or api_key_value:
463495
raise ValueError(
464496
"When providing a transport instance, "
465497
"provide its credentials directly."
@@ -471,6 +503,15 @@ def __init__(
471503
)
472504
self._transport = transport
473505
else:
506+
import google.auth._default # type: ignore
507+
508+
if api_key_value and hasattr(
509+
google.auth._default, "get_api_key_credentials"
510+
):
511+
credentials = google.auth._default.get_api_key_credentials(
512+
api_key_value
513+
)
514+
474515
Transport = type(self).get_transport_class(transport)
475516
self._transport = Transport(
476517
credentials=credentials,

packages/google-cloud-build/tests/unit/gapic/cloudbuild_v1/test_cloud_build.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,83 @@ def test_cloud_build_client_mtls_env_auto(
395395
)
396396

397397

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

4398+
# It is an error to provide an api_key and a transport instance.
4399+
transport = transports.CloudBuildGrpcTransport(
4400+
credentials=ga_credentials.AnonymousCredentials(),
4401+
)
4402+
options = client_options.ClientOptions()
4403+
options.api_key = "api_key"
4404+
with pytest.raises(ValueError):
4405+
client = CloudBuildClient(client_options=options, transport=transport,)
4406+
4407+
# It is an error to provide an api_key and a credential.
4408+
options = mock.Mock()
4409+
options.api_key = "api_key"
4410+
with pytest.raises(ValueError):
4411+
client = CloudBuildClient(
4412+
client_options=options, credentials=ga_credentials.AnonymousCredentials()
4413+
)
4414+
43214415
# It is an error to provide scopes and a transport instance.
43224416
transport = transports.CloudBuildGrpcTransport(
43234417
credentials=ga_credentials.AnonymousCredentials(),
@@ -5086,3 +5180,33 @@ def test_client_ctx():
50865180
with client:
50875181
pass
50885182
close.assert_called()
5183+
5184+
5185+
@pytest.mark.parametrize(
5186+
"client_class,transport_class",
5187+
[
5188+
(CloudBuildClient, transports.CloudBuildGrpcTransport),
5189+
(CloudBuildAsyncClient, transports.CloudBuildGrpcAsyncIOTransport),
5190+
],
5191+
)
5192+
def test_api_key_credentials(client_class, transport_class):
5193+
with mock.patch.object(
5194+
google.auth._default, "get_api_key_credentials", create=True
5195+
) as get_api_key_credentials:
5196+
mock_cred = mock.Mock()
5197+
get_api_key_credentials.return_value = mock_cred
5198+
options = client_options.ClientOptions()
5199+
options.api_key = "api_key"
5200+
with mock.patch.object(transport_class, "__init__") as patched:
5201+
patched.return_value = None
5202+
client = client_class(client_options=options)
5203+
patched.assert_called_once_with(
5204+
credentials=mock_cred,
5205+
credentials_file=None,
5206+
host=client.DEFAULT_ENDPOINT,
5207+
scopes=None,
5208+
client_cert_source_for_mtls=None,
5209+
quota_project_id=None,
5210+
client_info=transports.base.DEFAULT_CLIENT_INFO,
5211+
always_use_jwt_access=True,
5212+
)

0 commit comments

Comments
 (0)
0