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

Commit a6ea0c6

Browse files
feat: add api key support (#236)
* 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 d62a42b commit a6ea0c6

File tree

3 files changed

+252
-44
lines changed

3 files changed

+252
-44
lines changed

google/cloud/devtools/containeranalysis_v1/services/container_analysis/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
@@ -124,6 +124,42 @@ def from_service_account_file(cls, filename: str, *args, **kwargs):
124124

125125
from_service_account_json = from_service_account_file
126126

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

google/cloud/devtools/containeranalysis_v1/services/container_analysis/client.py

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

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

293-
# Create SSL credentials for mutual TLS if needed.
294-
if os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") not in (
295-
"true",
296-
"false",
297-
):
298-
raise ValueError(
299-
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
300-
)
301-
use_client_cert = (
302-
os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true"
360+
api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source(
361+
client_options
303362
)
304363

305-
client_cert_source_func = None
306-
is_mtls = False
307-
if use_client_cert:
308-
if client_options.client_cert_source:
309-
is_mtls = True
310-
client_cert_source_func = client_options.client_cert_source
311-
else:
312-
is_mtls = mtls.has_default_client_cert_source()
313-
if is_mtls:
314-
client_cert_source_func = mtls.default_client_cert_source()
315-
else:
316-
client_cert_source_func = None
317-
318-
# Figure out which api endpoint to use.
319-
if client_options.api_endpoint is not None:
320-
api_endpoint = client_options.api_endpoint
321-
else:
322-
use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
323-
if use_mtls_env == "never":
324-
api_endpoint = self.DEFAULT_ENDPOINT
325-
elif use_mtls_env == "always":
326-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
327-
elif use_mtls_env == "auto":
328-
if is_mtls:
329-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
330-
else:
331-
api_endpoint = self.DEFAULT_ENDPOINT
332-
else:
333-
raise MutualTLSChannelError(
334-
"Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted "
335-
"values: never, auto, always"
336-
)
364+
api_key_value = getattr(client_options, "api_key", None)
365+
if api_key_value and credentials:
366+
raise ValueError(
367+
"client_options.api_key and credentials are mutually exclusive"
368+
)
337369

338370
# Save or instantiate the transport.
339371
# Ordinarily, we provide the transport, but allowing a custom transport
340372
# instance provides an extensibility point for unusual situations.
341373
if isinstance(transport, ContainerAnalysisTransport):
342374
# transport is a ContainerAnalysisTransport instance.
343-
if credentials or client_options.credentials_file:
375+
if credentials or client_options.credentials_file or api_key_value:
344376
raise ValueError(
345377
"When providing a transport instance, "
346378
"provide its credentials directly."
@@ -352,6 +384,15 @@ def __init__(
352384
)
353385
self._transport = transport
354386
else:
387+
import google.auth._default # type: ignore
388+
389+
if api_key_value and hasattr(
390+
google.auth._default, "get_api_key_credentials"
391+
):
392+
credentials = google.auth._default.get_api_key_credentials(
393+
api_key_value
394+
)
395+
355396
Transport = type(self).get_transport_class(transport)
356397
self._transport = Transport(
357398
credentials=credentials,

tests/unit/gapic/containeranalysis_v1/test_container_analysis.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,87 @@ def test_container_analysis_client_mtls_env_auto(
415415
)
416416

417417

418+
@pytest.mark.parametrize(
419+
"client_class", [ContainerAnalysisClient, ContainerAnalysisAsyncClient]
420+
)
421+
@mock.patch.object(
422+
ContainerAnalysisClient,
423+
"DEFAULT_ENDPOINT",
424+
modify_default_endpoint(ContainerAnalysisClient),
425+
)
426+
@mock.patch.object(
427+
ContainerAnalysisAsyncClient,
428+
"DEFAULT_ENDPOINT",
429+
modify_default_endpoint(ContainerAnalysisAsyncClient),
430+
)
431+
def test_container_analysis_client_get_mtls_endpoint_and_cert_source(client_class):
432+
mock_client_cert_source = mock.Mock()
433+
434+
# Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "true".
435+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
436+
mock_api_endpoint = "foo"
437+
options = client_options.ClientOptions(
438+
client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint
439+
)
440+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source(
441+
options
442+
)
443+
assert api_endpoint == mock_api_endpoint
444+
assert cert_source == mock_client_cert_source
445+
446+
# Test the case GOOGLE_API_USE_CLIENT_CERTIFICATE is "false".
447+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "false"}):
448+
mock_client_cert_source = mock.Mock()
449+
mock_api_endpoint = "foo"
450+
options = client_options.ClientOptions(
451+
client_cert_source=mock_client_cert_source, api_endpoint=mock_api_endpoint
452+
)
453+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source(
454+
options
455+
)
456+
assert api_endpoint == mock_api_endpoint
457+
assert cert_source is None
458+
459+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "never".
460+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "never"}):
461+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
462+
assert api_endpoint == client_class.DEFAULT_ENDPOINT
463+
assert cert_source is None
464+
465+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "always".
466+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_MTLS_ENDPOINT": "always"}):
467+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
468+
assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT
469+
assert cert_source is None
470+
471+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert doesn't exist.
472+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
473+
with mock.patch(
474+
"google.auth.transport.mtls.has_default_client_cert_source",
475+
return_value=False,
476+
):
477+
api_endpoint, cert_source = client_class.get_mtls_endpoint_and_cert_source()
478+
assert api_endpoint == client_class.DEFAULT_ENDPOINT
479+
assert cert_source is None
480+
481+
# Test the case GOOGLE_API_USE_MTLS_ENDPOINT is "auto" and default cert exists.
482+
with mock.patch.dict(os.environ, {"GOOGLE_API_USE_CLIENT_CERTIFICATE": "true"}):
483+
with mock.patch(
484+
"google.auth.transport.mtls.has_default_client_cert_source",
485+
return_value=True,
486+
):
487+
with mock.patch(
488+
"google.auth.transport.mtls.default_client_cert_source",
489+
return_value=mock_client_cert_source,
490+
):
491+
(
492+
api_endpoint,
493+
cert_source,
494+
) = client_class.get_mtls_endpoint_and_cert_source()
495+
assert api_endpoint == client_class.DEFAULT_MTLS_ENDPOINT
496+
assert cert_source == mock_client_cert_source
497+
498+
418499
@pytest.mark.parametrize(
419500
"client_class,transport_class,transport_name",
420501
[
@@ -1428,6 +1509,23 @@ def test_credentials_transport_error():
14281509
transport=transport,
14291510
)
14301511

1512+
# It is an error to provide an api_key and a transport instance.
1513+
transport = transports.ContainerAnalysisGrpcTransport(
1514+
credentials=ga_credentials.AnonymousCredentials(),
1515+
)
1516+
options = client_options.ClientOptions()
1517+
options.api_key = "api_key"
1518+
with pytest.raises(ValueError):
1519+
client = ContainerAnalysisClient(client_options=options, transport=transport,)
1520+
1521+
# It is an error to provide an api_key and a credential.
1522+
options = mock.Mock()
1523+
options.api_key = "api_key"
1524+
with pytest.raises(ValueError):
1525+
client = ContainerAnalysisClient(
1526+
client_options=options, credentials=ga_credentials.AnonymousCredentials()
1527+
)
1528+
14311529
# It is an error to provide scopes and a transport instance.
14321530
transport = transports.ContainerAnalysisGrpcTransport(
14331531
credentials=ga_credentials.AnonymousCredentials(),
@@ -1966,3 +2064,36 @@ def test_client_ctx():
19662064
with client:
19672065
pass
19682066
close.assert_called()
2067+
2068+
2069+
@pytest.mark.parametrize(
2070+
"client_class,transport_class",
2071+
[
2072+
(ContainerAnalysisClient, transports.ContainerAnalysisGrpcTransport),
2073+
(
2074+
ContainerAnalysisAsyncClient,
2075+
transports.ContainerAnalysisGrpcAsyncIOTransport,
2076+
),
2077+
],
2078+
)
2079+
def test_api_key_credentials(client_class, transport_class):
2080+
with mock.patch.object(
2081+
google.auth._default, "get_api_key_credentials", create=True
2082+
) as get_api_key_credentials:
2083+
mock_cred = mock.Mock()
2084+
get_api_key_credentials.return_value = mock_cred
2085+
options = client_options.ClientOptions()
2086+
options.api_key = "api_key"
2087+
with mock.patch.object(transport_class, "__init__") as patched:
2088+
patched.return_value = None
2089+
client = client_class(client_options=options)
2090+
patched.assert_called_once_with(
2091+
credentials=mock_cred,
2092+
credentials_file=None,
2093+
host=client.DEFAULT_ENDPOINT,
2094+
scopes=None,
2095+
client_cert_source_for_mtls=None,
2096+
quota_project_id=None,
2097+
client_info=transports.base.DEFAULT_CLIENT_INFO,
2098+
always_use_jwt_access=True,
2099+
)

0 commit comments

Comments
 (0)