diff --git a/README.md b/README.md index dbcde8f8978..f65f5c0e34e 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 ``` @@ -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) | diff --git a/samples/install-without-bom/pom.xml b/samples/install-without-bom/pom.xml index a504dc626be..6334cf05ef9 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 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 00000000000..d0ff08eea6f --- /dev/null +++ b/samples/snippets/src/main/java/com/example/spanner/MutableCredentialsExample.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.example.spanner; + +// [START spanner_mutable_credentials] + +import com.google.auth.oauth2.ServiceAccountCredentials; +import com.google.cloud.spanner.MutableCredentials; +import com.google.cloud.spanner.Spanner; +import com.google.cloud.spanner.SpannerOptions; +import com.google.cloud.spanner.admin.database.v1.DatabaseAdminClient; +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]