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

Commit 3e91fb8

Browse files
feat: add api key support (#70)
* 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 fbce54d commit 3e91fb8

File tree

3 files changed

+252
-44
lines changed

3 files changed

+252
-44
lines changed

google/cloud/shell_v1/services/cloud_shell_service/async_client.py

+37-1
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
@@ -117,6 +117,42 @@ def from_service_account_file(cls, filename: str, *args, **kwargs):
117117

118118
from_service_account_json = from_service_account_file
119119

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

google/cloud/shell_v1/services/cloud_shell_service/client.py

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

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

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

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

340372
# Save or instantiate the transport.
341373
# Ordinarily, we provide the transport, but allowing a custom transport
342374
# instance provides an extensibility point for unusual situations.
343375
if isinstance(transport, CloudShellServiceTransport):
344376
# transport is a CloudShellServiceTransport instance.
345-
if credentials or client_options.credentials_file:
377+
if credentials or client_options.credentials_file or api_key_value:
346378
raise ValueError(
347379
"When providing a transport instance, "
348380
"provide its credentials directly."
@@ -354,6 +386,15 @@ def __init__(
354386
)
355387
self._transport = transport
356388
else:
389+
import google.auth._default # type: ignore
390+
391+
if api_key_value and hasattr(
392+
google.auth._default, "get_api_key_credentials"
393+
):
394+
credentials = google.auth._default.get_api_key_credentials(
395+
api_key_value
396+
)
397+
357398
Transport = type(self).get_transport_class(transport)
358399
self._transport = Transport(
359400
credentials=credentials,

tests/unit/gapic/shell_v1/test_cloud_shell_service.py

+131
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,87 @@ def test_cloud_shell_service_client_mtls_env_auto(
412412
)
413413

414414

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

1375+
# It is an error to provide an api_key and a transport instance.
1376+
transport = transports.CloudShellServiceGrpcTransport(
1377+
credentials=ga_credentials.AnonymousCredentials(),
1378+
)
1379+
options = client_options.ClientOptions()
1380+
options.api_key = "api_key"
1381+
with pytest.raises(ValueError):
1382+
client = CloudShellServiceClient(client_options=options, transport=transport,)
1383+
1384+
# It is an error to provide an api_key and a credential.
1385+
options = mock.Mock()
1386+
options.api_key = "api_key"
1387+
with pytest.raises(ValueError):
1388+
client = CloudShellServiceClient(
1389+
client_options=options, credentials=ga_credentials.AnonymousCredentials()
1390+
)
1391+
12941392
# It is an error to provide scopes and a transport instance.
12951393
transport = transports.CloudShellServiceGrpcTransport(
12961394
credentials=ga_credentials.AnonymousCredentials(),
@@ -1885,3 +1983,36 @@ def test_client_ctx():
18851983
with client:
18861984
pass
18871985
close.assert_called()
1986+
1987+
1988+
@pytest.mark.parametrize(
1989+
"client_class,transport_class",
1990+
[
1991+
(CloudShellServiceClient, transports.CloudShellServiceGrpcTransport),
1992+
(
1993+
CloudShellServiceAsyncClient,
1994+
transports.CloudShellServiceGrpcAsyncIOTransport,
1995+
),
1996+
],
1997+
)
1998+
def test_api_key_credentials(client_class, transport_class):
1999+
with mock.patch.object(
2000+
google.auth._default, "get_api_key_credentials", create=True
2001+
) as get_api_key_credentials:
2002+
mock_cred = mock.Mock()
2003+
get_api_key_credentials.return_value = mock_cred
2004+
options = client_options.ClientOptions()
2005+
options.api_key = "api_key"
2006+
with mock.patch.object(transport_class, "__init__") as patched:
2007+
patched.return_value = None
2008+
client = client_class(client_options=options)
2009+
patched.assert_called_once_with(
2010+
credentials=mock_cred,
2011+
credentials_file=None,
2012+
host=client.DEFAULT_ENDPOINT,
2013+
scopes=None,
2014+
client_cert_source_for_mtls=None,
2015+
quota_project_id=None,
2016+
client_info=transports.base.DEFAULT_CLIENT_INFO,
2017+
always_use_jwt_access=True,
2018+
)

0 commit comments

Comments
 (0)