>From Hussain Towaileb <[email protected]>:

Hussain Towaileb has uploaded this change for review. ( 
https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/19444 )


Change subject: [ASTERIXDB-3565][EXT]: Add impersonate service account auth for 
GCS
......................................................................

[ASTERIXDB-3565][EXT]: Add impersonate service account auth for GCS

- user model changes: no
- storage format changes: no
- interface changes: no

Details:
Add support to impersonating a service account as
an authentication method for GCS links. This works
by providing a target service account to impersonate
and provide source credentials (ours) to use to
impersonate the account. There is no need to store
any temporary credentials/tokens as the SDK automatically
picks up the token generated, and if expired, automatically
refresh it for subsequent requests.

Ext-ref: MB-65121
Change-Id: Ie1d69faa45a03550c8e0fe66eb18c2ae53a8454a
---
M 
asterixdb/asterix-app/src/test/resources/runtimets/results/api/cluster_state_1_full/cluster_state_1_full.1.regexadm
M 
asterixdb/asterix-app/src/test/resources/runtimets/results/api/cluster_state_1/cluster_state_1.1.regexadm
M 
asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/google/gcs/GCSConstants.java
M 
asterixdb/asterix-app/src/main/java/org/apache/asterix/app/external/ExternalCredentialsCacheUpdater.java
M 
asterixdb/asterix-cloud/src/main/java/org/apache/asterix/cloud/writer/GCSExternalFileWriterFactory.java
M 
asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
M 
asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/aws/s3/S3AuthUtils.java
M 
asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/input/record/reader/gcs/GCSInputStream.java
M 
asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/ExternalDataUtils.java
A 
asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/google/gcs/GCSAuthUtils.java
M 
asterixdb/asterix-app/src/test/resources/runtimets/results/api/cluster_state_1_less/cluster_state_1_less.1.regexadm
M 
asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/google/gcs/GCSUtils.java
M 
asterixdb/asterix-common/src/main/java/org/apache/asterix/common/config/ExternalProperties.java
M 
asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/input/record/reader/gcs/parquet/GCSParquetReaderFactory.java
M asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
M 
asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/input/record/reader/gcs/GCSInputStreamFactory.java
16 files changed, 297 insertions(+), 128 deletions(-)



  git pull ssh://asterix-gerrit.ics.uci.edu:29418/asterixdb 
refs/changes/44/19444/1

diff --git 
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/external/ExternalCredentialsCacheUpdater.java
 
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/external/ExternalCredentialsCacheUpdater.java
index 4c382d0..10f536b 100644
--- 
a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/external/ExternalCredentialsCacheUpdater.java
+++ 
b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/external/ExternalCredentialsCacheUpdater.java
@@ -78,14 +78,15 @@

         String type = 
configuration.get(ExternalDataConstants.KEY_EXTERNAL_SOURCE_TYPE);
         if (ExternalDataConstants.KEY_ADAPTER_NAME_AWS_S3.equals(type)) {
-            credentials = generateAwsCredentials(configuration);
+            return generateAwsCredentials(configuration);
+        } else {
+            // this should never happen
+            throw new IllegalArgumentException("Unsupported external source 
type: " + type);
         }
-
-        return credentials;
     }

     // TODO: this can probably be refactored out into something that is 
