This is an automated email from the ASF dual-hosted git repository.
yasithdev pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/airavata-portals.git
The following commit(s) were added to refs/heads/main by this push:
new ae936f4e3 feat(portal): repoint credential summary reads to gRPC
(Track D, D2) (#164)
ae936f4e3 is described below
commit ae936f4e3c1d875946bc31375407bd2a7b13b453
Author: Yasith Jayawardana <[email protected]>
AuthorDate: Mon Jun 8 19:15:18 2026 -0400
feat(portal): repoint credential summary reads to gRPC (Track D, D2) (#164)
Migrate CredentialSummaryViewSet reads (get_list, get_instance, ssh and
password actions) from the Thrift client to the gRPC credential facade
(get_all_credential_summaries / get_credential_summary). Write actions
(create_ssh, create_password, destroy) stay on Thrift pending D3.
Add a credential_summary adapter mapping the protobuf CredentialSummary
to the Thrift attribute names the existing serializer reads, so the REST
contract is unchanged. proto and Thrift SummaryType assign different
integers to the same member name (proto SSH=1 vs Thrift SSH=0), so the
adapter bridges the enum by NAME via a reusable _thrift_enum helper, and
proto_summary_type converts the other direction for facade requests.
CredentialSummarySerializer.userHasWriteAccess now resolves via the gRPC
sharing helper.
Verified: manage.py check clean; empty list path returns 200 against the
live backend; structural adapter test (all Thrift fields, enum bridge by
name); offline serializer render confirms type labels as SSH/PASSWD and
persistedTime renders ISO.
---
.../django_airavata/apps/api/grpc_adapters.py | 49 ++++++++++++++++++++++
.../django_airavata/apps/api/serializers.py | 6 +--
.../django_airavata/apps/api/views.py | 31 +++++++-------
3 files changed, 67 insertions(+), 19 deletions(-)
diff --git a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
index dd4280f49..df6135482 100644
--- a/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
+++ b/airavata-django-portal/django_airavata/apps/api/grpc_adapters.py
@@ -13,6 +13,37 @@ serializers are made protobuf-native.
from types import SimpleNamespace
+from airavata.model.credential.store.ttypes import SummaryType as
_ThriftSummaryType
+
+
+def _thrift_enum(pb, field, thrift_enum):
+ """Map a protobuf enum field to the Thrift enum value of the SAME name.
+
+ proto and Thrift enums frequently assign different integers to the same
+ member name (e.g. ``SummaryType.SSH`` is 1 in proto but 0 in Thrift), so
the
+ bridge must go by NAME, never by raw integer — otherwise an adapter would
+ silently mislabel one enum member as another.
+ """
+ enum_descriptor = pb.DESCRIPTOR.fields_by_name[field].enum_type
+ name = enum_descriptor.values_by_number[getattr(pb, field)].name
+ return getattr(thrift_enum, name)
+
+
+def proto_summary_type(thrift_summary_type):
+ """Thrift ``SummaryType`` -> proto ``SummaryType`` enum value (by name).
+
+ The credential facade's request messages take the proto enum value, so
views
+ that still speak in Thrift ``SummaryType`` (e.g. for delete dispatch)
convert
+ through here. Imported lazily so this module stays importable without the
+ gRPC SDK on the path (the SDK is required only once ``request.airavata`` is
+ actually used).
+ """
+ from airavata_sdk.generated.org.apache.airavata.model.credential.store
import (
+ credential_store_pb2,
+ )
+ return credential_store_pb2.SummaryType.Value(
+ _ThriftSummaryType(thrift_summary_type).name)
+
def project(pb):
"""gRPC ``Project`` protobuf -> ``ProjectSerializer`` (Thrift ``Project``)
shape."""
@@ -55,3 +86,21 @@ def experiment_summary(pb):
experimentStatus=pb.experiment_status,
statusUpdateTime=pb.status_update_time or None,
)
+
+
+def credential_summary(pb):
+ """gRPC ``CredentialSummary`` protobuf -> ``CredentialSummarySerializer``
shape."""
+ return SimpleNamespace(
+ # proto/Thrift SummaryType have different ints per name -> bridge by
name
+ # so the serializer's ThriftEnumField labels it correctly and
+ # perform_destroy's ``instance.type == SummaryType.SSH`` (Thrift)
holds.
+ type=_thrift_enum(pb, 'type', _ThriftSummaryType),
+ gatewayId=pb.gateway_id,
+ username=pb.username,
+ publicKey=pb.public_key,
+ # int64 epoch millis, like the Thrift field; the serializer's
+ # UTCPosixTimestampDateTimeField divides by 1000, so keep it an int.
+ persistedTime=pb.persisted_time,
+ token=pb.token,
+ description=pb.description,
+ )
diff --git a/airavata-django-portal/django_airavata/apps/api/serializers.py
b/airavata-django-portal/django_airavata/apps/api/serializers.py
index 0a45162c3..901f99969 100644
--- a/airavata-django-portal/django_airavata/apps/api/serializers.py
+++ b/airavata-django-portal/django_airavata/apps/api/serializers.py
@@ -1811,10 +1811,8 @@ class CredentialSummarySerializer(
userHasWriteAccess = serializers.SerializerMethodField()
def get_userHasWriteAccess(self, credential_summary):
- request = self.context['request']
- return request.airavata_client.userHasAccess(
- request.authz_token, credential_summary.token,
- ResourcePermissionType.WRITE)
+ return user_has_access(
+ self.context['request'], credential_summary.token)
class StoragePreferenceSerializer(
diff --git a/airavata-django-portal/django_airavata/apps/api/views.py
b/airavata-django-portal/django_airavata/apps/api/views.py
index 431d95d1b..ddb6e7fcd 100644
--- a/airavata-django-portal/django_airavata/apps/api/views.py
+++ b/airavata-django-portal/django_airavata/apps/api/views.py
@@ -1366,31 +1366,32 @@ class SharedEntityViewSet(mixins.RetrieveModelMixin,
class CredentialSummaryViewSet(APIBackedViewSet):
serializer_class = serializers.CredentialSummarySerializer
+ def _credential_summaries(self, summary_type):
+ return [
+ grpc_adapters.credential_summary(s)
+ for s in
self.request.airavata.credential.get_all_credential_summaries(
+ self.gateway_id,
grpc_adapters.proto_summary_type(summary_type))
+ ]
+
def get_list(self):
- ssh_creds = self.request.airavata_client.getAllCredentialSummaries(
- self.authz_token, SummaryType.SSH)
- pwd_creds = self.request.airavata_client.getAllCredentialSummaries(
- self.authz_token, SummaryType.PASSWD)
- return ssh_creds + pwd_creds
+ return (self._credential_summaries(SummaryType.SSH) +
+ self._credential_summaries(SummaryType.PASSWD))
def get_instance(self, lookup_value):
- return self.request.airavata_client.getCredentialSummary(
- self.authz_token, lookup_value)
+ return grpc_adapters.credential_summary(
+ self.request.airavata.credential.get_credential_summary(
+ lookup_value, self.gateway_id))
@action(detail=False)
def ssh(self, request):
- summaries = self.request.airavata_client.getAllCredentialSummaries(
- self.authz_token, SummaryType.SSH
- )
- serializer = self.get_serializer(summaries, many=True)
+ serializer = self.get_serializer(
+ self._credential_summaries(SummaryType.SSH), many=True)
return Response(serializer.data)
@action(detail=False)
def password(self, request):
- summaries = self.request.airavata_client.getAllCredentialSummaries(
- self.authz_token, SummaryType.PASSWD
- )
- serializer = self.get_serializer(summaries, many=True)
+ serializer = self.get_serializer(
+ self._credential_summaries(SummaryType.PASSWD), many=True)
return Response(serializer.data)
@action(methods=['post'], detail=False)