This is an automated email from the ASF dual-hosted git repository.
yuqi4733 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/gravitino.git
The following commit(s) were added to refs/heads/main by this push:
new 34938d52af [#10315]fix(client-python): Fix GCS credential handling for
gcsfs 2026.2.0 compatibility (#10317)
34938d52af is described below
commit 34938d52aff8d2485015941724b01a8a0edfa314
Author: geyanggang <[email protected]>
AuthorDate: Tue Mar 10 14:34:20 2026 +0800
[#10315]fix(client-python): Fix GCS credential handling for gcsfs 2026.2.0
compatibility (#10317)
### What changes were proposed in this pull request?
Wrap GCS OAuth2 access tokens in a google.auth.credentials.Credentials
object before
passing to gcsfs.GCSFileSystem. The new gcsfs version (2026.2.0)
requires Credentials
objects instead of raw token strings.
Changes:
Created StaticCredentials class implementing
google.auth.credentials.Credentials interface
Properly converts token string and expiry time to Credentials object
Uses google.auth._helpers.utcnow() for consistent time comparison
### Why are the changes needed?
The gcsfs library was upgraded from 2024.3.1 to 2026.2.0 in commit
https://github.com/datastrato/gravitino-internal/commit/79e37271216ebed689b1b4ea592adb12d6173170.
The new
version changed the token parameter handling and no longer accepts raw
OAuth2 token strings.
It now requires google.auth.credentials.Credentials objects.
Without this fix, all GCS credential-based tests fail with "Provided
token is either not
valid, or expired" error, breaking the credential vending feature for
GCS storage.
Fix: #10315
### Does this PR introduce _any_ user-facing change?
No.
### How was this patch tested?
The CI can pass.
---
.../gravitino/filesystem/gvfs_storage_handler.py | 51 +++++++++++++++++++++-
1 file changed, 50 insertions(+), 1 deletion(-)
diff --git a/clients/client-python/gravitino/filesystem/gvfs_storage_handler.py
b/clients/client-python/gravitino/filesystem/gvfs_storage_handler.py
index 63c215079a..22abc27aa9 100644
--- a/clients/client-python/gravitino/filesystem/gvfs_storage_handler.py
+++ b/clients/client-python/gravitino/filesystem/gvfs_storage_handler.py
@@ -19,6 +19,7 @@ import logging
import sys
import time
from abc import ABC, abstractmethod
+from datetime import datetime, timezone
from enum import Enum
from typing import Optional, Tuple, List, Dict, Any
@@ -40,6 +41,14 @@ from gravitino.api.credential.s3_token_credential import
S3TokenCredential
from gravitino.exceptions.base import GravitinoRuntimeException
from gravitino.filesystem.gvfs_config import GVFSConfig
+try:
+ from google.auth.credentials import Credentials
+ from google.auth.exceptions import RefreshError
+
+ GOOGLE_AUTH_AVAILABLE = True
+except ImportError:
+ GOOGLE_AUTH_AVAILABLE = False
+
TIME_WITHOUT_EXPIRATION = sys.maxsize
SLASH = "/"
@@ -403,10 +412,50 @@ class GCSStorageHandler(StorageHandler):
catalog_props,
)
if isinstance(credential, GCSTokenCredential):
+ if not GOOGLE_AUTH_AVAILABLE:
+ raise GravitinoRuntimeException(
+ "Failed to import google.auth. "
+ "Please ensure google-auth is installed."
+ )
+
+ # gcsfs expects a google.auth.credentials.Credentials
object
+ # We need to create a credentials object from the access
token
+ class StaticCredentials(Credentials):
+ """A credentials object that uses a static access
token."""
+
+ def __init__(self, token_value, expiry_time_ms):
+ super().__init__()
+ self.token = token_value
+ # Convert milliseconds to datetime
+ self.expiry = datetime.fromtimestamp(
+ expiry_time_ms / 1000.0, tz=timezone.utc
+ )
+
+ def refresh(self, request):
+ # Static token cannot be refreshed
+ raise RefreshError(
+ "Cannot refresh static access token. "
+ "Please request a new credential from
Gravitino."
+ )
+
+ @property
+ def expired(self):
+ if not self.expiry:
+ return False
+ return datetime.now(timezone.utc) >= self.expiry
+
+ @property
+ def valid(self):
+ return self.token is not None and not self.expired
+
+ creds = StaticCredentials(
+ credential.token(), credential.expire_time_in_ms()
+ )
+
return (
expire_time,
self.get_filesystem(
- token=credential.token(),
+ token=creds,
**kwargs,
),
)