3434import com .google .api .gax .tracing .ApiTracerFactory ;
3535import com .google .api .gax .tracing .BaseApiTracerFactory ;
3636import com .google .api .gax .tracing .OpencensusTracerFactory ;
37+ import com .google .auth .oauth2 .AccessToken ;
38+ import com .google .auth .oauth2 .GoogleCredentials ;
3739import com .google .cloud .NoCredentials ;
3840import com .google .cloud .ServiceDefaults ;
3941import com .google .cloud .ServiceOptions ;
5658import com .google .common .annotations .VisibleForTesting ;
5759import com .google .common .base .MoreObjects ;
5860import com .google .common .base .Preconditions ;
61+ import com .google .common .base .Strings ;
5962import com .google .common .collect .ImmutableMap ;
6063import com .google .common .collect .ImmutableSet ;
6164import com .google .common .util .concurrent .ThreadFactoryBuilder ;
7982import java .io .IOException ;
8083import java .net .MalformedURLException ;
8184import java .net .URL ;
85+ import java .nio .file .Files ;
86+ import java .nio .file .Paths ;
8287import java .time .Duration ;
8388import java .util .ArrayList ;
89+ import java .util .Base64 ;
8490import java .util .HashMap ;
8591import java .util .List ;
8692import java .util .Map ;
9298import java .util .concurrent .ThreadFactory ;
9399import java .util .concurrent .TimeUnit ;
94100import java .util .concurrent .atomic .AtomicInteger ;
101+ import java .util .regex .Pattern ;
95102import javax .annotation .Nonnull ;
96103import javax .annotation .Nullable ;
97104import javax .annotation .concurrent .GuardedBy ;
@@ -110,6 +117,11 @@ public class SpannerOptions extends ServiceOptions<Spanner, SpannerOptions> {
110117
111118 private static final String API_SHORT_NAME = "Spanner" ;
112119 private static final String DEFAULT_HOST = "https://spanner.googleapis.com" ;
120+ private static final String CLOUD_SPANNER_HOST_FORMAT = ".*\\ .googleapis\\ .com.*" ;
121+
122+ @ VisibleForTesting
123+ static final Pattern CLOUD_SPANNER_HOST_PATTERN = Pattern .compile (CLOUD_SPANNER_HOST_FORMAT );
124+
113125 private static final ImmutableSet <String > SCOPES =
114126 ImmutableSet .of (
115127 "https://www.googleapis.com/auth/spanner.admin" ,
@@ -843,8 +855,15 @@ default boolean isEnableEndToEndTracing() {
843855 default String getMonitoringHost () {
844856 return null ;
845857 }
858+
859+ default GoogleCredentials getDefaultExternalHostCredentials () {
860+ return null ;
861+ }
846862 }
847863
864+ static final String DEFAULT_SPANNER_EXTERNAL_HOST_CREDENTIALS =
865+ "SPANNER_EXTERNAL_HOST_AUTH_TOKEN" ;
866+
848867 /**
849868 * Default implementation of {@link SpannerEnvironment}. Reads all configuration from environment
850869 * variables.
@@ -900,6 +919,11 @@ public boolean isEnableEndToEndTracing() {
900919 public String getMonitoringHost () {
901920 return System .getenv (SPANNER_MONITORING_HOST );
902921 }
922+
923+ @ Override
924+ public GoogleCredentials getDefaultExternalHostCredentials () {
925+ return getOAuthTokenFromFile (System .getenv (DEFAULT_SPANNER_EXTERNAL_HOST_CREDENTIALS ));
926+ }
903927 }
904928
905929 /** Builder for {@link SpannerOptions} instances. */
@@ -967,6 +991,7 @@ public static class Builder
967991 private boolean enableBuiltInMetrics = SpannerOptions .environment .isEnableBuiltInMetrics ();
968992 private String monitoringHost = SpannerOptions .environment .getMonitoringHost ();
969993 private SslContext mTLSContext = null ;
994+ private boolean isExternalHost = false ;
970995
971996 private static String createCustomClientLibToken (String token ) {
972997 return token + " " + ServiceOptions .getGoogApiClientLibName ();
@@ -1459,6 +1484,9 @@ public Builder setDecodeMode(DecodeMode decodeMode) {
14591484 @ Override
14601485 public Builder setHost (String host ) {
14611486 super .setHost (host );
1487+ if (!CLOUD_SPANNER_HOST_PATTERN .matcher (host ).matches ()) {
1488+ this .isExternalHost = true ;
1489+ }
14621490 // Setting a host should override any SPANNER_EMULATOR_HOST setting.
14631491 setEmulatorHost (null );
14641492 return this ;
@@ -1629,6 +1657,8 @@ public SpannerOptions build() {
16291657 this .setChannelConfigurator (ManagedChannelBuilder ::usePlaintext );
16301658 // As we are using plain text, we should never send any credentials.
16311659 this .setCredentials (NoCredentials .getInstance ());
1660+ } else if (isExternalHost && credentials == null ) {
1661+ credentials = environment .getDefaultExternalHostCredentials ();
16321662 }
16331663 if (this .numChannels == null ) {
16341664 this .numChannels =
@@ -1669,6 +1699,24 @@ public static void useDefaultEnvironment() {
16691699 SpannerOptions .environment = SpannerEnvironmentImpl .INSTANCE ;
16701700 }
16711701
1702+ @ InternalApi
1703+ public static GoogleCredentials getDefaultExternalHostCredentialsFromSysEnv () {
1704+ return getOAuthTokenFromFile (System .getenv (DEFAULT_SPANNER_EXTERNAL_HOST_CREDENTIALS ));
1705+ }
1706+
1707+ private static @ Nullable GoogleCredentials getOAuthTokenFromFile (@ Nullable String file ) {
1708+ if (!Strings .isNullOrEmpty (file )) {
1709+ String token ;
1710+ try {
1711+ token = Base64 .getEncoder ().encodeToString (Files .readAllBytes (Paths .get (file )));
1712+ } catch (IOException e ) {
1713+ throw SpannerExceptionFactory .newSpannerException (e );
1714+ }
1715+ return GoogleCredentials .create (new AccessToken (token , null ));
1716+ }
1717+ return null ;
1718+ }
1719+
16721720 /**
16731721 * Enables OpenTelemetry traces. Enabling OpenTelemetry traces will disable OpenCensus traces. By
16741722 * default, OpenCensus traces are enabled.
0 commit comments