diff --git a/google-cloud-spanner/pom.xml b/google-cloud-spanner/pom.xml
index 95d059bc12..e76c35f50a 100644
--- a/google-cloud-spanner/pom.xml
+++ b/google-cloud-spanner/pom.xml
@@ -80,6 +80,10 @@
io.grpc
grpc-api
+
+ io.grpc
+ grpc-auth
+
io.grpc
grpc-context
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java
index 70cc945cbb..a6aa502909 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/SpannerOptions.java
@@ -37,6 +37,7 @@
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
+import io.grpc.CallCredentials;
import io.grpc.ManagedChannelBuilder;
import java.io.IOException;
import java.net.MalformedURLException;
@@ -72,6 +73,16 @@ public class SpannerOptions extends ServiceOptions {
private final InstanceAdminStubSettings instanceAdminStubSettings;
private final DatabaseAdminStubSettings databaseAdminStubSettings;
private final Duration partitionedDmlTimeout;
+ private final CallCredentialsProvider callCredentialsProvider;
+
+ /**
+ * Interface that can be used to provide {@link CallCredentials} instead of {@link Credentials} to
+ * {@link SpannerOptions}.
+ */
+ public static interface CallCredentialsProvider {
+ /** Return the {@link CallCredentials} to use for a gRPC call. */
+ CallCredentials getCallCredentials();
+ }
/** Default implementation of {@code SpannerFactory}. */
private static class DefaultSpannerFactory implements SpannerFactory {
@@ -119,6 +130,7 @@ private SpannerOptions(Builder builder) {
throw SpannerExceptionFactory.newSpannerException(e);
}
partitionedDmlTimeout = builder.partitionedDmlTimeout;
+ callCredentialsProvider = builder.callCredentialsProvider;
}
/** Builder for {@link SpannerOptions} instances. */
@@ -150,6 +162,7 @@ public static class Builder
private DatabaseAdminStubSettings.Builder databaseAdminStubSettingsBuilder =
DatabaseAdminStubSettings.newBuilder();
private Duration partitionedDmlTimeout = Duration.ofHours(2L);
+ private CallCredentialsProvider callCredentialsProvider;
private String emulatorHost = System.getenv("SPANNER_EMULATOR_HOST");
private Builder() {}
@@ -164,6 +177,7 @@ private Builder() {}
this.instanceAdminStubSettingsBuilder = options.instanceAdminStubSettings.toBuilder();
this.databaseAdminStubSettingsBuilder = options.databaseAdminStubSettings.toBuilder();
this.partitionedDmlTimeout = options.partitionedDmlTimeout;
+ this.callCredentialsProvider = options.callCredentialsProvider;
this.channelProvider = options.channelProvider;
this.channelConfigurator = options.channelConfigurator;
this.interceptorProvider = options.interceptorProvider;
@@ -355,6 +369,17 @@ public Builder setPartitionedDmlTimeout(Duration timeout) {
return this;
}
+ /**
+ * Sets a {@link CallCredentialsProvider} that can deliver {@link CallCredentials} to use on a
+ * per-gRPC basis. Any credentials returned by this {@link CallCredentialsProvider} will have
+ * preference above any {@link Credentials} that may have been set on the {@link SpannerOptions}
+ * instance.
+ */
+ public Builder setCallCredentialsProvider(CallCredentialsProvider callCredentialsProvider) {
+ this.callCredentialsProvider = callCredentialsProvider;
+ return this;
+ }
+
/**
* Specifying this will allow the client to prefetch up to {@code prefetchChunks} {@code
* PartialResultSet} chunks for each read and query. The data size of each chunk depends on the
@@ -452,6 +477,10 @@ public Duration getPartitionedDmlTimeout() {
return partitionedDmlTimeout;
}
+ public CallCredentialsProvider getCallCredentialsProvider() {
+ return callCredentialsProvider;
+ }
+
public int getPrefetchChunks() {
return prefetchChunks;
}
diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java
index 49d6d9077d..917a5fdb61 100644
--- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java
+++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/spi/v1/GapicSpannerRpc.java
@@ -40,12 +40,14 @@
import com.google.cloud.spanner.SpannerException;
import com.google.cloud.spanner.SpannerExceptionFactory;
import com.google.cloud.spanner.SpannerOptions;
+import com.google.cloud.spanner.SpannerOptions.CallCredentialsProvider;
import com.google.cloud.spanner.admin.database.v1.stub.DatabaseAdminStub;
import com.google.cloud.spanner.admin.database.v1.stub.GrpcDatabaseAdminStub;
import com.google.cloud.spanner.admin.instance.v1.stub.GrpcInstanceAdminStub;
import com.google.cloud.spanner.admin.instance.v1.stub.InstanceAdminStub;
import com.google.cloud.spanner.v1.stub.GrpcSpannerStub;
import com.google.cloud.spanner.v1.stub.SpannerStub;
+import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.MoreObjects;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
@@ -99,6 +101,7 @@
import com.google.spanner.v1.RollbackRequest;
import com.google.spanner.v1.Session;
import com.google.spanner.v1.Transaction;
+import io.grpc.CallCredentials;
import io.grpc.Context;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
@@ -174,6 +177,7 @@ private synchronized void shutdown() {
private final String projectId;
private final String projectName;
private final SpannerMetadataProvider metadataProvider;
+ private final CallCredentialsProvider callCredentialsProvider;
private final Duration waitTimeout =
systemProperty(PROPERTY_TIMEOUT_SECONDS, DEFAULT_TIMEOUT_SECONDS);
private final Duration idleTimeout =
@@ -216,6 +220,7 @@ public GapicSpannerRpc(final SpannerOptions options) {
SpannerMetadataProvider.create(
mergedHeaderProvider.getHeaders(),
internalHeaderProviderBuilder.getResourceHeaderKey());
+ this.callCredentialsProvider = options.getCallCredentialsProvider();
// Create a managed executor provider.
this.executorProvider =
@@ -702,7 +707,8 @@ private static T get(final Future future) throws SpannerException {
}
}
- private GrpcCallContext newCallContext(@Nullable Map