diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index 0bdb8c5e..5c178777 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -246,13 +246,17 @@ jobs:
secrets: |-
ALLOYDB_INSTANCE_NAME:${{ secrets.GOOGLE_CLOUD_PROJECT }}/ALLOYDB_INSTANCE_URI
ALLOYDB_CLUSTER_PASS:${{ secrets.GOOGLE_CLOUD_PROJECT }}/ALLOYDB_CLUSTER_PASS
+ ALLOYDB_IAM_USER:${{ secrets.GOOGLE_CLOUD_PROJECT }}/ALLOYDB_JAVA_IAM_USER
+ ALLOYDB_INSTANCE_IP:${{ secrets.GOOGLE_CLOUD_PROJECT }}/ALLOYDB_INSTANCE_IP
- name: Run tests
env:
ALLOYDB_DB: 'postgres'
ALLOYDB_USER: 'postgres'
+ ALLOYDB_IAM_USER: '${{ steps.secrets.outputs.ALLOYDB_IAM_USER }}'
ALLOYDB_PASS: '${{ steps.secrets.outputs.ALLOYDB_CLUSTER_PASS }}'
ALLOYDB_INSTANCE_NAME: '${{ steps.secrets.outputs.ALLOYDB_INSTANCE_NAME }}'
+ ALLOYDB_INSTANCE_IP: '${{ steps.secrets.outputs.ALLOYDB_INSTANCE_IP }}'
JOB_TYPE: integration
run: .kokoro/build.sh
shell: bash
diff --git a/alloydb-jdbc-connector/pom.xml b/alloydb-jdbc-connector/pom.xml
index 6e9a44cb..40ae710d 100644
--- a/alloydb-jdbc-connector/pom.xml
+++ b/alloydb-jdbc-connector/pom.xml
@@ -160,5 +160,11 @@
test
+
+ com.google.auth
+ google-auth-library-oauth2-http
+ test
+
+
diff --git a/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/AlloyDBDirectPathDataSource.java b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/AlloyDBDirectPathDataSource.java
new file mode 100644
index 00000000..9d77d610
--- /dev/null
+++ b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/AlloyDBDirectPathDataSource.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 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
+ *
+ * https://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.alloydb;
+
+import com.google.auth.oauth2.AccessToken;
+import com.google.auth.oauth2.GoogleCredentials;
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+import java.io.IOException;
+
+/**
+ * The AlloyDB direct path data source demonstrates how to enable Auto IAM AuthN without using the
+ * AlloyDB Java Connector. To do that, the code here overrides the HikariDataSource's getPassword
+ * method to dynamically retrieve an OAuth2 token. This means every new connection made by the
+ * connection pool will get a fresh OAuth2 token without relying on the AlloyDB Java Connector. For
+ * details on how this is used, see
+ * AlloyDbJdbcDirectPathDataSourceFactory
+ */
+public class AlloyDBDirectPathDataSource extends HikariDataSource {
+
+ public AlloyDBDirectPathDataSource(HikariConfig configuration) {
+ super(configuration);
+ }
+
+ @Override
+ public String getPassword() {
+ try {
+ GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
+ AccessToken accessToken = credentials.getAccessToken();
+ return accessToken.getTokenValue();
+ } catch (IOException e) {
+ throw new RuntimeException("failed to retrieve OAuth2 access token", e);
+ }
+ }
+}
diff --git a/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/AlloyDbJdbcDirectPathDataSourceFactory.java b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/AlloyDbJdbcDirectPathDataSourceFactory.java
new file mode 100644
index 00000000..6ffb291f
--- /dev/null
+++ b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/AlloyDbJdbcDirectPathDataSourceFactory.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2023 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
+ *
+ * https://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.alloydb;
+
+// [START alloydb_hikaricp_connect_iam_authn_direct]
+import com.zaxxer.hikari.HikariConfig;
+import com.zaxxer.hikari.HikariDataSource;
+
+public class AlloyDbJdbcDirectPathDataSourceFactory {
+
+ public static final String ALLOYDB_USER = System.getenv("ALLOYDB_IAM_USER");
+ public static final String ALLOYDB_INSTANCE_IP = System.getenv("ALLOYDB_INSTANCE_IP");
+
+ static HikariDataSource createDataSource() {
+ HikariConfig config = new HikariConfig();
+
+ config.setJdbcUrl(String.format("jdbc:postgresql://%s/postgres", ALLOYDB_INSTANCE_IP));
+ config.setUsername(ALLOYDB_USER); // e.g., "postgres"
+ // No need to set password, as that's dynamically configured in the
+ // AlloyDBDirectPathDataSource with a refreshed OAuth2 token.
+
+ return new AlloyDBDirectPathDataSource(config);
+ }
+}
+// [END alloydb_hikaricp_connect_iam_authn_direct]
diff --git a/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ITDirectPathTest.java b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ITDirectPathTest.java
new file mode 100644
index 00000000..03204744
--- /dev/null
+++ b/alloydb-jdbc-connector/src/test/java/com/google/cloud/alloydb/ITDirectPathTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2023 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
+ *
+ * https://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.alloydb;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.BoundType;
+import com.google.common.collect.Range;
+import com.zaxxer.hikari.HikariDataSource;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ITDirectPathTest {
+
+ private HikariDataSource dataSource;
+
+ @Before
+ public void setUp() {
+ this.dataSource = AlloyDbJdbcDirectPathDataSourceFactory.createDataSource();
+ }
+
+ @After
+ public void tearDown() {
+ if (this.dataSource != null) {
+ dataSource.close();
+ }
+ }
+
+ @Test
+ public void testConnect() throws SQLException {
+ try (Connection connection = dataSource.getConnection()) {
+ try (PreparedStatement statement = connection.prepareStatement("SELECT NOW()")) {
+ ResultSet resultSet = statement.executeQuery();
+ resultSet.next();
+ Timestamp timestamp = resultSet.getTimestamp(1);
+ Instant databaseInstant = timestamp.toInstant();
+
+ Instant now = Instant.now();
+ assertThat(databaseInstant)
+ .isIn(
+ Range.range(
+ now.minus(1, ChronoUnit.MINUTES),
+ BoundType.CLOSED,
+ now.plus(1, ChronoUnit.MINUTES),
+ BoundType.CLOSED));
+ }
+ }
+ }
+}