Skip to content

Commit 63bfeea

Browse files
feat: add httpx transport and async compute engine credentials
This commit adds a new async transport adapter using `httpx` and implements asynchronous Compute Engine credentials and metadata service interaction. Key changes: - Added `google.auth.transport._httpx_requests.py`: Async transport adapter using `httpx`. - Added `google.auth.compute_engine._credentials_async.py`: Async credentials for Compute Engine. - Added `google.auth.compute_engine._metadata_async.py`: Async interaction with GCE metadata server (transport-agnostic). - Updated `setup.py` to include `httpx` extra. - Added comprehensive unit tests in `tests_async/`. The implementation ensures proper decoupling of metadata logic from the transport layer and correctly handles `AuthorizedSession` lifecycle and concurrency.
1 parent 59a5f58 commit 63bfeea

File tree

7 files changed

+1247
-0
lines changed

7 files changed

+1247
-0
lines changed
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Copyright 2016 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
"""Google Compute Engine credentials.
16+
17+
This module provides authentication for an application running on Google
18+
Compute Engine using the Compute Engine metadata server.
19+
20+
"""
21+
22+
import google.auth._credentials_async as credentials_async
23+
from google.auth import exceptions
24+
from google.auth.compute_engine import _metadata_async as _metadata
25+
from google.auth.compute_engine import credentials as credentials_sync
26+
27+
28+
class Credentials(
29+
credentials_sync.Credentials,
30+
credentials_async.Credentials,
31+
credentials_async.Scoped,
32+
credentials_async.CredentialsWithQuotaProject
33+
):
34+
"""Async Compute Engine Credentials.
35+
36+
These credentials use the Google Compute Engine metadata server to obtain
37+
OAuth 2.0 access tokens associated with the instance's service account,
38+
and are also used for Cloud Run, Flex and App Engine (except for the Python
39+
2.7 runtime, which is supported only on older versions of this library).
40+
41+
For more information about Compute Engine authentication, including how
42+
to configure scopes, see the `Compute Engine authentication
43+
documentation`_.
44+
45+
.. note:: On Compute Engine the metadata server ignores requested scopes.
46+
On Cloud Run, Flex and App Engine the server honours requested scopes.
47+
48+
.. _Compute Engine authentication documentation:
49+
https://cloud.google.com/compute/docs/authentication#using
50+
"""
51+
52+
def __init__(
53+
self,
54+
service_account_email="default",
55+
quota_project_id=None,
56+
scopes=None,
57+
default_scopes=None,
58+
):
59+
"""
60+
Args:
61+
service_account_email (str): The service account email to use, or
62+
'default'. A Compute Engine instance may have multiple service
63+
accounts.
64+
quota_project_id (Optional[str]): The project ID used for quota and
65+
billing.
66+
scopes (Optional[Sequence[str]]): The list of scopes for the creden
67+
tials.
68+
default_scopes (Optional[Sequence[str]]): Default scopes passed by
69+
a Google client library. Use 'scopes' for user-defined scopes.
70+
"""
71+
super(Credentials, self).__init__()
72+
self._service_account_email = service_account_email
73+
self._quota_project_id = quota_project_id
74+
self._scopes = scopes
75+
self._default_scopes = default_scopes
76+
77+
async def _retrieve_info(self, request):
78+
"""Retrieve information about the service account.
79+
80+
Updates the scopes and retrieves the full service account email.
81+
82+
Args:
83+
request (google.auth.transport.Request): The object used to make
84+
HTTP requests.
85+
"""
86+
info = await _metadata.get_service_account_info(
87+
request, service_account=self._service_account_email
88+
)
89+
90+
self._service_account_email = info["email"]
91+
92+
# Don't override scopes requested by the user.
93+
if self._scopes is None:
94+
self._scopes = info["scopes"]
95+
96+
async def refresh(self, request):
97+
"""Refresh the access token and scopes.
98+
99+
Args:
100+
request (google.auth.transport.Request): The object used to make
101+
HTTP requests.
102+
103+
Raises:
104+
google.auth.exceptions.RefreshError: If the Compute Engine metadata
105+
service can't be reached or if the instance has no
106+
credentials.
107+
"""
108+
scopes = self._scopes if self._scopes is not None else self._default_scopes
109+
try:
110+
await self._retrieve_info(request)
111+
self.token, self.expiry = await _metadata.get_service_account_token(
112+
request, service_account=self._service_account_email, scopes=scopes
113+
)
114+
except exceptions.TransportError as caught_exc:
115+
new_exc = exceptions.RefreshError(caught_exc)
116+
raise new_exc from caught_exc

0 commit comments

Comments
 (0)