From ee7c46ce64578b4a3c48c29304207be6efe1186c Mon Sep 17 00:00:00 2001 From: ggivo Date: Tue, 21 Jan 2025 15:11:27 +0200 Subject: [PATCH] Lettuce EntraID integration tests (#3133) * EntraId integration test - integrate with cae infra - Read test endpoint's configuration from endpoint's json - Invoke only EntraId related test: mvn integration-test -Pentraid-it * Load EntraIdTest from environment variables directly remove dotenv dependency * Replace whoami check with get/set to not depend on username * Remove deprecated dnsResolver * use mset to test default connection, and ping for individual node connections * Add EntraId managed identity integration test --- pom.xml | 38 ++- src/test/java/io/lettuce/TestTags.java | 5 + .../authx/EntraIdClusterIntegrationTests.java | 140 +++++++++++ .../authx/EntraIdIntegrationTests.java | 97 ++++---- ...ntraIdManagedIdentityIntegrationTests.java | 105 ++++++++ .../io/lettuce/authx/EntraIdTestContext.java | 84 ++----- .../java/io/lettuce/test/env/Endpoints.java | 235 ++++++++++++++++++ 7 files changed, 584 insertions(+), 120 deletions(-) create mode 100644 src/test/java/io/lettuce/authx/EntraIdClusterIntegrationTests.java create mode 100644 src/test/java/io/lettuce/authx/EntraIdManagedIdentityIntegrationTests.java create mode 100644 src/test/java/io/lettuce/test/env/Endpoints.java diff --git a/pom.xml b/pom.xml index ef291c5d5..6c0843fc9 100644 --- a/pom.xml +++ b/pom.xml @@ -189,12 +189,6 @@ 0.1.1-beta1 test - - io.github.cdimascio - dotenv-java - 2.2.0 - test - @@ -1044,6 +1038,38 @@ ci + + entraid-it + + + + maven-surefire-plugin + + true + + + + maven-failsafe-plugin + + entraid + false + + **/*IntegrationTests + + + + + integration-test + + integration-test + verify + + + + + + + diff --git a/src/test/java/io/lettuce/TestTags.java b/src/test/java/io/lettuce/TestTags.java index 68a3434e0..06820f300 100644 --- a/src/test/java/io/lettuce/TestTags.java +++ b/src/test/java/io/lettuce/TestTags.java @@ -29,4 +29,9 @@ public class TestTags { */ public static final String API_GENERATOR = "api_generator"; + /** + * Tag for EntraId integration tests (require a running environment with configured microsoft EntraId authentication) + */ + public static final String ENTRA_ID = "entraid"; + } diff --git a/src/test/java/io/lettuce/authx/EntraIdClusterIntegrationTests.java b/src/test/java/io/lettuce/authx/EntraIdClusterIntegrationTests.java new file mode 100644 index 000000000..e8b3873de --- /dev/null +++ b/src/test/java/io/lettuce/authx/EntraIdClusterIntegrationTests.java @@ -0,0 +1,140 @@ +package io.lettuce.authx; + +import io.lettuce.core.ClientOptions; +import io.lettuce.core.RedisURI; +import io.lettuce.core.SocketOptions; +import io.lettuce.core.TimeoutOptions; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.cluster.ClusterClientOptions; +import io.lettuce.core.cluster.ClusterTopologyRefreshOptions; +import io.lettuce.core.cluster.RedisClusterClient; +import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; +import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands; +import io.lettuce.core.resource.ClientResources; +import io.lettuce.core.resource.DnsResolver; +import io.lettuce.test.env.Endpoints; +import io.lettuce.test.env.Endpoints.Endpoint; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import redis.clients.authentication.core.TokenAuthConfig; +import redis.clients.authentication.entraid.EntraIDTokenAuthConfigBuilder; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import static io.lettuce.TestTags.ENTRA_ID; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +@Tag(ENTRA_ID) +public class EntraIdClusterIntegrationTests { + + private static final EntraIdTestContext testCtx = EntraIdTestContext.DEFAULT; + + private static TokenBasedRedisCredentialsProvider credentialsProvider; + + private static RedisClusterClient clusterClient; + + private static ClientResources resources; + + private static Endpoint cluster; + + @BeforeAll + public static void setup() { + cluster = Endpoints.DEFAULT.getEndpoint("cluster-entraid-acl"); + if (cluster != null) { + Assumptions.assumeTrue(testCtx.getClientId() != null && testCtx.getClientSecret() != null, + "Skipping EntraID tests. Azure AD credentials not provided!"); + + // Configure timeout options to assure fast test failover + ClusterClientOptions clientOptions = ClusterClientOptions.builder() + .socketOptions(SocketOptions.builder().connectTimeout(Duration.ofSeconds(1)).build()) + .timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(1))) + // enable re-authentication + .reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS).build(); + + TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder().clientId(testCtx.getClientId()) + .secret(testCtx.getClientSecret()).authority(testCtx.getAuthority()).scopes(testCtx.getRedisScopes()) + .expirationRefreshRatio(0.0000001F).build(); + + credentialsProvider = TokenBasedRedisCredentialsProvider.create(tokenAuthConfig); + + resources = ClientResources.builder() + // .dnsResolver(DnsResolver.jvmDefault()) + .build(); + + List seedURI = new ArrayList<>(); + for (String addr : cluster.getRawEndpoints().get(0).getAddr()) { + seedURI.add(RedisURI.builder().withAuthentication(credentialsProvider).withHost(addr) + .withPort(cluster.getRawEndpoints().get(0).getPort()).build()); + } + + clusterClient = RedisClusterClient.create(resources, seedURI); + clusterClient.setOptions(clientOptions); + } + } + + @AfterAll + public static void cleanup() { + if (credentialsProvider != null) { + credentialsProvider.close(); + } + if (resources != null) { + resources.shutdown(); + } + } + + // T.1.1 + // Verify authentication using Azure AD with service principals using Redis Cluster Client + @Test + public void clusterWithSecret_azureServicePrincipalIntegrationTest() throws ExecutionException, InterruptedException { + assumeTrue(cluster != null, "Skipping EntraID tests. Redis host with enabled EntraId not provided!"); + + try (StatefulRedisClusterConnection defaultConnection = clusterClient.connect()) { + RedisAdvancedClusterCommands sync = defaultConnection.sync(); + String keyPrefix = UUID.randomUUID().toString(); + Map mset = prepareMset(keyPrefix); + + assertThat(sync.mset(mset)).isEqualTo("OK"); + + for (String mykey : mset.keySet()) { + assertThat(defaultConnection.sync().get(mykey)).isEqualTo("value-" + mykey); + assertThat(defaultConnection.async().get(mykey).get()).isEqualTo("value-" + mykey); + assertThat(defaultConnection.reactive().get(mykey).block()).isEqualTo("value-" + mykey); + } + assertThat(sync.del(mset.keySet().toArray(new String[0]))).isEqualTo(mset.keySet().size()); + + // Test connections to each node + defaultConnection.getPartitions().forEach((partition) -> { + StatefulRedisConnection nodeConnection = defaultConnection.getConnection(partition.getNodeId()); + assertThat(nodeConnection.sync().ping()).isEqualTo("PONG"); + }); + + defaultConnection.getPartitions().forEach((partition) -> { + StatefulRedisConnection nodeConnection = defaultConnection.getConnection(partition.getUri().getHost(), + partition.getUri().getPort()); + assertThat(nodeConnection.sync().ping()).isEqualTo("PONG"); + }); + } + } + + Map prepareMset(String keyPrefix) { + Map mset = new HashMap<>(); + for (char c = 'a'; c <= 'z'; c++) { + String keySuffix = new String(new char[] { c, c, c }); // Generates "aaa", "bbb", etc. + String key = String.format("%s-{%s}", keyPrefix, keySuffix); + mset.put(key, "value-" + key); + } + return mset; + } + +} diff --git a/src/test/java/io/lettuce/authx/EntraIdIntegrationTests.java b/src/test/java/io/lettuce/authx/EntraIdIntegrationTests.java index a4eba6704..ba2f08d76 100644 --- a/src/test/java/io/lettuce/authx/EntraIdIntegrationTests.java +++ b/src/test/java/io/lettuce/authx/EntraIdIntegrationTests.java @@ -9,67 +9,69 @@ import io.lettuce.core.TransactionResult; import io.lettuce.core.api.StatefulRedisConnection; import io.lettuce.core.api.async.RedisAsyncCommands; +import io.lettuce.core.api.sync.RedisCommands; import io.lettuce.core.cluster.ClusterClientOptions; -import io.lettuce.core.cluster.RedisClusterClient; -import io.lettuce.core.cluster.api.StatefulRedisClusterConnection; import io.lettuce.core.pubsub.StatefulRedisPubSubConnection; import io.lettuce.core.support.PubSubTestListener; import io.lettuce.test.Wait; +import io.lettuce.test.env.Endpoints; +import io.lettuce.test.env.Endpoints.Endpoint; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; import redis.clients.authentication.core.TokenAuthConfig; import redis.clients.authentication.entraid.EntraIDTokenAuthConfigBuilder; import java.time.Duration; +import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import static io.lettuce.TestTags.ENTRA_ID; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; +@Tag(ENTRA_ID) public class EntraIdIntegrationTests { - private static final EntraIdTestContext testCtx = EntraIdTestContext.DEFAULT;; - - private static ClusterClientOptions clientOptions; + private static final EntraIdTestContext testCtx = EntraIdTestContext.DEFAULT; private static TokenBasedRedisCredentialsProvider credentialsProvider; private static RedisClient client; - private static RedisClusterClient clusterClient; + private static Endpoint standalone; @BeforeAll public static void setup() { - Assumptions.assumeTrue(testCtx.host() != null && !testCtx.host().isEmpty(), - "Skipping EntraID tests. Redis host with enabled EntraId not provided!"); - - // Configure timeout options to assure fast test failover - clientOptions = ClusterClientOptions.builder() - .socketOptions(SocketOptions.builder().connectTimeout(Duration.ofSeconds(1)).build()) - .timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(1))) - .reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS).build(); - - TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder().clientId(testCtx.getClientId()) - .secret(testCtx.getClientSecret()).authority(testCtx.getAuthority()).scopes(testCtx.getRedisScopes()) - .expirationRefreshRatio(0.0000001F).build(); - - credentialsProvider = TokenBasedRedisCredentialsProvider.create(tokenAuthConfig); - - RedisURI uri = RedisURI.builder().withHost(testCtx.host()).withPort(testCtx.port()) - .withAuthentication(credentialsProvider).build(); + standalone = Endpoints.DEFAULT.getEndpoint("standalone-entraid-acl"); + if (standalone != null) { + Assumptions.assumeTrue(testCtx.getClientId() != null && testCtx.getClientSecret() != null, + "Skipping EntraID tests. Azure AD credentials not provided!"); + // Configure timeout options to assure fast test failover + ClusterClientOptions clientOptions = ClusterClientOptions.builder() + .socketOptions(SocketOptions.builder().connectTimeout(Duration.ofSeconds(1)).build()) + .timeoutOptions(TimeoutOptions.enabled(Duration.ofSeconds(1))) + // enable re-authentication + .reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS).build(); + + TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder().clientId(testCtx.getClientId()) + .secret(testCtx.getClientSecret()).authority(testCtx.getAuthority()).scopes(testCtx.getRedisScopes()) + .expirationRefreshRatio(0.0000001F).build(); + + credentialsProvider = TokenBasedRedisCredentialsProvider.create(tokenAuthConfig); + + RedisURI uri = RedisURI.create((standalone.getEndpoints().get(0))); + uri.setCredentialsProvider(credentialsProvider); + client = RedisClient.create(uri); + client.setOptions(clientOptions); - client = RedisClient.create(uri); - client.setOptions(clientOptions); - - RedisURI clusterUri = RedisURI.builder().withHost(testCtx.clusterHost().get(0)).withPort(testCtx.clusterPort()) - .withAuthentication(credentialsProvider).build(); - clusterClient = RedisClusterClient.create(clusterUri); - clusterClient.setOptions(clientOptions); + } } @AfterAll @@ -83,28 +85,16 @@ public static void cleanup() { // Verify authentication using Azure AD with service principals using Redis Standalone client @Test public void standaloneWithSecret_azureServicePrincipalIntegrationTest() throws ExecutionException, InterruptedException { - try (StatefulRedisConnection connection = client.connect()) { - assertThat(connection.sync().aclWhoami()).isEqualTo(testCtx.getSpOID()); - assertThat(connection.async().aclWhoami().get()).isEqualTo(testCtx.getSpOID()); - assertThat(connection.reactive().aclWhoami().block()).isEqualTo(testCtx.getSpOID()); - } - } - - // T.1.1 - // Verify authentication using Azure AD with service principals using Redis Cluster Client - @Test - public void clusterWithSecret_azureServicePrincipalIntegrationTest() throws ExecutionException, InterruptedException { - - try (StatefulRedisClusterConnection connection = clusterClient.connect()) { - assertThat(connection.sync().aclWhoami()).isEqualTo(testCtx.getSpOID()); - assertThat(connection.async().aclWhoami().get()).isEqualTo(testCtx.getSpOID()); - assertThat(connection.reactive().aclWhoami().block()).isEqualTo(testCtx.getSpOID()); + assumeTrue(standalone != null, "Skipping EntraID tests. Redis host with enabled EntraId not provided!"); - connection.getPartitions().forEach((partition) -> { - try (StatefulRedisConnection nodeConnection = connection.getConnection(partition.getNodeId())) { - assertThat(nodeConnection.sync().aclWhoami()).isEqualTo(testCtx.getSpOID()); - } - }); + try (StatefulRedisConnection connection = client.connect()) { + RedisCommands sync = connection.sync(); + String key = UUID.randomUUID().toString(); + sync.set(key, "value"); + assertThat(connection.sync().get(key)).isEqualTo("value"); + assertThat(connection.async().get(key).get()).isEqualTo("value"); + assertThat(connection.reactive().get(key).block()).isEqualTo("value"); + sync.del(key); } } @@ -112,6 +102,7 @@ public void clusterWithSecret_azureServicePrincipalIntegrationTest() throws Exec // Test that the Redis client is not blocked/interrupted during token renewal. @Test public void renewalDuringOperationsTest() throws InterruptedException { + assumeTrue(standalone != null, "Skipping EntraID tests. Redis host with enabled EntraId not provided!"); // Counter to track the number of command cycles AtomicInteger commandCycleCount = new AtomicInteger(0); @@ -162,6 +153,8 @@ public void renewalDuringOperationsTest() throws InterruptedException { // Test basic Pub/Sub functionality is not blocked/interrupted during token renewal. @Test public void renewalDuringPubSubOperationsTest() throws InterruptedException { + assumeTrue(standalone != null, "Skipping EntraID tests. Redis host with enabled EntraId not provided!"); + try (StatefulRedisPubSubConnection connectionPubSub = client.connectPubSub(); StatefulRedisPubSubConnection connectionPubSub1 = client.connectPubSub()) { @@ -183,7 +176,7 @@ public void renewalDuringPubSubOperationsTest() throws InterruptedException { latch.countDown(); }); - assertThat(latch.await(1, TimeUnit.SECONDS)).isTrue(); // Wait for at least 10 token renewals + assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue(); // Wait for at least 10 token renewals pubsubThread.join(); // Wait for the pub/sub thread to finish // Verify that all messages were received diff --git a/src/test/java/io/lettuce/authx/EntraIdManagedIdentityIntegrationTests.java b/src/test/java/io/lettuce/authx/EntraIdManagedIdentityIntegrationTests.java new file mode 100644 index 000000000..16cfea4a6 --- /dev/null +++ b/src/test/java/io/lettuce/authx/EntraIdManagedIdentityIntegrationTests.java @@ -0,0 +1,105 @@ +package io.lettuce.authx; + +import io.lettuce.core.ClientOptions; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisURI; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.sync.RedisCommands; +import io.lettuce.core.cluster.ClusterClientOptions; +import io.lettuce.test.env.Endpoints; +import io.lettuce.test.env.Endpoints.Endpoint; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import redis.clients.authentication.core.TokenAuthConfig; +import redis.clients.authentication.entraid.EntraIDTokenAuthConfigBuilder; +import redis.clients.authentication.entraid.ManagedIdentityInfo; + +import java.util.Collections; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + +import static io.lettuce.TestTags.ENTRA_ID; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +@Tag(ENTRA_ID) +public class EntraIdManagedIdentityIntegrationTests { + + private static final EntraIdTestContext testCtx = EntraIdTestContext.DEFAULT; + + private static RedisClient client; + + private static Endpoint standalone; + + private static Set managedIdentityAudience = Collections.singleton("https://redis.azure.com"); + + @BeforeAll + public static void setup() { + standalone = Endpoints.DEFAULT.getEndpoint("standalone-entraid-acl"); + assumeTrue(standalone != null, "Skipping test because no Redis endpoint is configured!"); + } + + @Test + public void withUserAssignedId_azureManagedIdentityIntegrationTest() throws ExecutionException, InterruptedException { + + // enable re-authentication + ClusterClientOptions clientOptions = ClusterClientOptions.builder() + .reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS).build(); + + TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder() + .userAssignedManagedIdentity(ManagedIdentityInfo.UserManagedIdentityType.OBJECT_ID, + testCtx.getUserAssignedManagedIdentity()) + .scopes(managedIdentityAudience).build(); + + try (TokenBasedRedisCredentialsProvider credentialsProvider = TokenBasedRedisCredentialsProvider + .create(tokenAuthConfig)) { + + RedisURI uri = RedisURI.create((standalone.getEndpoints().get(0))); + uri.setCredentialsProvider(credentialsProvider); + client = RedisClient.create(uri); + client.setOptions(clientOptions); + + try (StatefulRedisConnection connection = client.connect()) { + RedisCommands sync = connection.sync(); + String key = UUID.randomUUID().toString(); + sync.set(key, "value"); + assertThat(connection.sync().get(key)).isEqualTo("value"); + assertThat(connection.async().get(key).get()).isEqualTo("value"); + assertThat(connection.reactive().get(key).block()).isEqualTo("value"); + sync.del(key); + } + } + } + + @Test + public void withSystemAssignedId_azureManagedIdentityIntegrationTest() throws ExecutionException, InterruptedException { + // enable re-authentication + ClusterClientOptions clientOptions = ClusterClientOptions.builder() + .reauthenticateBehavior(ClientOptions.ReauthenticateBehavior.ON_NEW_CREDENTIALS).build(); + + TokenAuthConfig tokenAuthConfig = EntraIDTokenAuthConfigBuilder.builder().systemAssignedManagedIdentity() + .scopes(managedIdentityAudience).build(); + + try (TokenBasedRedisCredentialsProvider credentialsProvider = TokenBasedRedisCredentialsProvider + .create(tokenAuthConfig)) { + + RedisURI uri = RedisURI.create((standalone.getEndpoints().get(0))); + uri.setCredentialsProvider(credentialsProvider); + client = RedisClient.create(uri); + client.setOptions(clientOptions); + + try (StatefulRedisConnection connection = client.connect()) { + RedisCommands sync = connection.sync(); + String key = UUID.randomUUID().toString(); + sync.set(key, "value"); + assertThat(connection.sync().get(key)).isEqualTo("value"); + assertThat(connection.async().get(key).get()).isEqualTo("value"); + assertThat(connection.reactive().get(key).block()).isEqualTo("value"); + sync.del(key); + } + } + } + +} diff --git a/src/test/java/io/lettuce/authx/EntraIdTestContext.java b/src/test/java/io/lettuce/authx/EntraIdTestContext.java index 7abfac0fe..551dc0358 100644 --- a/src/test/java/io/lettuce/authx/EntraIdTestContext.java +++ b/src/test/java/io/lettuce/authx/EntraIdTestContext.java @@ -1,10 +1,7 @@ package io.lettuce.authx; -import io.github.cdimascio.dotenv.Dotenv; - import java.util.Arrays; import java.util.HashSet; -import java.util.List; import java.util.Set; public class EntraIdTestContext { @@ -13,21 +10,11 @@ public class EntraIdTestContext { private static final String AZURE_CLIENT_SECRET = "AZURE_CLIENT_SECRET"; - private static final String AZURE_SP_OID = "AZURE_SP_OID"; - private static final String AZURE_AUTHORITY = "AZURE_AUTHORITY"; private static final String AZURE_REDIS_SCOPES = "AZURE_REDIS_SCOPES"; - private static final String REDIS_AZURE_HOST = "REDIS_AZURE_HOST"; - - private static final String REDIS_AZURE_PORT = "REDIS_AZURE_PORT"; - - private static final String REDIS_AZURE_CLUSTER_HOST = "REDIS_AZURE_CLUSTER_HOST"; - - private static final String REDIS_AZURE_CLUSTER_PORT = "REDIS_AZURE_CLUSTER_PORT"; - - private static final String REDIS_AZURE_DB = "REDIS_AZURE_DB"; + private static final String AZURE_USER_ASSIGNED_MANAGED_ID = "AZURE_USER_ASSIGNED_MANAGED_ID"; private final String clientId; @@ -35,67 +22,32 @@ public class EntraIdTestContext { private final String clientSecret; - private final String spOID; - - private final Set redisScopes; + private Set redisScopes; - private final String redisHost; - - private final int redisPort; - - private final List redisClusterHost; - - private final int redisClusterPort; - - private static Dotenv dotenv; - static { - dotenv = Dotenv.configure().directory("src/test/resources").filename(".env.entraid").load(); - } + private String userAssignedManagedIdentity; public static final EntraIdTestContext DEFAULT = new EntraIdTestContext(); private EntraIdTestContext() { - // Using Dotenv directly here - clientId = dotenv.get(AZURE_CLIENT_ID, ""); - clientSecret = dotenv.get(AZURE_CLIENT_SECRET, ""); - spOID = dotenv.get(AZURE_SP_OID, ""); - authority = dotenv.get(AZURE_AUTHORITY, "https://login.microsoftonline.com/your-tenant-id"); - redisHost = dotenv.get(REDIS_AZURE_HOST); - redisPort = Integer.parseInt(dotenv.get(REDIS_AZURE_PORT, "6379")); - redisClusterHost = Arrays.asList(dotenv.get(REDIS_AZURE_CLUSTER_HOST, "").split(",")); - redisClusterPort = Integer.parseInt(dotenv.get(REDIS_AZURE_CLUSTER_PORT, "6379")); - String redisScopesEnv = dotenv.get(AZURE_REDIS_SCOPES, "https://redis.azure.com/.default"); - if (redisScopesEnv != null && !redisScopesEnv.isEmpty()) { - this.redisScopes = new HashSet<>(Arrays.asList(redisScopesEnv.split(";"))); - } else { - this.redisScopes = new HashSet<>(); - } - } - - public String host() { - return redisHost; - } - - public int port() { - return redisPort; - } - - public List clusterHost() { - return redisClusterHost; + clientId = System.getenv(AZURE_CLIENT_ID); + authority = System.getenv(AZURE_AUTHORITY); + clientSecret = System.getenv(AZURE_CLIENT_SECRET); + this.userAssignedManagedIdentity = System.getenv(AZURE_USER_ASSIGNED_MANAGED_ID); } - public int clusterPort() { - return redisClusterPort; + public EntraIdTestContext(String clientId, String authority, String clientSecret, Set redisScopes, + String userAssignedManagedIdentity) { + this.clientId = clientId; + this.authority = authority; + this.clientSecret = clientSecret; + this.redisScopes = redisScopes; + this.userAssignedManagedIdentity = userAssignedManagedIdentity; } public String getClientId() { return clientId; } - public String getSpOID() { - return spOID; - } - public String getAuthority() { return authority; } @@ -105,7 +57,15 @@ public String getClientSecret() { } public Set getRedisScopes() { + if (redisScopes == null) { + String redisScopesEnv = System.getenv(AZURE_REDIS_SCOPES); + this.redisScopes = new HashSet<>(Arrays.asList(redisScopesEnv.split(";"))); + } return redisScopes; } + public String getUserAssignedManagedIdentity() { + return userAssignedManagedIdentity; + } + } diff --git a/src/test/java/io/lettuce/test/env/Endpoints.java b/src/test/java/io/lettuce/test/env/Endpoints.java new file mode 100644 index 000000000..0631713cd --- /dev/null +++ b/src/test/java/io/lettuce/test/env/Endpoints.java @@ -0,0 +1,235 @@ +package io.lettuce.test.env; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; + +public class Endpoints { + + private static final Logger log = LoggerFactory.getLogger(Endpoints.class); + + private Map endpoints; + + public static final Endpoints DEFAULT; + + static { + String filePath = System.getenv("REDIS_ENDPOINTS_CONFIG_PATH"); + if (filePath == null || filePath.isEmpty()) { + log.info("REDIS_ENDPOINTS_CONFIG_PATH environment variable is not set. No Endpoints configuration will be loaded."); + DEFAULT = new Endpoints(Collections.emptyMap()); + } else { + DEFAULT = fromFile(filePath); + } + } + + private Endpoints(Map endpoints) { + this.endpoints = endpoints; + } + + /** + * Factory method to create an Endpoints instance from a file. + * + * @param filePath Path to the JSON file. + * @return Populated Endpoints instance. + */ + public static Endpoints fromFile(String filePath) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + File file = new File(filePath); + + HashMap endpoints = objectMapper.readValue(file, + objectMapper.getTypeFactory().constructMapType(HashMap.class, String.class, Endpoint.class)); + return new Endpoints(endpoints); + + } catch (IOException e) { + throw new RuntimeException("Failed to load Endpoints from file: " + filePath, e); + } + } + + /** + * Get an endpoint by name. + * + * @param name the name of the endpoint. + * @return the corresponding Endpoint or {@code null} if not found. + */ + public Endpoint getEndpoint(String name) { + return endpoints != null ? endpoints.get(name) : null; + } + + public Map getEndpoints() { + return endpoints; + } + + public void setEndpoints(Map endpoints) { + this.endpoints = endpoints; + } + + // Inner classes for Endpoint and RawEndpoint + public static class Endpoint { + + @JsonProperty("bdb_id") + private int bdbId; + + private String username; + + private String password; + + private boolean tls; + + @JsonProperty("raw_endpoints") + private List rawEndpoints; + + private List endpoints; + + // Getters and Setters + public int getBdbId() { + return bdbId; + } + + public void setBdbId(int bdbId) { + this.bdbId = bdbId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public boolean isTls() { + return tls; + } + + public void setTls(boolean tls) { + this.tls = tls; + } + + public List getRawEndpoints() { + return rawEndpoints; + } + + public void setRawEndpoints(List rawEndpoints) { + this.rawEndpoints = rawEndpoints; + } + + public List getEndpoints() { + return endpoints; + } + + public void setEndpoints(List endpoints) { + this.endpoints = endpoints; + } + + } + + public static class RawEndpoint { + + private List addr; + + @JsonProperty("addr_type") + private String addrType; + + @JsonProperty("dns_name") + private String dnsName; + + @JsonProperty("oss_cluster_api_preferred_endpoint_type") + private String preferredEndpointType; + + @JsonProperty("oss_cluster_api_preferred_ip_type") + private String preferredIpType; + + private int port; + + @JsonProperty("proxy_policy") + private String proxyPolicy; + + private String uid; + + // Getters and Setters + public List getAddr() { + return addr; + } + + public void setAddr(List addr) { + this.addr = addr; + } + + public String getAddrType() { + return addrType; + } + + public void setAddrType(String addrType) { + this.addrType = addrType; + } + + public String getDnsName() { + return dnsName; + } + + public void setDnsName(String dnsName) { + this.dnsName = dnsName; + } + + public String getPreferredEndpointType() { + return preferredEndpointType; + } + + public void setPreferredEndpointType(String preferredEndpointType) { + this.preferredEndpointType = preferredEndpointType; + } + + public String getPreferredIpType() { + return preferredIpType; + } + + public void setPreferredIpType(String preferredIpType) { + this.preferredIpType = preferredIpType; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getProxyPolicy() { + return proxyPolicy; + } + + public void setProxyPolicy(String proxyPolicy) { + this.proxyPolicy = proxyPolicy; + } + + public String getUid() { + return uid; + } + + public void setUid(String uid) { + this.uid = uid; + } + + } + +}