From 3556d723cfa9ce33f8f8b1ea4cd1a162166dc475 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Mon, 2 Mar 2026 09:59:06 -0500 Subject: [PATCH 01/52] feat: ability to update credentials on long running client --- .../connection/MutableCredentials.java | 86 ++++++++++++++++ .../connection/ITMutableCredentialsTest.java | 94 ++++++++++++++++++ .../connection/MutableCredentialsTest.java | 98 +++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java new file mode 100644 index 0000000000..82326bdac6 --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -0,0 +1,86 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed 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 com.google.cloud.spanner.connection; + +import com.google.auth.Credentials; +import com.google.auth.oauth2.ServiceAccountCredentials; + +import java.io.IOException; +import java.net.URI; +import java.util.List; +import java.util.Map; + +/** + * A mutable {@link Credentials} implementation that delegates authentication behavior to a scoped + * {@link ServiceAccountCredentials} instance. + * + *

This class is intended for scenarios where an application needs to rotate or replace the + * underlying service account credentials for a running Spanner Client. + * + *

All operations inherited from {@link Credentials} are forwarded to the current delegate, + * including request metadata retrieval and token refresh. Calling + * {@link #updateCredentials(ServiceAccountCredentials)} replaces the delegate with a newly scoped + * credentials instance created from the same scopes that were provided when this object was + * constructed. + */ +public class MutableCredentials extends Credentials { + ServiceAccountCredentials delegate; + List scopes; + + public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { + this. scopes = scopes; + delegate = (ServiceAccountCredentials) credentials.createScoped(scopes); + } + + /** + * Replaces the current delegate with a newly scoped credentials instance. + * + *

The provided {@link ServiceAccountCredentials} is scoped using the same scopes that were + * supplied when this {@link MutableCredentials} instance was created. + * + * @param credentials the new base service account credentials to scope and use for client + * authorization. + */ + public void updateCredentials(ServiceAccountCredentials credentials) { + delegate =(ServiceAccountCredentials) credentials.createScoped(scopes); + } + + @Override + public String getAuthenticationType() { + return delegate.getAuthenticationType(); + } + + @Override + public Map> getRequestMetadata(URI uri) throws IOException { + return delegate.getRequestMetadata(uri); + } + + @Override + public boolean hasRequestMetadata() { + return delegate.hasRequestMetadata(); + } + + @Override + public boolean hasRequestMetadataOnly() { + return delegate.hasRequestMetadataOnly(); + } + + @Override + public void refresh() throws IOException { + delegate.refresh(); + } +} + diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java new file mode 100644 index 0000000000..8cd37a3962 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java @@ -0,0 +1,94 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed 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 com.google.cloud.spanner.connection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.junit.Assume.assumeTrue; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.auth.oauth2.ServiceAccountCredentials; +import com.google.cloud.spanner.ErrorCode; +import com.google.cloud.spanner.ResultSet; +import com.google.cloud.spanner.SerialIntegrationTest; +import com.google.cloud.spanner.SpannerException; +import com.google.cloud.spanner.Statement; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@Category(SerialIntegrationTest.class) +@RunWith(JUnit4.class) +public class ITMutableCredentialsTest extends ITAbstractSpannerTest { + private static final String INVALID_KEY_FILE = + ITMutableCredentialsTest.class.getResource("test-key.json").getPath(); + + @Test + public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { + assumeTrue("This test requires a service account key file", hasValidKeyFile()); + + GoogleCredentials credentialsFromFile; + try (InputStream stream = Files.newInputStream(Paths.get(getKeyFile()))) { + credentialsFromFile = GoogleCredentials.fromStream(stream); + } + assumeTrue( + "This test requires service account credentials", + credentialsFromFile instanceof ServiceAccountCredentials); + + ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; + ServiceAccountCredentials invalidCredentials; + try (InputStream stream = Files.newInputStream(Paths.get(INVALID_KEY_FILE))) { + invalidCredentials = ServiceAccountCredentials.fromStream(stream); + } + + List scopes = new ArrayList<>(getTestEnv().getTestHelper().getOptions().getScopes()); + MutableCredentials mutableCredentials = new MutableCredentials(validCredentials, scopes); + + StringBuilder uri = + extractConnectionUrl(getTestEnv().getTestHelper().getOptions(), getDatabase()); + ConnectionOptions options = + ConnectionOptions.newBuilder() + .setUri(uri.toString()) + .setCredentials(mutableCredentials) + .build(); + + try (Connection connection = options.getConnection()) { + try (ResultSet rs = connection.executeQuery(Statement.of("SELECT 1"))) { + assertTrue(rs.next()); + } + + mutableCredentials.updateCredentials(invalidCredentials); + + try (ResultSet rs = connection.executeQuery(Statement.of("SELECT 2"))) { + rs.next(); + fail("Expected UNAUTHENTICATED after switching to invalid credentials"); + } catch (SpannerException e) { + assertEquals(ErrorCode.UNAUTHENTICATED, e.getErrorCode()); + } + } finally { + closeSpanner(); + } + } +} \ No newline at end of file diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java new file mode 100644 index 0000000000..8447802bb4 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java @@ -0,0 +1,98 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed 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 com.google.cloud.spanner.connection; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.auth.oauth2.ServiceAccountCredentials; +import java.io.IOException; +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class MutableCredentialsTest { + ServiceAccountCredentials initialCredentials = mock(ServiceAccountCredentials.class); + ServiceAccountCredentials initialScopedCredentials = mock(ServiceAccountCredentials.class); + ServiceAccountCredentials updatedCredentials = mock(ServiceAccountCredentials.class); + ServiceAccountCredentials updatedScopedCredentials = mock(ServiceAccountCredentials.class); + List scopes = Arrays.asList("scope-a", "scope-b"); + Map> initialMetadata = + Collections.singletonMap("Authorization", Collections.singletonList("v1")); + Map> updatedMetadata = + Collections.singletonMap("Authorization", Collections.singletonList("v2")); + String initialAuthType = "auth-1"; + String updatedAuthType = "auth-2"; + + @Test + public void testCreateMutableCredentialsAndUpdate() throws IOException { + setupInitialCredentials(); + setupUpdatedCredentials(); + + MutableCredentials credentials = new MutableCredentials(initialCredentials, scopes); + + assertEquals(initialAuthType, credentials.getAuthenticationType()); + assertTrue(credentials.hasRequestMetadata()); + assertTrue(credentials.hasRequestMetadataOnly()); + assertEquals(initialMetadata, credentials.getRequestMetadata(URI.create("https://spanner.googleapis.com"))); + + credentials.refresh(); + + verify(initialScopedCredentials, times(1)).refresh(); + + credentials.updateCredentials(updatedCredentials); + + assertEquals(updatedAuthType, credentials.getAuthenticationType()); + assertFalse(credentials.hasRequestMetadata()); + assertFalse(credentials.hasRequestMetadataOnly()); + assertSame(updatedMetadata, credentials.getRequestMetadata(URI.create("https://example.com"))); + + credentials.refresh(); + + verify(updatedScopedCredentials, times(1)).refresh(); + } + + private void setupInitialCredentials() throws IOException { + when(initialCredentials.createScoped(scopes)).thenReturn(initialScopedCredentials); + when(initialScopedCredentials.getAuthenticationType()).thenReturn(initialAuthType); + when(initialScopedCredentials.getRequestMetadata(any(URI.class))) + .thenReturn(initialMetadata); + when(initialScopedCredentials.hasRequestMetadata()).thenReturn(true); + when(initialScopedCredentials.hasRequestMetadataOnly()).thenReturn(true); + } + + private void setupUpdatedCredentials() throws IOException { + when(updatedCredentials.createScoped(scopes)).thenReturn(updatedScopedCredentials); + when(updatedScopedCredentials.getAuthenticationType()).thenReturn(updatedAuthType); + when(updatedScopedCredentials.getRequestMetadata(any(URI.class))).thenReturn(updatedMetadata); + when(updatedScopedCredentials.hasRequestMetadata()).thenReturn(false); + when(updatedScopedCredentials.hasRequestMetadataOnly()).thenReturn(false); + } +} \ No newline at end of file From 02a00ac97417bccc4cd2bd6d4b552acaff40b489 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Mon, 2 Mar 2026 15:02:54 +0000 Subject: [PATCH 02/52] chore: generate libraries at Mon Mar 2 15:00:10 UTC 2026 --- .../connection/MutableCredentials.java | 82 +++++++++---------- .../connection/ITMutableCredentialsTest.java | 4 +- .../connection/MutableCredentialsTest.java | 13 +-- 3 files changed, 49 insertions(+), 50 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java index 82326bdac6..8ada4f2ce0 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -17,7 +17,6 @@ import com.google.auth.Credentials; import com.google.auth.oauth2.ServiceAccountCredentials; - import java.io.IOException; import java.net.URI; import java.util.List; @@ -31,56 +30,55 @@ * underlying service account credentials for a running Spanner Client. * *

