Skip to content

Commit 901ab3b

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 65c19f3 commit 901ab3b

3 files changed

Lines changed: 259 additions & 44 deletions

File tree

packages/google-cloud-resource-settings/google/cloud/resourcesettings_v1/services/resource_settings_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
@@ -123,6 +123,42 @@ def from_service_account_file(cls, filename: str, *args, **kwargs):
123123

124124
from_service_account_json = from_service_account_file
125125

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

packages/google-cloud-resource-settings/google/cloud/resourcesettings_v1/services/resource_settings_service/client.py

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

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

301-
# Create SSL credentials for mutual TLS if needed.
302-
if os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") not in (
303-
"true",
304-
"false",
305-
):
306-
raise ValueError(
307-
"Environment variable `GOOGLE_API_USE_CLIENT_CERTIFICATE` must be either `true` or `false`"
308-
)
309-
use_client_cert = (
310-
os.getenv("GOOGLE_API_USE_CLIENT_CERTIFICATE", "false") == "true"
368+
api_endpoint, client_cert_source_func = self.get_mtls_endpoint_and_cert_source(
369+
client_options
311370
)
312371

313-
client_cert_source_func = None
314-
is_mtls = False
315-
if use_client_cert:
316-
if client_options.client_cert_source:
317-
is_mtls = True
318-
client_cert_source_func = client_options.client_cert_source
319-
else:
320-
is_mtls = mtls.has_default_client_cert_source()
321-
if is_mtls:
322-
client_cert_source_func = mtls.default_client_cert_source()
323-
else:
324-
client_cert_source_func = None
325-
326-
# Figure out which api endpoint to use.
327-
if client_options.api_endpoint is not None:
328-
api_endpoint = client_options.api_endpoint
329-
else:
330-
use_mtls_env = os.getenv("GOOGLE_API_USE_MTLS_ENDPOINT", "auto")
331-
if use_mtls_env == "never":
332-
api_endpoint = self.DEFAULT_ENDPOINT
333-
elif use_mtls_env == "always":
334-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
335-
elif use_mtls_env == "auto":
336-
if is_mtls:
337-
api_endpoint = self.DEFAULT_MTLS_ENDPOINT
338-
else:
339-
api_endpoint = self.DEFAULT_ENDPOINT
340-
else:
341-
raise MutualTLSChannelError(
342-
"Unsupported GOOGLE_API_USE_MTLS_ENDPOINT value. Accepted "
343-
"values: never, auto, always"
344-
)
372+
api_key_value = getattr(client_options, "api_key", None)
373+
if api_key_value and credentials:
374+
raise ValueError(
375+
"client_options.api_key and credentials are mutually exclusive"
376+
)
345377

346378
# Save or instantiate the transport.
347379
# Ordinarily, we provide the transport, but allowing a custom transport
348380
# instance provides an extensibility point for unusual situations.
349381
if isinstance(transport, ResourceSettingsServiceTransport):
350382
# transport is a ResourceSettingsServiceTransport instance.
351-
if credentials or client_options.credentials_file:
383+
if credentials or client_options.credentials_file or api_key_value:
352384
raise ValueError(
353385
"When providing a transport instance, "
354386
"provide its credentials directly."
@@ -360,6 +392,15 @@ def __init__(
360392
)
361393
self._transport = transport
362394
else:
395+
import google.auth._default # type: ignore
396+
397+
if api_key_value and hasattr(
398+
google.auth._default, "get_api_key_credentials"
399+
):
400+
credentials = google.auth._default.get_api_key_credentials(
401+
api_key_value
402+
)
403+
363404
Transport = type(self).get_transport_class(transport)
364405
self._transport = Transport(
365406
credentials=credentials,

packages/google-cloud-resource-settings/tests/unit/gapic/resourcesettings_v1/test_resource_settings_service.py

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,89 @@ def test_resource_settings_service_client_mtls_env_auto(
416416
)
417417

418418

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

1321+
# It is an error to provide an api_key and a transport instance.
1322+
transport = transports.ResourceSettingsServiceGrpcTransport(
1323+
credentials=ga_credentials.AnonymousCredentials(),
1324+
)
1325+
options = client_options.ClientOptions()
1326+
options.api_key = "api_key"
1327+
with pytest.raises(ValueError):
1328+
client = ResourceSettingsServiceClient(
1329+
client_options=options, transport=transport,
1330+
)
1331+
1332+
# It is an error to provide an api_key and a credential.
1333+
options = mock.Mock()
1334+
options.api_key = "api_key"
1335+
with pytest.raises(ValueError):
1336+
client = ResourceSettingsServiceClient(
1337+
client_options=options, credentials=ga_credentials.AnonymousCredentials()
1338+
)
1339+
12381340
# It is an error to provide scopes and a transport instance.
12391341
transport = transports.ResourceSettingsServiceGrpcTransport(
12401342
credentials=ga_credentials.AnonymousCredentials(),
@@ -1802,3 +1904,39 @@ def test_client_ctx():
18021904
with client:
18031905
pass
18041906
close.assert_called()
1907+
1908+
1909+
@pytest.mark.parametrize(
1910+
"client_class,transport_class",
1911+
[
1912+
(
1913+
ResourceSettingsServiceClient,
1914+
transports.ResourceSettingsServiceGrpcTransport,
1915+
),
1916+
(
1917+
ResourceSettingsServiceAsyncClient,
1918+
transports.ResourceSettingsServiceGrpcAsyncIOTransport,
1919+
),
1920+
],
1921+
)
1922+
def test_api_key_credentials(client_class, transport_class):
1923+
with mock.patch.object(
1924+
google.auth._default, "get_api_key_credentials", create=True
1925+
) as get_api_key_credentials:
1926+
mock_cred = mock.Mock()
1927+
get_api_key_credentials.return_value = mock_cred
1928+
options = client_options.ClientOptions()
1929+
options.api_key = "api_key"
1930+
with mock.patch.object(transport_class, "__init__") as patched:
1931+
patched.return_value = None
1932+
client = client_class(client_options=options)
1933+
patched.assert_called_once_with(
1934+
credentials=mock_cred,
1935+
credentials_file=None,
1936+
host=client.DEFAULT_ENDPOINT,
1937+
scopes=None,
1938+
client_cert_source_for_mtls=None,
1939+
quota_project_id=None,
1940+
client_info=transports.base.DEFAULT_CLIENT_INFO,
1941+
always_use_jwt_access=True,
1942+
)

0 commit comments

Comments
 (0)