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 5b63ff4fe44..149dc49d87e 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 @@ -34,6 +34,8 @@ import com.google.api.gax.tracing.ApiTracerFactory; import com.google.api.gax.tracing.BaseApiTracerFactory; import com.google.api.gax.tracing.OpencensusTracerFactory; +import com.google.auth.oauth2.AccessToken; +import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.NoCredentials; import com.google.cloud.ServiceDefaults; import com.google.cloud.ServiceOptions; @@ -79,8 +81,11 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Paths; import java.time.Duration; import java.util.ArrayList; +import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -92,6 +97,7 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; import javax.annotation.Nonnull; import javax.annotation.Nullable; import javax.annotation.concurrent.GuardedBy; @@ -110,6 +116,11 @@ public class SpannerOptions extends ServiceOptions { private static final String API_SHORT_NAME = "Spanner"; private static final String DEFAULT_HOST = "https://spanner.googleapis.com"; + private static final String CLOUD_SPANNER_HOST_FORMAT = ".*\\.googleapis\\.com.*"; + + @VisibleForTesting + static final Pattern CLOUD_SPANNER_HOST_PATTERN = Pattern.compile(CLOUD_SPANNER_HOST_FORMAT); + private static final ImmutableSet SCOPES = ImmutableSet.of( "https://www.googleapis.com/auth/spanner.admin", @@ -799,6 +810,18 @@ protected SpannerOptions(Builder builder) { enableBuiltInMetrics = builder.enableBuiltInMetrics; enableEndToEndTracing = builder.enableEndToEndTracing; monitoringHost = builder.monitoringHost; + String externalHostTokenPath = System.getenv("SPANNER_EXTERNAL_HOST_AUTH_TOKEN"); + if (builder.isExternalHost && builder.emulatorHost == null && externalHostTokenPath != null) { + String token; + try { + token = + Base64.getEncoder() + .encodeToString(Files.readAllBytes(Paths.get(externalHostTokenPath))); + } catch (IOException e) { + throw SpannerExceptionFactory.newSpannerException(e); + } + credentials = new GoogleCredentials(new AccessToken(token, null)); + } } /** @@ -967,6 +990,7 @@ public static class Builder private boolean enableBuiltInMetrics = SpannerOptions.environment.isEnableBuiltInMetrics(); private String monitoringHost = SpannerOptions.environment.getMonitoringHost(); private SslContext mTLSContext = null; + private boolean isExternalHost = false; private static String createCustomClientLibToken(String token) { return token + " " + ServiceOptions.getGoogApiClientLibName(); @@ -1459,6 +1483,9 @@ public Builder setDecodeMode(DecodeMode decodeMode) { @Override public Builder setHost(String host) { super.setHost(host); + if (this.emulatorHost == null && !CLOUD_SPANNER_HOST_PATTERN.matcher(host).matches()) { + this.isExternalHost = true; + } // Setting a host should override any SPANNER_EMULATOR_HOST setting. setEmulatorHost(null); return this; diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java index 9558947156c..dedc2a2deed 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/SpannerPool.java @@ -407,6 +407,9 @@ Spanner createSpanner(SpannerPoolKey key, ConnectionOptions options) { if (options.getConfigurator() != null) { options.getConfigurator().configure(builder); } + if (options.usesEmulator()) { + builder.setEmulatorHost(key.host); + } return builder.build().getService(); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java index 70482c0ffdd..72bbdf82eae 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/SpannerOptionsTest.java @@ -16,6 +16,7 @@ package com.google.cloud.spanner; +import static com.google.cloud.spanner.SpannerOptions.CLOUD_SPANNER_HOST_PATTERN; import static com.google.common.truth.Truth.assertThat; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -1164,4 +1165,12 @@ public void checkGlobalOpenTelemetryWhenNotInjected() { .build(); assertEquals(GlobalOpenTelemetry.get(), options.getOpenTelemetry()); } + + @Test + public void testCloudSpannerHostPattern() { + assertTrue(CLOUD_SPANNER_HOST_PATTERN.matcher("https://spanner.googleapis.com").matches()); + assertTrue( + CLOUD_SPANNER_HOST_PATTERN.matcher("https://product-area.googleapis.com:443").matches()); + assertFalse(CLOUD_SPANNER_HOST_PATTERN.matcher("https://some-company.com:443").matches()); + } }