All operations inherited from {@link Credentials} are forwarded to the current delegate, - * including request metadata retrieval and token refresh. Calling - * {@link #updateCredentials(ServiceAccountCredentials)} replaces the delegate with a newly scoped + * including request metadata retrieval and token refresh. Calling {@link + * #updateCredentials(ServiceAccountCredentials)} replaces the delegate with a newly scoped * credentials instance created from the same scopes that were provided when this object was * constructed. */ public class MutableCredentials extends Credentials { - ServiceAccountCredentials delegate; - List scopes; + ServiceAccountCredentials delegate; + List scopes; - public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { - this. scopes = scopes; - delegate = (ServiceAccountCredentials) credentials.createScoped(scopes); - } + public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { + this.scopes = scopes; + delegate = (ServiceAccountCredentials) credentials.createScoped(scopes); + } - /** - * Replaces the current delegate with a newly scoped credentials instance. - * - *

The provided {@link ServiceAccountCredentials} is scoped using the same scopes that were - * supplied when this {@link MutableCredentials} instance was created. - * - * @param credentials the new base service account credentials to scope and use for client - * authorization. - */ - public void updateCredentials(ServiceAccountCredentials credentials) { - delegate =(ServiceAccountCredentials) credentials.createScoped(scopes); - } + /** + * Replaces the current delegate with a newly scoped credentials instance. + * + *

The provided {@link ServiceAccountCredentials} is scoped using the same scopes that were + * supplied when this {@link MutableCredentials} instance was created. + * + * @param credentials the new base service account credentials to scope and use for client + * authorization. + */ + public void updateCredentials(ServiceAccountCredentials credentials) { + delegate = (ServiceAccountCredentials) credentials.createScoped(scopes); + } - @Override - public String getAuthenticationType() { - return delegate.getAuthenticationType(); - } + @Override + public String getAuthenticationType() { + return delegate.getAuthenticationType(); + } - @Override - public Map> getRequestMetadata(URI uri) throws IOException { - return delegate.getRequestMetadata(uri); - } + @Override + public Map> getRequestMetadata(URI uri) throws IOException { + return delegate.getRequestMetadata(uri); + } - @Override - public boolean hasRequestMetadata() { - return delegate.hasRequestMetadata(); - } + @Override + public boolean hasRequestMetadata() { + return delegate.hasRequestMetadata(); + } - @Override - public boolean hasRequestMetadataOnly() { - return delegate.hasRequestMetadataOnly(); - } + @Override + public boolean hasRequestMetadataOnly() { + return delegate.hasRequestMetadataOnly(); + } - @Override - public void refresh() throws IOException { - delegate.refresh(); - } + @Override + public void refresh() throws IOException { + delegate.refresh(); + } } - diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java index 8cd37a3962..4064151e13 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java @@ -54,7 +54,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( - "This test requires service account credentials", + "This test requires service account credentials", credentialsFromFile instanceof ServiceAccountCredentials); ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; @@ -91,4 +91,4 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I closeSpanner(); } } -} \ No newline at end of file +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java index 8447802bb4..e609cd0d26 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java @@ -45,9 +45,9 @@ public class MutableCredentialsTest { ServiceAccountCredentials updatedScopedCredentials = mock(ServiceAccountCredentials.class); List scopes = Arrays.asList("scope-a", "scope-b"); Map> initialMetadata = - Collections.singletonMap("Authorization", Collections.singletonList("v1")); + Collections.singletonMap("Authorization", Collections.singletonList("v1")); Map> updatedMetadata = - Collections.singletonMap("Authorization", Collections.singletonList("v2")); + Collections.singletonMap("Authorization", Collections.singletonList("v2")); String initialAuthType = "auth-1"; String updatedAuthType = "auth-2"; @@ -61,7 +61,9 @@ public void testCreateMutableCredentialsAndUpdate() throws IOException { assertEquals(initialAuthType, credentials.getAuthenticationType()); assertTrue(credentials.hasRequestMetadata()); assertTrue(credentials.hasRequestMetadataOnly()); - assertEquals(initialMetadata, credentials.getRequestMetadata(URI.create("https://spanner.googleapis.com"))); + assertEquals( + initialMetadata, + credentials.getRequestMetadata(URI.create("https://spanner.googleapis.com"))); credentials.refresh(); @@ -82,8 +84,7 @@ public void testCreateMutableCredentialsAndUpdate() throws IOException { private void setupInitialCredentials() throws IOException { when(initialCredentials.createScoped(scopes)).thenReturn(initialScopedCredentials); when(initialScopedCredentials.getAuthenticationType()).thenReturn(initialAuthType); - when(initialScopedCredentials.getRequestMetadata(any(URI.class))) - .thenReturn(initialMetadata); + when(initialScopedCredentials.getRequestMetadata(any(URI.class))).thenReturn(initialMetadata); when(initialScopedCredentials.hasRequestMetadata()).thenReturn(true); when(initialScopedCredentials.hasRequestMetadataOnly()).thenReturn(true); } @@ -95,4 +96,4 @@ private void setupUpdatedCredentials() throws IOException { when(updatedScopedCredentials.hasRequestMetadata()).thenReturn(false); when(updatedScopedCredentials.hasRequestMetadataOnly()).thenReturn(false); } -} \ No newline at end of file +} From bd89a334157d945cb1b909d879be42e58b2217f5 Mon Sep 17 00:00:00 2001 From: ldetmer <1771267+ldetmer@users.noreply.github.com> Date: Mon, 2 Mar 2026 10:13:57 -0500 Subject: [PATCH 03/52] Update google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- .../cloud/spanner/connection/MutableCredentials.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java index 8ada4f2ce0..d47299fc20 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -40,10 +40,10 @@ public class MutableCredentials extends Credentials { List scopes; public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { - this.scopes = scopes; - delegate = (ServiceAccountCredentials) credentials.createScoped(scopes); - } - + public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { + this.scopes = new java.util.ArrayList<>(scopes); + delegate = (ServiceAccountCredentials) credentials.createScoped(this.scopes); + } /** * Replaces the current delegate with a newly scoped credentials instance. * From e871f0f4c4ca21583e433742750a1c1e49cf9d9a Mon Sep 17 00:00:00 2001 From: ldetmer Date: Mon, 2 Mar 2026 10:17:47 -0500 Subject: [PATCH 04/52] fixed comp issue --- .../cloud/spanner/connection/MutableCredentials.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java index d47299fc20..f0f62209ca 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -39,11 +39,11 @@ public class MutableCredentials extends Credentials { ServiceAccountCredentials delegate; List scopes; + public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { - public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { - this.scopes = new java.util.ArrayList<>(scopes); - delegate = (ServiceAccountCredentials) credentials.createScoped(this.scopes); - } + this.scopes = new java.util.ArrayList<>(scopes); + delegate = (ServiceAccountCredentials) credentials.createScoped(this.scopes); + } /** * Replaces the current delegate with a newly scoped credentials instance. * From b802f0af43adc05bb262fb473d9d8ec1b5220add Mon Sep 17 00:00:00 2001 From: ldetmer Date: Mon, 2 Mar 2026 10:19:45 -0500 Subject: [PATCH 05/52] suggestions from gemini review + lint fixes --- .../spanner/connection/MutableCredentials.java | 8 ++++---- .../connection/ITMutableCredentialsTest.java | 1 + .../spanner/connection/MutableCredentialsTest.java | 14 ++++++++++++-- 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java index f0f62209ca..9d014e50b5 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -26,8 +26,8 @@ * A mutable {@link Credentials} implementation that delegates authentication behavior to a scoped * {@link ServiceAccountCredentials} instance. * - *

This class is intended for scenarios where an application needs to rotate or replace the - * underlying service account credentials for a running Spanner Client. + *

This class is intended for scenarios where an application needs to replace the underlying + * service account credentials for a long running Spanner Client. * *

All operations inherited from {@link Credentials} are forwarded to the current delegate, * including request metadata retrieval and token refresh. Calling {@link @@ -36,8 +36,8 @@ * constructed. */ public class MutableCredentials extends Credentials { - ServiceAccountCredentials delegate; - List scopes; + private volatile ServiceAccountCredentials delegate; + private final List scopes; public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java index 4064151e13..7661d9324a 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java @@ -92,3 +92,4 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I } } } + diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java index e609cd0d26..6adb7a48c2 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java @@ -52,9 +52,8 @@ public class MutableCredentialsTest { String updatedAuthType = "auth-2"; @Test - public void testCreateMutableCredentialsAndUpdate() throws IOException { + public void testCreateMutableCredentials() throws IOException { setupInitialCredentials(); - setupUpdatedCredentials(); MutableCredentials credentials = new MutableCredentials(initialCredentials, scopes); @@ -68,6 +67,16 @@ public void testCreateMutableCredentialsAndUpdate() throws IOException { credentials.refresh(); verify(initialScopedCredentials, times(1)).refresh(); + } + + @Test + public void testUpdateMutableCredentials() throws IOException { + setupInitialCredentials(); + setupUpdatedCredentials(); + + MutableCredentials credentials = new MutableCredentials(initialCredentials, scopes); + + assertEquals(initialAuthType, credentials.getAuthenticationType()); credentials.updateCredentials(updatedCredentials); @@ -97,3 +106,4 @@ private void setupUpdatedCredentials() throws IOException { when(updatedScopedCredentials.hasRequestMetadataOnly()).thenReturn(false); } } + From 40abd6dfed9d5bd9729f192ea1ecdfd5a36bd2b9 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Mon, 2 Mar 2026 15:23:05 +0000 Subject: [PATCH 06/52] chore: generate libraries at Mon Mar 2 15:20:24 UTC 2026 --- .../google/cloud/spanner/connection/MutableCredentials.java | 6 +++--- .../cloud/spanner/connection/ITMutableCredentialsTest.java | 1 - .../cloud/spanner/connection/MutableCredentialsTest.java | 1 - 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java index 9d014e50b5..78d250a133 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -39,11 +39,11 @@ public class MutableCredentials extends Credentials { private volatile ServiceAccountCredentials delegate; private final List scopes; - public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { - this.scopes = new java.util.ArrayList<>(scopes); - delegate = (ServiceAccountCredentials) credentials.createScoped(this.scopes); + this.scopes = new java.util.ArrayList<>(scopes); + delegate = (ServiceAccountCredentials) credentials.createScoped(this.scopes); } + /** * Replaces the current delegate with a newly scoped credentials instance. * diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java index 7661d9324a..4064151e13 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java @@ -92,4 +92,3 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I } } } - diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java index 6adb7a48c2..05bf943115 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java @@ -106,4 +106,3 @@ private void setupUpdatedCredentials() throws IOException { when(updatedScopedCredentials.hasRequestMetadataOnly()).thenReturn(false); } } - From e5654af42cd3bc728dd7b9b587f40b36eec3f263 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Mon, 2 Mar 2026 11:57:35 -0500 Subject: [PATCH 07/52] test IT test --- .../{ => it}/ITMutableCredentialsTest.java | 52 ++++++++++--------- 1 file changed, 27 insertions(+), 25 deletions(-) rename google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/{ => it}/ITMutableCredentialsTest.java (63%) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java similarity index 63% rename from google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java rename to google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 4064151e13..9d121334ae 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -14,26 +14,30 @@ * limitations under the License. */ -package com.google.cloud.spanner.connection; +package com.google.cloud.spanner.connection.it; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; +import static org.junit.Assert.*; import static org.junit.Assume.assumeTrue; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; -import com.google.cloud.spanner.ErrorCode; -import com.google.cloud.spanner.ResultSet; -import com.google.cloud.spanner.SerialIntegrationTest; -import com.google.cloud.spanner.SpannerException; -import com.google.cloud.spanner.Statement; +import com.google.cloud.spanner.*; + import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; + +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.cloud.spanner.connection.Connection; +import com.google.cloud.spanner.connection.ConnectionOptions; +import com.google.cloud.spanner.connection.ITAbstractSpannerTest; +import com.google.cloud.spanner.connection.MutableCredentials; +import com.google.spanner.admin.database.v1.Database; +import com.google.spanner.admin.database.v1.DatabaseName; +import com.google.spanner.admin.database.v1.InstanceName; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -44,13 +48,15 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { private static final String INVALID_KEY_FILE = ITMutableCredentialsTest.class.getResource("test-key.json").getPath(); + /*private static final String VALID_KEY_FILE = + ITMutableCredentialsTest.class.getResource("test-key-cloud-storage.json").getPath(); +*/ @Test public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { - assumeTrue("This test requires a service account key file", hasValidKeyFile()); GoogleCredentials credentialsFromFile; - try (InputStream stream = Files.newInputStream(Paths.get(getKeyFile()))) { + try (InputStream stream = Files.newInputStream(Paths.get(GceTestEnvConfig.GCE_CREDENTIALS_FILE))) { credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( @@ -66,23 +72,19 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I List scopes = new ArrayList<>(getTestEnv().getTestHelper().getOptions().getScopes()); MutableCredentials mutableCredentials = new MutableCredentials(validCredentials, scopes); - StringBuilder uri = - extractConnectionUrl(getTestEnv().getTestHelper().getOptions(), getDatabase()); - ConnectionOptions options = - ConnectionOptions.newBuilder() - .setUri(uri.toString()) + SpannerOptions options = + SpannerOptions.newBuilder() .setCredentials(mutableCredentials) .build(); - try (Connection connection = options.getConnection()) { - try (ResultSet rs = connection.executeQuery(Statement.of("SELECT 1"))) { - assertTrue(rs.next()); - } - - mutableCredentials.updateCredentials(invalidCredentials); - - try (ResultSet rs = connection.executeQuery(Statement.of("SELECT 2"))) { - rs.next(); + try (Spanner spanner = options.getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + String dbName = DatabaseName.of(GceTestEnvConfig.GCE_PROJECT_ID, getTestEnv().getTestHelper().getInstanceId().getInstance(), "TEST").toString(); + Database database = databaseAdminClient.getDatabase(dbName); + assertNotNull(database); + try { + mutableCredentials.updateCredentials(invalidCredentials); + databaseAdminClient.getDatabase(dbName); fail("Expected UNAUTHENTICATED after switching to invalid credentials"); } catch (SpannerException e) { assertEquals(ErrorCode.UNAUTHENTICATED, e.getErrorCode()); From 28bb07a3a8c4243d3d5524f8fac3d0c184491c04 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Mon, 2 Mar 2026 12:07:10 -0500 Subject: [PATCH 08/52] remove commented out code --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 9d121334ae..44f459a280 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -48,9 +48,6 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { private static final String INVALID_KEY_FILE = ITMutableCredentialsTest.class.getResource("test-key.json").getPath(); - /*private static final String VALID_KEY_FILE = - ITMutableCredentialsTest.class.getResource("test-key-cloud-storage.json").getPath(); -*/ @Test public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { From 1a302e8cf778ef0d6da54c9bca222c9d1fc69c86 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Mon, 2 Mar 2026 17:11:22 +0000 Subject: [PATCH 09/52] chore: generate libraries at Mon Mar 2 17:08:19 UTC 2026 --- .../it/ITMutableCredentialsTest.java | 32 +++++++++---------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 44f459a280..813471b79a 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -22,22 +22,17 @@ import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.spanner.*; - +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.cloud.spanner.connection.ITAbstractSpannerTest; +import com.google.cloud.spanner.connection.MutableCredentials; +import com.google.spanner.admin.database.v1.Database; +import com.google.spanner.admin.database.v1.DatabaseName; import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; - -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; -import com.google.cloud.spanner.connection.Connection; -import com.google.cloud.spanner.connection.ConnectionOptions; -import com.google.cloud.spanner.connection.ITAbstractSpannerTest; -import com.google.cloud.spanner.connection.MutableCredentials; -import com.google.spanner.admin.database.v1.Database; -import com.google.spanner.admin.database.v1.DatabaseName; -import com.google.spanner.admin.database.v1.InstanceName; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -53,7 +48,8 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { GoogleCredentials credentialsFromFile; - try (InputStream stream = Files.newInputStream(Paths.get(GceTestEnvConfig.GCE_CREDENTIALS_FILE))) { + try (InputStream stream = + Files.newInputStream(Paths.get(GceTestEnvConfig.GCE_CREDENTIALS_FILE))) { credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( @@ -69,14 +65,16 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I List scopes = new ArrayList<>(getTestEnv().getTestHelper().getOptions().getScopes()); MutableCredentials mutableCredentials = new MutableCredentials(validCredentials, scopes); - SpannerOptions options = - SpannerOptions.newBuilder() - .setCredentials(mutableCredentials) - .build(); + SpannerOptions options = SpannerOptions.newBuilder().setCredentials(mutableCredentials).build(); try (Spanner spanner = options.getService(); - DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { - String dbName = DatabaseName.of(GceTestEnvConfig.GCE_PROJECT_ID, getTestEnv().getTestHelper().getInstanceId().getInstance(), "TEST").toString(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + String dbName = + DatabaseName.of( + GceTestEnvConfig.GCE_PROJECT_ID, + getTestEnv().getTestHelper().getInstanceId().getInstance(), + "TEST") + .toString(); Database database = databaseAdminClient.getDatabase(dbName); assertNotNull(database); try { From 74995fecd0ad1e1f182113611e3643df663c85ca Mon Sep 17 00:00:00 2001 From: ldetmer Date: Tue, 3 Mar 2026 15:09:47 -0500 Subject: [PATCH 10/52] added missing override methods --- .../connection/MutableCredentials.java | 25 +++++++- .../connection/MutableCredentialsTest.java | 62 ++++++++++++++++--- 2 files changed, 78 insertions(+), 9 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java index 78d250a133..b37323eaaf 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -15,12 +15,16 @@ */ package com.google.cloud.spanner.connection; +import com.google.auth.CredentialTypeForMetrics; import com.google.auth.Credentials; +import com.google.auth.RequestMetadataCallback; import com.google.auth.oauth2.ServiceAccountCredentials; import java.io.IOException; import java.net.URI; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.Executor; /** * A mutable {@link Credentials} implementation that delegates authentication behavior to a scoped @@ -40,7 +44,11 @@ public class MutableCredentials extends Credentials { private final List scopes; public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { - this.scopes = new java.util.ArrayList<>(scopes); + if (scopes != null) { + this.scopes = new java.util.ArrayList<>(scopes); + } else { + this.scopes = Collections.emptyList(); + } delegate = (ServiceAccountCredentials) credentials.createScoped(this.scopes); } @@ -81,4 +89,19 @@ public boolean hasRequestMetadataOnly() { public void refresh() throws IOException { delegate.refresh(); } + + @Override + public void getRequestMetadata(URI uri, Executor executor, RequestMetadataCallback callback) { + delegate.getRequestMetadata(uri, executor, callback); + } + + @Override + public String getUniverseDomain() throws IOException { + return delegate.getUniverseDomain(); + } + + @Override + public CredentialTypeForMetrics getMetricsCredentialType() { + return delegate.getMetricsCredentialType(); + } } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java index 05bf943115..e4bf0820a2 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java @@ -26,6 +26,8 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import com.google.auth.CredentialTypeForMetrics; +import com.google.auth.RequestMetadataCallback; import com.google.auth.oauth2.ServiceAccountCredentials; import java.io.IOException; import java.net.URI; @@ -33,6 +35,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.Executor; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -50,22 +53,29 @@ public class MutableCredentialsTest { Collections.singletonMap("Authorization", Collections.singletonList("v2")); String initialAuthType = "auth-1"; String updatedAuthType = "auth-2"; + String initialUniverseDomain = "googleapis.com"; + String updatedUniverseDomain = "abc.goog"; + CredentialTypeForMetrics initialMetricsCredentialType = + CredentialTypeForMetrics.SERVICE_ACCOUNT_CREDENTIALS_JWT; + CredentialTypeForMetrics updatedMetricsCredentialType = + CredentialTypeForMetrics.SERVICE_ACCOUNT_CREDENTIALS_AT; @Test public void testCreateMutableCredentials() throws IOException { setupInitialCredentials(); MutableCredentials credentials = new MutableCredentials(initialCredentials, scopes); + URI testUri = URI.create("https://spanner.googleapis.com"); + Executor executor = mock(Executor.class); + RequestMetadataCallback callback = mock(RequestMetadataCallback.class); - assertEquals(initialAuthType, credentials.getAuthenticationType()); - assertTrue(credentials.hasRequestMetadata()); - assertTrue(credentials.hasRequestMetadataOnly()); - assertEquals( - initialMetadata, - credentials.getRequestMetadata(URI.create("https://spanner.googleapis.com"))); + validateInitialDelegatedCredentialsAreSet(credentials, testUri); + + credentials.getRequestMetadata(testUri, executor, callback); credentials.refresh(); + verify(initialScopedCredentials, times(1)).getRequestMetadata(testUri, executor, callback); verify(initialScopedCredentials, times(1)).refresh(); } @@ -75,25 +85,58 @@ public void testUpdateMutableCredentials() throws IOException { setupUpdatedCredentials(); MutableCredentials credentials = new MutableCredentials(initialCredentials, scopes); + URI testUri = URI.create("https://example.com"); + Executor executor = mock(Executor.class); + RequestMetadataCallback callback = mock(RequestMetadataCallback.class); - assertEquals(initialAuthType, credentials.getAuthenticationType()); + validateInitialDelegatedCredentialsAreSet(credentials, testUri); credentials.updateCredentials(updatedCredentials); assertEquals(updatedAuthType, credentials.getAuthenticationType()); assertFalse(credentials.hasRequestMetadata()); assertFalse(credentials.hasRequestMetadataOnly()); - assertSame(updatedMetadata, credentials.getRequestMetadata(URI.create("https://example.com"))); + assertSame(updatedMetadata, credentials.getRequestMetadata(testUri)); + assertEquals(updatedUniverseDomain, credentials.getUniverseDomain()); + assertEquals(updatedMetricsCredentialType, credentials.getMetricsCredentialType()); + + credentials.getRequestMetadata(testUri, executor, callback); credentials.refresh(); + verify(updatedScopedCredentials, times(1)).getRequestMetadata(testUri, executor, callback); verify(updatedScopedCredentials, times(1)).refresh(); } + @Test + public void testCreateMutableCredentialsNullScopes() throws IOException { + setupInitialCredentials(); + + MutableCredentials credentials = new MutableCredentials(initialCredentials, null); + URI testUri = URI.create("https://spanner.googleapis.com"); + + validateInitialDelegatedCredentialsAreSet(credentials, testUri); + } + + private void validateInitialDelegatedCredentialsAreSet( + MutableCredentials credentials, URI testUri) throws IOException { + assertEquals(initialAuthType, credentials.getAuthenticationType()); + assertTrue(credentials.hasRequestMetadata()); + assertTrue(credentials.hasRequestMetadataOnly()); + assertEquals(initialMetadata, credentials.getRequestMetadata(testUri)); + assertEquals(initialUniverseDomain, credentials.getUniverseDomain()); + assertEquals(initialMetricsCredentialType, credentials.getMetricsCredentialType()); + } + private void setupInitialCredentials() throws IOException { when(initialCredentials.createScoped(scopes)).thenReturn(initialScopedCredentials); + when(initialCredentials.createScoped(Collections.emptyList())) + .thenReturn(initialScopedCredentials); when(initialScopedCredentials.getAuthenticationType()).thenReturn(initialAuthType); when(initialScopedCredentials.getRequestMetadata(any(URI.class))).thenReturn(initialMetadata); + when(initialScopedCredentials.getUniverseDomain()).thenReturn(initialUniverseDomain); + when(initialScopedCredentials.getMetricsCredentialType()) + .thenReturn(initialMetricsCredentialType); when(initialScopedCredentials.hasRequestMetadata()).thenReturn(true); when(initialScopedCredentials.hasRequestMetadataOnly()).thenReturn(true); } @@ -102,6 +145,9 @@ private void setupUpdatedCredentials() throws IOException { when(updatedCredentials.createScoped(scopes)).thenReturn(updatedScopedCredentials); when(updatedScopedCredentials.getAuthenticationType()).thenReturn(updatedAuthType); when(updatedScopedCredentials.getRequestMetadata(any(URI.class))).thenReturn(updatedMetadata); + when(updatedScopedCredentials.getUniverseDomain()).thenReturn(updatedUniverseDomain); + when(updatedScopedCredentials.getMetricsCredentialType()) + .thenReturn(updatedMetricsCredentialType); when(updatedScopedCredentials.hasRequestMetadata()).thenReturn(false); when(updatedScopedCredentials.hasRequestMetadataOnly()).thenReturn(false); } From 61468f0ca37e724dd7d64d2f5f9bfe83a5b51907 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Tue, 3 Mar 2026 16:06:05 -0500 Subject: [PATCH 11/52] attempt to fix IT tests --- .../spanner/connection/it/ITMutableCredentialsTest.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 813471b79a..3df5689ab2 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -41,8 +41,8 @@ @Category(SerialIntegrationTest.class) @RunWith(JUnit4.class) public class ITMutableCredentialsTest extends ITAbstractSpannerTest { - private static final String INVALID_KEY_FILE = - ITMutableCredentialsTest.class.getResource("test-key.json").getPath(); + private static final String INVALID_KEY_RESOURCE = + "/com/google/cloud/spanner/connection/test-key.json"; @Test public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { @@ -58,7 +58,8 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; ServiceAccountCredentials invalidCredentials; - try (InputStream stream = Files.newInputStream(Paths.get(INVALID_KEY_FILE))) { + try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { + assertNotNull("Missing test resource: " + INVALID_KEY_RESOURCE, stream); invalidCredentials = ServiceAccountCredentials.fromStream(stream); } From fc8c68278cd19c9036e5d2454c283372f5998b63 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Tue, 3 Mar 2026 21:09:33 +0000 Subject: [PATCH 12/52] chore: generate libraries at Tue Mar 3 21:06:51 UTC 2026 --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 3df5689ab2..1e078c7d53 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -58,7 +58,8 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; ServiceAccountCredentials invalidCredentials; - try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { + try (InputStream stream = + ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { assertNotNull("Missing test resource: " + INVALID_KEY_RESOURCE, stream); invalidCredentials = ServiceAccountCredentials.fromStream(stream); } From 2cf5b89cacb6d96ca94aafe3fc72c5fcaeedbcc4 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Tue, 3 Mar 2026 16:24:33 -0500 Subject: [PATCH 13/52] try to use default key file --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 1e078c7d53..b10cb71347 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -49,7 +49,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I GoogleCredentials credentialsFromFile; try (InputStream stream = - Files.newInputStream(Paths.get(GceTestEnvConfig.GCE_CREDENTIALS_FILE))) { + Files.newInputStream(Paths.get(getKeyFile()))) { credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( From 1751af283b60f122de93a07b3c340d546c6f954d Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Tue, 3 Mar 2026 21:30:08 +0000 Subject: [PATCH 14/52] chore: generate libraries at Tue Mar 3 21:25:30 UTC 2026 --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index b10cb71347..3014fcd27f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -48,8 +48,7 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { GoogleCredentials credentialsFromFile; - try (InputStream stream = - Files.newInputStream(Paths.get(getKeyFile()))) { + try (InputStream stream = Files.newInputStream(Paths.get(getKeyFile()))) { credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( From 22a9ba714da4140d68725adb5592ceb3d077c77a Mon Sep 17 00:00:00 2001 From: ldetmer Date: Tue, 3 Mar 2026 17:12:46 -0500 Subject: [PATCH 15/52] try to use hardcoded service account file --- .../spanner/connection/it/ITMutableCredentialsTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 3014fcd27f..3aefff5448 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -41,6 +41,9 @@ @Category(SerialIntegrationTest.class) @RunWith(JUnit4.class) public class ITMutableCredentialsTest extends ITAbstractSpannerTest { + private static final String VALID_KEY_RESOURCE = + "/com/google/cloud/spanner/connection/test-key-cloud-storage.json"; + private static final String INVALID_KEY_RESOURCE = "/com/google/cloud/spanner/connection/test-key.json"; @@ -48,7 +51,8 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { GoogleCredentials credentialsFromFile; - try (InputStream stream = Files.newInputStream(Paths.get(getKeyFile()))) { + try (InputStream stream = + Files.newInputStream(Paths.get(VALID_KEY_RESOURCE))) { credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( @@ -57,8 +61,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; ServiceAccountCredentials invalidCredentials; - try (InputStream stream = - ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { + try (InputStream stream = Files.newInputStream(Paths.get(INVALID_KEY_RESOURCE))) { assertNotNull("Missing test resource: " + INVALID_KEY_RESOURCE, stream); invalidCredentials = ServiceAccountCredentials.fromStream(stream); } From 80b67bf2a23c44ead8b88aad87ee0b076313ae89 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Tue, 3 Mar 2026 22:16:01 +0000 Subject: [PATCH 16/52] chore: generate libraries at Tue Mar 3 22:13:15 UTC 2026 --- .../spanner/connection/it/ITMutableCredentialsTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 3aefff5448..58568551ad 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -42,7 +42,7 @@ @RunWith(JUnit4.class) public class ITMutableCredentialsTest extends ITAbstractSpannerTest { private static final String VALID_KEY_RESOURCE = - "/com/google/cloud/spanner/connection/test-key-cloud-storage.json"; + "/com/google/cloud/spanner/connection/test-key-cloud-storage.json"; private static final String INVALID_KEY_RESOURCE = "/com/google/cloud/spanner/connection/test-key.json"; @@ -51,8 +51,7 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { GoogleCredentials credentialsFromFile; - try (InputStream stream = - Files.newInputStream(Paths.get(VALID_KEY_RESOURCE))) { + try (InputStream stream = Files.newInputStream(Paths.get(VALID_KEY_RESOURCE))) { credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( @@ -61,7 +60,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; ServiceAccountCredentials invalidCredentials; - try (InputStream stream = Files.newInputStream(Paths.get(INVALID_KEY_RESOURCE))) { + try (InputStream stream = Files.newInputStream(Paths.get(INVALID_KEY_RESOURCE))) { assertNotNull("Missing test resource: " + INVALID_KEY_RESOURCE, stream); invalidCredentials = ServiceAccountCredentials.fromStream(stream); } From b0c151469dcfedc9dad821ade5798cdb8e65b68d Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 10:40:13 -0500 Subject: [PATCH 17/52] change to use resource as stream # Conflicts: # google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java --- .../spanner/connection/it/ITMutableCredentialsTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 58568551ad..1d06db226a 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -29,8 +29,6 @@ import com.google.spanner.admin.database.v1.DatabaseName; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import org.junit.Test; @@ -51,7 +49,8 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { GoogleCredentials credentialsFromFile; - try (InputStream stream = Files.newInputStream(Paths.get(VALID_KEY_RESOURCE))) { + try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(VALID_KEY_RESOURCE)) { + assertNotNull("Missing test resource: " + VALID_KEY_RESOURCE, stream); credentialsFromFile = GoogleCredentials.fromStream(stream); } assumeTrue( @@ -60,7 +59,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; ServiceAccountCredentials invalidCredentials; - try (InputStream stream = Files.newInputStream(Paths.get(INVALID_KEY_RESOURCE))) { + try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { assertNotNull("Missing test resource: " + INVALID_KEY_RESOURCE, stream); invalidCredentials = ServiceAccountCredentials.fromStream(stream); } From 910eb82dba2c7b84ed6dec30df5761102acf4506 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Wed, 4 Mar 2026 18:00:54 +0000 Subject: [PATCH 18/52] chore: generate libraries at Wed Mar 4 17:58:06 UTC 2026 --- .../spanner/connection/it/ITMutableCredentialsTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 1d06db226a..8fc04705ed 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -49,7 +49,8 @@ public class ITMutableCredentialsTest extends ITAbstractSpannerTest { public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { GoogleCredentials credentialsFromFile; - try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(VALID_KEY_RESOURCE)) { + try (InputStream stream = + ITMutableCredentialsTest.class.getResourceAsStream(VALID_KEY_RESOURCE)) { assertNotNull("Missing test resource: " + VALID_KEY_RESOURCE, stream); credentialsFromFile = GoogleCredentials.fromStream(stream); } @@ -59,7 +60,8 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; ServiceAccountCredentials invalidCredentials; - try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { + try (InputStream stream = + ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { assertNotNull("Missing test resource: " + INVALID_KEY_RESOURCE, stream); invalidCredentials = ServiceAccountCredentials.fromStream(stream); } From 816dcfce5feefa7dac63c88d0c3c164c49fe4eab Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 13:06:36 -0500 Subject: [PATCH 19/52] change to use correct project Id --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 8fc04705ed..7f6cfa57d2 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -75,7 +75,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { String dbName = DatabaseName.of( - GceTestEnvConfig.GCE_PROJECT_ID, + getTestEnv().getTestHelper().getInstanceId().getProject(), getTestEnv().getTestHelper().getInstanceId().getInstance(), "TEST") .toString(); From d3dab3407e27ded2aa2f50e57e9109c6f73288b1 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 13:26:51 -0500 Subject: [PATCH 20/52] change to use new api for test --- .../it/ITMutableCredentialsTest.java | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 7f6cfa57d2..75383efe68 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -31,6 +31,8 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.List; + +import com.google.spanner.admin.database.v1.InstanceName; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -73,17 +75,28 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I try (Spanner spanner = options.getService(); DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { - String dbName = + /* String dbName = DatabaseName.of( getTestEnv().getTestHelper().getInstanceId().getProject(), getTestEnv().getTestHelper().getInstanceId().getInstance(), "TEST") .toString(); - Database database = databaseAdminClient.getDatabase(dbName); - assertNotNull(database); + Database database = databaseAdminClient.getDatabase(dbName);*/ + InstanceName instanceName = InstanceName.of(getTestEnv().getTestHelper().getInstanceId().getProject(), getTestEnv().getTestHelper().getInstanceId().getProject()); + DatabaseAdminClient.ListDatabasesPagedResponse response = + databaseAdminClient.listDatabases(instanceName); + + boolean databaseFound = false; + for (DatabaseAdminClient.ListDatabasesPage page : response.iteratePages()) { + for (Database database : page.iterateAll()) { + System.out.println("\t" + database.getName()); + databaseFound = true; + } + } + assertTrue(databaseFound); try { mutableCredentials.updateCredentials(invalidCredentials); - databaseAdminClient.getDatabase(dbName); + databaseAdminClient.listDatabases(instanceName); fail("Expected UNAUTHENTICATED after switching to invalid credentials"); } catch (SpannerException e) { assertEquals(ErrorCode.UNAUTHENTICATED, e.getErrorCode()); From 9739af225751ffd9b08b1db472ec1b8021136de2 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Wed, 4 Mar 2026 18:30:09 +0000 Subject: [PATCH 21/52] chore: generate libraries at Wed Mar 4 18:27:30 UTC 2026 --- .../connection/it/ITMutableCredentialsTest.java | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 75383efe68..553d7f9327 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -26,13 +26,11 @@ import com.google.cloud.spanner.connection.ITAbstractSpannerTest; import com.google.cloud.spanner.connection.MutableCredentials; import com.google.spanner.admin.database.v1.Database; -import com.google.spanner.admin.database.v1.DatabaseName; +import com.google.spanner.admin.database.v1.InstanceName; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; - -import com.google.spanner.admin.database.v1.InstanceName; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -75,22 +73,25 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I try (Spanner spanner = options.getService(); DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { - /* String dbName = + /* String dbName = DatabaseName.of( getTestEnv().getTestHelper().getInstanceId().getProject(), getTestEnv().getTestHelper().getInstanceId().getInstance(), "TEST") .toString(); Database database = databaseAdminClient.getDatabase(dbName);*/ - InstanceName instanceName = InstanceName.of(getTestEnv().getTestHelper().getInstanceId().getProject(), getTestEnv().getTestHelper().getInstanceId().getProject()); + InstanceName instanceName = + InstanceName.of( + getTestEnv().getTestHelper().getInstanceId().getProject(), + getTestEnv().getTestHelper().getInstanceId().getProject()); DatabaseAdminClient.ListDatabasesPagedResponse response = - databaseAdminClient.listDatabases(instanceName); + databaseAdminClient.listDatabases(instanceName); boolean databaseFound = false; for (DatabaseAdminClient.ListDatabasesPage page : response.iteratePages()) { for (Database database : page.iterateAll()) { - System.out.println("\t" + database.getName()); - databaseFound = true; + System.out.println("\t" + database.getName()); + databaseFound = true; } } assertTrue(databaseFound); From 8c690d26ed67be21f366d53754773fa7001254a2 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 13:45:26 -0500 Subject: [PATCH 22/52] fix instance name --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 553d7f9327..8100e051f3 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -83,7 +83,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I InstanceName instanceName = InstanceName.of( getTestEnv().getTestHelper().getInstanceId().getProject(), - getTestEnv().getTestHelper().getInstanceId().getProject()); + getTestEnv().getTestHelper().getInstanceId().getInstance()); DatabaseAdminClient.ListDatabasesPagedResponse response = databaseAdminClient.listDatabases(instanceName); From 2d70ec5d466421de91d8bdd28bd5be19019c4b74 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 13:58:48 -0500 Subject: [PATCH 23/52] add invalid test key for IT tests --- .../connection/it/ITMutableCredentialsTest.java | 12 +++++++++--- .../cloud/spanner/connection/invalid-test-key.json | 13 +++++++++++++ 2 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 8100e051f3..c7a98f9d52 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -40,10 +40,10 @@ @RunWith(JUnit4.class) public class ITMutableCredentialsTest extends ITAbstractSpannerTest { private static final String VALID_KEY_RESOURCE = - "/com/google/cloud/spanner/connection/test-key-cloud-storage.json"; + "/com/google/cloud/spanner/connection/test-key.json"; private static final String INVALID_KEY_RESOURCE = - "/com/google/cloud/spanner/connection/test-key.json"; + "/com/google/cloud/spanner/connection/invalid-test-key.json"; @Test public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { @@ -97,7 +97,13 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I assertTrue(databaseFound); try { mutableCredentials.updateCredentials(invalidCredentials); - databaseAdminClient.listDatabases(instanceName); + DatabaseAdminClient.ListDatabasesPagedResponse responseFailure = + databaseAdminClient.listDatabases(instanceName); + for (DatabaseAdminClient.ListDatabasesPage page : responseFailure.iteratePages()) { + for (Database database : page.iterateAll()) { + System.out.println("\t" + database.getName()); + } + } fail("Expected UNAUTHENTICATED after switching to invalid credentials"); } catch (SpannerException e) { assertEquals(ErrorCode.UNAUTHENTICATED, e.getErrorCode()); diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json new file mode 100644 index 0000000000..424df9b36e --- /dev/null +++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "ldetmer-sanbox", + "private_key_id": "b9dbcfe52414109d71f74ccd648c100638e58eec", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQClpESvZWwMZ4fD\ncfWpcnVek6diphOYyuSuHKZQgY+zMkUwYT01HI8RzLZbDPK6Xyo3sovc+wOD74y3\npGNM05S3FdIb8ObO4rsnP3lTZiLLIgXW8oh5V3PFHiSNtAMkVaDEiRUyo/ERk9hM\nlIMytMk/ydlGRgHJg3I2qTHb+R3BLSy8EFMgWcbwlfBjqPXIQoTTeJ0+YFbw9MA6\nZWsyx7Nk1StiYXnVdNIAO6hkK8ZSOhRbd8gGv9h1TnCFYOyFGanR6OgpMm5tqLyc\nqB2QNX+Tsad/hd8ZekOKyLtWTnohKV6Y5FiOliZPclvMWDY2M0id4I5prxbz7CpM\nyeB+ocyDAgMBAAECggEAQPSeCroXGPYwgzBZSc2cwS3d4g2GedB2xOBvR/rGw1rf\nTw2S1xUP9cb1a9c0CGnxQE5AErRMuJxj7lAEsMf39aQU9OgPWuoGwmldxpqy4j3B\nVH1fj1YADDi51OfWo5UAqpGnQmiPzHjRxZYnrObAVMdu8OPbJ47oZw8Kgly6klnn\nGmMfNdJnrkQ0oww6IYt73RIf3jqXEdgCRb9BiJ7wj/QFcFmLaIbKFZ/COcor9vVZ\nQkQEeR9JLaX8lbTrVMMVR/ImuAeM6LQ8w3dYxlHlkDOUM19T8CRGvR29o+j6XgQi\nkMiZBGx6n6Hl5E1IFSo69s3Gtwee+oy0Go7WGc+2AQKBgQDXUhRSSR3mIgAH92uH\nKLo95t6WXVMzODNyTmJthHHJzYEVagcsldCwRcyyAbR7XwYCOimIJagKUoXHnfeP\n4onGtqfRb5OLhct8s2+HJFg/TS9pPVzcMNyOGAlpek7nbgAT5y3nFrXUzB+VN2J4\nLv78iGI24ZCLQ7goT9i5OZQMwQKBgQDE73sNEEZh0Po1AtfROGYn/93VJlskUKal\nuK1K+vOayj5liR4POurovygUsNX8PwOeL9YNOcn3XZAEycCcVFLTQhF88ai383KS\nWhlY4pLnFTW87yEW1gvju8b7ddnlvtnBszwuzGk7rVqcIC8xrARxXjO896kGTr88\nxA4Is+H2QwKBgQDSx7PC9XaCYQg8xDUL44+lp0qAUa1vt3WNUTRDV2L4lObnKpsJ\nR0M6O6ntG4QtPVEpfvxHHe3I5Q224mmE/dO3pfjUKfB6pagUU6c62RZWKV3fHMW5\ne098/gTAr41sOh9zXFxwGqg3Pvcv4D7Rvde5KF1Usi0IV2uAcuGKONY4QQKBgA+7\nHXYuraCUo9fmMT0aJzbcvmiPVspw0s78EIOjxh/ANfnAWTFYQHl1A4ubkIxEsFJL\neeq2igaDZ8SqJQOXzMHpTiJP321KOgWswseR2bAxxoggBeGgGXUIg92ETXKHqzdI\nzO7kDyfgMhO0knCCUByKLNHUaqEBW09MTd6uF8enAoGBAKM932cIoApo/B+Q4xgy\nLFnW70nAfh8NZ2AV0uBg6365pLGTzbbV3bKa5XpQCUvbCBclcHnYuqmADNiL25f8\nrbX1NHgTvKLdDqgZAEElYIqG4wlkq4Vkx2VOEpPiH5dAbdqFwogZm+phvAsa5zVL\nmWiIecySQuV4Wz+typKuQphw\n-----END PRIVATE KEY-----\n", + "client_email": "test-spanner@ldetmer-sanbox.iam.gserviceaccount.com", + "client_id": "106624917120625257013", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test-spanner%40ldetmer-sanbox.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} From 917cb2db2a40673d35262a2e351cded8970fab5d Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 14:00:06 -0500 Subject: [PATCH 24/52] change test key to be invalid --- .../com/google/cloud/spanner/connection/invalid-test-key.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json index 424df9b36e..a217110775 100644 --- a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json +++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json @@ -1,9 +1,9 @@ { "type": "service_account", - "project_id": "ldetmer-sanbox", + "project_id": "invalid", "private_key_id": "b9dbcfe52414109d71f74ccd648c100638e58eec", "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQClpESvZWwMZ4fD\ncfWpcnVek6diphOYyuSuHKZQgY+zMkUwYT01HI8RzLZbDPK6Xyo3sovc+wOD74y3\npGNM05S3FdIb8ObO4rsnP3lTZiLLIgXW8oh5V3PFHiSNtAMkVaDEiRUyo/ERk9hM\nlIMytMk/ydlGRgHJg3I2qTHb+R3BLSy8EFMgWcbwlfBjqPXIQoTTeJ0+YFbw9MA6\nZWsyx7Nk1StiYXnVdNIAO6hkK8ZSOhRbd8gGv9h1TnCFYOyFGanR6OgpMm5tqLyc\nqB2QNX+Tsad/hd8ZekOKyLtWTnohKV6Y5FiOliZPclvMWDY2M0id4I5prxbz7CpM\nyeB+ocyDAgMBAAECggEAQPSeCroXGPYwgzBZSc2cwS3d4g2GedB2xOBvR/rGw1rf\nTw2S1xUP9cb1a9c0CGnxQE5AErRMuJxj7lAEsMf39aQU9OgPWuoGwmldxpqy4j3B\nVH1fj1YADDi51OfWo5UAqpGnQmiPzHjRxZYnrObAVMdu8OPbJ47oZw8Kgly6klnn\nGmMfNdJnrkQ0oww6IYt73RIf3jqXEdgCRb9BiJ7wj/QFcFmLaIbKFZ/COcor9vVZ\nQkQEeR9JLaX8lbTrVMMVR/ImuAeM6LQ8w3dYxlHlkDOUM19T8CRGvR29o+j6XgQi\nkMiZBGx6n6Hl5E1IFSo69s3Gtwee+oy0Go7WGc+2AQKBgQDXUhRSSR3mIgAH92uH\nKLo95t6WXVMzODNyTmJthHHJzYEVagcsldCwRcyyAbR7XwYCOimIJagKUoXHnfeP\n4onGtqfRb5OLhct8s2+HJFg/TS9pPVzcMNyOGAlpek7nbgAT5y3nFrXUzB+VN2J4\nLv78iGI24ZCLQ7goT9i5OZQMwQKBgQDE73sNEEZh0Po1AtfROGYn/93VJlskUKal\nuK1K+vOayj5liR4POurovygUsNX8PwOeL9YNOcn3XZAEycCcVFLTQhF88ai383KS\nWhlY4pLnFTW87yEW1gvju8b7ddnlvtnBszwuzGk7rVqcIC8xrARxXjO896kGTr88\nxA4Is+H2QwKBgQDSx7PC9XaCYQg8xDUL44+lp0qAUa1vt3WNUTRDV2L4lObnKpsJ\nR0M6O6ntG4QtPVEpfvxHHe3I5Q224mmE/dO3pfjUKfB6pagUU6c62RZWKV3fHMW5\ne098/gTAr41sOh9zXFxwGqg3Pvcv4D7Rvde5KF1Usi0IV2uAcuGKONY4QQKBgA+7\nHXYuraCUo9fmMT0aJzbcvmiPVspw0s78EIOjxh/ANfnAWTFYQHl1A4ubkIxEsFJL\neeq2igaDZ8SqJQOXzMHpTiJP321KOgWswseR2bAxxoggBeGgGXUIg92ETXKHqzdI\nzO7kDyfgMhO0knCCUByKLNHUaqEBW09MTd6uF8enAoGBAKM932cIoApo/B+Q4xgy\nLFnW70nAfh8NZ2AV0uBg6365pLGTzbbV3bKa5XpQCUvbCBclcHnYuqmADNiL25f8\nrbX1NHgTvKLdDqgZAEElYIqG4wlkq4Vkx2VOEpPiH5dAbdqFwogZm+phvAsa5zVL\nmWiIecySQuV4Wz+typKuQphw\n-----END PRIVATE KEY-----\n", - "client_email": "test-spanner@ldetmer-sanbox.iam.gserviceaccount.com", + "client_email": "test-spanner@invalid.iam.gserviceaccount.com", "client_id": "106624917120625257013", "auth_uri": "https://accounts.google.com/o/oauth2/auth", "token_uri": "https://oauth2.googleapis.com/token", From fa7ea61405d1c2d51768afaa9d19f8414f9538e0 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Wed, 4 Mar 2026 19:03:52 +0000 Subject: [PATCH 25/52] chore: generate libraries at Wed Mar 4 19:01:08 UTC 2026 --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index c7a98f9d52..a3abf696ea 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -98,7 +98,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I try { mutableCredentials.updateCredentials(invalidCredentials); DatabaseAdminClient.ListDatabasesPagedResponse responseFailure = - databaseAdminClient.listDatabases(instanceName); + databaseAdminClient.listDatabases(instanceName); for (DatabaseAdminClient.ListDatabasesPage page : responseFailure.iteratePages()) { for (Database database : page.iterateAll()) { System.out.println("\t" + database.getName()); From 1d41b10a8b0e38388a736162e6571b139073437b Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 15:48:09 -0500 Subject: [PATCH 26/52] working IT test --- .../it/ITMutableCredentialsTest.java | 98 ++++++++----------- .../spanner/connection/invalid-test-key.json | 13 --- .../test-key-missing-permissions.json | 13 +++ 3 files changed, 54 insertions(+), 70 deletions(-) delete mode 100644 google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json create mode 100644 google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/test-key-missing-permissions.json diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index a3abf696ea..c6db28fe0f 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -17,19 +17,17 @@ package com.google.cloud.spanner.connection.it; import static org.junit.Assert.*; -import static org.junit.Assume.assumeTrue; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.spanner.*; import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; -import com.google.cloud.spanner.connection.ITAbstractSpannerTest; import com.google.cloud.spanner.connection.MutableCredentials; import com.google.spanner.admin.database.v1.Database; import com.google.spanner.admin.database.v1.InstanceName; import java.io.IOException; import java.io.InputStream; -import java.util.ArrayList; +import java.util.Collections; import java.util.List; import org.junit.Test; import org.junit.experimental.categories.Category; @@ -38,78 +36,64 @@ @Category(SerialIntegrationTest.class) @RunWith(JUnit4.class) -public class ITMutableCredentialsTest extends ITAbstractSpannerTest { - private static final String VALID_KEY_RESOURCE = - "/com/google/cloud/spanner/connection/test-key.json"; +public class ITMutableCredentialsTest { + private static final String MISSING_PERM_KEY = + "/com/google/cloud/spanner/connection/test-key-missing-permissions.json"; - private static final String INVALID_KEY_RESOURCE = - "/com/google/cloud/spanner/connection/invalid-test-key.json"; + private static final String INVALID_KEY = "/com/google/cloud/spanner/connection/test-key.json"; @Test public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { - GoogleCredentials credentialsFromFile; + GoogleCredentials missingPermissionCredentials; try (InputStream stream = - ITMutableCredentialsTest.class.getResourceAsStream(VALID_KEY_RESOURCE)) { - assertNotNull("Missing test resource: " + VALID_KEY_RESOURCE, stream); - credentialsFromFile = GoogleCredentials.fromStream(stream); + ITMutableCredentialsTest.class.getResourceAsStream(MISSING_PERM_KEY)) { + missingPermissionCredentials = GoogleCredentials.fromStream(stream); } - assumeTrue( - "This test requires service account credentials", - credentialsFromFile instanceof ServiceAccountCredentials); - - ServiceAccountCredentials validCredentials = (ServiceAccountCredentials) credentialsFromFile; ServiceAccountCredentials invalidCredentials; - try (InputStream stream = - ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY_RESOURCE)) { - assertNotNull("Missing test resource: " + INVALID_KEY_RESOURCE, stream); + try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY)) { invalidCredentials = ServiceAccountCredentials.fromStream(stream); } - - List scopes = new ArrayList<>(getTestEnv().getTestHelper().getOptions().getScopes()); - MutableCredentials mutableCredentials = new MutableCredentials(validCredentials, scopes); + List scopes = + Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"); + // create MutableCredentials first with missing permissions + MutableCredentials mutableCredentials = + new MutableCredentials((ServiceAccountCredentials) missingPermissionCredentials, scopes); SpannerOptions options = SpannerOptions.newBuilder().setCredentials(mutableCredentials).build(); - try (Spanner spanner = options.getService(); DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { - /* String dbName = - DatabaseName.of( - getTestEnv().getTestHelper().getInstanceId().getProject(), - getTestEnv().getTestHelper().getInstanceId().getInstance(), - "TEST") - .toString(); - Database database = databaseAdminClient.getDatabase(dbName);*/ - InstanceName instanceName = - InstanceName.of( - getTestEnv().getTestHelper().getInstanceId().getProject(), - getTestEnv().getTestHelper().getInstanceId().getInstance()); - DatabaseAdminClient.ListDatabasesPagedResponse response = - databaseAdminClient.listDatabases(instanceName); - - boolean databaseFound = false; - for (DatabaseAdminClient.ListDatabasesPage page : response.iteratePages()) { - for (Database database : page.iterateAll()) { - System.out.println("\t" + database.getName()); - databaseFound = true; - } + String project = "gcloud-devel"; + String instance = "java-client-integration-tests"; + try { + listDatabases(databaseAdminClient, project, instance); + } catch (Exception e) { + // specifically validate the permission denied error message + assertTrue(e.getMessage().contains("PERMISSION_DENIED")); + assertFalse(e.getMessage().contains("UNAUTHENTICATED")); } - assertTrue(databaseFound); + + // update mutableCredentials now to use an invalid credential + mutableCredentials.updateCredentials(invalidCredentials); try { - mutableCredentials.updateCredentials(invalidCredentials); - DatabaseAdminClient.ListDatabasesPagedResponse responseFailure = - databaseAdminClient.listDatabases(instanceName); - for (DatabaseAdminClient.ListDatabasesPage page : responseFailure.iteratePages()) { - for (Database database : page.iterateAll()) { - System.out.println("\t" + database.getName()); - } - } + listDatabases(databaseAdminClient, project, instance); fail("Expected UNAUTHENTICATED after switching to invalid credentials"); - } catch (SpannerException e) { - assertEquals(ErrorCode.UNAUTHENTICATED, e.getErrorCode()); + } catch (Exception e) { + assertTrue(e.getMessage().contains("UNAUTHENTICATED")); + assertFalse(e.getMessage().contains("PERMISSION_DENIED")); + } + } + } + + private static void listDatabases( + DatabaseAdminClient databaseAdminClient, String projectId, String instanceId) { + DatabaseAdminClient.ListDatabasesPagedResponse response = + databaseAdminClient.listDatabases(InstanceName.of(projectId, instanceId)); + + for (DatabaseAdminClient.ListDatabasesPage page : response.iteratePages()) { + for (Database database : page.iterateAll()) { + // no-op } - } finally { - closeSpanner(); } } } diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json deleted file mode 100644 index a217110775..0000000000 --- a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/invalid-test-key.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "service_account", - "project_id": "invalid", - "private_key_id": "b9dbcfe52414109d71f74ccd648c100638e58eec", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQClpESvZWwMZ4fD\ncfWpcnVek6diphOYyuSuHKZQgY+zMkUwYT01HI8RzLZbDPK6Xyo3sovc+wOD74y3\npGNM05S3FdIb8ObO4rsnP3lTZiLLIgXW8oh5V3PFHiSNtAMkVaDEiRUyo/ERk9hM\nlIMytMk/ydlGRgHJg3I2qTHb+R3BLSy8EFMgWcbwlfBjqPXIQoTTeJ0+YFbw9MA6\nZWsyx7Nk1StiYXnVdNIAO6hkK8ZSOhRbd8gGv9h1TnCFYOyFGanR6OgpMm5tqLyc\nqB2QNX+Tsad/hd8ZekOKyLtWTnohKV6Y5FiOliZPclvMWDY2M0id4I5prxbz7CpM\nyeB+ocyDAgMBAAECggEAQPSeCroXGPYwgzBZSc2cwS3d4g2GedB2xOBvR/rGw1rf\nTw2S1xUP9cb1a9c0CGnxQE5AErRMuJxj7lAEsMf39aQU9OgPWuoGwmldxpqy4j3B\nVH1fj1YADDi51OfWo5UAqpGnQmiPzHjRxZYnrObAVMdu8OPbJ47oZw8Kgly6klnn\nGmMfNdJnrkQ0oww6IYt73RIf3jqXEdgCRb9BiJ7wj/QFcFmLaIbKFZ/COcor9vVZ\nQkQEeR9JLaX8lbTrVMMVR/ImuAeM6LQ8w3dYxlHlkDOUM19T8CRGvR29o+j6XgQi\nkMiZBGx6n6Hl5E1IFSo69s3Gtwee+oy0Go7WGc+2AQKBgQDXUhRSSR3mIgAH92uH\nKLo95t6WXVMzODNyTmJthHHJzYEVagcsldCwRcyyAbR7XwYCOimIJagKUoXHnfeP\n4onGtqfRb5OLhct8s2+HJFg/TS9pPVzcMNyOGAlpek7nbgAT5y3nFrXUzB+VN2J4\nLv78iGI24ZCLQ7goT9i5OZQMwQKBgQDE73sNEEZh0Po1AtfROGYn/93VJlskUKal\nuK1K+vOayj5liR4POurovygUsNX8PwOeL9YNOcn3XZAEycCcVFLTQhF88ai383KS\nWhlY4pLnFTW87yEW1gvju8b7ddnlvtnBszwuzGk7rVqcIC8xrARxXjO896kGTr88\nxA4Is+H2QwKBgQDSx7PC9XaCYQg8xDUL44+lp0qAUa1vt3WNUTRDV2L4lObnKpsJ\nR0M6O6ntG4QtPVEpfvxHHe3I5Q224mmE/dO3pfjUKfB6pagUU6c62RZWKV3fHMW5\ne098/gTAr41sOh9zXFxwGqg3Pvcv4D7Rvde5KF1Usi0IV2uAcuGKONY4QQKBgA+7\nHXYuraCUo9fmMT0aJzbcvmiPVspw0s78EIOjxh/ANfnAWTFYQHl1A4ubkIxEsFJL\neeq2igaDZ8SqJQOXzMHpTiJP321KOgWswseR2bAxxoggBeGgGXUIg92ETXKHqzdI\nzO7kDyfgMhO0knCCUByKLNHUaqEBW09MTd6uF8enAoGBAKM932cIoApo/B+Q4xgy\nLFnW70nAfh8NZ2AV0uBg6365pLGTzbbV3bKa5XpQCUvbCBclcHnYuqmADNiL25f8\nrbX1NHgTvKLdDqgZAEElYIqG4wlkq4Vkx2VOEpPiH5dAbdqFwogZm+phvAsa5zVL\nmWiIecySQuV4Wz+typKuQphw\n-----END PRIVATE KEY-----\n", - "client_email": "test-spanner@invalid.iam.gserviceaccount.com", - "client_id": "106624917120625257013", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test-spanner%40ldetmer-sanbox.iam.gserviceaccount.com", - "universe_domain": "googleapis.com" -} diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/test-key-missing-permissions.json b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/test-key-missing-permissions.json new file mode 100644 index 0000000000..348e769f6f --- /dev/null +++ b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/test-key-missing-permissions.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "ldetmer-sanbox", + "private_key_id": "1f9be0fd206d51e759ab8577c32301333dda9103", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDgwaPePW0yK6Wg\nm0n0PgrmJbwbf0HgFQ9E5I5e+rZ+hl79pCxCkXcTVH6HsIgh+Lp1tyCTExjsE0cN\nDVRy9YD/BR4wKsxOhz5UKwmRhOg127EGvqMAwomEGOIyOuS+AMmdy710B/iBjc01\nLYTzNe1ldZ5SK69AJer9g1+mPn2H7AbZGQ8/ThKvzEAErmgd7PMATRwg6sAc5n6O\nIvcfQp4XApcYuCNH4KVmnXD3K9f0ySV5WKA14eHRIIxwjHagFvHaiMvU4/HXqCao\nClVoNmmoraUBfaDGj4JGMvj1JjTyz4VB0KGXxYTJJrViYhkKZs4zSo2k2qatPczn\nNHH6PkjLAgMBAAECggEAD+QWYPfjiOuQb1WWGgBqUYa+JmI4rHjwtm9D0wVTnTMv\nniwFu8MrMcuvPTVxupfVHyOOgwzo8zATEveBTrYlo7efQHVA3WsvMKZGVpVDZyNx\nqxy+y6agMHMjSGfk6mYwhclKS4eQviA3hRit0MBcWOhtruOgesmeNBnIy9PumOB+\nWVRp7uz8D/xaq55lFAShaH2DAEt316qetZW+LtNq473pPq9GlnFYsj+OPyWT84X2\nmeoWLxVwOpkx1RmmlAEOQCCK24H7GbZwsADiyHQ37cwQ/MfTs9qsib56TByVHSSb\nYD+lTMPT+5N/YY51AVq4op4kGPuVTHLE4D3uZduSEQKBgQDwmuSCjfvHr1LZxo/r\nVPR6+KiQC+o8qBzK0413D3rn+0pAWCcrkb9//PGwXxtRzjEodwbX9B+g8UGzkbfD\nH5scogl3Nd+3zUTsSp5D5IZUJsVv1lN2klv4y48zidey2qELOC8n6hPnrbOHfZqZ\nR22/o2/TeWxnWbmMUN2kx9r++QKBgQDvIyWePiJgFvlRLSLQQpZOiDV4z61Ixows\nDBrTeQyfAYG8gROA0LUS3zS4njA2Yr6xFj6M8rhUD9bLQ1+mGIJWi7ZI2cD+TDtH\ntdxTS7jBU8s26H2nisD8kvKpq61RxI1A2H7u+9gPzDweM0boBlERNqjyPUZhNbdD\n0+7AwmJC4wKBgB91kTVEzUvxr5qL7NtvUzwU8S1McYcW0BTxDkkn/AEDCVVacVyw\nBOL+NrfB57eNhz3sOjfYUp5fjSCmh+l6Y3Sd9zDgGW1V6JIgu4rTAYFVRHF4C5ew\nUVg5fXLWrh5TmcT2xquoXovnWVb45FLwVPg+rWtwL+1ffPRMyn42J3s5AoGAf6CR\ndigRLpl0THe7aczv7U/SwfyMrheRPfzj4FNtgftK43E8GHbK/Rx1RcbfUldXEKof\njhgIeozNhUQa60mPXmNIUQ8uakoDJV2RDj+OhleTUGW6kk2CfAptSlKeuNIe1Sn2\nbNOqV5wXxcJ2KGUepQI4HrjHNCB4A9I7TVMxICMCgYAPXO4/xTZJ/0Nmjd085yRo\nhDFBUTwWPHUTbUA1bBMd908F4RD0WnnLPzSC1hSxhhCGm119JGgusZfwL2Ey1nYh\n9B3b/EwArE/vC+Fl/tyILQR2G/D/f70dISuDut139cKEM8qBLJ2JRuYbKlEBPhGW\nw0x8SmTkNYepAG0SSaBu7g==\n-----END PRIVATE KEY-----\n", + "client_email": "test-mutable-credentials@ldetmer-sanbox.iam.gserviceaccount.com", + "client_id": "110488447517330409458", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test-mutable-credentials%40ldetmer-sanbox.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} From 037870676c4dbd998810f626a12515c2981af449 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 15:58:19 -0500 Subject: [PATCH 27/52] need to check error message on kokoro as its different then local --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index c6db28fe0f..83f5734131 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -69,6 +69,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I listDatabases(databaseAdminClient, project, instance); } catch (Exception e) { // specifically validate the permission denied error message + System.out.println("exception " + e.getMessage()); assertTrue(e.getMessage().contains("PERMISSION_DENIED")); assertFalse(e.getMessage().contains("UNAUTHENTICATED")); } From 9c4b12bc5d58fc3cbac78b054f8f2b6974dd00a3 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Wed, 4 Mar 2026 15:58:50 -0500 Subject: [PATCH 28/52] need to check error message on kokoro as its different then local --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 83f5734131..8ab0e283ea 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -67,6 +67,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I String instance = "java-client-integration-tests"; try { listDatabases(databaseAdminClient, project, instance); + fail("Expected PERMISSION_DENIED"); } catch (Exception e) { // specifically validate the permission denied error message System.out.println("exception " + e.getMessage()); From 89d47890018442d635c070a8403de1e2f478b771 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Thu, 5 Mar 2026 10:41:03 -0500 Subject: [PATCH 29/52] provide default scopes constructor --- .../connection/MutableCredentials.java | 19 +++++--- .../connection/MutableCredentialsTest.java | 18 ++----- .../it/ITMutableCredentialsTest.java | 48 ++++++++++--------- 3 files changed, 42 insertions(+), 43 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java index b37323eaaf..3b96439302 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -19,12 +19,14 @@ import com.google.auth.Credentials; import com.google.auth.RequestMetadataCallback; import com.google.auth.oauth2.ServiceAccountCredentials; +import com.google.cloud.spanner.SpannerOptions; import java.io.IOException; import java.net.URI; -import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.Executor; +import javax.annotation.Nonnull; /** * A mutable {@link Credentials} implementation that delegates authentication behavior to a scoped @@ -41,14 +43,17 @@ */ public class MutableCredentials extends Credentials { private volatile ServiceAccountCredentials delegate; - private final List scopes; + private final Set scopes; - public MutableCredentials(ServiceAccountCredentials credentials, List scopes) { - if (scopes != null) { - this.scopes = new java.util.ArrayList<>(scopes); - } else { - this.scopes = Collections.emptyList(); + public MutableCredentials(ServiceAccountCredentials credentials) { + this(credentials, SpannerOptions.getDefaultInstance().getScopes()); + } + + public MutableCredentials(ServiceAccountCredentials credentials, @Nonnull Set scopes) { + if (scopes.isEmpty()) { + throw new IllegalArgumentException("Scopes must not be empty"); } + this.scopes = new java.util.HashSet<>(scopes); delegate = (ServiceAccountCredentials) credentials.createScoped(this.scopes); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java index e4bf0820a2..550da48feb 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java @@ -31,10 +31,7 @@ import com.google.auth.oauth2.ServiceAccountCredentials; import java.io.IOException; import java.net.URI; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.concurrent.Executor; import org.junit.Test; import org.junit.runner.RunWith; @@ -46,7 +43,7 @@ public class MutableCredentialsTest { ServiceAccountCredentials initialScopedCredentials = mock(ServiceAccountCredentials.class); ServiceAccountCredentials updatedCredentials = mock(ServiceAccountCredentials.class); ServiceAccountCredentials updatedScopedCredentials = mock(ServiceAccountCredentials.class); - List scopes = Arrays.asList("scope-a", "scope-b"); + Set scopes = new HashSet<>(Arrays.asList("scope-a", "scope-b")); Map> initialMetadata = Collections.singletonMap("Authorization", Collections.singletonList("v1")); Map> updatedMetadata = @@ -108,14 +105,9 @@ public void testUpdateMutableCredentials() throws IOException { verify(updatedScopedCredentials, times(1)).refresh(); } - @Test - public void testCreateMutableCredentialsNullScopes() throws IOException { - setupInitialCredentials(); - - MutableCredentials credentials = new MutableCredentials(initialCredentials, null); - URI testUri = URI.create("https://spanner.googleapis.com"); - - validateInitialDelegatedCredentialsAreSet(credentials, testUri); + @Test(expected = IllegalArgumentException.class) + public void testCreateMutableCredentialsEmptyScopes() { + new MutableCredentials(initialCredentials, Collections.emptySet()); } private void validateInitialDelegatedCredentialsAreSet( diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 8ab0e283ea..96285f9c38 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -18,17 +18,17 @@ import static org.junit.Assert.*; +import com.google.api.gax.core.FixedCredentialsProvider; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.spanner.*; -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; import com.google.cloud.spanner.connection.MutableCredentials; -import com.google.spanner.admin.database.v1.Database; -import com.google.spanner.admin.database.v1.InstanceName; +import com.google.spanner.admin.instance.v1.ProjectName; import java.io.IOException; import java.io.InputStream; -import java.util.Collections; -import java.util.List; +import java.nio.file.Files; +import java.nio.file.Paths; import org.junit.Test; import org.junit.experimental.categories.Category; import org.junit.runner.RunWith; @@ -44,29 +44,33 @@ public class ITMutableCredentialsTest { @Test public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { - + System.out.println("property" + System.getenv("GOOGLE_ACCOUNT_CREDENTIALS")); GoogleCredentials missingPermissionCredentials; try (InputStream stream = - ITMutableCredentialsTest.class.getResourceAsStream(MISSING_PERM_KEY)) { + Files.newInputStream(Paths.get(System.getenv("GOOGLE_ACCOUNT_CREDENTIALS")))) { missingPermissionCredentials = GoogleCredentials.fromStream(stream); } ServiceAccountCredentials invalidCredentials; try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY)) { invalidCredentials = ServiceAccountCredentials.fromStream(stream); } - List scopes = - Collections.singletonList("https://www.googleapis.com/auth/cloud-platform"); - // create MutableCredentials first with missing permissions + + // create MutableCredentials first default account credentials MutableCredentials mutableCredentials = - new MutableCredentials((ServiceAccountCredentials) missingPermissionCredentials, scopes); + new MutableCredentials((ServiceAccountCredentials) missingPermissionCredentials); - SpannerOptions options = SpannerOptions.newBuilder().setCredentials(mutableCredentials).build(); + SpannerOptions options = + SpannerOptions.newBuilder() + .setCredentials(FixedCredentialsProvider.create(mutableCredentials).getCredentials()) + .build(); + System.out.println("initial credentials " + options.getCredentials()); + System.out.println("default projecct" + options.getProjectId()); try (Spanner spanner = options.getService(); - DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { String project = "gcloud-devel"; String instance = "java-client-integration-tests"; try { - listDatabases(databaseAdminClient, project, instance); + listInstances(instanceAdminClient, options.getProjectId(), instance); fail("Expected PERMISSION_DENIED"); } catch (Exception e) { // specifically validate the permission denied error message @@ -78,7 +82,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I // update mutableCredentials now to use an invalid credential mutableCredentials.updateCredentials(invalidCredentials); try { - listDatabases(databaseAdminClient, project, instance); + listInstances(instanceAdminClient, options.getProjectId(), instance); fail("Expected UNAUTHENTICATED after switching to invalid credentials"); } catch (Exception e) { assertTrue(e.getMessage().contains("UNAUTHENTICATED")); @@ -87,15 +91,13 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I } } - private static void listDatabases( - DatabaseAdminClient databaseAdminClient, String projectId, String instanceId) { - DatabaseAdminClient.ListDatabasesPagedResponse response = - databaseAdminClient.listDatabases(InstanceName.of(projectId, instanceId)); + private static void listInstances( + InstanceAdminClient instanceAdminClient, String projectId, String instanceId) { + InstanceAdminClient.ListInstancesPagedResponse response = + instanceAdminClient.listInstances(ProjectName.of(projectId)); - for (DatabaseAdminClient.ListDatabasesPage page : response.iteratePages()) { - for (Database database : page.iterateAll()) { - // no-op - } + for (InstanceAdminClient.ListInstancesPage page : response.iteratePages()) { + // no-op } } } From bb56c653ca4850135b02da31098cc017296cf6b8 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Thu, 5 Mar 2026 15:44:17 +0000 Subject: [PATCH 30/52] chore: generate libraries at Thu Mar 5 15:41:33 UTC 2026 --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 96285f9c38..c59fc07da4 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -64,7 +64,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I .setCredentials(FixedCredentialsProvider.create(mutableCredentials).getCredentials()) .build(); System.out.println("initial credentials " + options.getCredentials()); - System.out.println("default projecct" + options.getProjectId()); + System.out.println("default projecct" + options.getProjectId()); try (Spanner spanner = options.getService(); InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { String project = "gcloud-devel"; From 52b761b53caff328f4aadbb19b3737ad4bf63e6b Mon Sep 17 00:00:00 2001 From: ldetmer Date: Thu, 5 Mar 2026 10:59:01 -0500 Subject: [PATCH 31/52] testing default credential accesss --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index c59fc07da4..24f9a7acde 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -47,7 +47,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I System.out.println("property" + System.getenv("GOOGLE_ACCOUNT_CREDENTIALS")); GoogleCredentials missingPermissionCredentials; try (InputStream stream = - Files.newInputStream(Paths.get(System.getenv("GOOGLE_ACCOUNT_CREDENTIALS")))) { + Files.newInputStream(Paths.get("/tmpfs/src/gfile/secret_manager/java-it-service-account"))) { missingPermissionCredentials = GoogleCredentials.fromStream(stream); } ServiceAccountCredentials invalidCredentials; From 06103fd7d279d70dc76e53abe89b9afb64366956 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Thu, 5 Mar 2026 16:02:33 +0000 Subject: [PATCH 32/52] chore: generate libraries at Thu Mar 5 15:59:43 UTC 2026 --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 24f9a7acde..35cf83d146 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -47,7 +47,8 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I System.out.println("property" + System.getenv("GOOGLE_ACCOUNT_CREDENTIALS")); GoogleCredentials missingPermissionCredentials; try (InputStream stream = - Files.newInputStream(Paths.get("/tmpfs/src/gfile/secret_manager/java-it-service-account"))) { + Files.newInputStream( + Paths.get("/tmpfs/src/gfile/secret_manager/java-it-service-account"))) { missingPermissionCredentials = GoogleCredentials.fromStream(stream); } ServiceAccountCredentials invalidCredentials; From d5fb2f81cae9892d12f499b8c46fe4ddace6aeb9 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Thu, 5 Mar 2026 11:16:26 -0500 Subject: [PATCH 33/52] testing default credential accesss --- .../spanner/connection/it/ITMutableCredentialsTest.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 35cf83d146..01601f9694 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -60,6 +60,10 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I MutableCredentials mutableCredentials = new MutableCredentials((ServiceAccountCredentials) missingPermissionCredentials); + System.out.println("missingPermissionCredentials " + missingPermissionCredentials); + + System.out.println("application default " + GoogleCredentials.getApplicationDefault()); + SpannerOptions options = SpannerOptions.newBuilder() .setCredentials(FixedCredentialsProvider.create(mutableCredentials).getCredentials()) @@ -72,7 +76,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I String instance = "java-client-integration-tests"; try { listInstances(instanceAdminClient, options.getProjectId(), instance); - fail("Expected PERMISSION_DENIED"); + //fail("Expected PERMISSION_DENIED"); } catch (Exception e) { // specifically validate the permission denied error message System.out.println("exception " + e.getMessage()); From e5a4c93887d355b7d8e72a7dc842e2d9da377342 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Thu, 5 Mar 2026 16:19:45 +0000 Subject: [PATCH 34/52] chore: generate libraries at Thu Mar 5 16:16:58 UTC 2026 --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 01601f9694..7c8df5898d 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -76,7 +76,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I String instance = "java-client-integration-tests"; try { listInstances(instanceAdminClient, options.getProjectId(), instance); - //fail("Expected PERMISSION_DENIED"); + // fail("Expected PERMISSION_DENIED"); } catch (Exception e) { // specifically validate the permission denied error message System.out.println("exception " + e.getMessage()); From 3e71befc49973ceb9a44e59acc6150e2e3558863 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Thu, 5 Mar 2026 13:26:29 -0500 Subject: [PATCH 35/52] try to disable the nocredentials process --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 7c8df5898d..5931ea20e6 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -63,9 +63,9 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I System.out.println("missingPermissionCredentials " + missingPermissionCredentials); System.out.println("application default " + GoogleCredentials.getApplicationDefault()); - SpannerOptions options = SpannerOptions.newBuilder() + .setEmulatorHost(null) .setCredentials(FixedCredentialsProvider.create(mutableCredentials).getCredentials()) .build(); System.out.println("initial credentials " + options.getCredentials()); From bbfb3e7b23474d836445bf01025cca319f4ad557 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Thu, 5 Mar 2026 18:38:24 +0000 Subject: [PATCH 36/52] chore: generate libraries at Thu Mar 5 18:31:08 UTC 2026 --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 5931ea20e6..3067e7dccc 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -65,7 +65,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I System.out.println("application default " + GoogleCredentials.getApplicationDefault()); SpannerOptions options = SpannerOptions.newBuilder() - .setEmulatorHost(null) + .setEmulatorHost(null) .setCredentials(FixedCredentialsProvider.create(mutableCredentials).getCredentials()) .build(); System.out.println("initial credentials " + options.getCredentials()); From 2778372f0efc4d5d3ca968d3ed1f2ecdf29e0324 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Thu, 5 Mar 2026 14:16:38 -0500 Subject: [PATCH 37/52] hopefully fixed IT tests # Conflicts: # google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java --- .../connection/MutableCredentialsTest.java | 22 +++++- .../it/ITMutableCredentialsTest.java | 73 ++++++++----------- 2 files changed, 51 insertions(+), 44 deletions(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java index 550da48feb..c67f75ff6c 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java @@ -29,6 +29,7 @@ import com.google.auth.CredentialTypeForMetrics; import com.google.auth.RequestMetadataCallback; import com.google.auth.oauth2.ServiceAccountCredentials; +import com.google.cloud.spanner.SpannerOptions; import java.io.IOException; import java.net.URI; import java.util.*; @@ -76,6 +77,25 @@ public void testCreateMutableCredentials() throws IOException { verify(initialScopedCredentials, times(1)).refresh(); } + @Test + public void testCreateMutableCredentialsWithDefaultScopes() throws IOException { + Set defaultScopes = SpannerOptions.getDefaultInstance().getScopes(); + when(initialCredentials.createScoped(defaultScopes)).thenReturn(initialScopedCredentials); + when(initialScopedCredentials.getAuthenticationType()).thenReturn(initialAuthType); + when(initialScopedCredentials.getRequestMetadata(any(URI.class))).thenReturn(initialMetadata); + when(initialScopedCredentials.getUniverseDomain()).thenReturn(initialUniverseDomain); + when(initialScopedCredentials.getMetricsCredentialType()) + .thenReturn(initialMetricsCredentialType); + when(initialScopedCredentials.hasRequestMetadata()).thenReturn(true); + when(initialScopedCredentials.hasRequestMetadataOnly()).thenReturn(true); + + MutableCredentials credentials = new MutableCredentials(initialCredentials); + URI testUri = URI.create("https://spanner.googleapis.com"); + + validateInitialDelegatedCredentialsAreSet(credentials, testUri); + verify(initialCredentials).createScoped(defaultScopes); + } + @Test public void testUpdateMutableCredentials() throws IOException { setupInitialCredentials(); @@ -106,7 +126,7 @@ public void testUpdateMutableCredentials() throws IOException { } @Test(expected = IllegalArgumentException.class) - public void testCreateMutableCredentialsEmptyScopes() { + public void testCreateMutableCredentialsEmptyScopesThrowsError() { new MutableCredentials(initialCredentials, Collections.emptySet()); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 3067e7dccc..bc95ced662 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -17,8 +17,8 @@ package com.google.cloud.spanner.connection.it; import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; -import com.google.api.gax.core.FixedCredentialsProvider; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.spanner.*; @@ -37,72 +37,59 @@ @Category(SerialIntegrationTest.class) @RunWith(JUnit4.class) public class ITMutableCredentialsTest { - private static final String MISSING_PERM_KEY = - "/com/google/cloud/spanner/connection/test-key-missing-permissions.json"; - private static final String INVALID_KEY = "/com/google/cloud/spanner/connection/test-key.json"; + private static final String INVALID_CERT_PATH = + "/com/google/cloud/spanner/connection/test-key.json"; @Test public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { - System.out.println("property" + System.getenv("GOOGLE_ACCOUNT_CREDENTIALS")); - GoogleCredentials missingPermissionCredentials; - try (InputStream stream = - Files.newInputStream( - Paths.get("/tmpfs/src/gfile/secret_manager/java-it-service-account"))) { - missingPermissionCredentials = GoogleCredentials.fromStream(stream); + GoogleCredentials validCredentials; + + // accept cert path overridden by environment variable for local testing + if (System.getenv("GOOGLE_ACCOUNT_CREDENTIALS") != null) { + try (InputStream stream = + Files.newInputStream(Paths.get(System.getenv("GOOGLE_ACCOUNT_CREDENTIALS")))) { + validCredentials = GoogleCredentials.fromStream(stream); + } + } else { + validCredentials = GoogleCredentials.getApplicationDefault(); } + + // credentials must be ServiceAccountCredentials + assumeTrue(validCredentials instanceof ServiceAccountCredentials); + ServiceAccountCredentials invalidCredentials; - try (InputStream stream = ITMutableCredentialsTest.class.getResourceAsStream(INVALID_KEY)) { + try (InputStream stream = + ITMutableCredentialsTest.class.getResourceAsStream(INVALID_CERT_PATH)) { invalidCredentials = ServiceAccountCredentials.fromStream(stream); } - // create MutableCredentials first default account credentials + // create MutableCredentials first with valid credentials MutableCredentials mutableCredentials = - new MutableCredentials((ServiceAccountCredentials) missingPermissionCredentials); + new MutableCredentials((ServiceAccountCredentials) validCredentials); - System.out.println("missingPermissionCredentials " + missingPermissionCredentials); + System.out.println("validCredentials " + validCredentials); - System.out.println("application default " + GoogleCredentials.getApplicationDefault()); SpannerOptions options = SpannerOptions.newBuilder() - .setEmulatorHost(null) - .setCredentials(FixedCredentialsProvider.create(mutableCredentials).getCredentials()) + .setEmulatorHost( + null) // this setting is required otherwise SpannerOptions overrides credentials to + // NoCredentials + .setCredentials(mutableCredentials) .build(); System.out.println("initial credentials " + options.getCredentials()); - System.out.println("default projecct" + options.getProjectId()); + ProjectName projectName = ProjectName.of(options.getProjectId()); try (Spanner spanner = options.getService(); InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { - String project = "gcloud-devel"; - String instance = "java-client-integration-tests"; - try { - listInstances(instanceAdminClient, options.getProjectId(), instance); - // fail("Expected PERMISSION_DENIED"); - } catch (Exception e) { - // specifically validate the permission denied error message - System.out.println("exception " + e.getMessage()); - assertTrue(e.getMessage().contains("PERMISSION_DENIED")); - assertFalse(e.getMessage().contains("UNAUTHENTICATED")); - } - - // update mutableCredentials now to use an invalid credential + instanceAdminClient.listInstances(projectName); + // update mutableCredentials now to use an invalid credentials mutableCredentials.updateCredentials(invalidCredentials); try { - listInstances(instanceAdminClient, options.getProjectId(), instance); + instanceAdminClient.listInstances(projectName); fail("Expected UNAUTHENTICATED after switching to invalid credentials"); } catch (Exception e) { assertTrue(e.getMessage().contains("UNAUTHENTICATED")); - assertFalse(e.getMessage().contains("PERMISSION_DENIED")); } } } - - private static void listInstances( - InstanceAdminClient instanceAdminClient, String projectId, String instanceId) { - InstanceAdminClient.ListInstancesPagedResponse response = - instanceAdminClient.listInstances(ProjectName.of(projectId)); - - for (InstanceAdminClient.ListInstancesPage page : response.iteratePages()) { - // no-op - } - } } From 4c5ec92e531ef45ea40e9d67f9fe1b438425e5f6 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Thu, 5 Mar 2026 19:20:44 +0000 Subject: [PATCH 38/52] chore: generate libraries at Thu Mar 5 19:17:19 UTC 2026 --- .../cloud/spanner/connection/it/ITMutableCredentialsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index bc95ced662..5a05ad2684 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -74,7 +74,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I SpannerOptions.newBuilder() .setEmulatorHost( null) // this setting is required otherwise SpannerOptions overrides credentials to - // NoCredentials + // NoCredentials .setCredentials(mutableCredentials) .build(); System.out.println("initial credentials " + options.getCredentials()); From eb0164e53a0c54862a03e833d8dc11cc4de97c64 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Thu, 5 Mar 2026 15:28:08 -0500 Subject: [PATCH 39/52] cleaned up tasks + added sample code --- .../google/cloud/spanner/SpannerOptions.java | 2 +- .../connection/MutableCredentials.java | 2 +- .../connection/MutableCredentialsTest.java | 2 +- .../it/ITMutableCredentialsTest.java | 14 ++- .../spanner/MutableCredentialsExample.java | 92 +++++++++++++++++++ 5 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java 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 9eea86ab59..ca82f2e342 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 @@ -128,7 +128,7 @@ public class SpannerOptions extends ServiceOptions { private static final String GOOGLE_DEFAULT_UNIVERSE = "googleapis.com"; private static final String EXPERIMENTAL_HOST_PROJECT_ID = "default"; - private static final ImmutableSet SCOPES = + public static final ImmutableSet SCOPES = ImmutableSet.of( "https://www.googleapis.com/auth/spanner.admin", "https://www.googleapis.com/auth/spanner.data"); diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java index 3b96439302..b40d839228 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java @@ -46,7 +46,7 @@ public class MutableCredentials extends Credentials { private final Set scopes; public MutableCredentials(ServiceAccountCredentials credentials) { - this(credentials, SpannerOptions.getDefaultInstance().getScopes()); + this(credentials, SpannerOptions.SCOPES); } public MutableCredentials(ServiceAccountCredentials credentials, @Nonnull Set scopes) { diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java index c67f75ff6c..38c59083b0 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java @@ -79,7 +79,7 @@ public void testCreateMutableCredentials() throws IOException { @Test public void testCreateMutableCredentialsWithDefaultScopes() throws IOException { - Set defaultScopes = SpannerOptions.getDefaultInstance().getScopes(); + Set defaultScopes = SpannerOptions.SCOPES; when(initialCredentials.createScoped(defaultScopes)).thenReturn(initialScopedCredentials); when(initialScopedCredentials.getAuthenticationType()).thenReturn(initialAuthType); when(initialScopedCredentials.getRequestMetadata(any(URI.class))).thenReturn(initialMetadata); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 5a05ad2684..51798cd1e6 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -43,7 +43,7 @@ public class ITMutableCredentialsTest { @Test public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { - GoogleCredentials validCredentials; + GoogleCredentials validCredentials = null; // accept cert path overridden by environment variable for local testing if (System.getenv("GOOGLE_ACCOUNT_CREDENTIALS") != null) { @@ -52,7 +52,10 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I validCredentials = GoogleCredentials.fromStream(stream); } } else { - validCredentials = GoogleCredentials.getApplicationDefault(); + try { + validCredentials = GoogleCredentials.getApplicationDefault(); + } catch (IOException e) { + } } // credentials must be ServiceAccountCredentials @@ -68,8 +71,6 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I MutableCredentials mutableCredentials = new MutableCredentials((ServiceAccountCredentials) validCredentials); - System.out.println("validCredentials " + validCredentials); - SpannerOptions options = SpannerOptions.newBuilder() .setEmulatorHost( @@ -77,14 +78,17 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I // NoCredentials .setCredentials(mutableCredentials) .build(); - System.out.println("initial credentials " + options.getCredentials()); + ProjectName projectName = ProjectName.of(options.getProjectId()); try (Spanner spanner = options.getService(); InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { instanceAdminClient.listInstances(projectName); + // update mutableCredentials now to use an invalid credentials mutableCredentials.updateCredentials(invalidCredentials); + try { + // this call should now fail with new invalid credentials instanceAdminClient.listInstances(projectName); fail("Expected UNAUTHENTICATED after switching to invalid credentials"); } catch (Exception e) { diff --git a/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java new file mode 100644 index 0000000000..9bfaa64a29 --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java @@ -0,0 +1,92 @@ +/* + * Copyright 2026 Google LLC + * + * Licensed 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 com.example.spanner; + +//[START mutable_credentials] + +import com.google.auth.oauth2.ServiceAccountCredentials; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +import com.google.cloud.spanner.connection.MutableCredentials; +import com.google.spanner.admin.database.v1.DatabaseName; +import com.google.spanner.admin.database.v1.GetDatabaseDdlResponse; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.FileTime; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +public class MutableCredentialsExample { + + + static void createClientWithMutableCredentials() throws IOException { + final String credentialsPath = "location_of_service_account_credential_json"; + Path path = Paths.get(credentialsPath); + // Use an array to hold the mutable lastModifiedTime so it can be accessed in the lambda + FileTime[] lastModifiedTime = new FileTime[]{ Files.getLastModifiedTime(path) }; + + // 1 - create service account credentials + ServiceAccountCredentials serviceAccountCredentials; + try (FileInputStream is = new FileInputStream(credentialsPath)) { + serviceAccountCredentials = ServiceAccountCredentials.fromStream(is); + } + + // 2 - wrap credentials from step 1 in a MutableCredentials instance + MutableCredentials mutableCredentials = new MutableCredentials(serviceAccountCredentials); + + // 3 - set credentials on your SpannerOptions builder to your mutableCredentials + SpannerOptions options = SpannerOptions.newBuilder() + .setCredentials(mutableCredentials) + .build(); + + // 4 - include logic for when to how your mutableCredentials + // In this example we'll use a SchedulerExecutorService to periodically check for updates + ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); + executorService.scheduleAtFixedRate(() -> { + try { + FileTime currentModifiedTime = Files.getLastModifiedTime(path); + if (currentModifiedTime.compareTo(lastModifiedTime[0]) > 0) { + lastModifiedTime[0] = currentModifiedTime; + try (FileInputStream is = new FileInputStream(credentialsPath)) { + ServiceAccountCredentials credentials = ServiceAccountCredentials.fromStream(is); + mutableCredentials.updateCredentials(credentials); + System.out.println("Credentials rotated."); + } + } + } catch (IOException e) { + System.err.println("Failed to check or update credentials: " + e.getMessage()); + } + }, 15, 15, TimeUnit.MINUTES); + + // 5. Use the client + try (Spanner spanner = options.getService(); + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + // Perform operations... + // long running client operations will always use the latest credentials wrapped in + // mutableCredentials + } finally { + // Ensure the executor is shut down when the application exits or the client is closed + executorService.shutdown(); + } + } +} +//[END mutable_credentials] From bf52fa211ca23f2d9038541accf7136726fa4f65 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Thu, 5 Mar 2026 20:31:18 +0000 Subject: [PATCH 40/52] chore: generate libraries at Thu Mar 5 20:28:36 UTC 2026 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dbcde8f897..768788fc65 100644 --- a/README.md +++ b/README.md @@ -381,6 +381,7 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-spanner/tree/ | List Databases Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/ListDatabasesSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/ListDatabasesSample.java) | | List Instance Config Operations Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigOperationsSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigOperationsSample.java) | | List Instance Configs Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigsSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigsSample.java) | +| Mutable Credentials Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java) | | Pg Alter Sequence Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgAlterSequenceSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgAlterSequenceSample.java) | | Pg Async Query To List Async Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgAsyncQueryToListAsyncExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgAsyncQueryToListAsyncExample.java) | | Pg Async Runner Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgAsyncRunnerExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgAsyncRunnerExample.java) | From a50eee8575a2d63a6b9cfdd5aade699fce735dcd Mon Sep 17 00:00:00 2001 From: ldetmer Date: Thu, 5 Mar 2026 16:10:01 -0500 Subject: [PATCH 41/52] bump sample dependency so code samples compile --- samples/install-without-bom/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index a504dc626b..6334cf05ef 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -33,7 +33,7 @@ com.google.cloud google-cloud-spanner - 6.110.0 + 6.111.1 From 8d640ef3c58f988d2e8bd065fdd4a5c5ae1df145 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Thu, 5 Mar 2026 21:13:16 +0000 Subject: [PATCH 42/52] chore: generate libraries at Thu Mar 5 21:10:30 UTC 2026 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 768788fc65..f65f5c0e34 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ If you are using Maven without the BOM, add this to your dependencies: com.google.cloud google-cloud-spanner - 6.110.0 + 6.111.1 ``` From 38ddfb42c584bf7b1cdf34a255e2477838f0f9b7 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Thu, 5 Mar 2026 16:49:11 -0500 Subject: [PATCH 43/52] bump sample dependency so code samples compile --- samples/snippets/pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index 23c24291e2..a6e976dfe1 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -45,6 +45,7 @@ com.google.cloud google-cloud-spanner + 6.111.1 From 4ed2f0bed3fd859b5c43116c18e3147ad46d99a0 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Thu, 5 Mar 2026 21:52:37 +0000 Subject: [PATCH 44/52] chore: generate libraries at Thu Mar 5 21:49:47 UTC 2026 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f65f5c0e34..9a63ba392d 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ If you are using Maven with [BOM][libraries-bom], add this to your pom.xml file: com.google.cloud google-cloud-spanner + 6.111.1 ``` From 09c1e6387e67b06da65dfb7d242ff9d7023ed8fb Mon Sep 17 00:00:00 2001 From: ldetmer Date: Fri, 6 Mar 2026 09:37:37 -0500 Subject: [PATCH 45/52] updates from PR review --- .../{connection => }/MutableCredentials.java | 7 ++- .../google/cloud/spanner/SpannerOptions.java | 2 +- .../MutableCredentialsTest.java | 12 +++- .../it/ITMutableCredentialsTest.java | 19 +++--- .../test-key-missing-permissions.json | 13 ----- .../spanner/MutableCredentialsExample.java | 58 ++++++++++--------- 6 files changed, 58 insertions(+), 53 deletions(-) rename google-cloud-spanner/src/main/java/com/google/cloud/spanner/{connection => }/MutableCredentials.java (94%) rename google-cloud-spanner/src/test/java/com/google/cloud/spanner/{connection => }/MutableCredentialsTest.java (97%) delete mode 100644 google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/test-key-missing-permissions.json diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MutableCredentials.java similarity index 94% rename from google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java rename to google-cloud-spanner/src/main/java/com/google/cloud/spanner/MutableCredentials.java index b40d839228..b6eff497b2 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/connection/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MutableCredentials.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.google.cloud.spanner.connection; +package com.google.cloud.spanner; import com.google.auth.CredentialTypeForMetrics; import com.google.auth.Credentials; import com.google.auth.RequestMetadataCallback; import com.google.auth.oauth2.ServiceAccountCredentials; -import com.google.cloud.spanner.SpannerOptions; + import java.io.IOException; import java.net.URI; import java.util.List; @@ -33,7 +33,7 @@ * {@link ServiceAccountCredentials} instance. * *

This class is intended for scenarios where an application needs to replace the underlying - * service account credentials for a long running Spanner Client. + * service account credentials for a long-running Spanner Client. * *

All operations inherited from {@link Credentials} are forwarded to the current delegate, * including request metadata retrieval and token refresh. Calling {@link @@ -45,6 +45,7 @@ public class MutableCredentials extends Credentials { private volatile ServiceAccountCredentials delegate; private final Set scopes; + /** Creates a MutableCredentials instance with default spanner scopes {@link SpannerOptions.SCOPES} */ public MutableCredentials(ServiceAccountCredentials credentials) { this(credentials, SpannerOptions.SCOPES); } 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 ca82f2e342..1672744187 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 @@ -128,7 +128,7 @@ public class SpannerOptions extends ServiceOptions { private static final String GOOGLE_DEFAULT_UNIVERSE = "googleapis.com"; private static final String EXPERIMENTAL_HOST_PROJECT_ID = "default"; - public static final ImmutableSet SCOPES = + static final ImmutableSet SCOPES = ImmutableSet.of( "https://www.googleapis.com/auth/spanner.admin", "https://www.googleapis.com/auth/spanner.data"); diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutableCredentialsTest.java similarity index 97% rename from google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java rename to google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutableCredentialsTest.java index 38c59083b0..8e131f7e18 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/MutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutableCredentialsTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.spanner.connection; +package com.google.cloud.spanner; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -29,10 +29,16 @@ import com.google.auth.CredentialTypeForMetrics; import com.google.auth.RequestMetadataCallback; import com.google.auth.oauth2.ServiceAccountCredentials; -import com.google.cloud.spanner.SpannerOptions; + import java.io.IOException; import java.net.URI; -import java.util.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.Executor; import org.junit.Test; import org.junit.runner.RunWith; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 51798cd1e6..0eef81ef01 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -16,14 +16,19 @@ package com.google.cloud.spanner.connection.it; -import static org.junit.Assert.*; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; -import com.google.cloud.spanner.*; + +import com.google.cloud.spanner.SerialIntegrationTest; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; -import com.google.cloud.spanner.connection.MutableCredentials; +import com.google.cloud.spanner.MutableCredentials; import com.google.spanner.admin.instance.v1.ProjectName; import java.io.IOException; import java.io.InputStream; @@ -73,15 +78,15 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I SpannerOptions options = SpannerOptions.newBuilder() - .setEmulatorHost( - null) // this setting is required otherwise SpannerOptions overrides credentials to - // NoCredentials + // this setting is required in the scenario SPANNER_EMULATOR_HOST is set otherwise + // SpannerOptions overrides credentials to NoCredentials + .setEmulatorHost(null) .setCredentials(mutableCredentials) .build(); ProjectName projectName = ProjectName.of(options.getProjectId()); try (Spanner spanner = options.getService(); - InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { + InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { instanceAdminClient.listInstances(projectName); // update mutableCredentials now to use an invalid credentials diff --git a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/test-key-missing-permissions.json b/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/test-key-missing-permissions.json deleted file mode 100644 index 348e769f6f..0000000000 --- a/google-cloud-spanner/src/test/resources/com/google/cloud/spanner/connection/test-key-missing-permissions.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "service_account", - "project_id": "ldetmer-sanbox", - "private_key_id": "1f9be0fd206d51e759ab8577c32301333dda9103", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDgwaPePW0yK6Wg\nm0n0PgrmJbwbf0HgFQ9E5I5e+rZ+hl79pCxCkXcTVH6HsIgh+Lp1tyCTExjsE0cN\nDVRy9YD/BR4wKsxOhz5UKwmRhOg127EGvqMAwomEGOIyOuS+AMmdy710B/iBjc01\nLYTzNe1ldZ5SK69AJer9g1+mPn2H7AbZGQ8/ThKvzEAErmgd7PMATRwg6sAc5n6O\nIvcfQp4XApcYuCNH4KVmnXD3K9f0ySV5WKA14eHRIIxwjHagFvHaiMvU4/HXqCao\nClVoNmmoraUBfaDGj4JGMvj1JjTyz4VB0KGXxYTJJrViYhkKZs4zSo2k2qatPczn\nNHH6PkjLAgMBAAECggEAD+QWYPfjiOuQb1WWGgBqUYa+JmI4rHjwtm9D0wVTnTMv\nniwFu8MrMcuvPTVxupfVHyOOgwzo8zATEveBTrYlo7efQHVA3WsvMKZGVpVDZyNx\nqxy+y6agMHMjSGfk6mYwhclKS4eQviA3hRit0MBcWOhtruOgesmeNBnIy9PumOB+\nWVRp7uz8D/xaq55lFAShaH2DAEt316qetZW+LtNq473pPq9GlnFYsj+OPyWT84X2\nmeoWLxVwOpkx1RmmlAEOQCCK24H7GbZwsADiyHQ37cwQ/MfTs9qsib56TByVHSSb\nYD+lTMPT+5N/YY51AVq4op4kGPuVTHLE4D3uZduSEQKBgQDwmuSCjfvHr1LZxo/r\nVPR6+KiQC+o8qBzK0413D3rn+0pAWCcrkb9//PGwXxtRzjEodwbX9B+g8UGzkbfD\nH5scogl3Nd+3zUTsSp5D5IZUJsVv1lN2klv4y48zidey2qELOC8n6hPnrbOHfZqZ\nR22/o2/TeWxnWbmMUN2kx9r++QKBgQDvIyWePiJgFvlRLSLQQpZOiDV4z61Ixows\nDBrTeQyfAYG8gROA0LUS3zS4njA2Yr6xFj6M8rhUD9bLQ1+mGIJWi7ZI2cD+TDtH\ntdxTS7jBU8s26H2nisD8kvKpq61RxI1A2H7u+9gPzDweM0boBlERNqjyPUZhNbdD\n0+7AwmJC4wKBgB91kTVEzUvxr5qL7NtvUzwU8S1McYcW0BTxDkkn/AEDCVVacVyw\nBOL+NrfB57eNhz3sOjfYUp5fjSCmh+l6Y3Sd9zDgGW1V6JIgu4rTAYFVRHF4C5ew\nUVg5fXLWrh5TmcT2xquoXovnWVb45FLwVPg+rWtwL+1ffPRMyn42J3s5AoGAf6CR\ndigRLpl0THe7aczv7U/SwfyMrheRPfzj4FNtgftK43E8GHbK/Rx1RcbfUldXEKof\njhgIeozNhUQa60mPXmNIUQ8uakoDJV2RDj+OhleTUGW6kk2CfAptSlKeuNIe1Sn2\nbNOqV5wXxcJ2KGUepQI4HrjHNCB4A9I7TVMxICMCgYAPXO4/xTZJ/0Nmjd085yRo\nhDFBUTwWPHUTbUA1bBMd908F4RD0WnnLPzSC1hSxhhCGm119JGgusZfwL2Ey1nYh\n9B3b/EwArE/vC+Fl/tyILQR2G/D/f70dISuDut139cKEM8qBLJ2JRuYbKlEBPhGW\nw0x8SmTkNYepAG0SSaBu7g==\n-----END PRIVATE KEY-----\n", - "client_email": "test-mutable-credentials@ldetmer-sanbox.iam.gserviceaccount.com", - "client_id": "110488447517330409458", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", - "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/test-mutable-credentials%40ldetmer-sanbox.iam.gserviceaccount.com", - "universe_domain": "googleapis.com" -} diff --git a/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java index 9bfaa64a29..33b2ace4c2 100644 --- a/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java +++ b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java @@ -16,15 +16,13 @@ package com.example.spanner; -//[START mutable_credentials] +// [START spanner_mutable_credentials] import com.google.auth.oauth2.ServiceAccountCredentials; import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; import com.google.cloud.spanner.connection.MutableCredentials; -import com.google.spanner.admin.database.v1.DatabaseName; -import com.google.spanner.admin.database.v1.GetDatabaseDdlResponse; import java.io.FileInputStream; import java.io.IOException; import java.nio.file.Files; @@ -33,16 +31,16 @@ import java.nio.file.attribute.FileTime; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; public class MutableCredentialsExample { - static void createClientWithMutableCredentials() throws IOException { final String credentialsPath = "location_of_service_account_credential_json"; Path path = Paths.get(credentialsPath); // Use an array to hold the mutable lastModifiedTime so it can be accessed in the lambda - FileTime[] lastModifiedTime = new FileTime[]{ Files.getLastModifiedTime(path) }; + FileTime[] lastModifiedTime = new FileTime[] {Files.getLastModifiedTime(path)}; // 1 - create service account credentials ServiceAccountCredentials serviceAccountCredentials; @@ -54,32 +52,40 @@ static void createClientWithMutableCredentials() throws IOException { MutableCredentials mutableCredentials = new MutableCredentials(serviceAccountCredentials); // 3 - set credentials on your SpannerOptions builder to your mutableCredentials - SpannerOptions options = SpannerOptions.newBuilder() - .setCredentials(mutableCredentials) - .build(); + SpannerOptions options = SpannerOptions.newBuilder().setCredentials(mutableCredentials).build(); - // 4 - include logic for when to how your mutableCredentials + // 4 - include logic for when/how to update your mutableCredentials // In this example we'll use a SchedulerExecutorService to periodically check for updates - ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); - executorService.scheduleAtFixedRate(() -> { - try { - FileTime currentModifiedTime = Files.getLastModifiedTime(path); - if (currentModifiedTime.compareTo(lastModifiedTime[0]) > 0) { - lastModifiedTime[0] = currentModifiedTime; - try (FileInputStream is = new FileInputStream(credentialsPath)) { - ServiceAccountCredentials credentials = ServiceAccountCredentials.fromStream(is); - mutableCredentials.updateCredentials(credentials); - System.out.println("Credentials rotated."); + ThreadFactory daemonThreadFactory = + runnable -> { + Thread thread = new Thread(runnable, "spanner-mutable-credentials-rotator"); + thread.setDaemon(true); + return thread; + }; + ScheduledExecutorService executorService = + Executors.newSingleThreadScheduledExecutor(daemonThreadFactory); + executorService.scheduleAtFixedRate( + () -> { + try { + FileTime currentModifiedTime = Files.getLastModifiedTime(path); + if (currentModifiedTime.compareTo(lastModifiedTime[0]) > 0) { + lastModifiedTime[0] = currentModifiedTime; + try (FileInputStream is = new FileInputStream(credentialsPath)) { + ServiceAccountCredentials credentials = ServiceAccountCredentials.fromStream(is); + mutableCredentials.updateCredentials(credentials); + } + } + } catch (IOException e) { + System.err.println("Failed to check or update credentials: " + e.getMessage()); } - } - } catch (IOException e) { - System.err.println("Failed to check or update credentials: " + e.getMessage()); - } - }, 15, 15, TimeUnit.MINUTES); + }, + 15, + 15, + TimeUnit.MINUTES); // 5. Use the client try (Spanner spanner = options.getService(); - DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { + DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { // Perform operations... // long running client operations will always use the latest credentials wrapped in // mutableCredentials @@ -89,4 +95,4 @@ static void createClientWithMutableCredentials() throws IOException { } } } -//[END mutable_credentials] +// [END spanner_mutable_credentials] From 8805dd7d91664ff351b4a3335a503b2a76fd70e4 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Fri, 6 Mar 2026 14:41:01 +0000 Subject: [PATCH 46/52] chore: generate libraries at Fri Mar 6 14:38:15 UTC 2026 --- .../java/com/google/cloud/spanner/MutableCredentials.java | 5 +++-- .../com/google/cloud/spanner/MutableCredentialsTest.java | 2 -- .../spanner/connection/it/ITMutableCredentialsTest.java | 6 ++---- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MutableCredentials.java index b6eff497b2..670a6cdc0e 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MutableCredentials.java @@ -19,7 +19,6 @@ import com.google.auth.Credentials; import com.google.auth.RequestMetadataCallback; import com.google.auth.oauth2.ServiceAccountCredentials; - import java.io.IOException; import java.net.URI; import java.util.List; @@ -45,7 +44,9 @@ public class MutableCredentials extends Credentials { private volatile ServiceAccountCredentials delegate; private final Set scopes; - /** Creates a MutableCredentials instance with default spanner scopes {@link SpannerOptions.SCOPES} */ + /** + * Creates a MutableCredentials instance with default spanner scopes {@link SpannerOptions.SCOPES} + */ public MutableCredentials(ServiceAccountCredentials credentials) { this(credentials, SpannerOptions.SCOPES); } diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutableCredentialsTest.java index 8e131f7e18..2082d56b4e 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/MutableCredentialsTest.java @@ -29,10 +29,8 @@ import com.google.auth.CredentialTypeForMetrics; import com.google.auth.RequestMetadataCallback; import com.google.auth.oauth2.ServiceAccountCredentials; - import java.io.IOException; import java.net.URI; - import java.util.Arrays; import java.util.Collections; import java.util.HashSet; diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java index 0eef81ef01..e0cebf064e 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java @@ -16,19 +16,17 @@ package com.google.cloud.spanner.connection.it; - import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import com.google.auth.oauth2.GoogleCredentials; import com.google.auth.oauth2.ServiceAccountCredentials; - +import com.google.cloud.spanner.MutableCredentials; import com.google.cloud.spanner.SerialIntegrationTest; import com.google.cloud.spanner.Spanner; import com.google.cloud.spanner.SpannerOptions; import com.google.cloud.spanner.admin.instance.v1.InstanceAdminClient; -import com.google.cloud.spanner.MutableCredentials; import com.google.spanner.admin.instance.v1.ProjectName; import java.io.IOException; import java.io.InputStream; @@ -86,7 +84,7 @@ public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws I ProjectName projectName = ProjectName.of(options.getProjectId()); try (Spanner spanner = options.getService(); - InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { + InstanceAdminClient instanceAdminClient = spanner.createInstanceAdminClient()) { instanceAdminClient.listInstances(projectName); // update mutableCredentials now to use an invalid credentials From 90c6b7bea88982cdeb0ec943d807b41b848dcef9 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Fri, 6 Mar 2026 09:41:49 -0500 Subject: [PATCH 47/52] moved IT test to correct package --- .../spanner/{connection => }/it/ITMutableCredentialsTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename google-cloud-spanner/src/test/java/com/google/cloud/spanner/{connection => }/it/ITMutableCredentialsTest.java (97%) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITMutableCredentialsTest.java similarity index 97% rename from google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java rename to google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITMutableCredentialsTest.java index e0cebf064e..d2bb02af36 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/connection/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITMutableCredentialsTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.google.cloud.spanner.connection.it; +package com.google.cloud.spanner.it; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; @@ -42,7 +42,7 @@ public class ITMutableCredentialsTest { private static final String INVALID_CERT_PATH = - "/com/google/cloud/spanner/connection/test-key.json"; + "/com/google/cloud/spanner/connection/test-key.json"; @Test public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { From ef64649e0ab947c568c1956e56f4b265082a7314 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Fri, 6 Mar 2026 14:45:19 +0000 Subject: [PATCH 48/52] chore: generate libraries at Fri Mar 6 14:42:23 UTC 2026 --- .../com/google/cloud/spanner/it/ITMutableCredentialsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITMutableCredentialsTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITMutableCredentialsTest.java index d2bb02af36..469401f09c 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITMutableCredentialsTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/it/ITMutableCredentialsTest.java @@ -42,7 +42,7 @@ public class ITMutableCredentialsTest { private static final String INVALID_CERT_PATH = - "/com/google/cloud/spanner/connection/test-key.json"; + "/com/google/cloud/spanner/connection/test-key.json"; @Test public void testMutableCredentialsUpdateAuthorizationForRunningClient() throws IOException { From 4119282e88d27d4ff0089f549e243d905d3d4840 Mon Sep 17 00:00:00 2001 From: ldetmer Date: Fri, 6 Mar 2026 10:12:45 -0500 Subject: [PATCH 49/52] remove spanner version in samples --- samples/snippets/pom.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/samples/snippets/pom.xml b/samples/snippets/pom.xml index a6e976dfe1..23c24291e2 100644 --- a/samples/snippets/pom.xml +++ b/samples/snippets/pom.xml @@ -45,7 +45,6 @@ com.google.cloud google-cloud-spanner - 6.111.1 From 325db5d27125751f4b1f719e173597c23286ac2e Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Fri, 6 Mar 2026 15:16:01 +0000 Subject: [PATCH 50/52] chore: generate libraries at Fri Mar 6 15:13:14 UTC 2026 --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 9a63ba392d..f65f5c0e34 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,6 @@ If you are using Maven with [BOM][libraries-bom], add this to your pom.xml file: com.google.cloud google-cloud-spanner - 6.111.1 ``` From 960e41ee078c74601926476b537e59e78edb800d Mon Sep 17 00:00:00 2001 From: ldetmer Date: Fri, 6 Mar 2026 10:23:18 -0500 Subject: [PATCH 51/52] remove MutableCredentials to separate PR --- .../cloud/spanner/MutableCredentials.java | 2 +- samples/install-without-bom/pom.xml | 2 +- .../spanner/MutableCredentialsExample.java | 98 ------------------- 3 files changed, 2 insertions(+), 100 deletions(-) delete mode 100644 samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MutableCredentials.java index 670a6cdc0e..694899cd4d 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MutableCredentials.java @@ -45,7 +45,7 @@ public class MutableCredentials extends Credentials { private final Set scopes; /** - * Creates a MutableCredentials instance with default spanner scopes {@link SpannerOptions.SCOPES} + * Creates a MutableCredentials instance with default spanner scopes. */ public MutableCredentials(ServiceAccountCredentials credentials) { this(credentials, SpannerOptions.SCOPES); diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index 6334cf05ef..a504dc626b 100644 --- a/samples/install-without-bom/pom.xml +++ b/samples/install-without-bom/pom.xml @@ -33,7 +33,7 @@ com.google.cloud google-cloud-spanner - 6.111.1 + 6.110.0 diff --git a/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java deleted file mode 100644 index 33b2ace4c2..0000000000 --- a/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2026 Google LLC - * - * Licensed 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 com.example.spanner; - -// [START spanner_mutable_credentials] - -import com.google.auth.oauth2.ServiceAccountCredentials; -import com.google.cloud.spanner.Spanner; -import com.google.cloud.spanner.SpannerOptions; -import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; -import com.google.cloud.spanner.connection.MutableCredentials; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.attribute.FileTime; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.TimeUnit; - -public class MutableCredentialsExample { - - static void createClientWithMutableCredentials() throws IOException { - final String credentialsPath = "location_of_service_account_credential_json"; - Path path = Paths.get(credentialsPath); - // Use an array to hold the mutable lastModifiedTime so it can be accessed in the lambda - FileTime[] lastModifiedTime = new FileTime[] {Files.getLastModifiedTime(path)}; - - // 1 - create service account credentials - ServiceAccountCredentials serviceAccountCredentials; - try (FileInputStream is = new FileInputStream(credentialsPath)) { - serviceAccountCredentials = ServiceAccountCredentials.fromStream(is); - } - - // 2 - wrap credentials from step 1 in a MutableCredentials instance - MutableCredentials mutableCredentials = new MutableCredentials(serviceAccountCredentials); - - // 3 - set credentials on your SpannerOptions builder to your mutableCredentials - SpannerOptions options = SpannerOptions.newBuilder().setCredentials(mutableCredentials).build(); - - // 4 - include logic for when/how to update your mutableCredentials - // In this example we'll use a SchedulerExecutorService to periodically check for updates - ThreadFactory daemonThreadFactory = - runnable -> { - Thread thread = new Thread(runnable, "spanner-mutable-credentials-rotator"); - thread.setDaemon(true); - return thread; - }; - ScheduledExecutorService executorService = - Executors.newSingleThreadScheduledExecutor(daemonThreadFactory); - executorService.scheduleAtFixedRate( - () -> { - try { - FileTime currentModifiedTime = Files.getLastModifiedTime(path); - if (currentModifiedTime.compareTo(lastModifiedTime[0]) > 0) { - lastModifiedTime[0] = currentModifiedTime; - try (FileInputStream is = new FileInputStream(credentialsPath)) { - ServiceAccountCredentials credentials = ServiceAccountCredentials.fromStream(is); - mutableCredentials.updateCredentials(credentials); - } - } - } catch (IOException e) { - System.err.println("Failed to check or update credentials: " + e.getMessage()); - } - }, - 15, - 15, - TimeUnit.MINUTES); - - // 5. Use the client - try (Spanner spanner = options.getService(); - DatabaseAdminClient databaseAdminClient = spanner.createDatabaseAdminClient()) { - // Perform operations... - // long running client operations will always use the latest credentials wrapped in - // mutableCredentials - } finally { - // Ensure the executor is shut down when the application exits or the client is closed - executorService.shutdown(); - } - } -} -// [END spanner_mutable_credentials] From de735adf7a1338152340d3c3ead918c41100e3f3 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Fri, 6 Mar 2026 15:26:38 +0000 Subject: [PATCH 52/52] chore: generate libraries at Fri Mar 6 15:23:50 UTC 2026 --- README.md | 3 +-- .../java/com/google/cloud/spanner/MutableCredentials.java | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f65f5c0e34..dbcde8f897 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ If you are using Maven without the BOM, add this to your dependencies: com.google.cloud google-cloud-spanner - 6.111.1 + 6.110.0 ``` @@ -381,7 +381,6 @@ Samples are in the [`samples/`](https://github.com/googleapis/java-spanner/tree/ | List Databases Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/ListDatabasesSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/ListDatabasesSample.java) | | List Instance Config Operations Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigOperationsSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigOperationsSample.java) | | List Instance Configs Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigsSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/ListInstanceConfigsSample.java) | -| Mutable Credentials Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.java) | | Pg Alter Sequence Sample | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgAlterSequenceSample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgAlterSequenceSample.java) | | Pg Async Query To List Async Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgAsyncQueryToListAsyncExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgAsyncQueryToListAsyncExample.java) | | Pg Async Runner Example | [source code](https://github.com/googleapis/java-spanner/blob/main/samples/snippets/src/main/java/com/example/spanner/PgAsyncRunnerExample.java) | [![Open in Cloud Shell][shell_img]](https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/googleapis/java-spanner&page=editor&open_in_editor=samples/snippets/src/main/java/com/example/spanner/PgAsyncRunnerExample.java) | diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MutableCredentials.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MutableCredentials.java index 694899cd4d..013805671c 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MutableCredentials.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/MutableCredentials.java @@ -44,9 +44,7 @@ public class MutableCredentials extends Credentials { private volatile ServiceAccountCredentials delegate; private final Set scopes; - /** - * Creates a MutableCredentials instance with default spanner scopes. - */ + /** Creates a MutableCredentials instance with default spanner scopes. */ public MutableCredentials(ServiceAccountCredentials credentials) { this(credentials, SpannerOptions.SCOPES); }