AWS-specific
-    private Object generateAwsCredentials(Map<String, String> configuration)
+    private AwsSessionCredentials generateAwsCredentials(Map<String, String> 
configuration)
             throws HyracksDataException, CompilationException {
         String key = configuration.get(ExternalDataConstants.KEY_ENTITY_ID);
         AwsSessionCredentials credentials;
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/cluster_state_1/cluster_state_1.1.regexadm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/cluster_state_1/cluster_state_1.1.regexadm
index 3ffb080..c6d7ba9 100644
--- 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/cluster_state_1/cluster_state_1.1.regexadm
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/cluster_state_1/cluster_state_1.1.regexadm
@@ -61,6 +61,7 @@
     "compiler\.textsearchmemory" : 163840,
     "compiler\.windowmemory" : 196608,
     "default\.dir" : "target/io/dir/asterixdb",
+    "gcp.impersonate.service.account.duration" : 900,
     "library\.deploy\.timeout" : 1800,
     "log\.dir" : "logs/",
     "log\.level" : "DEBUG",
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/cluster_state_1_full/cluster_state_1_full.1.regexadm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/cluster_state_1_full/cluster_state_1_full.1.regexadm
index aeb3cea..562e195 100644
--- 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/cluster_state_1_full/cluster_state_1_full.1.regexadm
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/cluster_state_1_full/cluster_state_1_full.1.regexadm
@@ -61,6 +61,7 @@
     "compiler\.textsearchmemory" : 163840,
     "compiler\.windowmemory" : 196608,
     "default\.dir" : "target/io/dir/asterixdb",
+    "gcp.impersonate.service.account.duration" : 900,
     "library\.deploy\.timeout" : 1800,
     "log\.dir" : "logs/",
     "log\.level" : "WARN",
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/cluster_state_1_less/cluster_state_1_less.1.regexadm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/cluster_state_1_less/cluster_state_1_less.1.regexadm
index b475612..132fa5b 100644
--- 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/api/cluster_state_1_less/cluster_state_1_less.1.regexadm
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/api/cluster_state_1_less/cluster_state_1_less.1.regexadm
@@ -61,6 +61,7 @@
     "compiler\.textsearchmemory" : 163840,
     "compiler\.windowmemory" : 196608,
     "default\.dir" : "target/io/dir/asterixdb",
+    "gcp.impersonate.service.account.duration" : 900,
     "library\.deploy\.timeout" : 1800,
     "log\.dir" : "logs/",
     "log\.level" : "WARN",
diff --git 
a/asterixdb/asterix-cloud/src/main/java/org/apache/asterix/cloud/writer/GCSExternalFileWriterFactory.java
 
b/asterixdb/asterix-cloud/src/main/java/org/apache/asterix/cloud/writer/GCSExternalFileWriterFactory.java
index 63a8366..1f1874f 100644
--- 
a/asterixdb/asterix-cloud/src/main/java/org/apache/asterix/cloud/writer/GCSExternalFileWriterFactory.java
+++ 
b/asterixdb/asterix-cloud/src/main/java/org/apache/asterix/cloud/writer/GCSExternalFileWriterFactory.java
@@ -27,7 +27,7 @@
 import org.apache.asterix.common.api.IApplicationContext;
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.external.util.ExternalDataConstants;
-import org.apache.asterix.external.util.google.gcs.GCSUtils;
+import org.apache.asterix.external.util.google.gcs.GCSAuthUtils;
 import org.apache.asterix.runtime.writer.ExternalFileWriterConfiguration;
 import org.apache.asterix.runtime.writer.IExternalFileWriter;
 import org.apache.asterix.runtime.writer.IExternalFileWriterFactory;
@@ -64,7 +64,7 @@
     @Override
     ICloudClient createCloudClient(IApplicationContext appCtx) throws 
CompilationException {
         GCSClientConfig config = GCSClientConfig.of(configuration, 
writeBufferSize);
-        return new GCSCloudClient(config, GCSUtils.buildClient(configuration),
+        return new GCSCloudClient(config, GCSAuthUtils.buildClient(appCtx, 
configuration),
                 ICloudGuardian.NoOpCloudGuardian.INSTANCE);
     }

diff --git 
a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/config/ExternalProperties.java
 
b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/config/ExternalProperties.java
index 62437b2..d233606 100644
--- 
a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/config/ExternalProperties.java
+++ 
b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/config/ExternalProperties.java
@@ -64,7 +64,12 @@
                 75,
                 "Percentage of duration passed before assume role credentials 
need to be refreshed, the value ranges "
                         + "from 25 to 90, default is 75. For example, if the 
value is set to 65, this means the "
-                        + "credentials need to be refreshed if 65% of the 
total expiration duration is already passed");
+                        + "credentials need to be refreshed if 65% of the 
total expiration duration is already passed"),
+        GCP_IMPERSONATE_SERVICE_ACCOUNT_DURATION(
+                getRangedIntegerType(60, 3600),
+                900,
+                "GCS impersonating service account duration in seconds. "
+                        + "Range from 60 seconds (1 min) to 3600 seconds (1 
hour)");

         private final IOptionType type;
         private final Object defaultValue;
@@ -94,6 +99,7 @@
                 case AZURE_REQUEST_TIMEOUT:
                 case AWS_ASSUME_ROLE_DURATION:
                 case AWS_REFRESH_ASSUME_ROLE_THRESHOLD_PERCENTAGE:
+                case GCP_IMPERSONATE_SERVICE_ACCOUNT_DURATION:
                     return Section.COMMON;
                 case CC_JAVA_OPTS:
                 case NC_JAVA_OPTS:
@@ -182,4 +188,8 @@
     public int getAwsRefreshAssumeRoleThresholdPercentage() {
         return 
accessor.getInt(Option.AWS_REFRESH_ASSUME_ROLE_THRESHOLD_PERCENTAGE);
     }
+
+    public int getGcpImpersonateServiceAccountDuration() {
+        return 
accessor.getInt(Option.GCP_IMPERSONATE_SERVICE_ACCOUNT_DURATION);
+    }
 }
diff --git 
a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
 
b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
index d06a9e5..1fa078b 100644
--- 
a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
+++ 
b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
@@ -320,6 +320,8 @@
     FAILED_EXTERNAL_CROSS_ACCOUNT_AUTHENTICATION(1213),
     LONG_LIVED_CREDENTIALS_NEEDED_TO_ASSUME_ROLE(1214),
     TEMPORARY_CREDENTIALS_CANNOT_BE_USED_TO_ASSUME_ROLE(1215),
+    NO_VALID_AUTHENTICATION_PARAMS_PROVIDED(1216),
+    
NO_VALID_AUTHENTICATION_PARAMS_PROVIDED_TO_IMPERSONATE_SERVICE_ACCOUNT(1217),

     // Feed errors
     DATAFLOW_ILLEGAL_STATE(3001),
diff --git 
a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties 
b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
index c1ffd13..e1cc667 100644
--- a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
+++ b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
@@ -322,6 +322,8 @@
 1213 = Failed to perform cross-account authentication. Encountered error : 
'%1$s'
 1214 = Long-lived credentials are required to assume a role
 1215 = Temporary credentials cannot be used to assume a role
+1216 = No valid authentication parameters were provided
+1217 = No valid authentication parameters were provided to impersonate service 
account

 # Feed Errors
 3001 = Illegal state.
diff --git 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/input/record/reader/gcs/GCSInputStream.java
 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/input/record/reader/gcs/GCSInputStream.java
index 89da065..1ef3fcd 100644
--- 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/input/record/reader/gcs/GCSInputStream.java
+++ 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/input/record/reader/gcs/GCSInputStream.java
@@ -27,13 +27,14 @@
 import java.util.concurrent.TimeUnit;
 import java.util.zip.GZIPInputStream;

+import org.apache.asterix.common.api.IApplicationContext;
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.exceptions.RuntimeDataException;
 import 
org.apache.asterix.external.input.filter.embedder.IExternalFilterValueEmbedder;
 import 
org.apache.asterix.external.input.record.reader.abstracts.AbstractExternalInputStream;
 import org.apache.asterix.external.util.ExternalDataConstants;
-import org.apache.asterix.external.util.google.gcs.GCSUtils;
+import org.apache.asterix.external.util.google.gcs.GCSAuthUtils;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.hyracks.api.exceptions.HyracksDataException;
 import org.apache.hyracks.api.util.CleanupUtils;
@@ -46,13 +47,15 @@

 public class GCSInputStream extends AbstractExternalInputStream {

+    private final IApplicationContext ncAppCtx;
     private final Storage client;
     private final String container;
     private static final int MAX_ATTEMPTS = 5; // We try a total of 5 times in 
case of retryable errors

-    public GCSInputStream(Map<String, String> configuration, List<String> 
filePaths,
+    public GCSInputStream(IApplicationContext ncAppCtx, Map<String, String> 
configuration, List<String> filePaths,
             IExternalFilterValueEmbedder valueEmbedder) throws 
HyracksDataException {
         super(configuration, filePaths, valueEmbedder);
+        this.ncAppCtx = ncAppCtx;
         this.client = buildClient(configuration);
         this.container = 
configuration.get(ExternalDataConstants.CONTAINER_NAME_FIELD_NAME);
     }
@@ -136,7 +139,7 @@

     private Storage buildClient(Map<String, String> configuration) throws 
HyracksDataException {
         try {
-            return GCSUtils.buildClient(configuration);
+            return GCSAuthUtils.buildClient(ncAppCtx, configuration);
         } catch (CompilationException ex) {
             throw HyracksDataException.create(ex);
         }
diff --git 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/input/record/reader/gcs/GCSInputStreamFactory.java
 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/input/record/reader/gcs/GCSInputStreamFactory.java
index b6ad3cd..2de55a0 100644
--- 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/input/record/reader/gcs/GCSInputStreamFactory.java
+++ 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/input/record/reader/gcs/GCSInputStreamFactory.java
@@ -23,6 +23,7 @@
 import java.util.Map;
 import java.util.PriorityQueue;

+import org.apache.asterix.common.api.IApplicationContext;
 import org.apache.asterix.common.external.IExternalFilterEvaluator;
 import org.apache.asterix.common.external.IExternalFilterEvaluatorFactory;
 import org.apache.asterix.external.api.AsterixInputStream;
@@ -47,7 +48,9 @@
     public AsterixInputStream createInputStream(IExternalDataRuntimeContext 
context) throws HyracksDataException {
         IExternalFilterValueEmbedder valueEmbedder = 
context.getValueEmbedder();
         int partition = context.getPartition();
-        return new GCSInputStream(configuration, 
partitionWorkLoadsBasedOnSize.get(partition).getFilePaths(),
+        IApplicationContext ncAppCtx = (IApplicationContext) 
context.getTaskContext().getJobletContext()
+                .getServiceContext().getApplicationContext();
+        return new GCSInputStream(ncAppCtx, configuration, 
partitionWorkLoadsBasedOnSize.get(partition).getFilePaths(),
                 valueEmbedder);
     }

@@ -65,7 +68,8 @@
         configuration.put(ExternalDataPrefix.PREFIX_ROOT_FIELD_NAME, 
externalDataPrefix.getRoot());

         // get the items
-        List<Blob> filesOnly = GCSUtils.listItems(configuration, 
includeExcludeMatcher, warningCollector,
+        IApplicationContext appCtx = (IApplicationContext) 
ctx.getApplicationContext();
+        List<Blob> filesOnly = GCSUtils.listItems(appCtx, configuration, 
includeExcludeMatcher, warningCollector,
                 externalDataPrefix, evaluator);

         // Distribute work load amongst the partitions
diff --git 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/input/record/reader/gcs/parquet/GCSParquetReaderFactory.java
 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/input/record/reader/gcs/parquet/GCSParquetReaderFactory.java
index 17cad3e..67b72f3 100644
--- 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/input/record/reader/gcs/parquet/GCSParquetReaderFactory.java
+++ 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/input/record/reader/gcs/parquet/GCSParquetReaderFactory.java
@@ -23,6 +23,7 @@
 import java.util.Map;
 import java.util.Set;

+import org.apache.asterix.common.api.IApplicationContext;
 import org.apache.asterix.common.external.IExternalFilterEvaluator;
 import org.apache.asterix.common.external.IExternalFilterEvaluatorFactory;
 import org.apache.asterix.external.input.HDFSDataSourceFactory;
@@ -59,7 +60,8 @@
         configuration.put(ExternalDataPrefix.PREFIX_ROOT_FIELD_NAME, 
externalDataPrefix.getRoot());

         String container = 
configuration.get(ExternalDataConstants.CONTAINER_NAME_FIELD_NAME);
-        List<Blob> filesOnly = GCSUtils.listItems(configuration, 
includeExcludeMatcher, warningCollector,
+        IApplicationContext appCtx = (IApplicationContext) 
serviceCtx.getApplicationContext();
+        List<Blob> filesOnly = GCSUtils.listItems(appCtx, configuration, 
includeExcludeMatcher, warningCollector,
                 externalDataPrefix, evaluator);

         // get path
diff --git 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/ExternalDataUtils.java
 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/ExternalDataUtils.java
index 82b5dad..0298f4c 100644
--- 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/ExternalDataUtils.java
+++ 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/ExternalDataUtils.java
@@ -725,7 +725,7 @@
                 validateAzureDataLakeProperties(configuration, srcLoc, 
collector, appCtx);
                 break;
             case ExternalDataConstants.KEY_ADAPTER_NAME_GCS:
-                validateProperties(configuration, srcLoc, collector);
+                validateProperties(appCtx, configuration, srcLoc, collector);
                 break;
             case ExternalDataConstants.KEY_ADAPTER_NAME_HDFS:
                 HDFSUtils.validateProperties(configuration, srcLoc, collector);
diff --git 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/aws/s3/S3AuthUtils.java
 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/aws/s3/S3AuthUtils.java
index 4e0a435..b39341d 100644
--- 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/aws/s3/S3AuthUtils.java
+++ 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/aws/s3/S3AuthUtils.java
@@ -104,7 +104,7 @@
 public class S3AuthUtils {
     enum AuthenticationType {
         ANONYMOUS,
-        ARN,
+        ARN_ASSUME_ROLE,
         INSTANCE_PROFILE,
         ACCESS_KEYS,
         BAD_AUTHENTICATION
@@ -119,7 +119,8 @@
     }

     public static boolean isArnAssumedRoleExpiredToken(Map<String, String> 
configuration, String errorCode) {
-        return ERROR_EXPIRED_TOKEN.equals(errorCode) && 
getAuthenticationType(configuration) == AuthenticationType.ARN;
+        return ERROR_EXPIRED_TOKEN.equals(errorCode)
+                && getAuthenticationType(configuration) == 
AuthenticationType.ARN_ASSUME_ROLE;
     }

     /**
@@ -167,7 +168,7 @@
         switch (authenticationType) {
             case ANONYMOUS:
                 return AnonymousCredentialsProvider.create();
-            case ARN:
+            case ARN_ASSUME_ROLE:
                 return getTrustAccountCredentials(appCtx, configuration);
             case INSTANCE_PROFILE:
                 return getInstanceProfileCredentials(configuration);
@@ -205,7 +206,7 @@
         if (noAuth(configuration)) {
             return AuthenticationType.ANONYMOUS;
         } else if (roleArn != null) {
-            return AuthenticationType.ARN;
+            return AuthenticationType.ARN_ASSUME_ROLE;
         } else if (instanceProfile != null) {
             return AuthenticationType.INSTANCE_PROFILE;
         } else if (accessKeyId != null || secretAccessKey != null) {
@@ -231,11 +232,11 @@
     }

     /**
-     * Returns the cached credentials if valid, otherwise, generates new 
credentials by assume a role
+     * Returns the cached credentials if valid, otherwise, generates new 
credentials
      *
      * @param appCtx application context
      * @param configuration configuration
-     * @return returns the cached credentials if valid, otherwise, generates 
new credentials by assume a role
+     * @return returns the cached credentials if valid, otherwise, generates 
new credentials
      * @throws CompilationException CompilationException
      */
     public static AwsCredentialsProvider 
getTrustAccountCredentials(IApplicationContext appCtx,
@@ -277,20 +278,8 @@
         if (externalId != null) {
             builder.externalId(externalId);
         }
-
-        // credentials to be used to assume the role
-        AwsCredentialsProvider credentialsProvider;
         AssumeRoleRequest request = builder.build();
-        String instanceProfile = 
configuration.get(INSTANCE_PROFILE_FIELD_NAME);
-        String accessKeyId = configuration.get(ACCESS_KEY_ID_FIELD_NAME);
-        String secretAccessKey = 
configuration.get(SECRET_ACCESS_KEY_FIELD_NAME);
-        if ("true".equalsIgnoreCase(instanceProfile)) {
-            credentialsProvider = getInstanceProfileCredentials(configuration, 
true);
-        } else if (accessKeyId != null && secretAccessKey != null) {
-            credentialsProvider = getAccessKeyCredentials(configuration, true);
-        } else {
-            throw new 
CompilationException(ErrorCode.NO_AWS_VALID_PARAMS_FOUND_FOR_CROSS_ACCOUNT_TRUST_AUTHENTICATION);
-        }
+        AwsCredentialsProvider credentialsProvider = 
getCredentialsToAssumeRole(configuration);

         // assume the role from the provided arn
         try (StsClient stsClient =
@@ -304,13 +293,22 @@
         }
     }

-    private static AwsCredentialsProvider 
getInstanceProfileCredentials(Map<String, String> configuration)
+    private static AwsCredentialsProvider 
getCredentialsToAssumeRole(Map<String, String> configuration)
             throws CompilationException {
-        return getInstanceProfileCredentials(configuration, false);
+        String instanceProfile = 
configuration.get(INSTANCE_PROFILE_FIELD_NAME);
+        String accessKeyId = configuration.get(ACCESS_KEY_ID_FIELD_NAME);
+        String secretAccessKey = 
configuration.get(SECRET_ACCESS_KEY_FIELD_NAME);
+        if (instanceProfile != null) {
+            return getInstanceProfileCredentials(configuration);
+        } else if (accessKeyId != null || secretAccessKey != null) {
+            return getAccessKeyCredentials(configuration);
+        } else {
+            throw new 
CompilationException(ErrorCode.NO_AWS_VALID_PARAMS_FOUND_FOR_CROSS_ACCOUNT_TRUST_AUTHENTICATION);
+        }
     }

-    private static AwsCredentialsProvider 
getInstanceProfileCredentials(Map<String, String> configuration,
-            boolean assumeRoleAuthentication) throws CompilationException {
+    private static AwsCredentialsProvider 
getInstanceProfileCredentials(Map<String, String> configuration)
+            throws CompilationException {
         String instanceProfile = 
configuration.get(INSTANCE_PROFILE_FIELD_NAME);

         // only "true" value is allowed
@@ -318,24 +316,17 @@
             throw new CompilationException(INVALID_PARAM_VALUE_ALLOWED_VALUE, 
INSTANCE_PROFILE_FIELD_NAME, "true");
         }

-        if (!assumeRoleAuthentication) {
-            String notAllowed = getNonNull(configuration, 
ACCESS_KEY_ID_FIELD_NAME, SECRET_ACCESS_KEY_FIELD_NAME,
-                    SESSION_TOKEN_FIELD_NAME);
-            if (notAllowed != null) {
-                throw new 
CompilationException(PARAM_NOT_ALLOWED_IF_PARAM_IS_PRESENT, notAllowed,
-                        INSTANCE_PROFILE_FIELD_NAME);
-            }
+        String notAllowed = getNonNull(configuration, 
ACCESS_KEY_ID_FIELD_NAME, SECRET_ACCESS_KEY_FIELD_NAME,
+                SESSION_TOKEN_FIELD_NAME);
+        if (notAllowed != null) {
+            throw new 
CompilationException(PARAM_NOT_ALLOWED_IF_PARAM_IS_PRESENT, notAllowed,
+                    INSTANCE_PROFILE_FIELD_NAME);
         }
         return InstanceProfileCredentialsProvider.create();
     }

     private static AwsCredentialsProvider getAccessKeyCredentials(Map<String, 
String> configuration)
             throws CompilationException {
-        return getAccessKeyCredentials(configuration, false);
-    }
-
-    private static AwsCredentialsProvider getAccessKeyCredentials(Map<String, 
String> configuration,
-            boolean assumeRoleAuthentication) throws CompilationException {
         String accessKeyId = configuration.get(ACCESS_KEY_ID_FIELD_NAME);
         String secretAccessKey = 
configuration.get(SECRET_ACCESS_KEY_FIELD_NAME);
         String sessionToken = configuration.get(SESSION_TOKEN_FIELD_NAME);
@@ -349,14 +340,6 @@
                     ACCESS_KEY_ID_FIELD_NAME);
         }

-        if (!assumeRoleAuthentication) {
-            String notAllowed = getNonNull(configuration, 
INSTANCE_PROFILE_FIELD_NAME, EXTERNAL_ID_FIELD_NAME);
-            if (notAllowed != null) {
-                throw new 
CompilationException(PARAM_NOT_ALLOWED_IF_PARAM_IS_PRESENT, notAllowed,
-                        INSTANCE_PROFILE_FIELD_NAME);
-            }
-        }
-
         // use session token if provided
         if (sessionToken != null) {
             return StaticCredentialsProvider
@@ -422,13 +405,13 @@
      * @param configuration external details configuration
      */
     private static void setHadoopCredentials(IApplicationContext appCtx, 
JobConf jobConf,
-            Map<String, String> configuration) {
+            Map<String, String> configuration) throws CompilationException {
         AuthenticationType authenticationType = 
getAuthenticationType(configuration);
         switch (authenticationType) {
             case ANONYMOUS:
                 jobConf.set(HADOOP_CREDENTIAL_PROVIDER_KEY, HADOOP_ANONYMOUS);
                 break;
-            case ARN:
+            case ARN_ASSUME_ROLE:
                 jobConf.set(HADOOP_CREDENTIAL_PROVIDER_KEY, 
HADOOP_ASSUMED_ROLE);
                 jobConf.set(HADOOP_ASSUME_ROLE_ARN, 
configuration.get(ROLE_ARN_FIELD_NAME));
                 jobConf.set(HADOOP_ASSUME_ROLE_EXTERNAL_ID, 
configuration.get(EXTERNAL_ID_FIELD_NAME));
@@ -457,6 +440,7 @@
                 }
                 break;
             case BAD_AUTHENTICATION:
+                throw new 
CompilationException(ErrorCode.NO_VALID_AUTHENTICATION_PARAMS_PROVIDED);
         }
     }

diff --git 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/google/gcs/GCSAuthUtils.java
 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/google/gcs/GCSAuthUtils.java
new file mode 100644
index 0000000..cf3e64e
--- /dev/null
+++ 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/google/gcs/GCSAuthUtils.java
@@ -0,0 +1,189 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.external.util.google.gcs;
+
+import static 
org.apache.asterix.common.exceptions.ErrorCode.EXTERNAL_SOURCE_ERROR;
+import static 
org.apache.asterix.common.exceptions.ErrorCode.PARAM_NOT_ALLOWED_IF_PARAM_IS_PRESENT;
+import static 
org.apache.asterix.external.util.google.gcs.GCSConstants.APPLICATION_DEFAULT_CREDENTIALS_FIELD_NAME;
+import static 
org.apache.asterix.external.util.google.gcs.GCSConstants.ENDPOINT_FIELD_NAME;
+import static 
org.apache.asterix.external.util.google.gcs.GCSConstants.JSON_CREDENTIALS_FIELD_NAME;
+import static 
org.apache.asterix.external.util.google.gcs.GCSConstants.TARGET_SERVICE_ACCOUNT_FIELD_NAME;
+import static org.apache.hyracks.api.util.ExceptionUtils.getMessageOrToString;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.asterix.common.api.IApplicationContext;
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+
+import com.google.auth.Credentials;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auth.oauth2.ImpersonatedCredentials;
+import com.google.cloud.NoCredentials;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+
+public class GCSAuthUtils {
+    enum AuthenticationType {
+        ANONYMOUS,
+        IMPERSONATE_SERVICE_ACCOUNT,
+        APPLICATION_DEFAULT_CREDENTIALS,
+        SERVICE_ACCOUNT_KEY_JSON_CREDENTIALS,
+        BAD_AUTHENTICATION
+    }
+
+    private static final List<String> READ_WRITE_SCOPE_PERMISSION =
+            
Collections.singletonList("https://www.googleapis.com/auth/devstorage.read_write";);
+
+    private GCSAuthUtils() {
+        throw new AssertionError("do not instantiate");
+    }
+
+    /**
+     * Builds the client using the provided configuration
+     *
+     * @param appCtx application context
+     * @param configuration properties
+     * @return Storage client
+     * @throws CompilationException CompilationException
+     */
+    public static Storage buildClient(IApplicationContext appCtx, Map<String, 
String> configuration)
+            throws CompilationException {
+        String endpoint = configuration.get(ENDPOINT_FIELD_NAME);
+
+        Credentials credentials = buildCredentials(appCtx, configuration);
+        StorageOptions.Builder builder = StorageOptions.newBuilder();
+        builder.setCredentials(credentials);
+
+        if (endpoint != null) {
+            builder.setHost(endpoint);
+        }
+
+        return builder.build().getService();
+    }
+
+    public static Credentials buildCredentials(IApplicationContext appCtx, 
Map<String, String> configuration) throws CompilationException {
+        AuthenticationType authenticationType = 
getAuthenticationType(configuration);
+        return switch (authenticationType) {
+            case ANONYMOUS -> NoCredentials.getInstance();
+            case IMPERSONATE_SERVICE_ACCOUNT -> 
getImpersonatedServiceAccountCredentials(appCtx, configuration);
+            case APPLICATION_DEFAULT_CREDENTIALS -> 
getApplicationDefaultCredentials(configuration);
+            case SERVICE_ACCOUNT_KEY_JSON_CREDENTIALS -> 
getServiceAccountKeyCredentials(configuration);
+            case BAD_AUTHENTICATION -> throw new 
CompilationException(ErrorCode.NO_VALID_AUTHENTICATION_PARAMS_PROVIDED);
+        };
+    }
+
+    private static AuthenticationType getAuthenticationType(Map<String, 
String> configuration) {
+        String impersonateServiceAccount = 
configuration.get(TARGET_SERVICE_ACCOUNT_FIELD_NAME);
+        String applicationDefaultCredentials = 
configuration.get(APPLICATION_DEFAULT_CREDENTIALS_FIELD_NAME);
+        String jsonCredentials = 
configuration.get(JSON_CREDENTIALS_FIELD_NAME);
+
+        if (noAuth(configuration)) {
+            return AuthenticationType.ANONYMOUS;
+        } else if (impersonateServiceAccount != null) {
+            return AuthenticationType.IMPERSONATE_SERVICE_ACCOUNT;
+        } else if (applicationDefaultCredentials != null) {
+            return AuthenticationType.APPLICATION_DEFAULT_CREDENTIALS;
+        } else if (jsonCredentials != null) {
+            return AuthenticationType.SERVICE_ACCOUNT_KEY_JSON_CREDENTIALS;
+        } else {
+            return AuthenticationType.BAD_AUTHENTICATION;
+        }
+    }
+
+    private static boolean noAuth(Map<String, String> configuration) {
+        return getNonNull(configuration, 
APPLICATION_DEFAULT_CREDENTIALS_FIELD_NAME, JSON_CREDENTIALS_FIELD_NAME,
+                TARGET_SERVICE_ACCOUNT_FIELD_NAME) == null;
+    }
+
+    /**
+     * Returns the cached credentials if valid, otherwise, generates new 
credentials
+     *
+     * @param appCtx application context
+     * @param configuration configuration
+     * @return returns the cached credentials if valid, otherwise, generates 
new credentials
+     * @throws CompilationException CompilationException
+     */
+    public static GoogleCredentials 
getImpersonatedServiceAccountCredentials(IApplicationContext appCtx,
+            Map<String, String> configuration) throws CompilationException {
+        GoogleCredentials sourceCredentials = 
getCredentialsToImpersonateServiceAccount(configuration);
+        String impersonateServiceAccount = 
configuration.get(TARGET_SERVICE_ACCOUNT_FIELD_NAME);
+        int duration = 
appCtx.getExternalProperties().getGcpImpersonateServiceAccountDuration();
+
+        // Create impersonated credentials
+        return ImpersonatedCredentials.create(sourceCredentials, 
impersonateServiceAccount, null,
+                READ_WRITE_SCOPE_PERMISSION, duration);
+    }
+
+    private static GoogleCredentials 
getCredentialsToImpersonateServiceAccount(Map<String, String> configuration)
+            throws CompilationException {
+        String applicationDefaultCredentials = 
configuration.get(APPLICATION_DEFAULT_CREDENTIALS_FIELD_NAME);
+        String jsonCredentials = 
configuration.get(JSON_CREDENTIALS_FIELD_NAME);
+
+        if (applicationDefaultCredentials != null) {
+            return getApplicationDefaultCredentials(configuration);
+        } else if (jsonCredentials != null) {
+            return getServiceAccountKeyCredentials(configuration);
+        } else {
+            throw new CompilationException(
+                    
ErrorCode.NO_VALID_AUTHENTICATION_PARAMS_PROVIDED_TO_IMPERSONATE_SERVICE_ACCOUNT);
+        }
+    }
+
+    private static GoogleCredentials 
getApplicationDefaultCredentials(Map<String, String> configuration)
+            throws CompilationException {
+        try {
+            String notAllowed = getNonNull(configuration, 
JSON_CREDENTIALS_FIELD_NAME);
+            if (notAllowed != null) {
+                throw new 
CompilationException(PARAM_NOT_ALLOWED_IF_PARAM_IS_PRESENT, notAllowed,
+                        APPLICATION_DEFAULT_CREDENTIALS_FIELD_NAME);
+            }
+            return GoogleCredentials.getApplicationDefault();
+        } catch (Exception ex) {
+            throw CompilationException.create(EXTERNAL_SOURCE_ERROR, ex, 
getMessageOrToString(ex));
+        }
+    }
+
+    private static GoogleCredentials 
getServiceAccountKeyCredentials(Map<String, String> configuration)
+            throws CompilationException {
+        String jsonCredentials = 
configuration.get(JSON_CREDENTIALS_FIELD_NAME);
+        try (InputStream credentialsStream = new 
ByteArrayInputStream(jsonCredentials.getBytes())) {
+            return GoogleCredentials.fromStream(credentialsStream);
+        } catch (IOException ex) {
+            throw CompilationException.create(EXTERNAL_SOURCE_ERROR, ex, 
getMessageOrToString(ex));
+        } catch (Exception ex) {
+            throw new CompilationException(EXTERNAL_SOURCE_ERROR,
+                    "Encountered an issue while processing the JSON 
credentials. Please ensure the provided credentials are valid.");
+        }
+    }
+
+    private static String getNonNull(Map<String, String> configuration, 
String... fieldNames) {
+        for (String fieldName : fieldNames) {
+            if (configuration.get(fieldName) != null) {
+                return fieldName;
+            }
+        }
+        return null;
+    }
+}
diff --git 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/google/gcs/GCSConstants.java
 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/google/gcs/GCSConstants.java
index 2613f34..f9f4c75 100644
--- 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/google/gcs/GCSConstants.java
+++ 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/google/gcs/GCSConstants.java
@@ -24,6 +24,7 @@
     }

     public static final String APPLICATION_DEFAULT_CREDENTIALS_FIELD_NAME = 
"applicationDefaultCredentials";
+    public static final String TARGET_SERVICE_ACCOUNT_FIELD_NAME = 
"targetServiceAccount";
     public static final String JSON_CREDENTIALS_FIELD_NAME = "jsonCredentials";
     public static final String ENDPOINT_FIELD_NAME = "endpoint";
     public static final String STORAGE_PREFIX = "prefix";
diff --git 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/google/gcs/GCSUtils.java
 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/google/gcs/GCSUtils.java
index 481b7ff..913a913 100644
--- 
a/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/google/gcs/GCSUtils.java
+++ 
b/asterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/google/gcs/GCSUtils.java
@@ -19,13 +19,11 @@
 package org.apache.asterix.external.util.google.gcs;

 import static 
org.apache.asterix.common.exceptions.ErrorCode.EXTERNAL_SOURCE_ERROR;
-import static 
org.apache.asterix.common.exceptions.ErrorCode.INVALID_PARAM_VALUE_ALLOWED_VALUE;
-import static 
org.apache.asterix.common.exceptions.ErrorCode.PARAM_NOT_ALLOWED_IF_PARAM_IS_PRESENT;
 import static org.apache.asterix.external.util.ExternalDataUtils.getPrefix;
 import static org.apache.asterix.external.util.ExternalDataUtils.isDeltaTable;
 import static 
org.apache.asterix.external.util.ExternalDataUtils.validateDeltaTableProperties;
 import static 
org.apache.asterix.external.util.ExternalDataUtils.validateIncludeExclude;
-import static 
org.apache.asterix.external.util.google.gcs.GCSConstants.APPLICATION_DEFAULT_CREDENTIALS_FIELD_NAME;
+import static 
org.apache.asterix.external.util.google.gcs.GCSAuthUtils.buildClient;
 import static 
org.apache.asterix.external.util.google.gcs.GCSConstants.ENDPOINT_FIELD_NAME;
 import static 
org.apache.asterix.external.util.google.gcs.GCSConstants.HADOOP_AUTH_TYPE;
 import static 
org.apache.asterix.external.util.google.gcs.GCSConstants.HADOOP_AUTH_UNAUTHENTICATED;
@@ -34,15 +32,13 @@
 import static 
org.apache.asterix.external.util.google.gcs.GCSConstants.JSON_CREDENTIALS_FIELD_NAME;
 import static org.apache.hyracks.api.util.ExceptionUtils.getMessageOrToString;

-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import java.util.function.BiPredicate;
 import java.util.regex.Matcher;

+import org.apache.asterix.common.api.IApplicationContext;
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.external.IExternalFilterEvaluator;
@@ -63,12 +59,9 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.api.gax.paging.Page;
-import com.google.auth.oauth2.GoogleCredentials;
 import com.google.cloud.BaseServiceException;
-import com.google.cloud.NoCredentials;
 import com.google.cloud.storage.Blob;
 import com.google.cloud.storage.Storage;
-import com.google.cloud.storage.StorageOptions;

 public class GCSUtils {
     private GCSUtils() {
@@ -82,66 +75,16 @@
     }

     /**
-     * Builds the client using the provided configuration
-     *
-     * @param configuration properties
-     * @return 
clientasterixdb/asterix-external-data/src/main/java/org/apache/asterix/external/util/ExternalDataUtils.java
-     * @throws CompilationException CompilationException
-     */
-    public static Storage buildClient(Map<String, String> configuration) 
throws CompilationException {
-        String applicationDefaultCredentials = 
configuration.get(APPLICATION_DEFAULT_CREDENTIALS_FIELD_NAME);
-        String jsonCredentials = 
configuration.get(JSON_CREDENTIALS_FIELD_NAME);
-        String endpoint = configuration.get(ENDPOINT_FIELD_NAME);
-
-        StorageOptions.Builder builder = StorageOptions.newBuilder();
-
-        // default credentials provider
-        if (applicationDefaultCredentials != null) {
-            // only "true" value is allowed
-            if (!applicationDefaultCredentials.equalsIgnoreCase("true")) {
-                throw new 
CompilationException(INVALID_PARAM_VALUE_ALLOWED_VALUE,
-                        APPLICATION_DEFAULT_CREDENTIALS_FIELD_NAME, "true");
-            }
-
-            // no other authentication parameters are allowed
-            if (jsonCredentials != null) {
-                throw new 
CompilationException(PARAM_NOT_ALLOWED_IF_PARAM_IS_PRESENT, 
JSON_CREDENTIALS_FIELD_NAME,
-                        APPLICATION_DEFAULT_CREDENTIALS_FIELD_NAME);
-            }
-
-            try {
-                
builder.setCredentials(GoogleCredentials.getApplicationDefault());
-            } catch (Exception ex) {
-                throw CompilationException.create(EXTERNAL_SOURCE_ERROR, ex, 
getMessageOrToString(ex));
-            }
-        } else if (jsonCredentials != null) {
-            try (InputStream credentialsStream = new 
ByteArrayInputStream(jsonCredentials.getBytes())) {
-                
builder.setCredentials(GoogleCredentials.fromStream(credentialsStream));
-            } catch (IOException ex) {
-                throw CompilationException.create(EXTERNAL_SOURCE_ERROR, ex, 
getMessageOrToString(ex));
-            } catch (Exception ex) {
-                throw new CompilationException(EXTERNAL_SOURCE_ERROR,
-                        "Encountered an issue while processing the JSON 
credentials. Please ensure the provided credentials are valid.");
-            }
-        } else {
-            builder.setCredentials(NoCredentials.getInstance());
-        }
-
-        if (endpoint != null) {
-            builder.setHost(endpoint);
-        }
-
-        return builder.build().getService();
-    }
-
-    /**
      * Validate external dataset properties
      *
+     * @param appCtx application context
      * @param configuration properties
+     * @param srcLoc source location
+     * @param collector warning collector
      * @throws CompilationException Compilation exception
      */
-    public static void validateProperties(Map<String, String> configuration, 
SourceLocation srcLoc,
-            IWarningCollector collector) throws CompilationException {
+    public static void validateProperties(IApplicationContext appCtx, 
Map<String, String> configuration,
+            SourceLocation srcLoc, IWarningCollector collector) throws 
CompilationException {
         if (isDeltaTable(configuration)) {
             validateDeltaTableProperties(configuration);
         }
@@ -163,7 +106,7 @@
         try {
             Storage.BlobListOption limitOption = 
Storage.BlobListOption.pageSize(1);
             Storage.BlobListOption prefixOption = 
Storage.BlobListOption.prefix(getPrefix(configuration));
-            Storage storage = buildClient(configuration);
+            Storage storage = buildClient(appCtx, configuration);
             Page<Blob> items = storage.list(container, limitOption, 
prefixOption);

             if (!items.iterateAll().iterator().hasNext() && 
collector.shouldWarn()) {
@@ -177,13 +120,14 @@
         }
     }

-    public static List<Blob> listItems(Map<String, String> configuration, 
IncludeExcludeMatcher includeExcludeMatcher,
-            IWarningCollector warningCollector, ExternalDataPrefix 
externalDataPrefix,
-            IExternalFilterEvaluator evaluator) throws CompilationException, 
HyracksDataException {
+    public static List<Blob> listItems(IApplicationContext appCtx, Map<String, 
String> configuration,
+            IncludeExcludeMatcher includeExcludeMatcher, IWarningCollector 
warningCollector,
+            ExternalDataPrefix externalDataPrefix, IExternalFilterEvaluator 
evaluator)
+            throws CompilationException, HyracksDataException {
         // Prepare to retrieve the objects
         List<Blob> filesOnly = new ArrayList<>();
         String container = 
configuration.get(ExternalDataConstants.CONTAINER_NAME_FIELD_NAME);
-        Storage gcs = buildClient(configuration);
+        Storage gcs = buildClient(appCtx, configuration);
         Storage.BlobListOption options = 
Storage.BlobListOption.prefix(ExternalDataUtils.getPrefix(configuration));
         Page<Blob> items;


--
To view, visit https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/19444
To unsubscribe, or for help writing mail filters, visit 
https://asterix-gerrit.ics.uci.edu/settings

Gerrit-Project: asterixdb
Gerrit-Branch: master
Gerrit-Change-Id: Ie1d69faa45a03550c8e0fe66eb18c2ae53a8454a
Gerrit-Change-Number: 19444
Gerrit-PatchSet: 1
Gerrit-Owner: Hussain Towaileb <[email protected]>
Gerrit-MessageType: newchange

Reply via email to