diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Console/Microsoft.UnifiedRedisPlatform.ManagementConsole.csproj b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Console/Microsoft.UnifiedRedisPlatform.ManagementConsole.csproj index 8a5db9e..2704d33 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Console/Microsoft.UnifiedRedisPlatform.ManagementConsole.csproj +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Console/Microsoft.UnifiedRedisPlatform.ManagementConsole.csproj @@ -2,17 +2,17 @@ Exe - netcoreapp3.1 + net8.0 - - - + + + - + diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Console/Program.cs b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Console/Program.cs index c9b74d3..a1f8478 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Console/Program.cs +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Console/Program.cs @@ -20,6 +20,7 @@ class Program private static string _appName; private static string _appSecret; private static string _location; + private static string _managedIdentityClientId; private static UnifiedConnectionMultiplexer _mux; private static IUnifiedDatabase _database; private static int _mode = AdvancedMode; @@ -105,6 +106,7 @@ static void Main(string[] args) AppName = _appName, AppSecret = _appSecret, Region = _location, + ManagedIdentityClientId = _managedIdentityClientId, ConnectionRetryProtocol = connectionTimeout > 0 && connectionMaxRetry > 0 ? new RetryProtocol() { TimeoutInMs = connectionTimeout > 0 ? connectionTimeout : RetryProtocol.GetDefaultConnectionProtocol().TimeoutInMs, @@ -135,7 +137,7 @@ static void Main(string[] args) if (_mux == null) { - _mux = UnifiedConnectionMultiplexer.Connect(_clusterName, _appName, _appSecret, preferredLocation: _location) as UnifiedConnectionMultiplexer; + _mux = UnifiedConnectionMultiplexer.Connect(_clusterName, _appName, _appSecret, managedIdentityClientId: _managedIdentityClientId, preferredLocation: _location) as UnifiedConnectionMultiplexer; } _database = _mux.GetDatabase() as IUnifiedDatabase; @@ -219,6 +221,7 @@ private static void SetupDefaultAppDetails() _appName = _configuration["AppName"]; _appSecret = _configuration["AppSecret"]; _location = _configuration["AppLocation"]; + _managedIdentityClientId = _configuration["ManagedIdentityClientId"]; Console.WriteLine("Default configurations received."); } @@ -238,6 +241,9 @@ private static void SetAppDetails() Console.Write("Region: "); _location = Console.ReadLine(); + + Console.Write("Managed Identity Client ID (leave empty for local debugging): "); + _managedIdentityClientId = Console.ReadLine(); } private static void ShowOperations() @@ -322,7 +328,9 @@ private static void ShowAllKeys() if (choice.KeyChar.ToString().ToLower() == "e") return; +#pragma warning disable CS0618 // Perf issues are acceptable in Management Console var keys = AsyncMode ? _mux.GetKeysAsync().Result : _mux.GetKeys(); +#pragma warning restore CS0618 if (!keys.Any()) Console.WriteLine("No keys found in cache"); else @@ -387,10 +395,12 @@ private static void Flush() if (choice.KeyChar.ToString().ToLower() == "e") return; +#pragma warning disable CS0618 // Perf issues are acceptable in Management Console if (AsyncMode) _mux.FlushAsync().Wait(); else _mux.FlushAsync(); +#pragma warning restore CS0618 Console.WriteLine("Cache flushed"); } @@ -415,10 +425,12 @@ private static void FlushSecondary() if (choice.KeyChar.ToString().ToLower() == "e") return; +#pragma warning disable CS0618 // Perf issues are acceptable in Management Console if (AsyncMode) _mux.FlushSecondaryAsync().Wait(); else _mux.FlushSecondary(); +#pragma warning restore CS0618 Console.WriteLine("Cache flushed"); } diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Console/appsettings.json b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Console/appsettings.json index d4711f9..fe6e3f3 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Console/appsettings.json +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Console/appsettings.json @@ -1,6 +1,7 @@ -{ +{ "ClusterName": "PS-PreProd-01", "AppName": "FXP-Service-SIT", "AppSecret": "", - "AppLocation": "eastus" + "AppLocation": "eastus", + "ManagedIdentityClientId": "" } diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Commands/FlushKeysCommand.cs b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Commands/FlushKeysCommand.cs index f53657f..0e248cb 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Commands/FlushKeysCommand.cs +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Commands/FlushKeysCommand.cs @@ -6,7 +6,7 @@ namespace Microsoft.UnifiedRedisPlatform.Manager.Domain.Commands { public class FlushKeysCommand : Command { - public override string DisplayName => "Flush Application Command"; + public override string DisplayName => "Flush Keys Command"; private readonly string _id; public override string Id => _id; @@ -16,7 +16,7 @@ public class FlushKeysCommand : Command public string SearchText { get; set; } public bool DeleteSecondary { get; set; } - public FlushKeysCommand(string cluster, string application, string searchText, bool deleteSecondary = false) + public FlushKeysCommand(string cluster, string application, string searchText, bool deleteSecondary) { _id = Guid.NewGuid().ToString(); Cluster = cluster; @@ -25,20 +25,24 @@ public FlushKeysCommand(string cluster, string application, string searchText, b DeleteSecondary = deleteSecondary; } - public FlushKeysCommand(string cluster, string application) - : this(cluster, application, null) + public FlushKeysCommand(string cluster, string application, string searchText) + : this(cluster, application, searchText, false) { } public override bool Validate(out string ValidationErrorMessage) { ValidationErrorMessage = null; - if (string.IsNullOrWhiteSpace(Cluster)) - ValidationErrorMessage += "Cluster name cannot be empty."; + { + ValidationErrorMessage = "Cluster is not provided"; + return false; + } if (string.IsNullOrWhiteSpace(Application)) - ValidationErrorMessage += "Application name cannot be empty."; - - return string.IsNullOrWhiteSpace(ValidationErrorMessage); + { + ValidationErrorMessage = "Application is not provided"; + return false; + } + return true; } } } diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Commands/Handlers/CreateKeyCommandHandler.cs b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Commands/Handlers/CreateKeyCommandHandler.cs index bc2e9a3..7ea7ede 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Commands/Handlers/CreateKeyCommandHandler.cs +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Commands/Handlers/CreateKeyCommandHandler.cs @@ -5,7 +5,6 @@ using AppInsights.EnterpriseTelemetry.Context; using Microsoft.UnifiedPlatform.Service.Common.Configuration; using Microsoft.UnifiedPlatform.Service.Common.Authentication; -using AppInsights.EnterpriseTelemetry; namespace Microsoft.UnifiedRedisPlatform.Manager.Domain.Commands.Handlers { diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Microsoft.UnifiedRedisPlatform.Manager.Domain.csproj b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Microsoft.UnifiedRedisPlatform.Manager.Domain.csproj index 40a9a95..f6e0420 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Microsoft.UnifiedRedisPlatform.Manager.Domain.csproj +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Microsoft.UnifiedRedisPlatform.Manager.Domain.csproj @@ -1,14 +1,15 @@ - + netstandard2.1 - + + diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Queries/GetKeyQuery.cs b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Queries/GetKeyQuery.cs index d366674..3a53718 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Queries/GetKeyQuery.cs +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/Queries/GetKeyQuery.cs @@ -1,8 +1,6 @@ -using Microsoft.CQRS; +using System; +using Microsoft.CQRS; using Microsoft.UnifiedPlatform.Service.Common.Models; -using System; -using System.Collections.Generic; -using System.Text; namespace Microsoft.UnifiedRedisPlatform.Manager.Domain.Queries { @@ -10,7 +8,7 @@ public class GetKeyQuery : Query { public override string DisplayName => "Get Key Query"; - private string _id; + private readonly string _id; public override string Id => _id; public string Cluster { get; set; } @@ -28,15 +26,22 @@ public GetKeyQuery(string cluster, string application, string key) public override bool Validate(out string ValidationErrorMessage) { ValidationErrorMessage = null; - if (string.IsNullOrWhiteSpace(Cluster)) - ValidationErrorMessage += "Cluster name cannot be empty."; + { + ValidationErrorMessage = "Cluster is not provided"; + return false; + } if (string.IsNullOrWhiteSpace(Application)) - ValidationErrorMessage += "Application name cannot be empty."; + { + ValidationErrorMessage = "Application is not provided"; + return false; + } if (string.IsNullOrWhiteSpace(Key)) - ValidationErrorMessage += "Key cannot be empty"; - - return string.IsNullOrWhiteSpace(ValidationErrorMessage); + { + ValidationErrorMessage = "Key is not provided"; + return false; + } + return true; } } } diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/UnifiedConnectionMultiplexerFactory.cs b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/UnifiedConnectionMultiplexerFactory.cs index 2068026..beb2127 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/UnifiedConnectionMultiplexerFactory.cs +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Core/Domain/UnifiedConnectionMultiplexerFactory.cs @@ -9,9 +9,16 @@ public interface IUnifiedConnectionMultiplexerFactory public class UnifiedConnectionMultiplexerFactory : IUnifiedConnectionMultiplexerFactory { + private readonly string _managedIdentityClientId; + + public UnifiedConnectionMultiplexerFactory(string managedIdentityClientId = null) + { + _managedIdentityClientId = managedIdentityClientId; + } + public IUnifiedConnectionMultiplexer Create(string clusterName, string applicationName, string appSecret) { - return UnifiedConnectionMultiplexer.Connect(clusterName, applicationName, appSecret) as IUnifiedConnectionMultiplexer; + return UnifiedConnectionMultiplexer.Connect(clusterName, applicationName, appSecret, managedIdentityClientId: _managedIdentityClientId) as IUnifiedConnectionMultiplexer; } } } diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Microsoft.UnifiedRedisPlatform.Manager.sln b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Microsoft.UnifiedRedisPlatform.Manager.sln index 7fda87b..f1034db 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Microsoft.UnifiedRedisPlatform.Manager.sln +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Microsoft.UnifiedRedisPlatform.Manager.sln @@ -25,7 +25,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.UnifiedPlatform.S EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.UnifiedPlatform.Service.Common", "..\..\service\Microsoft.UnifiedRedisPlatform.Service\SharedKernel\Common\Microsoft.UnifiedPlatform.Service.Common.csproj", "{0C33A24D-5FE5-4566-9104-0F43E67FA798}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.UnifiedRedisPlatform.Core", "..\..\sdk\Microsoft.UnifiedRedisPlatform.SDK\Core\Microsoft.UnifiedRedisPlatform.Core.csproj", "{C45F1E14-800A-47C6-8E6D-3FB82A3AA743}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.UnifiedRedisPlatform.Core", "..\..\sdk\Core\Microsoft.UnifiedRedisPlatform.Core.csproj", "{C45F1E14-800A-47C6-8E6D-3FB82A3AA743}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.UnifiedPlatform.Storage", "..\..\service\Microsoft.UnifiedRedisPlatform.Service\Infrastructure\Storage\Microsoft.UnifiedPlatform.Storage.csproj", "{57DE9E2F-18B7-41F7-9435-FD9A0A446E05}" EndProject diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/NuGet.Config b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/NuGet.Config new file mode 100644 index 0000000..19bcbee --- /dev/null +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/NuGet.Config @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Dependency/DependencyResolver.cs b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Dependency/DependencyResolver.cs index cae2bee..8618122 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Dependency/DependencyResolver.cs +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Dependency/DependencyResolver.cs @@ -247,9 +247,14 @@ protected virtual void RegisterAuthenticators(ContainerBuilder builder) protected virtual void RegisterRequestHandlerResolver(ContainerBuilder builder) { - builder.RegisterType() - .As() - .SingleInstance(); + builder.Register(ctx => + { + var config = ctx.Resolve(); + var managedIdentityClientId = config["ManagedIdentityClientId"]; + return new UnifiedConnectionMultiplexerFactory(managedIdentityClientId); + }) + .As() + .SingleInstance(); builder.Register(ctx => { diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Microsoft.UnifiedRedisPlatform.Manager.API.csproj b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Microsoft.UnifiedRedisPlatform.Manager.API.csproj index 082a534..fdfe222 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Microsoft.UnifiedRedisPlatform.Manager.API.csproj +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Microsoft.UnifiedRedisPlatform.Manager.API.csproj @@ -1,7 +1,7 @@ - + - netcoreapp3.1 + net8.0 /subscriptions/05a315f7-744f-4692-b9dd-1aed7c6cee64/resourcegroups/RG-FieldExperiencePlatform-Dev-01/providers/microsoft.insights/components/aifxpdev @@ -10,13 +10,16 @@ - - - + + + + + - - + + + diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Microsoft.UnifiedRedisPlatform.Manager.API.xml b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Microsoft.UnifiedRedisPlatform.Manager.API.xml index bff6c7f..a8c2f31 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Microsoft.UnifiedRedisPlatform.Manager.API.xml +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Microsoft.UnifiedRedisPlatform.Manager.API.xml @@ -1,4 +1,4 @@ - + Microsoft.UnifiedRedisPlatform.Manager.API diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Program.cs b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Program.cs index 6a1dd49..2e08419 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Program.cs +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Program.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore; +using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Hosting; diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Properties/launchSettings.json b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Properties/launchSettings.json index b58f6a5..c5e43de 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Properties/launchSettings.json +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Properties/launchSettings.json @@ -1,4 +1,4 @@ -{ +{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Startup.cs b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Startup.cs index a20c063..d8dfb7f 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Startup.cs +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/Startup.cs @@ -1,4 +1,4 @@ -using System; +using System; using Autofac; using System.IO; using System.Reflection; @@ -71,7 +71,7 @@ protected virtual void AddAuthentication(IServiceCollection services) protected virtual void AddTelemetry(IServiceCollection services, IConfiguration configuration) { - services.AddEnterpriseLogger(configuration); + services.AddEnterpriseTelemetry(configuration); services.AddSingleton(); services.AddSingleton(); } diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/appsettings.Development.json b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/appsettings.Development.json index 8983e0f..c9294ca 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/appsettings.Development.json +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/appsettings.Development.json @@ -1,4 +1,4 @@ -{ +{ "Logging": { "LogLevel": { "Default": "Information", diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/appsettings.json b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/appsettings.json index 0f57624..900a694 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/appsettings.json +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/API/appsettings.json @@ -1,4 +1,4 @@ -{ +{ "Logging": { "LogLevel": { "Default": "Information", @@ -60,5 +60,6 @@ "ConfigurationTable": "UnifiedRedisPlatform", "BackoffInterval": 1000, "MaxAttempt": 10 - } + }, + "ManagedIdentityClientId": "ddcbb4aa-01a9-46aa-8c11-03e1b789d9cd" } \ No newline at end of file diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Data/WeatherForecast.cs b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Data/WeatherForecast.cs index 15d7d90..00cfbc3 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Data/WeatherForecast.cs +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Data/WeatherForecast.cs @@ -1,4 +1,4 @@ -using System; +using System; namespace Microsoft.UnifiedRedisPlatform.Manager.App.Data { diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Data/WeatherForecastService.cs b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Data/WeatherForecastService.cs index 7ecaa45..092634e 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Data/WeatherForecastService.cs +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Data/WeatherForecastService.cs @@ -1,4 +1,4 @@ -using Newtonsoft.Json; +using Newtonsoft.Json; using System; using System.Linq; using System.Net.Http; diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Microsoft.UnifiedRedisPlatform.Manager.App.csproj b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Microsoft.UnifiedRedisPlatform.Manager.App.csproj index a60839b..91777f3 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Microsoft.UnifiedRedisPlatform.Manager.App.csproj +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Microsoft.UnifiedRedisPlatform.Manager.App.csproj @@ -1,13 +1,14 @@ - + - netcoreapp3.1 + net8.0 aspnet-App-2F04C961-379D-4CE3-9A2A-CC2B9D19F142 0 - + + diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Program.cs b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Program.cs index 1e6e350..60f495f 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Program.cs +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.IO; using System.Linq; diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Properties/launchSettings.json b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Properties/launchSettings.json index dadb32b..80912b5 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Properties/launchSettings.json +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Properties/launchSettings.json @@ -1,4 +1,4 @@ -{ +{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Startup.cs b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Startup.cs index bc70269..352ad5a 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Startup.cs +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/Startup.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -33,16 +33,14 @@ public Startup(IConfiguration configuration) public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) - .AddMicrosoftIdentityWebApp(options => - { - Configuration.Bind("AzureAd", options); - options.ResponseType = "code"; - options.SaveTokens = true; - options.Scope.Add("user_impersonation"); - options.ClientId = "6f40053e-5319-40e5-a90b-6f714506d96d"; - options.ClientSecret = "KEY_VAULT"; - options.Resource = "6f40053e-5319-40e5-a90b-6f714506d96d"; - }); + .AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd")); + + services.Configure(OpenIdConnectDefaults.AuthenticationScheme, options => + { + options.ResponseType = "code"; + options.SaveTokens = true; + options.Scope.Add("user_impersonation"); + }); services.AddHttpClient(); services.AddScoped(); @@ -53,7 +51,7 @@ public void ConfigureServices(IServiceCollection services) .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(policy)); - }); + }).AddMicrosoftIdentityUI(); diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/appsettings.Development.json b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/appsettings.Development.json index 5173757..1fb2fa8 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/appsettings.Development.json +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/appsettings.Development.json @@ -1,4 +1,4 @@ -{ +{ "DetailedErrors": true, "Logging": { "LogLevel": { diff --git a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/appsettings.json b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/appsettings.json index 5e85b61..177cf22 100644 --- a/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/appsettings.json +++ b/src/manager/Microsoft.UnifiedRedisPlatform.Manager/Web/App/appsettings.json @@ -1,10 +1,12 @@ -{ +{ "AzureAd": { "Instance": "https://login.microsoftonline.com/", "Domain": "microsoft.onmicrosoft.com", "TenantId": "72f988bf-86f1-41af-91ab-2d7cd011db47", "ClientId": "6f40053e-5319-40e5-a90b-6f714506d96d", - "CallbackPath": "/signin-oidc" + "ClientSecret": "KEY_VAULT", + "CallbackPath": "/signin-oidc", + "SignedOutCallbackPath": "/signout-oidc" }, "Logging": { "LogLevel": { diff --git a/src/sdk/Core/Configurations/UnifiedConfigurationLocalOptions.cs b/src/sdk/Core/Configurations/UnifiedConfigurationLocalOptions.cs index 1181655..3f34e5c 100644 --- a/src/sdk/Core/Configurations/UnifiedConfigurationLocalOptions.cs +++ b/src/sdk/Core/Configurations/UnifiedConfigurationLocalOptions.cs @@ -5,8 +5,14 @@ namespace Microsoft.UnifiedRedisPlatform.Core { + /// + /// Configuration options for direct Redis connection without using URP service. + /// Use this when you have the Redis connection details and want to connect directly. + /// public class UnifiedConfigurationLocalOptions: UnifiedConfigurationOptions { + // ManagedIdentityClientId and UseManagedIdentity are inherited from base class + public override object Clone() { return new UnifiedConfigurationLocalOptions() @@ -14,15 +20,17 @@ public override object Clone() AppName = this.AppName, ClusterName = this.ClusterName, WritePolicy = this.WritePolicy, - BaseConfigurationOptions = this.BaseConfigurationOptions.Clone(), - ConnectionRetryProtocol = (RetryProtocol)this.ConnectionRetryProtocol.Clone(), - DiagnosticSettings = (LogConfiguration)this.DiagnosticSettings.Clone(), + BaseConfigurationOptions = this.BaseConfigurationOptions?.Clone(), + ConnectionRetryProtocol = this.ConnectionRetryProtocol != null ? (RetryProtocol)this.ConnectionRetryProtocol.Clone() : null, + DiagnosticSettings = this.DiagnosticSettings != null ? (LogConfiguration)this.DiagnosticSettings.Clone() : null, KeyPrefix = this.KeyPrefix, Logger = this.Logger, - OperationsRetryProtocol = (RetryProtocol)this.OperationsRetryProtocol.Clone(), + OperationsRetryProtocol = this.OperationsRetryProtocol != null ? (RetryProtocol)this.OperationsRetryProtocol.Clone() : null, + Region = this.Region, SecondaryConfigurationsOptions = this.SecondaryConfigurationsOptions.Any() ? this.SecondaryConfigurationsOptions.Select(options => options.Clone()).ToList() - : new List() + : new List(), + ManagedIdentityClientId = this.ManagedIdentityClientId }; } } diff --git a/src/sdk/Core/Configurations/UnifiedConfigurationOptions.cs b/src/sdk/Core/Configurations/UnifiedConfigurationOptions.cs index aaa4783..e9da5ca 100644 --- a/src/sdk/Core/Configurations/UnifiedConfigurationOptions.cs +++ b/src/sdk/Core/Configurations/UnifiedConfigurationOptions.cs @@ -4,10 +4,15 @@ using System.Collections.Generic; using Microsoft.UnifiedRedisPlatform.Core.Logging; using Microsoft.UnifiedRedisPlatform.Core.Exceptions; +using Microsoft.UnifiedRedisPlatform.Core.Constants; namespace Microsoft.UnifiedRedisPlatform.Core { - public abstract class UnifiedConfigurationOptions: ICloneable + /// + /// Configuration options for connecting to Unified Redis Platform. + /// Can be instantiated directly for backward compatibility, or use derived classes. + /// + public class UnifiedConfigurationOptions: ICloneable { public string ClusterName { get; set; } public string AppName { get; set; } @@ -15,6 +20,35 @@ public abstract class UnifiedConfigurationOptions: ICloneable public string WritePolicy { get; set; } public string Region { get; set; } + /// + /// Application secret for authentication with URP service. + /// Not required when using Managed Identity. + /// + public virtual string AppSecret { get; set; } + + /// + /// The client ID of the User-Assigned Managed Identity to use for Redis authentication. + /// When set, the SDK will automatically configure Azure AD authentication. + /// Leave null/empty to use access key authentication (backward compatible). + /// + public virtual string ManagedIdentityClientId { get; set; } + + /// + /// Returns true if Managed Identity should be used for authentication. + /// + public bool UseManagedIdentity => !string.IsNullOrWhiteSpace(ManagedIdentityClientId); + + private string _serviceEndpoint; + /// + /// The URP service endpoint URL for fetching configuration. + /// Defaults to the production endpoint if not specified. + /// + public virtual string ServiceEndpoint + { + get => !string.IsNullOrWhiteSpace(_serviceEndpoint) && Uri.IsWellFormedUriString(_serviceEndpoint, UriKind.Absolute) ? _serviceEndpoint : Constant.OperationApi.DefaultUrl; + set => _serviceEndpoint = value; + } + private RetryProtocol _operationsRetryProtocol; public RetryProtocol OperationsRetryProtocol { @@ -45,7 +79,30 @@ public LogConfiguration DiagnosticSettings public List SecondaryConfigurationsOptions { get; set; } = new List(); - public abstract object Clone(); + /// + /// Creates a deep copy of this configuration. + /// + public virtual object Clone() + { + return new UnifiedConfigurationOptions() + { + AppName = this.AppName, + AppSecret = this.AppSecret, + ManagedIdentityClientId = this.ManagedIdentityClientId, + WritePolicy = this.WritePolicy, + BaseConfigurationOptions = this.BaseConfigurationOptions?.Clone(), + ClusterName = this.ClusterName, + ConnectionRetryProtocol = this.ConnectionRetryProtocol != null ? (RetryProtocol)this.ConnectionRetryProtocol.Clone() : null, + DiagnosticSettings = this.DiagnosticSettings != null ? (LogConfiguration)this.DiagnosticSettings.Clone() : null, + KeyPrefix = this.KeyPrefix, + Logger = this.Logger, + OperationsRetryProtocol = this.OperationsRetryProtocol != null ? (RetryProtocol)this.OperationsRetryProtocol.Clone() : null, + Region = this.Region, + SecondaryConfigurationsOptions = this.SecondaryConfigurationsOptions.Any() ? + this.SecondaryConfigurationsOptions.Select(options => options.Clone()).ToList() + : new List() + }; + } public virtual void Validate() { diff --git a/src/sdk/Core/Configurations/UnifiedConfigurationServerOptions.cs b/src/sdk/Core/Configurations/UnifiedConfigurationServerOptions.cs index 035ae4b..8cb5b1e 100644 --- a/src/sdk/Core/Configurations/UnifiedConfigurationServerOptions.cs +++ b/src/sdk/Core/Configurations/UnifiedConfigurationServerOptions.cs @@ -8,19 +8,15 @@ namespace Microsoft.UnifiedRedisPlatform.Core { + /// + /// Configuration options for connecting to URP via the service endpoint. + /// Use this when you want the SDK to fetch Redis configuration from the URP service. + /// public class UnifiedConfigurationServerOptions : UnifiedConfigurationOptions { public UnifiedConfigurationServerOptions() { } - - public string AppSecret { get; set; } - - private string _serviceEndpoint; - public string ServiceEndpoint - { - get => !string.IsNullOrWhiteSpace(_serviceEndpoint) && Uri.IsWellFormedUriString(_serviceEndpoint, UriKind.Absolute) ? _serviceEndpoint : Constant.OperationApi.DefaultUrl; - set => _serviceEndpoint = value; - } + // AppSecret, ManagedIdentityClientId, and ServiceEndpoint are inherited from base class public override object Clone() { @@ -28,14 +24,16 @@ public override object Clone() { AppName = this.AppName, AppSecret = this.AppSecret, + ManagedIdentityClientId = this.ManagedIdentityClientId, WritePolicy = this.WritePolicy, - BaseConfigurationOptions = this.BaseConfigurationOptions.Clone(), + BaseConfigurationOptions = this.BaseConfigurationOptions?.Clone(), ClusterName = this.ClusterName, - ConnectionRetryProtocol = (RetryProtocol)this.ConnectionRetryProtocol.Clone(), - DiagnosticSettings = (LogConfiguration)this.DiagnosticSettings.Clone(), + ConnectionRetryProtocol = this.ConnectionRetryProtocol != null ? (RetryProtocol)this.ConnectionRetryProtocol.Clone() : null, + DiagnosticSettings = this.DiagnosticSettings != null ? (LogConfiguration)this.DiagnosticSettings.Clone() : null, KeyPrefix = this.KeyPrefix, Logger = this.Logger, - OperationsRetryProtocol = (RetryProtocol)this.OperationsRetryProtocol.Clone(), + OperationsRetryProtocol = this.OperationsRetryProtocol != null ? (RetryProtocol)this.OperationsRetryProtocol.Clone() : null, + Region = this.Region, SecondaryConfigurationsOptions = this.SecondaryConfigurationsOptions.Any() ? this.SecondaryConfigurationsOptions.Select(options => options.Clone()).ToList() : new List() diff --git a/src/sdk/Core/Database/UnifiedRedisDatabase.NewMethods.cs b/src/sdk/Core/Database/UnifiedRedisDatabase.NewMethods.cs new file mode 100644 index 0000000..ee7d6cb --- /dev/null +++ b/src/sdk/Core/Database/UnifiedRedisDatabase.NewMethods.cs @@ -0,0 +1,399 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using StackExchange.Redis; + +namespace Microsoft.UnifiedRedisPlatform.Core.Database +{ + /// + /// Contains new interface methods added in StackExchange.Redis 2.6+ + /// All methods are passthrough to the underlying _primaryDatabase + /// + public partial class UnifiedRedisDatabase + { + #region Geo Operations (New) + + public GeoRadiusResult[] GeoSearch(RedisKey key, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.GeoSearch(CreateAppKey(key), member, shape, count, demandClosest, order, options, flags); + + public GeoRadiusResult[] GeoSearch(RedisKey key, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.GeoSearch(CreateAppKey(key), longitude, latitude, shape, count, demandClosest, order, options, flags); + + public long GeoSearchAndStore(RedisKey sourceKey, RedisKey destinationKey, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.GeoSearchAndStore(CreateAppKey(sourceKey), CreateAppKey(destinationKey), member, shape, count, demandClosest, order, storeDistances, flags); + + public long GeoSearchAndStore(RedisKey sourceKey, RedisKey destinationKey, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.GeoSearchAndStore(CreateAppKey(sourceKey), CreateAppKey(destinationKey), longitude, latitude, shape, count, demandClosest, order, storeDistances, flags); + + public Task GeoSearchAsync(RedisKey key, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.GeoSearchAsync(CreateAppKey(key), member, shape, count, demandClosest, order, options, flags); + + public Task GeoSearchAsync(RedisKey key, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, GeoRadiusOptions options = GeoRadiusOptions.Default, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.GeoSearchAsync(CreateAppKey(key), longitude, latitude, shape, count, demandClosest, order, options, flags); + + public Task GeoSearchAndStoreAsync(RedisKey sourceKey, RedisKey destinationKey, RedisValue member, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.GeoSearchAndStoreAsync(CreateAppKey(sourceKey), CreateAppKey(destinationKey), member, shape, count, demandClosest, order, storeDistances, flags); + + public Task GeoSearchAndStoreAsync(RedisKey sourceKey, RedisKey destinationKey, double longitude, double latitude, GeoSearchShape shape, int count = -1, bool demandClosest = true, Order? order = null, bool storeDistances = false, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.GeoSearchAndStoreAsync(CreateAppKey(sourceKey), CreateAppKey(destinationKey), longitude, latitude, shape, count, demandClosest, order, storeDistances, flags); + + #endregion + + #region Hash Operations (New) + + public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashFieldExpire(CreateAppKey(key), hashFields, expiry, when, flags); + + public ExpireResult[] HashFieldExpire(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashFieldExpire(CreateAppKey(key), hashFields, expiry, when, flags); + + public long[] HashFieldGetExpireDateTime(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashFieldGetExpireDateTime(CreateAppKey(key), hashFields, flags); + + public PersistResult[] HashFieldPersist(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashFieldPersist(CreateAppKey(key), hashFields, flags); + + public long[] HashFieldGetTimeToLive(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashFieldGetTimeToLive(CreateAppKey(key), hashFields, flags); + + public RedisValue HashRandomField(RedisKey key, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashRandomField(CreateAppKey(key), flags); + + public RedisValue[] HashRandomFields(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashRandomFields(CreateAppKey(key), count, flags); + + public HashEntry[] HashRandomFieldsWithValues(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashRandomFieldsWithValues(CreateAppKey(key), count, flags); + + public IEnumerable HashScanNoValues(RedisKey key, RedisValue pattern = default, int pageSize = 250, long cursor = 0, int pageOffset = 0, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashScanNoValues(CreateAppKey(key), pattern, pageSize, cursor, pageOffset, flags); + + public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, TimeSpan expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashFieldExpireAsync(CreateAppKey(key), hashFields, expiry, when, flags); + + public Task HashFieldExpireAsync(RedisKey key, RedisValue[] hashFields, DateTime expiry, ExpireWhen when = ExpireWhen.Always, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashFieldExpireAsync(CreateAppKey(key), hashFields, expiry, when, flags); + + public Task HashFieldGetExpireDateTimeAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashFieldGetExpireDateTimeAsync(CreateAppKey(key), hashFields, flags); + + public Task HashFieldPersistAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashFieldPersistAsync(CreateAppKey(key), hashFields, flags); + + public Task HashFieldGetTimeToLiveAsync(RedisKey key, RedisValue[] hashFields, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashFieldGetTimeToLiveAsync(CreateAppKey(key), hashFields, flags); + + public Task HashRandomFieldAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashRandomFieldAsync(CreateAppKey(key), flags); + + public Task HashRandomFieldsAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashRandomFieldsAsync(CreateAppKey(key), count, flags); + + public Task HashRandomFieldsWithValuesAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashRandomFieldsWithValuesAsync(CreateAppKey(key), count, flags); + + public IAsyncEnumerable HashScanNoValuesAsync(RedisKey key, RedisValue pattern = default, int pageSize = 250, long cursor = 0, int pageOffset = 0, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.HashScanNoValuesAsync(CreateAppKey(key), pattern, pageSize, cursor, pageOffset, flags); + + #endregion + + #region Key Operations (New) + + public bool KeyCopy(RedisKey sourceKey, RedisKey destinationKey, int destinationDatabase = -1, bool replace = false, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.KeyCopy(CreateAppKey(sourceKey), CreateAppKey(destinationKey), destinationDatabase, replace, flags); + + public string KeyEncoding(RedisKey key, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.KeyEncoding(CreateAppKey(key), flags); + + public bool KeyExpire(RedisKey key, TimeSpan? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.KeyExpire(CreateAppKey(key), expiry, when, flags); + + public bool KeyExpire(RedisKey key, DateTime? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.KeyExpire(CreateAppKey(key), expiry, when, flags); + + public DateTime? KeyExpireTime(RedisKey key, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.KeyExpireTime(CreateAppKey(key), flags); + + public long? KeyFrequency(RedisKey key, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.KeyFrequency(CreateAppKey(key), flags); + + public long? KeyRefCount(RedisKey key, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.KeyRefCount(CreateAppKey(key), flags); + + public Task KeyCopyAsync(RedisKey sourceKey, RedisKey destinationKey, int destinationDatabase = -1, bool replace = false, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.KeyCopyAsync(CreateAppKey(sourceKey), CreateAppKey(destinationKey), destinationDatabase, replace, flags); + + public Task KeyEncodingAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.KeyEncodingAsync(CreateAppKey(key), flags); + + public Task KeyExpireAsync(RedisKey key, TimeSpan? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.KeyExpireAsync(CreateAppKey(key), expiry, when, flags); + + public Task KeyExpireAsync(RedisKey key, DateTime? expiry, ExpireWhen when, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.KeyExpireAsync(CreateAppKey(key), expiry, when, flags); + + public Task KeyExpireTimeAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.KeyExpireTimeAsync(CreateAppKey(key), flags); + + public Task KeyFrequencyAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.KeyFrequencyAsync(CreateAppKey(key), flags); + + public Task KeyRefCountAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.KeyRefCountAsync(CreateAppKey(key), flags); + + #endregion + + #region List Operations (New) + + public RedisValue[] ListLeftPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ListLeftPop(CreateAppKey(key), count, flags); + + public ListPopResult ListLeftPop(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ListLeftPop(CreateAppKeys(keys), count, flags); + + public long ListPosition(RedisKey key, RedisValue element, long rank = 1, long count = 1, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ListPosition(CreateAppKey(key), element, rank, count, flags); + + public long[] ListPositions(RedisKey key, RedisValue element, long rank = 1, long count = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ListPositions(CreateAppKey(key), element, rank, count, maxLength, flags); + + public RedisValue ListMove(RedisKey sourceKey, RedisKey destinationKey, ListSide sourceSide, ListSide destinationSide, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ListMove(CreateAppKey(sourceKey), CreateAppKey(destinationKey), sourceSide, destinationSide, flags); + + public RedisValue[] ListRightPop(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ListRightPop(CreateAppKey(key), count, flags); + + public ListPopResult ListRightPop(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ListRightPop(CreateAppKeys(keys), count, flags); + + public Task ListLeftPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ListLeftPopAsync(CreateAppKey(key), count, flags); + + public Task ListLeftPopAsync(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ListLeftPopAsync(CreateAppKeys(keys), count, flags); + + public Task ListPositionAsync(RedisKey key, RedisValue element, long rank = 1, long count = 1, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ListPositionAsync(CreateAppKey(key), element, rank, count, flags); + + public Task ListPositionsAsync(RedisKey key, RedisValue element, long rank = 1, long count = 1, long maxLength = 0, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ListPositionsAsync(CreateAppKey(key), element, rank, count, maxLength, flags); + + public Task ListMoveAsync(RedisKey sourceKey, RedisKey destinationKey, ListSide sourceSide, ListSide destinationSide, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ListMoveAsync(CreateAppKey(sourceKey), CreateAppKey(destinationKey), sourceSide, destinationSide, flags); + + public Task ListRightPopAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ListRightPopAsync(CreateAppKey(key), count, flags); + + public Task ListRightPopAsync(RedisKey[] keys, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ListRightPopAsync(CreateAppKeys(keys), count, flags); + + #endregion + + #region Script Operations (New) + + public RedisResult ScriptEvaluateReadOnly(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ScriptEvaluateReadOnly(script, keys != null ? CreateAppKeys(keys) : null, values, flags); + + public RedisResult ScriptEvaluateReadOnly(byte[] hash, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ScriptEvaluateReadOnly(hash, keys != null ? CreateAppKeys(keys) : null, values, flags); + + public Task ScriptEvaluateReadOnlyAsync(string script, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ScriptEvaluateReadOnlyAsync(script, keys != null ? CreateAppKeys(keys) : null, values, flags); + + public Task ScriptEvaluateReadOnlyAsync(byte[] hash, RedisKey[] keys = null, RedisValue[] values = null, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.ScriptEvaluateReadOnlyAsync(hash, keys != null ? CreateAppKeys(keys) : null, values, flags); + + #endregion + + #region Set Operations (New) + + public bool[] SetContains(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SetContains(CreateAppKey(key), values, flags); + + public long SetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SetIntersectionLength(CreateAppKeys(keys), limit, flags); + + public Task SetContainsAsync(RedisKey key, RedisValue[] values, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SetContainsAsync(CreateAppKey(key), values, flags); + + public Task SetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SetIntersectionLengthAsync(CreateAppKeys(keys), limit, flags); + + #endregion + + #region Sorted Set Operations (New) + + public bool SortedSetAdd(RedisKey key, RedisValue member, double score, SortedSetWhen when, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetAdd(CreateAppKey(key), member, score, when, flags); + + public long SortedSetAdd(RedisKey key, SortedSetEntry[] values, SortedSetWhen when, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetAdd(CreateAppKey(key), values, when, flags); + + public RedisValue[] SortedSetCombine(SetOperation operation, RedisKey[] keys, double[] weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetCombine(operation, CreateAppKeys(keys), weights, aggregate, flags); + + public SortedSetEntry[] SortedSetCombineWithScores(SetOperation operation, RedisKey[] keys, double[] weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetCombineWithScores(operation, CreateAppKeys(keys), weights, aggregate, flags); + + public long SortedSetIntersectionLength(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetIntersectionLength(CreateAppKeys(keys), limit, flags); + + public RedisValue SortedSetRandomMember(RedisKey key, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetRandomMember(CreateAppKey(key), flags); + + public RedisValue[] SortedSetRandomMembers(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetRandomMembers(CreateAppKey(key), count, flags); + + public SortedSetEntry[] SortedSetRandomMembersWithScores(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetRandomMembersWithScores(CreateAppKey(key), count, flags); + + public long SortedSetRangeAndStore(RedisKey sourceKey, RedisKey destinationKey, RedisValue start, RedisValue stop, SortedSetOrder sortedSetOrder = SortedSetOrder.ByRank, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long? take = null, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetRangeAndStore(CreateAppKey(sourceKey), CreateAppKey(destinationKey), start, stop, sortedSetOrder, exclude, order, skip, take, flags); + + public double?[] SortedSetScores(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetScores(CreateAppKey(key), members, flags); + + public SortedSetPopResult SortedSetPop(RedisKey[] keys, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetPop(CreateAppKeys(keys), count, order, flags); + + public bool SortedSetUpdate(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetUpdate(CreateAppKey(key), member, score, when, flags); + + public long SortedSetUpdate(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetUpdate(CreateAppKey(key), values, when, flags); + + public Task SortedSetAddAsync(RedisKey key, RedisValue member, double score, SortedSetWhen when, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetAddAsync(CreateAppKey(key), member, score, when, flags); + + public Task SortedSetAddAsync(RedisKey key, SortedSetEntry[] values, SortedSetWhen when, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetAddAsync(CreateAppKey(key), values, when, flags); + + public Task SortedSetCombineAsync(SetOperation operation, RedisKey[] keys, double[] weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetCombineAsync(operation, CreateAppKeys(keys), weights, aggregate, flags); + + public Task SortedSetCombineWithScoresAsync(SetOperation operation, RedisKey[] keys, double[] weights = null, Aggregate aggregate = Aggregate.Sum, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetCombineWithScoresAsync(operation, CreateAppKeys(keys), weights, aggregate, flags); + + public Task SortedSetIntersectionLengthAsync(RedisKey[] keys, long limit = 0, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetIntersectionLengthAsync(CreateAppKeys(keys), limit, flags); + + public Task SortedSetRandomMemberAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetRandomMemberAsync(CreateAppKey(key), flags); + + public Task SortedSetRandomMembersAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetRandomMembersAsync(CreateAppKey(key), count, flags); + + public Task SortedSetRandomMembersWithScoresAsync(RedisKey key, long count, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetRandomMembersWithScoresAsync(CreateAppKey(key), count, flags); + + public Task SortedSetRangeAndStoreAsync(RedisKey sourceKey, RedisKey destinationKey, RedisValue start, RedisValue stop, SortedSetOrder sortedSetOrder = SortedSetOrder.ByRank, Exclude exclude = Exclude.None, Order order = Order.Ascending, long skip = 0, long? take = null, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetRangeAndStoreAsync(CreateAppKey(sourceKey), CreateAppKey(destinationKey), start, stop, sortedSetOrder, exclude, order, skip, take, flags); + + public Task SortedSetScoresAsync(RedisKey key, RedisValue[] members, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetScoresAsync(CreateAppKey(key), members, flags); + + public Task SortedSetPopAsync(RedisKey[] keys, long count, Order order = Order.Ascending, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetPopAsync(CreateAppKeys(keys), count, order, flags); + + public Task SortedSetUpdateAsync(RedisKey key, RedisValue member, double score, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetUpdateAsync(CreateAppKey(key), member, score, when, flags); + + public Task SortedSetUpdateAsync(RedisKey key, SortedSetEntry[] values, SortedSetWhen when = SortedSetWhen.Always, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.SortedSetUpdateAsync(CreateAppKey(key), values, when, flags); + + #endregion + + #region Stream Operations (New) + + public StreamAutoClaimResult StreamAutoClaim(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StreamAutoClaim(CreateAppKey(key), consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, flags); + + public StreamAutoClaimIdsOnlyResult StreamAutoClaimIdsOnly(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StreamAutoClaimIdsOnly(CreateAppKey(key), consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, flags); + + public Task StreamAutoClaimAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StreamAutoClaimAsync(CreateAppKey(key), consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, flags); + + public Task StreamAutoClaimIdsOnlyAsync(RedisKey key, RedisValue consumerGroup, RedisValue claimingConsumer, long minIdleTimeInMs, RedisValue startAtId, int? count = null, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StreamAutoClaimIdsOnlyAsync(CreateAppKey(key), consumerGroup, claimingConsumer, minIdleTimeInMs, startAtId, count, flags); + + #endregion + + #region String Operations (New) + + public long StringBitCount(RedisKey key, long start, long end, StringIndexType indexType, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringBitCount(CreateAppKey(key), start, end, indexType, flags); + + public long StringBitPosition(RedisKey key, bool bit, long start, long end, StringIndexType indexType, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringBitPosition(CreateAppKey(key), bit, start, end, indexType, flags); + + public RedisValue StringGetSetExpiry(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringGetSetExpiry(CreateAppKey(key), expiry, flags); + + public RedisValue StringGetSetExpiry(RedisKey key, DateTime expiry, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringGetSetExpiry(CreateAppKey(key), expiry, flags); + + public RedisValue StringGetDelete(RedisKey key, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringGetDelete(CreateAppKey(key), flags); + + public string StringLongestCommonSubsequence(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringLongestCommonSubsequence(CreateAppKey(first), CreateAppKey(second), flags); + + public long StringLongestCommonSubsequenceLength(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringLongestCommonSubsequenceLength(CreateAppKey(first), CreateAppKey(second), flags); + + public LCSMatchResult StringLongestCommonSubsequenceWithMatches(RedisKey first, RedisKey second, long minLength = 0, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringLongestCommonSubsequenceWithMatches(CreateAppKey(first), CreateAppKey(second), minLength, flags); + + public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry, When when) + => _primaryDatabase.StringSet(CreateAppKey(key), value, expiry, when); + + public bool StringSet(RedisKey key, RedisValue value, TimeSpan? expiry, bool keepTtl, When when = When.Always, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringSet(CreateAppKey(key), value, expiry, keepTtl, when, flags); + + public RedisValue StringSetAndGet(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringSetAndGet(CreateAppKey(key), value, expiry, when, flags); + + public RedisValue StringSetAndGet(RedisKey key, RedisValue value, TimeSpan? expiry, bool keepTtl, When when = When.Always, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringSetAndGet(CreateAppKey(key), value, expiry, keepTtl, when, flags); + + public Task StringBitCountAsync(RedisKey key, long start, long end, StringIndexType indexType, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringBitCountAsync(CreateAppKey(key), start, end, indexType, flags); + + public Task StringBitPositionAsync(RedisKey key, bool bit, long start, long end, StringIndexType indexType, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringBitPositionAsync(CreateAppKey(key), bit, start, end, indexType, flags); + + public Task StringGetSetExpiryAsync(RedisKey key, TimeSpan? expiry, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringGetSetExpiryAsync(CreateAppKey(key), expiry, flags); + + public Task StringGetSetExpiryAsync(RedisKey key, DateTime expiry, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringGetSetExpiryAsync(CreateAppKey(key), expiry, flags); + + public Task StringGetDeleteAsync(RedisKey key, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringGetDeleteAsync(CreateAppKey(key), flags); + + public Task StringLongestCommonSubsequenceAsync(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringLongestCommonSubsequenceAsync(CreateAppKey(first), CreateAppKey(second), flags); + + public Task StringLongestCommonSubsequenceLengthAsync(RedisKey first, RedisKey second, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringLongestCommonSubsequenceLengthAsync(CreateAppKey(first), CreateAppKey(second), flags); + + public Task StringLongestCommonSubsequenceWithMatchesAsync(RedisKey first, RedisKey second, long minLength = 0, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringLongestCommonSubsequenceWithMatchesAsync(CreateAppKey(first), CreateAppKey(second), minLength, flags); + + public Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when) + => _primaryDatabase.StringSetAsync(CreateAppKey(key), value, expiry, when); + + public Task StringSetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, bool keepTtl, When when = When.Always, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringSetAsync(CreateAppKey(key), value, expiry, keepTtl, when, flags); + + public Task StringSetAndGetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, When when, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringSetAndGetAsync(CreateAppKey(key), value, expiry, when, flags); + + public Task StringSetAndGetAsync(RedisKey key, RedisValue value, TimeSpan? expiry, bool keepTtl, When when = When.Always, CommandFlags flags = CommandFlags.None) + => _primaryDatabase.StringSetAndGetAsync(CreateAppKey(key), value, expiry, keepTtl, when, flags); + + #endregion + } +} diff --git a/src/sdk/Core/Microsoft.UnifiedRedisPlatform.Core.csproj b/src/sdk/Core/Microsoft.UnifiedRedisPlatform.Core.csproj index 32f6416..8910e35 100644 --- a/src/sdk/Core/Microsoft.UnifiedRedisPlatform.Core.csproj +++ b/src/sdk/Core/Microsoft.UnifiedRedisPlatform.Core.csproj @@ -3,24 +3,32 @@ netstandard2.0 latest - 1.0.2 + 1.5.0 false Pratik Bhattacharya Microsoft Unified Redis Platform Lighweight library to connect to a common Redis Cache from multiple applications - Fixed get keys by pattern - UnifiedRedisPlatform + +v1.3.0: +- Added Managed Identity (MI) support for Azure Redis Cache authentication +- Upgraded to Microsoft.Azure.StackExchangeRedis 3.2.1 +- Added ConnectAsync() method for async connection initialization +- Updated Azure.Identity to 1.17.1 +- Backward compatible with existing AccessKey authentication + + Microsoft.UnifiedRedisPlatform.Core git - redis; azure; unifed redis platform; unified redis framework; redis cache; azure cache; azure redis cache; azure cost + redis; azure; unifed redis platform; unified redis framework; redis cache; azure cache; azure redis cache; managed identity https://github.com/microsoft/UnifiedRedisPlatform.Core https://github.com/microsoft/UnifiedRedisPlatform.Core.git - - - + + + + diff --git a/src/sdk/Core/Microsoft.UnifiedRedisPlatform.Core.nuspec b/src/sdk/Core/Microsoft.UnifiedRedisPlatform.Core.nuspec index 6200e60..192e72c 100644 --- a/src/sdk/Core/Microsoft.UnifiedRedisPlatform.Core.nuspec +++ b/src/sdk/Core/Microsoft.UnifiedRedisPlatform.Core.nuspec @@ -1,21 +1,39 @@ - + Microsoft.UnifiedRedisPlatform.Core - 1.2.0-rc-1.0 + 1.5.0 Microsoft.UnifiedRedisPlatform.Core Pratik Bhattacharya Microsoft https://dev.azure.com/MicrosoftIT/OneITVSO/_workitems/edit/4288461 false Lighweight library to connect to a common Redis Cache from multiple applications - Added methods for automatically adding absolute and sliding expiring keys + +v1.5.0: +- Added Managed Identity support for Manager API +- Improved configuration handling for ManagedIdentityClientId +- Code quality improvements and file encoding standardization + +v1.4.0: +- Added Managed Identity (MI) support for Azure Redis Cache authentication (opt-in via ManagedIdentityClientId) +- Upgraded to Microsoft.Azure.StackExchangeRedis 3.2.1 (brings StackExchange.Redis 2.8.x) +- Added 120+ new IDatabase methods from SE.Redis 2.8.x +- Added ServerMaintenanceEvent for Azure Cache maintenance notifications +- Added ConnectAsync() method for async connection initialization +- Updated Azure.Identity to 1.17.1 (fixed deprecated 1.14.0) +- Updated Newtonsoft.Json to 13.0.3 +- Backward compatible: existing AccessKey authentication works as-is + Copyright 2019 - Unified Redis Platform URP + Unified Redis Platform URP Managed Identity Azure - - + + + + + diff --git a/src/sdk/Core/Multiplexer/UnifiedConnectionMultiplexer.Events.cs b/src/sdk/Core/Multiplexer/UnifiedConnectionMultiplexer.Events.cs index 0c9d84a..9da4f09 100644 --- a/src/sdk/Core/Multiplexer/UnifiedConnectionMultiplexer.Events.cs +++ b/src/sdk/Core/Multiplexer/UnifiedConnectionMultiplexer.Events.cs @@ -1,5 +1,6 @@ using System; using StackExchange.Redis; +using StackExchange.Redis.Maintenance; using System.Collections.Generic; namespace Microsoft.UnifiedRedisPlatform.Core @@ -13,6 +14,7 @@ public partial class UnifiedConnectionMultiplexer public event EventHandler ConfigurationChanged; public event EventHandler ConfigurationChangedBroadcast; public event EventHandler HashSlotMoved; + public event EventHandler ServerMaintenanceEvent; private void SetupEventHandlers() { @@ -53,6 +55,10 @@ private void SetupEventHandlers() connection.HashSlotMoved += (sender, e) => HashSlotMoved(sender, e); connection.HashSlotMoved += (sender, e) => _logger.LogEvent("Redis:HashSlotMoved", 0.0, new Dictionary() { { "OldEndpoint", e.OldEndPoint.ToString() }, { "NewEndpoint", e.NewEndPoint.ToString() }, { "HashSlot", e.HashSlot.ToString() } }); + + connection.ServerMaintenanceEvent += (sender, e) => ServerMaintenanceEvent?.Invoke(sender, e); + connection.ServerMaintenanceEvent += (sender, e) => _logger.LogEvent("Redis:ServerMaintenanceEvent", 0.0, + new Dictionary() { { "RawMessage", e.RawMessage } }); } } } diff --git a/src/sdk/Core/Multiplexer/UnifiedConnectionMultiplexer.Utils.cs b/src/sdk/Core/Multiplexer/UnifiedConnectionMultiplexer.Utils.cs index 74091ac..e71f78c 100644 --- a/src/sdk/Core/Multiplexer/UnifiedConnectionMultiplexer.Utils.cs +++ b/src/sdk/Core/Multiplexer/UnifiedConnectionMultiplexer.Utils.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using System.Linq; using System.Net; using StackExchange.Redis; using System.Threading.Tasks; @@ -39,7 +40,9 @@ public bool IsConnecting } } +#pragma warning disable CS0618 // IncludeDetailInExceptions is obsolete but needed for interface compatibility public bool IncludeDetailInExceptions { get => _baseConnectionMux.IncludeDetailInExceptions; set => _baseConnectionMux.IncludeDetailInExceptions = value; } +#pragma warning restore CS0618 public int StormLogThreshold { get => _baseConnectionMux.StormLogThreshold; set => _baseConnectionMux.StormLogThreshold = value; } public string AppSecret { get; } @@ -80,6 +83,8 @@ public Task CloseAsync(bool allowCommandsToComplete = true) public IServer GetServer(EndPoint endpoint, object asyncState = null) => _baseConnectionMux.GetServer(endpoint, asyncState); + public IServer[] GetServers() => _baseConnectionMux.GetServers(); + public string GetStatus() => _baseConnectionMux.GetStatus(); public void GetStatus(TextWriter log) => _baseConnectionMux.GetStatus(log); @@ -103,6 +108,18 @@ public Task CloseAsync(bool allowCommandsToComplete = true) public T Wait(Task task) => _baseConnectionMux.Wait(task); public void WaitAll(params Task[] tasks) => _baseConnectionMux.WaitAll(tasks); + + public void AddLibraryNameSuffix(string suffix) => _baseConnectionMux.AddLibraryNameSuffix(suffix); + + public ValueTask DisposeAsync() + { + var disposeTasks = new List(); + foreach (var connection in GetAllConnectionMultiplexers()) + { + disposeTasks.Add(connection.DisposeAsync()); + } + return new ValueTask(Task.WhenAll(disposeTasks.Select(vt => vt.AsTask()))); + } #endregion Connection Mux Methods } } diff --git a/src/sdk/Core/Multiplexer/UnifiedConnectionMultiplexer.cs b/src/sdk/Core/Multiplexer/UnifiedConnectionMultiplexer.cs index 54e704e..e4666b4 100644 --- a/src/sdk/Core/Multiplexer/UnifiedConnectionMultiplexer.cs +++ b/src/sdk/Core/Multiplexer/UnifiedConnectionMultiplexer.cs @@ -3,6 +3,10 @@ using StackExchange.Redis; using System.Collections.Generic; using System.Collections.Concurrent; +using System.Threading.Tasks; +using Azure.Core; +using Azure.Identity; +using Microsoft.Azure.StackExchangeRedis; using Microsoft.UnifiedRedisPlatform.Core.Logging; using Microsoft.UnifiedRedisPlatform.Core.Constants; @@ -23,7 +27,7 @@ public sealed partial class UnifiedConnectionMultiplexer : IUnifiedConnectionMul private static readonly ConcurrentBag _pool = new ConcurrentBag(); - private UnifiedConnectionMultiplexer(string clusterName, string appName, string appSecret, ILogger logger = null, string serviceEndpoint = null, string preferredLocation = null) + private UnifiedConnectionMultiplexer(string clusterName, string appName, string appSecret, string serviceEndpoint = null, string preferredLocation = null, string managedIdentityClientId = null) { ClusterName = clusterName; AppName = appName; @@ -31,7 +35,7 @@ private UnifiedConnectionMultiplexer(string clusterName, string appName, string _serviceEndpoint = !string.IsNullOrWhiteSpace(serviceEndpoint) && Uri.IsWellFormedUriString(serviceEndpoint, UriKind.Absolute) ? serviceEndpoint : Constant.OperationApi.DefaultUrl; - _redisConnectionProvider = new RedisConnectionBuilder(_serviceEndpoint, ClusterName, appName, appSecret, preferredLocation); + _redisConnectionProvider = new RedisConnectionBuilder(_serviceEndpoint, ClusterName, appName, appSecret, preferredLocation, managedIdentityClientId); _unifiedConfigurations = _redisConnectionProvider.GetConfiguration().Result; ConnectToBaseMultiplexer(); SetupTelemetry(); @@ -44,7 +48,7 @@ private UnifiedConnectionMultiplexer(UnifiedConfigurationServerOptions serverCon AppSecret = serverConfigurationOptions.AppSecret; _serviceEndpoint = serverConfigurationOptions.ServiceEndpoint; - _redisConnectionProvider = new RedisConnectionBuilder(_serviceEndpoint, ClusterName, AppName, AppSecret, serverConfigurationOptions.Region); + _redisConnectionProvider = new RedisConnectionBuilder(_serviceEndpoint, ClusterName, AppName, AppSecret, serverConfigurationOptions.Region, serverConfigurationOptions.ManagedIdentityClientId); _unifiedConfigurations = _redisConnectionProvider.GetConfiguration(serverConfigurationOptions).Result; ConnectToBaseMultiplexer(); SetupTelemetry(); @@ -58,7 +62,7 @@ private UnifiedConnectionMultiplexer(UnifiedConfigurationLocalOptions localConfi SetupTelemetry(); } - public static UnifiedConnectionMultiplexer Connect(string clusterName, string appName, string appSecret, ILogger logger = null, string serviceEndpoint = null, string preferredLocation = null) + public static UnifiedConnectionMultiplexer Connect(string clusterName, string appName, string appSecret, string serviceEndpoint = null, string preferredLocation = null, string managedIdentityClientId = null) { UnifiedConnectionMultiplexer pooledConnection = _pool.FirstOrDefault(connection => connection.ClusterName == clusterName && connection.AppName == appName); if (pooledConnection != null) @@ -66,15 +70,59 @@ public static UnifiedConnectionMultiplexer Connect(string clusterName, string ap if (!pooledConnection.IsConnected) { pooledConnection.Close(); - pooledConnection = new UnifiedConnectionMultiplexer(clusterName, appName, appSecret, logger, serviceEndpoint, preferredLocation); + pooledConnection = new UnifiedConnectionMultiplexer(clusterName, appName, appSecret, serviceEndpoint, preferredLocation, managedIdentityClientId); } return pooledConnection; } - var newConnection = new UnifiedConnectionMultiplexer(clusterName, appName, appSecret, logger, serviceEndpoint, preferredLocation); + var newConnection = new UnifiedConnectionMultiplexer(clusterName, appName, appSecret, serviceEndpoint, preferredLocation, managedIdentityClientId); _pool.Add(newConnection); return newConnection; } + /// + /// Connects using base configuration options. Automatically determines the correct connection method. + /// This overload provides backward compatibility for clients using UnifiedConfigurationOptions directly. + /// + /// Configuration options (base class, ServerOptions, or LocalOptions) + /// A connected UnifiedConnectionMultiplexer instance + public static UnifiedConnectionMultiplexer Connect(UnifiedConfigurationOptions configurations) + { + if (configurations == null) + throw new ArgumentNullException(nameof(configurations)); + + // If it's already a derived type, use the specific overload + if (configurations is UnifiedConfigurationServerOptions serverOptions) + { + return Connect(serverOptions); + } + else if (configurations is UnifiedConfigurationLocalOptions localOptions) + { + return Connect(localOptions); + } + + // For backward compatibility: treat base UnifiedConfigurationOptions as server options + // This allows existing clients using "new UnifiedConfigurationOptions()" to continue working + var serverConfig = new UnifiedConfigurationServerOptions + { + ClusterName = configurations.ClusterName, + AppName = configurations.AppName, + AppSecret = configurations.AppSecret, + ManagedIdentityClientId = configurations.ManagedIdentityClientId, + ServiceEndpoint = configurations.ServiceEndpoint, + KeyPrefix = configurations.KeyPrefix, + WritePolicy = configurations.WritePolicy, + Region = configurations.Region, + OperationsRetryProtocol = configurations.OperationsRetryProtocol, + ConnectionRetryProtocol = configurations.ConnectionRetryProtocol, + DiagnosticSettings = configurations.DiagnosticSettings, + Logger = configurations.Logger, + BaseConfigurationOptions = configurations.BaseConfigurationOptions, + SecondaryConfigurationsOptions = configurations.SecondaryConfigurationsOptions + }; + + return Connect(serverConfig); + } + public static UnifiedConnectionMultiplexer Connect(UnifiedConfigurationServerOptions serverConfigurations) { UnifiedConnectionMultiplexer pooledConnection = _pool.FirstOrDefault(connection => connection.ClusterName == serverConfigurations.ClusterName && connection.AppName == serverConfigurations.AppName); @@ -93,22 +141,56 @@ public static UnifiedConnectionMultiplexer Connect(UnifiedConfigurationServerOpt } public static UnifiedConnectionMultiplexer Connect(UnifiedConfigurationLocalOptions localConfiguration) + { + return ConnectAsync(localConfiguration).GetAwaiter().GetResult(); + } + + public static async Task ConnectAsync(UnifiedConfigurationLocalOptions localConfiguration) { UnifiedConnectionMultiplexer pooledConnection = _pool.FirstOrDefault(connection => connection.ClusterName == localConfiguration.ClusterName && connection.AppName == localConfiguration.AppName); if (pooledConnection != null) { if (!pooledConnection.IsConnected) { + await ConfigureManagedIdentityIfNeeded(localConfiguration).ConfigureAwait(false); pooledConnection = new UnifiedConnectionMultiplexer(localConfiguration); } return pooledConnection; } + + await ConfigureManagedIdentityIfNeeded(localConfiguration).ConfigureAwait(false); UnifiedConnectionMultiplexer newConnection = new UnifiedConnectionMultiplexer(localConfiguration); _pool.Add(newConnection); return newConnection; } + private static async Task ConfigureManagedIdentityIfNeeded(UnifiedConfigurationLocalOptions localConfiguration) + { + if (localConfiguration.UseManagedIdentity) + { + var credential = CreateTokenCredential(localConfiguration.ManagedIdentityClientId); + await localConfiguration.BaseConfigurationOptions.ConfigureForAzureWithTokenCredentialAsync(credential).ConfigureAwait(false); + + // Also configure secondary connections if any + foreach (var secondaryConfig in localConfiguration.SecondaryConfigurationsOptions) + { + await secondaryConfig.ConfigureForAzureWithTokenCredentialAsync(credential).ConfigureAwait(false); + } + } + } + + private static TokenCredential CreateTokenCredential(string managedIdentityClientId) + { +#if DEBUG + // Use AzureCliCredential for local development (works with VS Code + az login) + return new AzureCliCredential(); +#else + // Use User-Assigned Managed Identity for production + return new ManagedIdentityCredential(managedIdentityClientId); +#endif + } + private void ConnectToBaseMultiplexer() { _unifiedConfigurations.Validate(); diff --git a/src/sdk/Core/RedisConnectionBuilder.cs b/src/sdk/Core/RedisConnectionBuilder.cs index c1552d2..8f68675 100644 --- a/src/sdk/Core/RedisConnectionBuilder.cs +++ b/src/sdk/Core/RedisConnectionBuilder.cs @@ -7,6 +7,8 @@ using Microsoft.UnifiedRedisPlatform.Core.Constants; using Microsoft.UnifiedRedisPlatform.Core.Services.Models; using Microsoft.UnifiedRedisPlatform.Core.Services.Interfaces; +using Azure.Core; +using Azure.Identity; namespace Microsoft.UnifiedRedisPlatform.Core { @@ -16,15 +18,39 @@ internal class RedisConnectionBuilder private readonly string _clusterName; private readonly string _appName; private readonly string _appSecret; + private readonly string _managedIdentityClientId; private readonly IUnifiedRedisPlatformServiceClient _urpClient; + private readonly TokenCredential _tokenCredential; + private readonly bool _useManagedIdentity; + private static readonly string[] RedisScopes = new[] { "https://redis.azure.com/.default" }; - public RedisConnectionBuilder(string serviceEndpoint, string clusterName, string appName, string appSecret, string preferredLocation = null) + public RedisConnectionBuilder(string serviceEndpoint, string clusterName, string appName, string appSecret, string preferredLocation = null, string managedIdentityClientId = null) { _clusterName = clusterName; _appName = appName; _appSecret = appSecret; + _managedIdentityClientId = managedIdentityClientId; _serviceEndpoint = !string.IsNullOrWhiteSpace(serviceEndpoint) ? serviceEndpoint : Constant.OperationApi.DefaultUrl; - _urpClient = new UnifiedRedisPlatformServiceClient(_serviceEndpoint, _clusterName, _appName, _appSecret, preferredLocation); + + // Set MI flag BEFORE creating the service client + _useManagedIdentity = !string.IsNullOrWhiteSpace(managedIdentityClientId); + _urpClient = new UnifiedRedisPlatformServiceClient(_serviceEndpoint, _clusterName, _appName, _appSecret, preferredLocation, _useManagedIdentity); + + if (_useManagedIdentity) + { + _tokenCredential = CreateTokenCredential(managedIdentityClientId); + } + } + + private static TokenCredential CreateTokenCredential(string managedIdentityClientId) + { +#if DEBUG + // Use AzureCliCredential for local development (works with VS Code + az login) + return new AzureCliCredential(); +#else + // Use User-Assigned Managed Identity for production + return new ManagedIdentityCredential(managedIdentityClientId); +#endif } public async Task GetConfiguration() @@ -46,13 +72,13 @@ public async Task GetConfiguration() Region = clusterConfiguration.PrimaryRedisRegion, WritePolicy = applicationConfiguration.WritePolicy }; - unifiedConfiguration.BaseConfigurationOptions = CreateRedisConfigurationOption(primaryConnectionString, applicationConfiguration, isSecondaryConnection: false); + unifiedConfiguration.BaseConfigurationOptions = await CreateRedisConfigurationOptionAsync(primaryConnectionString, applicationConfiguration, isSecondaryConnection: false); if (clusterConfiguration.AreSecondaryConnectionsPresent) { foreach (var secondaryConnectionString in clusterConfiguration.SecondaryRedisConnectionStrings) { - unifiedConfiguration.SecondaryConfigurationsOptions.Add(CreateRedisConfigurationOption(secondaryConnectionString, applicationConfiguration, isSecondaryConnection: true)); + unifiedConfiguration.SecondaryConfigurationsOptions.Add(await CreateRedisConfigurationOptionAsync(secondaryConnectionString, applicationConfiguration, isSecondaryConnection: true)); } } @@ -81,14 +107,14 @@ public async Task GetConfiguration(UnifiedConfigura currentConfiguration.DiagnosticSettings = applicationPreferredConfiguration?.DiagnosticSettings; currentConfiguration.BaseConfigurationOptions = - CreateRedisConfigurationOptionsFromExistingConfigurations(connectionString, currentConfiguration.BaseConfigurationOptions, applicationPreferredConfiguration, isSecondaryConnection: false); + await CreateRedisConfigurationOptionsFromExistingConfigurationsAsync(connectionString, currentConfiguration.BaseConfigurationOptions, applicationPreferredConfiguration, isSecondaryConnection: false); if (clusterPreferredConfiguration.AreSecondaryConnectionsPresent) { currentConfiguration.SecondaryConfigurationsOptions = new List(); // Secondary connections are added from settings foreach(var secondaryConnection in clusterPreferredConfiguration.SecondaryRedisConnectionStrings) { - currentConfiguration.SecondaryConfigurationsOptions.Add(CreateRedisConfigurationOption(secondaryConnection, applicationPreferredConfiguration, isSecondaryConnection: true)); + currentConfiguration.SecondaryConfigurationsOptions.Add(await CreateRedisConfigurationOptionAsync(secondaryConnection, applicationPreferredConfiguration, isSecondaryConnection: true)); } } @@ -96,7 +122,22 @@ public async Task GetConfiguration(UnifiedConfigura } #region Private Builders - private ConfigurationOptions CreateRedisConfigurationOption(string connectionString, ApplicationConfiguration applicationConfiguration, bool isSecondaryConnection) + /// + /// Gets an access token for Redis using Azure Managed Identity. + /// Only called when _useManagedIdentity is true. + /// + private async Task GetRedisAccessTokenAsync() + { + if (!_useManagedIdentity || _tokenCredential == null) + { + return null; + } + var tokenRequestContext = new TokenRequestContext(RedisScopes); + var accessToken = await _tokenCredential.GetTokenAsync(tokenRequestContext, default); + return accessToken.Token; + } + + private async Task CreateRedisConfigurationOptionAsync(string connectionString, ApplicationConfiguration applicationConfiguration, bool isSecondaryConnection) { ConfigurationOptions options = ConfigurationOptions.Parse(connectionString); options.ClientName = $"{_appName}-{Guid.NewGuid().ToString()}"; @@ -106,6 +147,14 @@ private ConfigurationOptions CreateRedisConfigurationOption(string connectionStr options.AbortOnConnectFail = false; options.Ssl = true; + // Use Managed Identity token for Redis authentication if configured + // Otherwise, keep the password from the connection string (legacy behavior) + if (_useManagedIdentity) + { + var token = await GetRedisAccessTokenAsync(); + options.Password = token; + } + options.ReconnectRetryPolicy = applicationConfiguration.ConnectionPreference.ConnectionRetryProtocol; options.ConnectRetry = applicationConfiguration.ConnectionPreference.ConnectionRetryProtocol.MaxRetryCount; options.ConnectTimeout = applicationConfiguration.ConnectionPreference.ConnectionRetryProtocol.TimeoutInMs; @@ -121,7 +170,7 @@ private ConfigurationOptions CreateRedisConfigurationOption(string connectionStr return options; } - private ConfigurationOptions CreateRedisConfigurationOptionsFromExistingConfigurations(string connectionString, ConfigurationOptions existingConfiguration, ApplicationConfiguration applicationConfiguration, bool isSecondaryConnection) + private async Task CreateRedisConfigurationOptionsFromExistingConfigurationsAsync(string connectionString, ConfigurationOptions existingConfiguration, ApplicationConfiguration applicationConfiguration, bool isSecondaryConnection) { var serverConfiguration = ConfigurationOptions.Parse(connectionString); @@ -131,7 +180,6 @@ private ConfigurationOptions CreateRedisConfigurationOptionsFromExistingConfigur } else { - existingConfiguration.Password = serverConfiguration.Password; existingConfiguration.EndPoints.Clear(); foreach (var endpoint in serverConfiguration.EndPoints) { @@ -147,6 +195,18 @@ private ConfigurationOptions CreateRedisConfigurationOptionsFromExistingConfigur existingConfiguration.ResolveDns = serverConfiguration.ResolveDns; } + // Use Managed Identity token for Redis authentication if configured + // Otherwise, keep the password from the connection string (legacy behavior) + if (_useManagedIdentity) + { + var token = await GetRedisAccessTokenAsync(); + existingConfiguration.Password = token; + } + else + { + existingConfiguration.Password = serverConfiguration.Password; + } + existingConfiguration.AllowAdmin = existingConfiguration.AllowAdmin || serverConfiguration.AllowAdmin; diff --git a/src/sdk/Core/Services/UnifiedRedisPlatformServiceClient.cs b/src/sdk/Core/Services/UnifiedRedisPlatformServiceClient.cs index e420e8f..d72d0a7 100644 --- a/src/sdk/Core/Services/UnifiedRedisPlatformServiceClient.cs +++ b/src/sdk/Core/Services/UnifiedRedisPlatformServiceClient.cs @@ -18,22 +18,24 @@ internal class UnifiedRedisPlatformServiceClient : IUnifiedRedisPlatformServiceC private readonly string _appName; private readonly string _appSecret; private readonly string _preferredLocation; + private readonly bool _useManagedIdentity; private readonly IHttpClientFactory _httpClientFactory; private readonly ICache _internalCache; - public UnifiedRedisPlatformServiceClient(string serviceEndpoint, string clusterName, string appName, string appSecret, string preferredLocation, IHttpClientFactory clientFactory, ICache internalCache) + public UnifiedRedisPlatformServiceClient(string serviceEndpoint, string clusterName, string appName, string appSecret, string preferredLocation, bool useManagedIdentity, IHttpClientFactory clientFactory, ICache internalCache) { _serviceEndpoint = serviceEndpoint; _clusterName = clusterName; _appName = appName; _appSecret = appSecret; _preferredLocation = preferredLocation; + _useManagedIdentity = useManagedIdentity; _httpClientFactory = clientFactory; _internalCache = internalCache; } - public UnifiedRedisPlatformServiceClient(string serviceEndpoint, string clusterName, string appName, string appSecret, string preferredLocation) - : this(serviceEndpoint, clusterName, appName, appSecret, preferredLocation, new HttpClientFactory(maxRetry: 10, backOffInterval: 2500), new InternalCache()) + public UnifiedRedisPlatformServiceClient(string serviceEndpoint, string clusterName, string appName, string appSecret, string preferredLocation, bool useManagedIdentity = false) + : this(serviceEndpoint, clusterName, appName, appSecret, preferredLocation, useManagedIdentity, new HttpClientFactory(maxRetry: 10, backOffInterval: 2500), new InternalCache()) { } public async Task GetAuthToken() @@ -91,6 +93,8 @@ await GetAuthToken() request.Headers.Add("Authorization", $"Bearer {authResult.Token}"); if (!string.IsNullOrWhiteSpace(_preferredLocation)) request.Headers.Add("x-location", _preferredLocation); + if (_useManagedIdentity) + request.Headers.Add("x-use-managed-identity", "true"); using (var response = await client.SendAsync(request).ConfigureAwait(false)) { diff --git a/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/Microsoft.Extensions.Caching.UnifiedRedisPlatform.csproj b/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/Microsoft.Extensions.Caching.UnifiedRedisPlatform.csproj index 49e6dff..1d909b2 100644 --- a/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/Microsoft.Extensions.Caching.UnifiedRedisPlatform.csproj +++ b/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/Microsoft.Extensions.Caching.UnifiedRedisPlatform.csproj @@ -2,6 +2,7 @@ netstandard2.0 + 1.0.2 Pratik Bhattacharya Microsoft Unified Redis Platform @@ -9,17 +10,21 @@ https://github.com/microsoft/UnifiedRedisPlatform.Core.git git https://github.com/microsoft/UnifiedRedisPlatform.Core - DistributedCache.Extensions.UnifiedRedisPlatform - IDistributedCache redis azure caching distributed_cache azure-cache-for-redus azure-cache aspnetcore-cache urp unifedredisplatform unifiedredisframework - Initial Release + DistributedCache.Extensions.UnifiedRedisPlatform + IDistributedCache redis azure caching distributed_cache azure-cache-for-redus azure-cache aspnetcore-cache urp unifedredisplatform unifiedredisframework managed-identity + +v1.0.0-rc-2.0: +- Added ManagedIdentityClientId option for Azure AD authentication +- Added ServiceEndpoint option for custom URP service endpoints +- Backward compatible with existing configurations + - + - - - + + diff --git a/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/UnifedRedisCache.Keys.cs b/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/UnifedRedisCache.Keys.cs index c6ba155..65f3808 100644 --- a/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/UnifedRedisCache.Keys.cs +++ b/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/UnifedRedisCache.Keys.cs @@ -6,9 +6,11 @@ namespace Microsoft.Extensions.Caching.UnifiedRedisPlatform { public partial class UnifiedRedisCache { +#pragma warning disable CS0618 // GetKeysAsync is intentionally exposed for cache management scenarios public async Task> GetKeys(string pattern = "") { return await _connectionMultiplexer.GetKeysAsync(pattern); } +#pragma warning restore CS0618 } } diff --git a/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/UnifedRedisPlatformOptions.cs b/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/UnifedRedisPlatformOptions.cs index 1f67e05..422d757 100644 --- a/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/UnifedRedisPlatformOptions.cs +++ b/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/UnifedRedisPlatformOptions.cs @@ -23,11 +23,22 @@ public class UnifedRedisPlatformOptions : IOptions /// public string AppSecret { get; set; } + /// + /// The URP service endpoint URL. Defaults to production endpoint if not specified. + /// + public string ServiceEndpoint { get; set; } + /// /// Preferred location of the primary Redis server /// public string PreferredLocation { get; set; } + /// + /// The client ID of the User-Assigned Managed Identity to use for Redis authentication. + /// Leave null/empty to use access key authentication (backward compatible). + /// + public string ManagedIdentityClientId { get; set; } + /// /// Configuration options for connecting to Unified Redis Cluster /// diff --git a/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/UnifiedRedisCache.cs b/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/UnifiedRedisCache.cs index 7dc2b2e..69f8bc9 100644 --- a/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/UnifiedRedisCache.cs +++ b/src/sdk/Extensions/Microsoft.Extensions.Caching.UnifiedRedisPlatform/UnifiedRedisCache.cs @@ -30,12 +30,13 @@ private IUnifiedConnectionMultiplexer CreateConnection(UnifedRedisPlatformOption } if (string.IsNullOrWhiteSpace(options.Cluster) - || string.IsNullOrWhiteSpace(options.Application) - || string.IsNullOrWhiteSpace(options.AppSecret)) + || string.IsNullOrWhiteSpace(options.Application)) throw new ArgumentNullException("Either ConfigurationOptions or Cluster-App details must be provided"); + string serviceEndpoint = !string.IsNullOrWhiteSpace(options.ServiceEndpoint) ? options.ServiceEndpoint : null; string preferredLocation = !string.IsNullOrWhiteSpace(options.PreferredLocation) ? options.PreferredLocation : null; - connectionMux = UnifiedConnectionMultiplexer.Connect(options.Cluster, options.Application, options.AppSecret, preferredLocation: preferredLocation); + string managedIdentityClientId = !string.IsNullOrWhiteSpace(options.ManagedIdentityClientId) ? options.ManagedIdentityClientId : null; + connectionMux = UnifiedConnectionMultiplexer.Connect(options.Cluster, options.Application, options.AppSecret, serviceEndpoint, preferredLocation, managedIdentityClientId); return connectionMux; } diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/AuthenticateClientCommand.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/AuthenticateClientCommand.cs index fd247fc..c9bb69a 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/AuthenticateClientCommand.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/AuthenticateClientCommand.cs @@ -1,5 +1,5 @@ using System; -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; namespace Microsoft.UnifiedPlatform.Service.Application.Commands { diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/Handlers/AuthenticateClientCommandHandler.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/Handlers/AuthenticateClientCommandHandler.cs index 8ac5ff6..95788f0 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/Handlers/AuthenticateClientCommandHandler.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/Handlers/AuthenticateClientCommandHandler.cs @@ -1,5 +1,5 @@ using System; -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; using System.Threading.Tasks; using System.Collections.Generic; using Microsoft.UnifiedPlatform.Service.Common.Configuration; diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/Handlers/LogClientInfoCommandHandler.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/Handlers/LogClientInfoCommandHandler.cs index 0fecccf..07f7793 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/Handlers/LogClientInfoCommandHandler.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/Handlers/LogClientInfoCommandHandler.cs @@ -1,6 +1,6 @@ using System; using System.Linq; -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; using System.Threading.Tasks; using AppInsights.EnterpriseTelemetry; using System.Collections.Generic; diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/LogClientInfoCommand.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/LogClientInfoCommand.cs index b57bd15..3b03e69 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/LogClientInfoCommand.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Commands/LogClientInfoCommand.cs @@ -1,6 +1,6 @@ using System; using System.Linq; -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; using System.Collections.Generic; using Microsoft.UnifiedPlatform.Service.Common.Models; diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Microsoft.UnifiedPlatform.Service.Application.csproj b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Microsoft.UnifiedPlatform.Service.Application.csproj index 9466692..a6ed372 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Microsoft.UnifiedPlatform.Service.Application.csproj +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Microsoft.UnifiedPlatform.Service.Application.csproj @@ -5,9 +5,9 @@ - - - + + + diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/GetAllClustersQuery.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/GetAllClustersQuery.cs index 90e81aa..5aaf891 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/GetAllClustersQuery.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/GetAllClustersQuery.cs @@ -1,5 +1,5 @@ using System; -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; using System.Collections.Generic; using Microsoft.UnifiedPlatform.Service.Common.Models; diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/GetClusterConfigurationQuery.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/GetClusterConfigurationQuery.cs index dda33ee..4632938 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/GetClusterConfigurationQuery.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/GetClusterConfigurationQuery.cs @@ -1,5 +1,5 @@ using System; -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; using Microsoft.UnifiedPlatform.Service.Common.Models; namespace Microsoft.UnifiedPlatform.Service.Application.Queries @@ -14,11 +14,13 @@ public class GetClusterConfigurationQuery : Query public string ClusterName { get; } public string AppName { get; } public string PreferredLocation { get; set; } + public bool UseManagedIdentity { get; set; } - public GetClusterConfigurationQuery(string clusterName, string appName, string preferredLocation) + public GetClusterConfigurationQuery(string clusterName, string appName, string preferredLocation, bool useManagedIdentity = false) :this(clusterName, appName) { PreferredLocation = preferredLocation; + UseManagedIdentity = useManagedIdentity; } public GetClusterConfigurationQuery(string clusterName, string appName) diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/Handlers/GetAllClustersQueryHandler.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/Handlers/GetAllClustersQueryHandler.cs index cd6e6ad..f89dcc5 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/Handlers/GetAllClustersQueryHandler.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/Handlers/GetAllClustersQueryHandler.cs @@ -1,5 +1,5 @@ using System.Linq; -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; using System.Threading.Tasks; using System.Collections.Generic; using Microsoft.UnifiedPlatform.Service.Common.Models; diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/Handlers/GetClusterConfigurationQueryHandler.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/Handlers/GetClusterConfigurationQueryHandler.cs index 1331904..6ea2dbb 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/Handlers/GetClusterConfigurationQueryHandler.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/Handlers/GetClusterConfigurationQueryHandler.cs @@ -1,5 +1,5 @@ using System.Linq; -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; using Microsoft.AzureRegion; using System.Threading.Tasks; using System.Collections.Generic; @@ -27,7 +27,7 @@ protected override async Task ProcessRequest(GetCluster var applicationConfiguration = await _configurationProvider.GetApplicationDetails(request.ClusterName, request.AppName); clusterConfiguration.Applications = new List() { applicationConfiguration }; - var clusterConnectionStrings = await _configurationProvider.GetClusterConnectionStrings(request.ClusterName, request.AppName); + var clusterConnectionStrings = await _configurationProvider.GetClusterConnectionStrings(request.ClusterName, request.AppName, request.UseManagedIdentity); if (clusterConnectionStrings.Count == 1) { clusterConfiguration.RedisConnectionString = clusterConnectionStrings.First().ConnectionString; diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/Handlers/StreamUncommittedLogsQueryHandler.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/Handlers/StreamUncommittedLogsQueryHandler.cs index 32f4719..7e9fd3a 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/Handlers/StreamUncommittedLogsQueryHandler.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/Handlers/StreamUncommittedLogsQueryHandler.cs @@ -1,4 +1,4 @@ -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; using System.Threading.Tasks; using Microsoft.UnifiedPlatform.Service.Common.Redis; using Microsoft.UnifiedPlatform.Service.Common.Configuration; diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/StreamUncommitedLogsQuery.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/StreamUncommitedLogsQuery.cs index b98c1ed..e74b500 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/StreamUncommitedLogsQuery.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Core/Application/Queries/StreamUncommitedLogsQuery.cs @@ -1,5 +1,5 @@ using System; -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; using System.Threading.Channels; using System.Collections.Generic; using Microsoft.UnifiedPlatform.Service.Common.Models; diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Authentication/Microsoft.UnifiedPlatform.Service.Authentication.csproj b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Authentication/Microsoft.UnifiedPlatform.Service.Authentication.csproj index 006204d..8a3ab3b 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Authentication/Microsoft.UnifiedPlatform.Service.Authentication.csproj +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Authentication/Microsoft.UnifiedPlatform.Service.Authentication.csproj @@ -6,8 +6,8 @@ - - + + diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Configuration/ClusterConfigurationProvider.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Configuration/ClusterConfigurationProvider.cs index 9143cd7..4939db9 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Configuration/ClusterConfigurationProvider.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Configuration/ClusterConfigurationProvider.cs @@ -18,6 +18,14 @@ public ClusterConfigurationProvider(BaseConfigurationProvider configurationProvi _configurationProvider = configurationProvider; } + /// + /// Appends "-msi" suffix to connection string key when MI is enabled + /// + private string GetConnectionStringKey(string baseKey, bool useManagedIdentity) + { + return useManagedIdentity ? $"{baseKey}-msi" : baseKey; + } + public async Task> GetAllClusters() { try @@ -91,14 +99,15 @@ public async Task GetApplicationSecret(string clusterName, string appNam } } - public async Task> GetClusterConnectionStrings(string clusterName, string appName) + public async Task> GetClusterConnectionStrings(string clusterName, string appName, bool useManagedIdentity = false) { var clusterDetails = await GetClusterDetails(clusterName); try { if (clusterDetails.ConnectionStrings == null || !clusterDetails.ConnectionStrings.Any()) { - var redisConnectionString = await _configurationProvider.GetConfiguration(clusterName, "Redis-ConnectionString"); + var connectionStringKey = GetConnectionStringKey("Redis-ConnectionString", useManagedIdentity); + var redisConnectionString = await _configurationProvider.GetConfiguration(clusterName, connectionStringKey); return new List() { new ConnectionStringDto() @@ -117,7 +126,8 @@ public async Task> GetClusterConnectionStrings(string var fetchConnectionStringTask = Task.Run(async () => { var region = connectionStringConfig.Region?.Name; - var connectionStringKey = string.IsNullOrWhiteSpace(region) ? connectionStringConfig.ConnectionStringLocation : $"Redis-ConnectionString-{region}"; + var baseKey = string.IsNullOrWhiteSpace(region) ? connectionStringConfig.ConnectionStringLocation : $"Redis-ConnectionString-{region}"; + var connectionStringKey = GetConnectionStringKey(baseKey, useManagedIdentity); var connectionString = await _configurationProvider.GetConfiguration(clusterName, connectionStringKey); var connectionStringDto = new ConnectionStringDto() { @@ -138,7 +148,8 @@ public async Task> GetClusterConnectionStrings(string } catch (ConfigurationNotFoundException exception) { - throw new IncompleteConfigurationException(clusterName, appName, $"RedisConnectionString: {clusterName}-Redis-ConnectionString", exception); + var suffix = useManagedIdentity ? "-msi" : ""; + throw new IncompleteConfigurationException(clusterName, appName, $"RedisConnectionString: {clusterName}-Redis-ConnectionString{suffix}", exception); } } } diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Configuration/Microsoft.UnifiedPlatform.Service.Configuration.csproj b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Configuration/Microsoft.UnifiedPlatform.Service.Configuration.csproj index 3c5f001..33d4f8b 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Configuration/Microsoft.UnifiedPlatform.Service.Configuration.csproj +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Configuration/Microsoft.UnifiedPlatform.Service.Configuration.csproj @@ -5,9 +5,9 @@ - + - + diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Graph/Microsoft.UnifiedPlatform.Service.Graph.csproj b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Graph/Microsoft.UnifiedPlatform.Service.Graph.csproj index 37720e5..6718cd4 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Graph/Microsoft.UnifiedPlatform.Service.Graph.csproj +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Graph/Microsoft.UnifiedPlatform.Service.Graph.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Redis/Microsoft.UnifiedPlatform.Service.Redis.csproj b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Redis/Microsoft.UnifiedPlatform.Service.Redis.csproj index bd8ae38..0b4d8da 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Redis/Microsoft.UnifiedPlatform.Service.Redis.csproj +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Redis/Microsoft.UnifiedPlatform.Service.Redis.csproj @@ -6,9 +6,10 @@ - - - + + + + diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Redis/RedisConnectionManger.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Redis/RedisConnectionManger.cs index 5fce5a0..89f0648 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Redis/RedisConnectionManger.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Redis/RedisConnectionManger.cs @@ -1,27 +1,105 @@ -using StackExchange.Redis; -using System.Collections.Generic; +using Azure.Core; +using Azure.Identity; +using Microsoft.Azure.StackExchangeRedis; +using StackExchange.Redis; +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; using Microsoft.UnifiedPlatform.Service.Common.Redis; +using AppInsights.EnterpriseTelemetry; +using AppInsights.EnterpriseTelemetry.Context; namespace Microsoft.UnifiedPlatform.Service.Redis { public class RedisConnectionManger: IRedisConnectionManager { - public static Dictionary Multiplexers = new Dictionary(); + private static readonly ConcurrentDictionary Multiplexers = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary ConnectionLocks = new ConcurrentDictionary(); + private readonly TokenCredential _tokenCredential; + private readonly bool _useManagedIdentity; + private readonly ILogger _logger; - public IConnectionMultiplexer CreateConnection(string connectionString) + public RedisConnectionManger(string managedIdentityClientId = null, ILogger logger = null) { - if (Multiplexers.ContainsKey(connectionString)) + _logger = logger; + _useManagedIdentity = !string.IsNullOrWhiteSpace(managedIdentityClientId); + + if (_useManagedIdentity) { - var mux = Multiplexers[connectionString]; - if (mux.IsConnected) - return mux; - mux = ConnectionMultiplexer.Connect(connectionString); - return mux; + _tokenCredential = CreateTokenCredential(managedIdentityClientId); } - var newMux = ConnectionMultiplexer.Connect(connectionString); - Multiplexers.Add(connectionString, newMux); - return newMux; + } + + private static TokenCredential CreateTokenCredential(string managedIdentityClientId) + { +#if DEBUG + // Use AzureCliCredential for local development (works with VS Code + az login) + return new AzureCliCredential(); +#else + return new ManagedIdentityCredential(managedIdentityClientId); +#endif + } + + public async Task CreateConnectionAsync(string connectionString) + { + var redisHost = connectionString?.Split(',')[0] ?? "unknown"; + + // Fast path: return existing healthy connection + if (Multiplexers.TryGetValue(connectionString, out var existingMux) && existingMux.IsConnected) + return existingMux; + + // Get or create a lock for this connection string to prevent duplicate connections + var connectionLock = ConnectionLocks.GetOrAdd(connectionString, _ => new SemaphoreSlim(1, 1)); + await connectionLock.WaitAsync().ConfigureAwait(false); + + try + { + // Double-check after acquiring lock + if (Multiplexers.TryGetValue(connectionString, out existingMux) && existingMux.IsConnected) + return existingMux; + + var perfContext = new PerformanceContext($"RedisConnection:{(_useManagedIdentity ? "MI" : "AccessKey")}"); + perfContext.Start(); + try + { + var options = await CreateConfigurationOptionsAsync(connectionString).ConfigureAwait(false); + var mux = await ConnectionMultiplexer.ConnectAsync(options).ConfigureAwait(false); + Multiplexers[connectionString] = mux; + + perfContext.Stop(); + perfContext.Properties["RedisHost"] = redisHost; + _logger?.Log(perfContext); + + return mux; + } + catch (Exception ex) + { + perfContext.Stop(); + _logger?.Log(new ExceptionContext { Exception = ex }); + throw; + } + } + finally + { + connectionLock.Release(); + } + } + + private async Task CreateConfigurationOptionsAsync(string connectionString) + { + var options = ConfigurationOptions.Parse(connectionString); + options.AbortOnConnectFail = false; + options.Ssl = true; + + if (_useManagedIdentity && _tokenCredential != null) + { + await options.ConfigureForAzureWithTokenCredentialAsync(_tokenCredential); + } + + return options; } } } diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Redis/RedisStream.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Redis/RedisStream.cs index f16807d..6eeaa4a 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Redis/RedisStream.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Redis/RedisStream.cs @@ -35,7 +35,7 @@ public async Task StreamLogs(ClusterConfigurationDto clusterConfiguration, AppCo private async Task StreamLogsFromRedisConnection(string redisConnectionString, string listKey, Channel> logsChannel, int batchSize, bool commitLog, bool closeChannel) { - var connection = _redisConnectionManager.CreateConnection(redisConnectionString); + var connection = await _redisConnectionManager.CreateConnectionAsync(redisConnectionString); var database = connection.GetDatabase(); ChannelWriter> logsChannelWriter = logsChannel.Writer; diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Secrets/Microsoft.UnifiedPlatform.Service.Secrets.csproj b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Secrets/Microsoft.UnifiedPlatform.Service.Secrets.csproj index 9900337..0501fe7 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Secrets/Microsoft.UnifiedPlatform.Service.Secrets.csproj +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Secrets/Microsoft.UnifiedPlatform.Service.Secrets.csproj @@ -5,10 +5,10 @@ - - + + - + diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Secrets/libman.json b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Secrets/libman.json index ceee271..3a8c617 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Secrets/libman.json +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Secrets/libman.json @@ -1,4 +1,4 @@ -{ +{ "version": "1.0", "defaultProvider": "cdnjs", "libraries": [] diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Storage/Client/StorageClientManager.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Storage/Client/StorageClientManager.cs index 946ae96..1992374 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Storage/Client/StorageClientManager.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Storage/Client/StorageClientManager.cs @@ -19,11 +19,12 @@ public StorageClientManager(IDefaultAzureCredentialProvider defaultAzureCredenti { _defaultAzureCredentialProvider = defaultAzureCredentialProvider; var storageaccountName = configuration?.StorageAccountName; + var userAssignedClientId = configuration?.UserAssignedClientId; // Create BlobServiceClient with managed identity - _blobServiceClient = new BlobServiceClient(new Uri(string.Format("https://{0}.blob.core.windows.net", storageaccountName)), _defaultAzureCredentialProvider.GetDefaultAzureCredential()); + _blobServiceClient = new BlobServiceClient(new Uri(string.Format("https://{0}.blob.core.windows.net", storageaccountName)), _defaultAzureCredentialProvider.GetDefaultAzureCredential(userAssignedClientId)); // Create TableServiceClient with managed identity - _tableServiceClient = new TableServiceClient(new Uri(string.Format("https://{0}.table.core.windows.net", storageaccountName)), _defaultAzureCredentialProvider.GetDefaultAzureCredential()); + _tableServiceClient = new TableServiceClient(new Uri(string.Format("https://{0}.table.core.windows.net", storageaccountName)), _defaultAzureCredentialProvider.GetDefaultAzureCredential(userAssignedClientId)); } public async Task CreateTable(string tableName) diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Storage/Microsoft.UnifiedPlatform.Storage.csproj b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Storage/Microsoft.UnifiedPlatform.Storage.csproj index 239fb33..1a357b8 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Storage/Microsoft.UnifiedPlatform.Storage.csproj +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Storage/Microsoft.UnifiedPlatform.Storage.csproj @@ -1,4 +1,4 @@ - + netstandard2.1 @@ -6,10 +6,10 @@ - + - + diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Storage/libman.json b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Storage/libman.json index ceee271..3a8c617 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Storage/libman.json +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Infrastructure/Storage/libman.json @@ -1,4 +1,4 @@ -{ +{ "version": "1.0", "defaultProvider": "cdnjs", "libraries": [] diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Library/AzureRegion/Microsoft.AzureRegion.csproj b/src/service/Microsoft.UnifiedRedisPlatform.Service/Library/AzureRegion/Microsoft.AzureRegion.csproj index bb94296..249373c 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Library/AzureRegion/Microsoft.AzureRegion.csproj +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Library/AzureRegion/Microsoft.AzureRegion.csproj @@ -5,9 +5,9 @@ - + - + diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/NuGet.Config b/src/service/Microsoft.UnifiedRedisPlatform.Service/NuGet.Config index dc0a42f..19bcbee 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/NuGet.Config +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/NuGet.Config @@ -3,6 +3,7 @@ + diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Configuration/IClusterConfigurationProvider.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Configuration/IClusterConfigurationProvider.cs index 2c7148b..c6c5e34 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Configuration/IClusterConfigurationProvider.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Configuration/IClusterConfigurationProvider.cs @@ -11,6 +11,6 @@ public interface IClusterConfigurationProvider Task GetClusterDetails(string clusterName); Task GetApplicationDetails(string clusterName, string appName); Task GetApplicationSecret(string clusterName, string appName); - Task> GetClusterConnectionStrings(string clusterName, string appName); + Task> GetClusterConnectionStrings(string clusterName, string appName, bool useManagedIdentity = false); } } diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Configuration/StorageConfiguration.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Configuration/StorageConfiguration.cs index 323493b..5161db3 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Configuration/StorageConfiguration.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Configuration/StorageConfiguration.cs @@ -10,5 +10,6 @@ public class StorageConfiguration: BaseConfiguration public TimeSpan BackoffInternal { get; set; } public int MaxAttempt { get; set; } public string ConfigurationTableName { get; set; } + public string UserAssignedClientId { get; set; } } } diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Helpers/DefaultAzureCredentialProvider.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Helpers/DefaultAzureCredentialProvider.cs index 9686a6a..0ad0f53 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Helpers/DefaultAzureCredentialProvider.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Helpers/DefaultAzureCredentialProvider.cs @@ -4,11 +4,17 @@ namespace Microsoft.UnifiedPlatform.Service.Common.Helpers { + /// + /// Credential provider for local development/debugging only. + /// Uses VisualStudioCredential to authenticate via Visual Studio login. + /// This provider is only registered in DEBUG builds. + /// [ExcludeFromCodeCoverage] public class DefaultAzureCredentialProvider : IDefaultAzureCredentialProvider { - public TokenCredential GetDefaultAzureCredential(string userManagedIdentity) + public TokenCredential GetDefaultAzureCredential(string userManagedIdentity = "") { + // For local development, use Visual Studio credentials return new VisualStudioCredential(); } } diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Helpers/UserAssignedIdentityCredentialProvider.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Helpers/UserAssignedIdentityCredentialProvider.cs index cfccaf4..dac36a9 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Helpers/UserAssignedIdentityCredentialProvider.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Helpers/UserAssignedIdentityCredentialProvider.cs @@ -1,16 +1,32 @@ using Azure.Core; using Azure.Identity; +using System; using System.Diagnostics.CodeAnalysis; namespace Microsoft.UnifiedPlatform.Service.Common.Helpers { + /// + /// Credential provider for production/Release builds only. + /// Uses ManagedIdentityCredential with user-assigned managed identity. + /// This provider is only registered in Release builds. + /// [ExcludeFromCodeCoverage] public class UserAssignedIdentityCredentialProvider : IDefaultAzureCredentialProvider { - public TokenCredential GetDefaultAzureCredential(string userAssignedClientId) + public TokenCredential GetDefaultAzureCredential(string userAssignedClientId = "") { + // Use provided value, or fall back to AZURE_CLIENT_ID environment variable + var clientId = !string.IsNullOrEmpty(userAssignedClientId) + ? userAssignedClientId + : Environment.GetEnvironmentVariable("AZURE_CLIENT_ID"); + + if (string.IsNullOrEmpty(clientId)) + { + // Fall back to system-assigned managed identity + return new ManagedIdentityCredential(); + } return new ManagedIdentityCredential( - ManagedIdentityId.FromUserAssignedClientId(userAssignedClientId)); + ManagedIdentityId.FromUserAssignedClientId(clientId)); } } } diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Microsoft.UnifiedPlatform.Service.Common.csproj b/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Microsoft.UnifiedPlatform.Service.Common.csproj index f70d10d..333f64f 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Microsoft.UnifiedPlatform.Service.Common.csproj +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Microsoft.UnifiedPlatform.Service.Common.csproj @@ -7,9 +7,9 @@ - + - + diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Redis/IRedisConnectionManager.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Redis/IRedisConnectionManager.cs index 8e60bc8..d7a7d3b 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Redis/IRedisConnectionManager.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/SharedKernel/Common/Redis/IRedisConnectionManager.cs @@ -1,9 +1,10 @@ using StackExchange.Redis; +using System.Threading.Tasks; namespace Microsoft.UnifiedPlatform.Service.Common.Redis { public interface IRedisConnectionManager { - IConnectionMultiplexer CreateConnection(string connectionString); + Task CreateConnectionAsync(string connectionString); } } diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Claims/UserClaimsTransformer.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Claims/UserClaimsTransformer.cs index 79fa869..e59ac77 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Claims/UserClaimsTransformer.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Claims/UserClaimsTransformer.cs @@ -1,5 +1,5 @@ using Microsoft.AspNetCore.Authentication; -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; using System; using System.Collections.Generic; using System.Linq; diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/BaseController.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/BaseController.cs index f54bf2c..0abf060 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/BaseController.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/BaseController.cs @@ -49,10 +49,7 @@ protected string GetPreferredLocation() protected void AddHeaderValue(string headerKey, string headerValue) { - if (Request.Headers.ContainsKey(headerKey)) - Request.Headers[headerKey] = headerValue; - else - Request.Headers.Add(headerKey, new Extensions.Primitives.StringValues(headerValue)); + Request.Headers[headerKey] = headerValue; } protected string GetSingleHeaderValue(string headerKey) diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/ConfigurationController.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/ConfigurationController.cs index 247615a..234f612 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/ConfigurationController.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/ConfigurationController.cs @@ -1,4 +1,5 @@ -using CQRS.Mediatr.Lite; +using System.Linq; +using Microsoft.CQRS; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Routing; @@ -27,8 +28,9 @@ public async Task Get() var clusterName = GetClusterFromClaims(); var appName = GetAppFromClaims(); var preferredLocaltion = GetPreferredLocation(); + var useManagedIdentity = Request.Headers["x-use-managed-identity"].FirstOrDefault()?.Equals("true", System.StringComparison.OrdinalIgnoreCase) ?? false; - var query = new GetClusterConfigurationQuery(clusterName, appName, preferredLocaltion) + var query = new GetClusterConfigurationQuery(clusterName, appName, preferredLocaltion, useManagedIdentity) { CorrelationId = GetCorrelationId(), TransactionId = GetTransactionId() diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/LogsController.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/LogsController.cs index f13dc0e..2315d91 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/LogsController.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/LogsController.cs @@ -1,5 +1,5 @@ using System.Linq; -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using System.Collections.Generic; diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/TokenController.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/TokenController.cs index f9b1b33..19f05ee 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/TokenController.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Controllers/TokenController.cs @@ -1,5 +1,5 @@ using System.Linq; -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using AppInsights.EnterpriseTelemetry.Web.Extension.Filters; diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/ExceptionHandler/GlobalExceptionHandler.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/ExceptionHandler/GlobalExceptionHandler.cs index f581347..34c6b56 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/ExceptionHandler/GlobalExceptionHandler.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/ExceptionHandler/GlobalExceptionHandler.cs @@ -1,7 +1,7 @@ using System; using System.Net; using Microsoft.AspNetCore.Http; -using CQRS.Mediatr.Lite.Exceptions; +using Microsoft.CQRS.Exceptions; using AppInsights.EnterpriseTelemetry; using Microsoft.AspNetCore.Http.Features; using Microsoft.Extensions.Configuration; @@ -13,10 +13,10 @@ namespace Microsoft.UnifiedRedisPlatform.Service.API.ExceptionHandler { public class GlobalExceptionHandler : IGlobalExceptionHandler { - private readonly ILogger _logger; + private readonly AppInsights.EnterpriseTelemetry.ILogger _logger; private readonly string _correlationIdHeader; - public GlobalExceptionHandler(ILogger logger, IConfiguration configuration) + public GlobalExceptionHandler(AppInsights.EnterpriseTelemetry.ILogger logger, IConfiguration configuration) { _logger = logger; _correlationIdHeader = configuration.GetValue("Application:CorrelationIdHeaderKey"); diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Microsoft.UnifiedRedisPlatform.Service.API.csproj b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Microsoft.UnifiedRedisPlatform.Service.API.csproj index b1a92a5..dc67a5a 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Microsoft.UnifiedRedisPlatform.Service.API.csproj +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Microsoft.UnifiedRedisPlatform.Service.API.csproj @@ -1,22 +1,20 @@  - netcoreapp3.1 - InProcess + net8.0 + InProcess /subscriptions/05a315f7-744f-4692-b9dd-1aed7c6cee64/resourcegroups/RG-FieldExperiencePlatform-Dev-01/providers/microsoft.insights/components/aifxpdev /subscriptions/05a315f7-744f-4692-b9dd-1aed7c6cee64/resourcegroups/RG-FieldExperiencePlatform-Dev-01/providers/microsoft.insights/components/aifxpdev - - - - - - - - - + + + + + + + diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/PS-PreProd-01-FXP-ConfigSvc-SIT.json b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/PS-PreProd-01-FXP-ConfigSvc-SIT.json index 2ae93c0..da01533 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/PS-PreProd-01-FXP-ConfigSvc-SIT.json +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/PS-PreProd-01-FXP-ConfigSvc-SIT.json @@ -1,4 +1,4 @@ -{ +{ "AppName": "FXP-ConfigSvc-SIT", "SupportContact": "fxpswe@microsoft.com", "RedisCachePrefix": "fcsit", diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/PS-PreProd-01-FXP-ConfigSvc.json b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/PS-PreProd-01-FXP-ConfigSvc.json index 773db27..8bb9527 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/PS-PreProd-01-FXP-ConfigSvc.json +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/PS-PreProd-01-FXP-ConfigSvc.json @@ -1,4 +1,4 @@ -{ +{ "AppName": "FXP-ConfigSvc", "SupportContact": "fxpswe@microsoft.com", "RedisCachePrefix": "fcfg", diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Properties/launchSettings.json b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Properties/launchSettings.json index 0bee7a6..27b51b1 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Properties/launchSettings.json +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/Properties/launchSettings.json @@ -1,4 +1,4 @@ -{ +{ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/appsettings.Development.json b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/appsettings.Development.json index 8053e64..af8c109 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/appsettings.Development.json +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/appsettings.Development.json @@ -1,4 +1,4 @@ -{ +{ "Logging": { "LogLevel": { "Default": "Debug", @@ -7,7 +7,7 @@ } }, "ApplicationInsights": { - "InstrumentationKey": "c57f485a-5a59-417f-93ab-4424e855856d", + "InstrumentationKey": "d4c48d4e-d318-46ff-8281-dd7f13de407f", "TraceLevel": "0" }, "KeyVault": { diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/appsettings.Test.json b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/appsettings.Test.json index 9ad0405..c5c35f3 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/appsettings.Test.json +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/appsettings.Test.json @@ -1,4 +1,4 @@ -{ +{ "Logging": { "LogLevel": { "Default": "Debug", diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/appsettings.json b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/appsettings.json index 8d5deae..6bbb3d1 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/appsettings.json +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/API/appsettings.json @@ -1,4 +1,4 @@ -{ +{ "Logging": { "LogLevel": { "Default": "Information", @@ -80,7 +80,7 @@ "Issuer": "Microsoft" }, "LocalDebugging": { - "CertificateThumbprint": "27d6d3122675fcc4fe11e4977a540fc74169e1f1" + "CertificateThumbprint": "e1f9d7ed25869552124a068432b3ca33285a5cdc" }, "UserAssignedClientId": "ddcbb4aa-01a9-46aa-8c11-03e1b789d9cd" }, diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Dependencies/CommonDependencyModule.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Dependencies/CommonDependencyModule.cs index 38214c9..d359cd9 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Dependencies/CommonDependencyModule.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Dependencies/CommonDependencyModule.cs @@ -1,7 +1,7 @@ using Autofac; using System.Linq; using Autofac.Core; -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; using Microsoft.AzureRegion; using System.Collections.Generic; using Microsoft.UnifiedPlatform.Storage; @@ -156,10 +156,6 @@ protected virtual void RegisterStorageConfigurationProvider(ContainerBuilder bui (pi, ctx) => pi.Name.ToLowerInvariant() == "secretConfigurationProvider".ToLowerInvariant(), (pi, ctx) => ctx.ResolveKeyed(SecretsConfigurationProviderKey))); - builder.RegisterType() - .As() - .SingleInstance(); - builder.Register(ctx => { var storageConfigResolver = ctx.Resolve>(); @@ -244,6 +240,12 @@ protected virtual void RegisterRedisProviders(ContainerBuilder builder) { builder.RegisterType() .As() + .WithParameter(new ResolvedParameter( + (pi, ctx) => pi.Name == "managedIdentityClientId", + (pi, ctx) => ctx.ResolveKeyed(AppSettingsConfigurationProviderKey).GetConfiguration("Authentication", "UserAssignedClientId").Result)) + .WithParameter(new ResolvedParameter( + (pi, ctx) => pi.Name == "logger", + (pi, ctx) => ctx.ResolveOptional())) .SingleInstance(); builder.RegisterType() diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Dependencies/Microsoft.UnifiedRedisPlatform.Service.Dependencies.csproj b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Dependencies/Microsoft.UnifiedRedisPlatform.Service.Dependencies.csproj index 930eb0c..6e43714 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Dependencies/Microsoft.UnifiedRedisPlatform.Service.Dependencies.csproj +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Dependencies/Microsoft.UnifiedRedisPlatform.Service.Dependencies.csproj @@ -1,11 +1,11 @@ - + netstandard2.1 - + diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/CommitLogs.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/CommitLogs.cs index e051643..e0638fe 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/CommitLogs.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/CommitLogs.cs @@ -1,7 +1,7 @@ -using System; +using System; using Autofac; using System.Linq; -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; using Microsoft.AspNetCore.Mvc; diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/GetConfigurations.cs b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/GetConfigurations.cs index 59ad455..9e1ddc0 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/GetConfigurations.cs +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/GetConfigurations.cs @@ -1,7 +1,7 @@ -using System; +using System; using Autofac; using System.Linq; -using CQRS.Mediatr.Lite; +using Microsoft.CQRS; using System.Threading.Tasks; using Microsoft.Azure.WebJobs; using Microsoft.AspNetCore.Mvc; diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/Microsoft.UnifiedRedisPlatform.Service.Function.csproj b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/Microsoft.UnifiedRedisPlatform.Service.Function.csproj index 88913b1..f874013 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/Microsoft.UnifiedRedisPlatform.Service.Function.csproj +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/Microsoft.UnifiedRedisPlatform.Service.Function.csproj @@ -1,16 +1,13 @@  - netcoreapp3.1 - v3 + net8.0 + v4 + enable - + - - - - - + @@ -20,6 +17,9 @@ Always + + Always + PreserveNewest diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/PS-PreProd-01-FXP-ConfigSvc-SIT.json b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/PS-PreProd-01-FXP-ConfigSvc-SIT.json index 2ae93c0..da01533 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/PS-PreProd-01-FXP-ConfigSvc-SIT.json +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/PS-PreProd-01-FXP-ConfigSvc-SIT.json @@ -1,4 +1,4 @@ -{ +{ "AppName": "FXP-ConfigSvc-SIT", "SupportContact": "fxpswe@microsoft.com", "RedisCachePrefix": "fcsit", diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/PS-PreProd-01-FXP-ConfigSvc.json b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/PS-PreProd-01-FXP-ConfigSvc.json index 773db27..8bb9527 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/PS-PreProd-01-FXP-ConfigSvc.json +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/PS-PreProd-01-FXP-ConfigSvc.json @@ -1,4 +1,4 @@ -{ +{ "AppName": "FXP-ConfigSvc", "SupportContact": "fxpswe@microsoft.com", "RedisCachePrefix": "fcfg", diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/Properties/launchSettings.json b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/Properties/launchSettings.json new file mode 100644 index 0000000..78c0b42 --- /dev/null +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/Properties/launchSettings.json @@ -0,0 +1,12 @@ +{ + "profiles": { + "Microsoft.UnifiedRedisPlatform.Service.Function": { + "commandName": "Project", + "commandLineArgs": "start --csharp", + "environmentVariables": { + "AZURE_FUNCTIONS_ENVIRONMENT": "Development", + "WEBSITE_SITE_NAME": "UnifiedRedisPlatform-Local" + } + } + } +} diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/appsettings.Development.json b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/appsettings.Development.json new file mode 100644 index 0000000..82579ee --- /dev/null +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/appsettings.Development.json @@ -0,0 +1,90 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Debug", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + }, + "ClientSideErrorSuppressionEnabled": true, + "EnvironmentInitializerEnabled": false, + "ResponseCodeTranslationEnabled": true, + "AutoTrackingEnabled": true, + "RequestTelemetryEnhanced": true, + "RequestBodyTrackingEnabled": true, + "ResponseBodyTrackingEnabled": false, + "PropertySplittingEnabled": false, + "ExceptionTrimmingEnabled": true, + "MaxExceptionDepth": 20, + "MaxMessageSize": 2000, + "MaxPropertySize": 8192, + "Properties": { + "CorrelationId": "XCV", + "EndToEnd": "E2E", + "Tenant": "Tenant", + "TransactionId": "MessageId", + "User": "User" + } + }, + "Application": { + "EnvironmentName": "Development", + "ServiceOffering": "Professional Services", + "ServiceLine": "Global Capacity Management", + "Service": "Field Experience Platform", + "Capability": "Unified Redis Platform Service", + "Component": "Field Experience Platform", + "ComponentId": "8a33c96b-7377-4cd4-99c4-7287d3c6915a", + "IctoId": "ICTO-11595", + "AllowedHosts": "*", + "TenantNameHeaderKey": "x-ms-tenant", + "CorrelationIdHeaderKey": "x-correlationid", + "TransactionIdHeaderKey": "x-messageid", + "EndToEndTrackingHeaderKey": "x-e2e-id", + "SecretsHeaderKey": "x-UPR-secret", + "PreferredLocationHeaderKey": "x-location", + "Region": { + "Name": "eastus", + "DisplayName": "East US" + } + }, + "ItTelemetryExtensions": { + "ServiceOffering": "Professional Services", + "ServiceLine": "Global Capacity Management", + "Service": "Field Experience Platform", + "Component": "ESXP Delegation", + "ComponentId": "8a33c96b-7377-4cd4-99c4-7287d3c6915a", + "CorrelationKey": "x-correlationid", + "EnvironmentName": "Development" + }, + "Claims": { + "ClusterKey": "cluster", + "AppKey": "app" + }, + "AllowedHosts": "*", + "KeyVault": { + "Name": "kv-unifiedredisplat-eus" + }, + "Authentication": { + "AAD": { + "Authority": "https://login.microsoftonline.com/microsoft.onmicrosoft.com", + "Audience": "6f40053e-5319-40e5-a90b-6f714506d96d" + }, + "RedisCluster": { + "Audience": "UnifiedRedisPlatform", + "Issuer": "Microsoft" + }, + "LocalDebugging": { + "CertificateThumbprint": "" + }, + "UserAssignedClientId": "" + }, + "ApplicationInsights": { + "InstrumentationKey": "d4c48d4e-d318-46ff-8281-dd7f13de407f", + "TraceLevel": "0" + }, + "Storage": { + "Name": "fxpstorageprodeus", + "ConfigurationTable": "UnifiedRedisPlatform", + "BackoffInterval": 1000, + "MaxAttempt": 10 + } +} diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/appsettings.json b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/appsettings.json index 19d4991..9343caa 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/appsettings.json +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/appsettings.json @@ -1,4 +1,4 @@ -{ +{ "Logging": { "LogLevel": { "Default": "Information", @@ -71,7 +71,8 @@ "RedisCluster": { "Audience": "UnifiedRedisPlatform", "Issuer": "Microsoft" - } + }, + "UserAssignedClientId": "ddcbb4aa-01a9-46aa-8c11-03e1b789d9cd" }, "ApplicationInsights": { "InstrumentationKey": "d4c48d4e-d318-46ff-8281-dd7f13de407f", diff --git a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/host.json b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/host.json index b9f92c0..ed2a8ce 100644 --- a/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/host.json +++ b/src/service/Microsoft.UnifiedRedisPlatform.Service/Web/Function/host.json @@ -1,3 +1,3 @@ -{ +{ "version": "2.0" } \ No newline at end of file diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/FunctionalTests/ConfigurationTests.cs b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/FunctionalTests/ConfigurationTests.cs index 611b687..faea832 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/FunctionalTests/ConfigurationTests.cs +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/FunctionalTests/ConfigurationTests.cs @@ -1,4 +1,4 @@ -using System.Linq; +using System.Linq; using System.Threading.Tasks; using System.Collections.Generic; using Microsoft.VisualStudio.TestTools.UnitTesting; diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/FunctionalTests/Microsoft.UnifiedRedisPlatform.FunctionalTests.csproj b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/FunctionalTests/Microsoft.UnifiedRedisPlatform.FunctionalTests.csproj index 731aeb2..dd50adf 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/FunctionalTests/Microsoft.UnifiedRedisPlatform.FunctionalTests.csproj +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/FunctionalTests/Microsoft.UnifiedRedisPlatform.FunctionalTests.csproj @@ -1,4 +1,4 @@ - + netcoreapp3.1 diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/LocalRedisTest/LocalRedisTest.csproj b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/LocalRedisTest/LocalRedisTest.csproj new file mode 100644 index 0000000..238b794 --- /dev/null +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/LocalRedisTest/LocalRedisTest.csproj @@ -0,0 +1,14 @@ + + + + Exe + net8.0 + Microsoft.UnifiedRedisPlatform.LocalTest + + + + + + + + diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/LocalRedisTest/Program.cs b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/LocalRedisTest/Program.cs new file mode 100644 index 0000000..fdc9219 --- /dev/null +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/LocalRedisTest/Program.cs @@ -0,0 +1,706 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Azure.Identity; +using Microsoft.Azure.StackExchangeRedis; +using Microsoft.UnifiedPlatform.Service.Redis; +using Microsoft.UnifiedRedisPlatform.Core; +using StackExchange.Redis; + +// Alias to avoid conflict with Microsoft.Azure.StackExchangeRedis namespace +using AzureIdentity = Azure.Identity; + +namespace Microsoft.UnifiedRedisPlatform.LocalTest +{ + /// + /// Comprehensive test program for RedisConnectionManager with Managed Identity + /// Tests all Redis operations using Azure AD authentication + /// + class Program + { + // POC Redis Cache - Update this for your environment + private const string RedisHost = "cache-urp-mi-test-01.redis.cache.windows.net"; + private const int RedisPort = 6380; + + private static int _passedTests = 0; + private static int _failedTests = 0; + private static readonly List _failedTestNames = new List(); + private static bool _sdkConnectionFailed = false; + private static string _sdkConnectionError = null; + + static async Task Main(string[] args) + { + Console.WriteLine("==========================================="); + Console.WriteLine("Redis Managed Identity - Comprehensive Tests"); + Console.WriteLine($"Target: {RedisHost}:{RedisPort}"); + Console.WriteLine($"Time: {DateTime.UtcNow:O}"); + Console.WriteLine("===========================================\n"); + + RedisConnectionManger connectionManager = null; + IConnectionMultiplexer connection = null; + IDatabase db = null; + + try + { + // Test 1: Create RedisConnectionManager + await RunTest("Create RedisConnectionManager", async () => + { + // Pass a non-empty string to enable MI mode (uses VisualStudioCredential in DEBUG) + // In production, this would be the actual User-Assigned MI Client ID + connectionManager = new RedisConnectionManger(managedIdentityClientId: "use-visual-studio-credential"); + Console.WriteLine(" Using VisualStudioCredential for authentication (DEBUG mode)"); + await Task.CompletedTask; + }); + + // Test 2: Connect with Managed Identity + await RunTest("Connect with Azure AD/MI", async () => + { + var connectionString = $"{RedisHost}:{RedisPort}"; + connection = await connectionManager.CreateConnectionAsync(connectionString); + + if (!connection.IsConnected) + throw new Exception("Connection created but not connected"); + + var endpoints = connection.GetEndPoints(); + Console.WriteLine($" Connected to: {string.Join(", ", endpoints.Select(e => e.ToString()))}"); + db = connection.GetDatabase(); + }); + + // Test 3: PING + await RunTest("PING Command", async () => + { + var sw = Stopwatch.StartNew(); + var pingResult = await db.PingAsync(); + sw.Stop(); + Console.WriteLine($" Response time: {pingResult.TotalMilliseconds:F2}ms (total: {sw.ElapsedMilliseconds}ms)"); + }); + + // Test 4: String SET/GET + await RunTest("String SET/GET Operations", async () => + { + var key = $"urp:mi:test:string:{Guid.NewGuid():N}".Substring(0, 40); + var value = $"Test value created at {DateTime.UtcNow:O}"; + + await db.StringSetAsync(key, value, TimeSpan.FromMinutes(5)); + var retrieved = await db.StringGetAsync(key); + + if (retrieved != value) + throw new Exception($"Value mismatch: expected '{value}', got '{retrieved}'"); + + await db.KeyDeleteAsync(key); + Console.WriteLine($" Key: {key}"); + }); + + // Test 5: Hash Operations + await RunTest("Hash Operations (HSET/HGET/HGETALL)", async () => + { + var hashKey = $"urp:mi:test:hash:{Guid.NewGuid():N}".Substring(0, 40); + + await db.HashSetAsync(hashKey, new HashEntry[] + { + new HashEntry("field1", "value1"), + new HashEntry("field2", "value2"), + new HashEntry("field3", "value3") + }); + + var field1 = await db.HashGetAsync(hashKey, "field1"); + if (field1 != "value1") + throw new Exception($"Hash field mismatch: expected 'value1', got '{field1}'"); + + var allFields = await db.HashGetAllAsync(hashKey); + if (allFields.Length != 3) + throw new Exception($"Expected 3 hash fields, got {allFields.Length}"); + + await db.KeyDeleteAsync(hashKey); + Console.WriteLine($" Hash key: {hashKey}, Fields: 3"); + }); + + // Test 6: List Operations + await RunTest("List Operations (LPUSH/RPUSH/LRANGE)", async () => + { + var listKey = $"urp:mi:test:list:{Guid.NewGuid():N}".Substring(0, 40); + + await db.ListLeftPushAsync(listKey, "item1"); + await db.ListRightPushAsync(listKey, "item2"); + await db.ListRightPushAsync(listKey, "item3"); + + var listLength = await db.ListLengthAsync(listKey); + if (listLength != 3) + throw new Exception($"Expected list length 3, got {listLength}"); + + var items = await db.ListRangeAsync(listKey, 0, -1); + Console.WriteLine($" List items: {string.Join(", ", items.Select(i => i.ToString()))}"); + + await db.KeyDeleteAsync(listKey); + }); + + // Test 7: Set Operations + await RunTest("Set Operations (SADD/SMEMBERS/SISMEMBER)", async () => + { + var setKey = $"urp:mi:test:set:{Guid.NewGuid():N}".Substring(0, 40); + + await db.SetAddAsync(setKey, new RedisValue[] { "member1", "member2", "member3" }); + + var isMember = await db.SetContainsAsync(setKey, "member2"); + if (!isMember) + throw new Exception("Expected 'member2' to be in set"); + + var members = await db.SetMembersAsync(setKey); + if (members.Length != 3) + throw new Exception($"Expected 3 set members, got {members.Length}"); + + await db.KeyDeleteAsync(setKey); + Console.WriteLine($" Set members: {members.Length}"); + }); + + // Test 8: Sorted Set Operations + await RunTest("Sorted Set Operations (ZADD/ZRANGE)", async () => + { + var zsetKey = $"urp:mi:test:zset:{Guid.NewGuid():N}".Substring(0, 40); + + await db.SortedSetAddAsync(zsetKey, new SortedSetEntry[] + { + new SortedSetEntry("player1", 100), + new SortedSetEntry("player2", 200), + new SortedSetEntry("player3", 150) + }); + + var topPlayers = await db.SortedSetRangeByRankAsync(zsetKey, 0, -1, Order.Descending); + if (topPlayers[0] != "player2") + throw new Exception($"Expected top player 'player2', got '{topPlayers[0]}'"); + + await db.KeyDeleteAsync(zsetKey); + Console.WriteLine($" Top player: {topPlayers[0]}"); + }); + + // Test 9: Key Expiration + await RunTest("Key Expiration (TTL)", async () => + { + var ttlKey = $"urp:mi:test:ttl:{Guid.NewGuid():N}".Substring(0, 40); + + await db.StringSetAsync(ttlKey, "expiring-value", TimeSpan.FromSeconds(60)); + var ttl = await db.KeyTimeToLiveAsync(ttlKey); + + if (!ttl.HasValue || ttl.Value.TotalSeconds < 50) + throw new Exception($"Expected TTL ~60s, got {ttl?.TotalSeconds ?? 0}s"); + + await db.KeyDeleteAsync(ttlKey); + Console.WriteLine($" TTL: {ttl.Value.TotalSeconds:F0} seconds"); + }); + + // Test 10: Connection Reuse + await RunTest("Connection Reuse (Same Connection)", async () => + { + var connectionString = $"{RedisHost}:{RedisPort}"; + var connection2 = await connectionManager.CreateConnectionAsync(connectionString); + + // Should return the same cached connection + if (!ReferenceEquals(connection, connection2)) + Console.WriteLine(" Note: New connection created (expected if reconnecting)"); + else + Console.WriteLine(" Connection reused from cache"); + + await Task.CompletedTask; + }); + + // Test 11: Batch Operations + await RunTest("Batch Operations", async () => + { + var batchKey = $"urp:mi:test:batch:{Guid.NewGuid():N}".Substring(0, 40); + var batch = db.CreateBatch(); + + var setTask = batch.StringSetAsync(batchKey, "batch-value"); + var getTask = batch.StringGetAsync(batchKey); + + batch.Execute(); + + await setTask; + var result = await getTask; + + await db.KeyDeleteAsync(batchKey); + Console.WriteLine($" Batch result: {result}"); + }); + + // Test 12: Increment/Decrement + await RunTest("Increment/Decrement Operations", async () => + { + var counterKey = $"urp:mi:test:counter:{Guid.NewGuid():N}".Substring(0, 40); + + await db.StringSetAsync(counterKey, 10); + var incremented = await db.StringIncrementAsync(counterKey, 5); + var decremented = await db.StringDecrementAsync(counterKey, 3); + + if (incremented != 15) + throw new Exception($"Expected 15 after increment, got {incremented}"); + if (decremented != 12) + throw new Exception($"Expected 12 after decrement, got {decremented}"); + + await db.KeyDeleteAsync(counterKey); + Console.WriteLine($" Final value: {decremented}"); + }); + + // ===================================================== + // SDK CORE TESTS - What clients actually use + // ===================================================== + Console.WriteLine("\n-------------------------------------------"); + Console.WriteLine("SDK CORE TESTS (UnifiedConnectionMultiplexer)"); + Console.WriteLine("Client Connection Methods Demonstrated"); + Console.WriteLine("-------------------------------------------\n"); + + IUnifiedConnectionMultiplexer sdkMux = null; + IUnifiedDatabase sdkDb = null; + + // ===================================================== + // METHOD 1: UnifiedConfigurationLocalOptions with ManagedIdentityClientId + // This is the RECOMMENDED way for clients to use MI + // ===================================================== + await RunTest("SDK Method 1: LocalOptions + ManagedIdentityClientId (Recommended)", async () => + { + try + { + // Client just sets ManagedIdentityClientId - SDK handles everything! + var localOptions = new UnifiedConfigurationLocalOptions + { + ClusterName = "POC-MI-Test-Method1", + AppName = "LocalRedisTest-Method1", + KeyPrefix = "urp:sdk:m1", + BaseConfigurationOptions = ConfigurationOptions.Parse($"{RedisHost}:{RedisPort},ssl=true,abortConnect=false"), + ManagedIdentityClientId = "my-managed-identity-client-id", // SDK auto-configures MI! + ConnectionRetryProtocol = RetryProtocol.GetDefaultConnectionProtocol(), + OperationsRetryProtocol = RetryProtocol.GetDefaultOperationProtocol() + }; + + // Sync connect + sdkMux = UnifiedConnectionMultiplexer.Connect(localOptions); + + if (!sdkMux.IsConnected) + throw new Exception("SDK connection created but not connected"); + + Console.WriteLine($" ✓ Connected via ManagedIdentityClientId property"); + Console.WriteLine($" ClusterName: {sdkMux.ClusterName}, AppName: {sdkMux.AppName}"); + await Task.CompletedTask; + } + catch (Exception ex) + { + _sdkConnectionFailed = true; + _sdkConnectionError = ex.Message; + throw; + } + }); + + // ===================================================== + // METHOD 2: UnifiedConfigurationLocalOptions with pre-configured BaseConfigurationOptions + // For clients who want manual control over the credential + // ===================================================== + await RunTest("SDK Method 2: LocalOptions + Manual ConfigureForAzure (Advanced)", async () => + { + try + { + // Client manually configures MI on BaseConfigurationOptions + var baseOptions = ConfigurationOptions.Parse($"{RedisHost}:{RedisPort}"); + baseOptions.AbortOnConnectFail = false; + baseOptions.Ssl = true; + + // Manual MI configuration with custom credential + var credential = new AzureIdentity.VisualStudioCredential(); + await baseOptions.ConfigureForAzureWithTokenCredentialAsync(credential); + + var localOptions = new UnifiedConfigurationLocalOptions + { + ClusterName = "POC-MI-Test-Method2", + AppName = "LocalRedisTest-Method2", + KeyPrefix = "urp:sdk:m2", + BaseConfigurationOptions = baseOptions, // Already MI-configured + // ManagedIdentityClientId NOT set - using pre-configured options + ConnectionRetryProtocol = RetryProtocol.GetDefaultConnectionProtocol(), + OperationsRetryProtocol = RetryProtocol.GetDefaultOperationProtocol() + }; + + var mux2 = UnifiedConnectionMultiplexer.Connect(localOptions); + + if (!mux2.IsConnected) + throw new Exception("SDK connection created but not connected"); + + Console.WriteLine($" ✓ Connected via manual ConfigureForAzureWithTokenCredentialAsync"); + Console.WriteLine($" ClusterName: {mux2.ClusterName}, AppName: {mux2.AppName}"); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($" âš  {ex.Message}"); + Console.ResetColor(); + throw; + } + }); + + // ===================================================== + // METHOD 3: Direct Connect with managedIdentityClientId parameter + // Simplest way - one-liner connection + // ===================================================== + await RunTest("SDK Method 3: Direct Connect(clusterName, appName, ..., miClientId)", async () => + { + try + { + // One-liner connection with MI + // In production: managedIdentityClientId would be your actual MI client ID + // The SDK uses VisualStudioCredential in DEBUG, ManagedIdentityCredential in Release + var mux3 = UnifiedConnectionMultiplexer.Connect( + clusterName: "POC-MI-Test-Method3", + appName: "LocalRedisTest-Method3", + appSecret: "not-used-with-MI", + managedIdentityClientId: "my-managed-identity-client-id" + // serviceEndpoint and preferredLocation are optional + ); + + // Note: This method requires URP service endpoint to fetch configuration + // For this test, it may not connect if URP service is not available + Console.WriteLine($" ✓ Connect() with managedIdentityClientId parameter"); + Console.WriteLine($" (Requires URP service for configuration)"); + await Task.CompletedTask; + } + catch (Exception ex) + { + // Expected if URP service is not running + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($" âš  Skipped - Requires URP service: {ex.Message.Split('\n')[0]}"); + Console.ResetColor(); + // Don't throw - this is expected in local testing + } + }); + + // ===================================================== + // METHOD 4: Async connect with LocalOptions + // For async-aware applications + // ===================================================== + await RunTest("SDK Method 4: ConnectAsync (Async version)", async () => + { + try + { + var localOptions = new UnifiedConfigurationLocalOptions + { + ClusterName = "POC-MI-Test-Method4", + AppName = "LocalRedisTest-Method4", + KeyPrefix = "urp:sdk:m4", + BaseConfigurationOptions = ConfigurationOptions.Parse($"{RedisHost}:{RedisPort},ssl=true,abortConnect=false"), + ManagedIdentityClientId = "my-managed-identity-client-id", + ConnectionRetryProtocol = RetryProtocol.GetDefaultConnectionProtocol(), + OperationsRetryProtocol = RetryProtocol.GetDefaultOperationProtocol() + }; + + // Async connect - better for async applications + var mux4 = await UnifiedConnectionMultiplexer.ConnectAsync(localOptions); + + if (!mux4.IsConnected) + throw new Exception("SDK connection created but not connected"); + + Console.WriteLine($" ✓ Connected via ConnectAsync()"); + Console.WriteLine($" ClusterName: {mux4.ClusterName}, AppName: {mux4.AppName}"); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($" âš  {ex.Message}"); + Console.ResetColor(); + throw; + } + }); + + // ===================================================== + // METHOD 5: Access Key Authentication (Backward Compatible) + // For clients not yet ready for MI + // ===================================================== + await RunTest("SDK Method 5: Access Key Auth (Backward Compatible)", async () => + { + try + { + // Traditional access key auth still works! + var baseOptions = ConfigurationOptions.Parse($"{RedisHost}:{RedisPort}"); + baseOptions.AbortOnConnectFail = false; + baseOptions.Ssl = true; + baseOptions.Password = "your-access-key-here"; // Traditional auth + + var localOptions = new UnifiedConfigurationLocalOptions + { + ClusterName = "POC-MI-Test-Method5", + AppName = "LocalRedisTest-Method5", + KeyPrefix = "urp:sdk:m5", + BaseConfigurationOptions = baseOptions, + // ManagedIdentityClientId NOT set = uses Password from BaseConfigurationOptions + ConnectionRetryProtocol = RetryProtocol.GetDefaultConnectionProtocol(), + OperationsRetryProtocol = RetryProtocol.GetDefaultOperationProtocol() + }; + + // This demonstrates backward compatibility + // Skip actual connection since we don't have a valid access key + Console.WriteLine($" ✓ Access key auth still supported (backward compatible)"); + Console.WriteLine($" Set BaseConfigurationOptions.Password instead of ManagedIdentityClientId"); + await Task.CompletedTask; + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($" âš  {ex.Message}"); + Console.ResetColor(); + } + }); + + // Now use the first successful connection for remaining tests + Console.WriteLine("\n--- SDK Operations Tests ---\n"); + + // Test: SDK - Get Database + await RunTest("SDK: GetDatabase", async () => + { + if (_sdkConnectionFailed || sdkMux == null) + throw new Exception($"Skipped - SDK connection failed: {_sdkConnectionError ?? "No connection"}"); + + sdkDb = sdkMux.GetDatabase() as IUnifiedDatabase; + if (sdkDb == null) + throw new Exception("Failed to get IUnifiedDatabase"); + Console.WriteLine(" Got IUnifiedDatabase successfully"); + await Task.CompletedTask; + }); + + // Test 15: SDK - StringSet/StringGet + await RunTest("SDK: StringSet/StringGet Operations", async () => + { + if (_sdkConnectionFailed || sdkDb == null) + throw new Exception("Skipped - SDK connection/database not available"); + var key = $"sdk:test:string:{Guid.NewGuid():N}".Substring(0, 35); + var value = $"SDK Test Value at {DateTime.UtcNow:O}"; + + await sdkDb.StringSetAsync(key, value); + var retrieved = await sdkDb.StringGetAsync(key); + + if (retrieved != value) + throw new Exception($"Value mismatch: expected '{value}', got '{retrieved}'"); + + await sdkDb.KeyDeleteAsync(key); + Console.WriteLine($" Key with prefix: urp:sdk:test:{key}"); + }); + + // Test 16: SDK - Hash Operations + await RunTest("SDK: Hash Operations", async () => + { + if (_sdkConnectionFailed || sdkDb == null) + throw new Exception("Skipped - SDK connection/database not available"); + + var hashKey = $"sdk:test:hash:{Guid.NewGuid():N}".Substring(0, 35); + + await sdkDb.HashSetAsync(hashKey, new HashEntry[] + { + new HashEntry("name", "Test User"), + new HashEntry("email", "test@example.com"), + new HashEntry("role", "Developer") + }); + + var name = await sdkDb.HashGetAsync(hashKey, "name"); + if (name != "Test User") + throw new Exception($"Hash field mismatch: expected 'Test User', got '{name}'"); + + var allFields = await sdkDb.HashGetAllAsync(hashKey); + await sdkDb.KeyDeleteAsync(hashKey); + Console.WriteLine($" Hash fields count: {allFields.Length}"); + }); + + // Test 17: SDK - List Operations + await RunTest("SDK: List Operations", async () => + { + if (_sdkConnectionFailed || sdkDb == null) + throw new Exception("Skipped - SDK connection/database not available"); + + var listKey = $"sdk:test:list:{Guid.NewGuid():N}".Substring(0, 35); + + await sdkDb.ListRightPushAsync(listKey, "item1"); + await sdkDb.ListRightPushAsync(listKey, "item2"); + await sdkDb.ListLeftPushAsync(listKey, "item0"); + + var length = await sdkDb.ListLengthAsync(listKey); + if (length != 3) + throw new Exception($"Expected list length 3, got {length}"); + + var items = await sdkDb.ListRangeAsync(listKey); + await sdkDb.KeyDeleteAsync(listKey); + Console.WriteLine($" List items: {string.Join(", ", items.Select(i => i.ToString()))}"); + }); + + // Test 18: SDK - Set Operations + await RunTest("SDK: Set Operations", async () => + { + if (_sdkConnectionFailed || sdkDb == null) + throw new Exception("Skipped - SDK connection/database not available"); + + var setKey = $"sdk:test:set:{Guid.NewGuid():N}".Substring(0, 35); + + await sdkDb.SetAddAsync(setKey, new RedisValue[] { "member1", "member2", "member3" }); + + var isMember = await sdkDb.SetContainsAsync(setKey, "member2"); + if (!isMember) + throw new Exception("Expected member2 to be in set"); + + var members = await sdkDb.SetMembersAsync(setKey); + await sdkDb.KeyDeleteAsync(setKey); + Console.WriteLine($" Set members count: {members.Length}"); + }); + + // Test 19: SDK - Sorted Set Operations + await RunTest("SDK: Sorted Set Operations", async () => + { + if (_sdkConnectionFailed || sdkDb == null) + throw new Exception("Skipped - SDK connection/database not available"); + + var zsetKey = $"sdk:test:zset:{Guid.NewGuid():N}".Substring(0, 35); + + await sdkDb.SortedSetAddAsync(zsetKey, new SortedSetEntry[] + { + new SortedSetEntry("alice", 100), + new SortedSetEntry("bob", 200), + new SortedSetEntry("charlie", 150) + }); + + var topScorer = await sdkDb.SortedSetRangeByRankAsync(zsetKey, -1, -1); + if (topScorer.FirstOrDefault() != "bob") + throw new Exception($"Expected top scorer 'bob', got '{topScorer.FirstOrDefault()}'"); + + await sdkDb.KeyDeleteAsync(zsetKey); + Console.WriteLine($" Top scorer: {topScorer.FirstOrDefault()}"); + }); + + // Test 20: SDK - Key Expiration + await RunTest("SDK: Key Expiration (TTL)", async () => + { + if (_sdkConnectionFailed || sdkDb == null) + throw new Exception("Skipped - SDK connection/database not available"); + + var expKey = $"sdk:test:exp:{Guid.NewGuid():N}".Substring(0, 35); + + await sdkDb.StringSetAsync(expKey, "will-expire", TimeSpan.FromSeconds(30)); + + var ttl = await sdkDb.KeyTimeToLiveAsync(expKey); + if (!ttl.HasValue || ttl.Value.TotalSeconds < 25) + throw new Exception($"Expected TTL ~30s, got {ttl?.TotalSeconds}s"); + + await sdkDb.KeyDeleteAsync(expKey); + Console.WriteLine($" TTL: {ttl?.TotalSeconds:F0} seconds"); + }); + + // Test 21: SDK - Increment/Decrement + await RunTest("SDK: Increment/Decrement Operations", async () => + { + if (_sdkConnectionFailed || sdkDb == null) + throw new Exception("Skipped - SDK connection/database not available"); + + var counterKey = $"sdk:test:counter:{Guid.NewGuid():N}".Substring(0, 35); + + await sdkDb.StringSetAsync(counterKey, 100); + var incremented = await sdkDb.StringIncrementAsync(counterKey, 25); + var decremented = await sdkDb.StringDecrementAsync(counterKey, 10); + + if (incremented != 125) + throw new Exception($"Expected 125 after increment, got {incremented}"); + if (decremented != 115) + throw new Exception($"Expected 115 after decrement, got {decremented}"); + + await sdkDb.KeyDeleteAsync(counterKey); + Console.WriteLine($" Final value: {decremented}"); + }); + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"\n✗ Fatal error: {ex.Message}"); + Console.WriteLine(ex.ToString()); + Console.ResetColor(); + } + + // Print Summary + Console.WriteLine("\n==========================================="); + Console.WriteLine("TEST SUMMARY"); + Console.WriteLine("==========================================="); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine($" Passed: {_passedTests}"); + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($" Failed: {_failedTests}"); + Console.ResetColor(); + + if (_failedTestNames.Any()) + { + // Separate SDK skipped tests from real failures + var skippedTests = _failedTestNames.Where(n => n.StartsWith("SDK:") && _failedTests > 0).ToList(); + var realFailures = _failedTestNames.Except(skippedTests).ToList(); + + if (realFailures.Any()) + { + Console.WriteLine("\n Failed Tests:"); + foreach (var name in realFailures) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($" - {name}"); + Console.ResetColor(); + } + } + + if (_sdkConnectionFailed && skippedTests.Any()) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($"\n SDK Tests Skipped ({skippedTests.Count}) - SDK Core needs ServerMaintenanceEvent implementation:"); + Console.ResetColor(); + foreach (var name in skippedTests) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine($" âš  {name}"); + Console.ResetColor(); + } + } + } + + Console.WriteLine("==========================================="); + + // If only SDK tests failed due to compatibility issue, consider it a partial success + var nonSdkFailures = _failedTestNames.Count(n => !n.StartsWith("SDK:")); + if (_failedTests == 0) + { + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("✓ ALL TESTS PASSED!"); + } + else if (nonSdkFailures == 0 && _sdkConnectionFailed) + { + Console.ForegroundColor = ConsoleColor.Yellow; + Console.WriteLine("âš  SERVICE LAYER TESTS PASSED - SDK Core needs update"); + Console.WriteLine(" (SDK Core missing ServerMaintenanceEvent event implementation)"); + } + else + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("✗ SOME TESTS FAILED!"); + } + Console.ResetColor(); + + Console.WriteLine("\nPress any key to exit..."); + Console.ReadKey(); + } + + private static async Task RunTest(string testName, Func testAction) + { + Console.Write($"[Test] {testName}... "); + try + { + await testAction(); + Console.ForegroundColor = ConsoleColor.Green; + Console.WriteLine("✓ PASSED"); + Console.ResetColor(); + _passedTests++; + } + catch (Exception ex) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine($"✗ FAILED"); + Console.WriteLine($" Error: {ex.Message}"); + Console.ResetColor(); + _failedTests++; + _failedTestNames.Add(testName); + } + } + } +} diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsole461.SDK/Microsoft.UnifiedRedisPlatform.TestConsole461.SDK.csproj b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsole461.SDK/Microsoft.UnifiedRedisPlatform.TestConsole461.SDK.csproj index 2b3d51a..9f542a9 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsole461.SDK/Microsoft.UnifiedRedisPlatform.TestConsole461.SDK.csproj +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsole461.SDK/Microsoft.UnifiedRedisPlatform.TestConsole461.SDK.csproj @@ -8,7 +8,7 @@ Exe TestConsole461.SDK TestConsole461.SDK - v4.6.1 + v4.7.2 512 true true @@ -33,34 +33,28 @@ 4 - - ..\packages\Microsoft.Bcl.AsyncInterfaces.5.0.0\lib\net461\Microsoft.Bcl.AsyncInterfaces.dll + + ..\packages\Microsoft.Bcl.AsyncInterfaces.8.0.0\lib\net462\Microsoft.Bcl.AsyncInterfaces.dll - - ..\packages\UnifiedRedisPlatform.1.0.2\lib\netstandard2.0\Microsoft.UnifiedRedisPlatform.Core.dll + + ..\packages\Microsoft.UnifiedRedisPlatform.Core.1.4.0\lib\netstandard2.0\Microsoft.UnifiedRedisPlatform.Core.dll - - ..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll + + ..\packages\Newtonsoft.Json.13.0.3\lib\net45\Newtonsoft.Json.dll - ..\packages\Pipelines.Sockets.Unofficial.2.2.0\lib\net461\Pipelines.Sockets.Unofficial.dll + ..\packages\Pipelines.Sockets.Unofficial.2.2.8\lib\net472\Pipelines.Sockets.Unofficial.dll - ..\packages\StackExchange.Redis.2.2.4\lib\net461\StackExchange.Redis.dll + ..\packages\StackExchange.Redis.2.8.24\lib\net472\StackExchange.Redis.dll ..\packages\System.Buffers.4.5.1\lib\net461\System.Buffers.dll - - ..\packages\System.Diagnostics.PerformanceCounter.5.0.0\lib\net461\System.Diagnostics.PerformanceCounter.dll - - - ..\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll - - - ..\packages\System.IO.Pipelines.5.0.0\lib\net461\System.IO.Pipelines.dll + + ..\packages\System.IO.Pipelines.5.0.1\lib\net461\System.IO.Pipelines.dll ..\packages\System.Memory.4.5.4\lib\net461\System.Memory.dll @@ -69,14 +63,8 @@ ..\packages\System.Numerics.Vectors.4.5.0\lib\net46\System.Numerics.Vectors.dll - - ..\packages\System.Runtime.CompilerServices.Unsafe.4.5.3\lib\net461\System.Runtime.CompilerServices.Unsafe.dll - - - ..\packages\System.Runtime.InteropServices.RuntimeInformation.4.3.0\lib\net45\System.Runtime.InteropServices.RuntimeInformation.dll - - - ..\packages\System.Threading.Channels.5.0.0\lib\net461\System.Threading.Channels.dll + + ..\packages\System.Runtime.CompilerServices.Unsafe.6.0.0\lib\net461\System.Runtime.CompilerServices.Unsafe.dll ..\packages\System.Threading.Tasks.Extensions.4.5.4\lib\net461\System.Threading.Tasks.Extensions.dll diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsole461.SDK/packages.config b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsole461.SDK/packages.config index 584d541..73e5dbd 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsole461.SDK/packages.config +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsole461.SDK/packages.config @@ -1,18 +1,27 @@  - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.Latest/Program.cs b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.Latest/Program.cs index 7594b48..5ec98cb 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.Latest/Program.cs +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.Latest/Program.cs @@ -20,6 +20,7 @@ class Program private static string _appName; private static string _appSecret; private static string _location; + private static string _managedIdentityClientId; private static UnifiedConnectionMultiplexer _mux; private static IUnifiedDatabase _database; private static int _mode = AdvancedMode; @@ -104,6 +105,7 @@ static void Main(string[] args) ClusterName = _clusterName, AppName = _appName, AppSecret = _appSecret, + ManagedIdentityClientId = _managedIdentityClientId, Region = _location, ConnectionRetryProtocol = connectionTimeout > 0 && connectionMaxRetry > 0 ? new RetryProtocol() { @@ -135,7 +137,7 @@ static void Main(string[] args) if (_mux == null) { - _mux = UnifiedConnectionMultiplexer.Connect(_clusterName, _appName, _appSecret, preferredLocation: _location) as UnifiedConnectionMultiplexer; + _mux = UnifiedConnectionMultiplexer.Connect(_clusterName, _appName, _appSecret, managedIdentityClientId: _managedIdentityClientId, preferredLocation: _location) as UnifiedConnectionMultiplexer; } _database = _mux.GetDatabase() as IUnifiedDatabase; @@ -222,8 +224,14 @@ private static void SetupDefaultAppDetails() _appName = _configuration["AppName"]; _appSecret = _configuration["AppSecret"]; _location = _configuration["AppLocation"]; + _managedIdentityClientId = _configuration["ManagedIdentityClientId"]; Console.WriteLine("Default configurations received."); + if (!string.IsNullOrWhiteSpace(_managedIdentityClientId)) + { + Console.WriteLine(" Using Managed Identity authentication"); + Console.WriteLine($" MI Client ID: {_managedIdentityClientId}"); + } } private static void SetAppDetails() @@ -236,11 +244,14 @@ private static void SetAppDetails() Console.Write("App Name: "); _appName = Console.ReadLine(); - Console.Write("App Secret: "); + Console.Write("App Secret (leave blank for MI auth): "); _appSecret = Console.ReadLine(); Console.Write("Region: "); _location = Console.ReadLine(); + + Console.Write("Managed Identity Client ID (leave blank for VisualStudioCredential): "); + _managedIdentityClientId = Console.ReadLine(); } private static void ShowOperations() diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.Latest/appsettings.json b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.Latest/appsettings.json index 58222c3..542fb04 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.Latest/appsettings.json +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.Latest/appsettings.json @@ -1,6 +1,7 @@ -{ +{ "ClusterName": "PS-PreProd-01", "AppName": "OneProfile-UAT", "AppSecret": "", - "AppLocation": "eastus" + "AppLocation": "eastus", + "ManagedIdentityClientId": "" } diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.SDK/Microsoft.UnifiedRedisPlatform.TestConsoleCore.SDK.csproj b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.SDK/Microsoft.UnifiedRedisPlatform.TestConsoleCore.SDK.csproj index a3621b0..8e8ca9a 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.SDK/Microsoft.UnifiedRedisPlatform.TestConsoleCore.SDK.csproj +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.SDK/Microsoft.UnifiedRedisPlatform.TestConsoleCore.SDK.csproj @@ -9,7 +9,7 @@ - + diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.SDK/Program.cs b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.SDK/Program.cs index 9e34cb0..dcbfaf3 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.SDK/Program.cs +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.SDK/Program.cs @@ -19,6 +19,8 @@ class Program private static string _clusterName; private static string _appName; private static string _appSecret; + private static string _location; + private static string _managedIdentityClientId; private static UnifiedConnectionMultiplexer _mux; private static IDatabase _database; private static int _mode = AdvancedMode; @@ -80,6 +82,8 @@ static void Main(string[] args) ClusterName = _clusterName, AppName = _appName, AppSecret = _appSecret, + ManagedIdentityClientId = _managedIdentityClientId, + Region = _location, ConnectionRetryProtocol = connectionTimeout > 0 && connectionMaxRetry > 0 ? new RetryProtocol() { TimeoutInMs = connectionTimeout > 0 ? connectionTimeout : RetryProtocol.GetDefaultConnectionProtocol().TimeoutInMs, @@ -113,7 +117,7 @@ static void Main(string[] args) if (_mux == null) { //_mux = UnifiedConnectionMultiplexer.Connect(_clusterName, _appName, _appSecret, serviceEndpoint: "https://localhost:44316") as UnifiedConnectionMultiplexer; - _mux = UnifiedConnectionMultiplexer.Connect(_clusterName, _appName, _appSecret) as UnifiedConnectionMultiplexer; + _mux = UnifiedConnectionMultiplexer.Connect(_clusterName, _appName, _appSecret, managedIdentityClientId: _managedIdentityClientId, preferredLocation: _location) as UnifiedConnectionMultiplexer; } _database = _mux.GetDatabase(); @@ -173,20 +177,22 @@ static void Main(string[] args) private static void SetupDefaultAppDetails() { - //Console.WriteLine(); - //Console.ForegroundColor = ConsoleColor.Blue; - //Console.WriteLine("Please wait while we get the default configuration..."); - - //_clusterName = _configuration["ClusterName"]; - //_appName = _configuration["ClientName"]; - - //var keyVaultName = _configuration["KeyVaultName"]; - //var secretsProvider = new SecretProvider(keyVaultName); + Console.WriteLine(); + Console.ForegroundColor = ConsoleColor.Blue; + Console.WriteLine("Please wait while we get the default configuration..."); - //var appSecretLocation = _configuration["AppSecretName"]; - //_appSecret = secretsProvider.GetSecret(appSecretLocation).Result; + _clusterName = _configuration["ClusterName"]; + _appName = _configuration["AppName"]; + _appSecret = _configuration["AppSecret"]; + _location = _configuration["AppLocation"]; + _managedIdentityClientId = _configuration["ManagedIdentityClientId"]; - //Console.WriteLine("Default configurations received."); + Console.WriteLine("Default configurations received."); + if (!string.IsNullOrWhiteSpace(_managedIdentityClientId)) + { + Console.WriteLine(" Using Managed Identity authentication"); + Console.WriteLine($" MI Client ID: {_managedIdentityClientId}"); + } } private static void SetAppDetails() @@ -199,8 +205,14 @@ private static void SetAppDetails() Console.Write("App Name: "); _appName = Console.ReadLine(); - Console.Write("App Secret: "); + Console.Write("App Secret (leave blank for MI auth): "); _appSecret = Console.ReadLine(); + + Console.Write("Region: "); + _location = Console.ReadLine(); + + Console.Write("Managed Identity Client ID (leave blank for VisualStudioCredential): "); + _managedIdentityClientId = Console.ReadLine(); } private static void ShowOperations() diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.SDK/appsettings.json b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.SDK/appsettings.json index 384eb16..120119a 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.SDK/appsettings.json +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestConsoleCore.SDK/appsettings.json @@ -1,6 +1,9 @@ -{ +{ "KeyVaultName": "kv-unifiedredisplat-eus", "ClusterName": "Test-Tenant-1", "AppName": "Test-Tenant-1", - "AppSecretName": "Test-Tenant-1-Test-App-1-Secret" + "AppSecret": "", + "AppSecretName": "Test-Tenant-1-Test-App-1-Secret", + "AppLocation": "eastus", + "ManagedIdentityClientId": "" } diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/Program.cs b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/Program.cs index d7c8c36..cbfdca3 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/Program.cs +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/Startup.cs b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/Startup.cs index 00b9614..66c5cbd 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/Startup.cs +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/Startup.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; @@ -32,6 +32,7 @@ public void ConfigureServices(IServiceCollection services) options.Application = Configuration.GetValue("URP:Application"); options.AppSecret = Configuration.GetValue("URP:ApplicationSecret"); options.PreferredLocation = Configuration.GetValue("URP:Location"); + options.ManagedIdentityClientId = Configuration.GetValue("URP:ManagedIdentityClientId"); }); services.AddControllers(); } diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/appsettings.Development.json b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/appsettings.Development.json index 8983e0f..c9294ca 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/appsettings.Development.json +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/appsettings.Development.json @@ -1,4 +1,4 @@ -{ +{ "Logging": { "LogLevel": { "Default": "Information", diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/appsettings.json b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/appsettings.json index 85ca434..1c18e96 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/appsettings.json +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.Latest/appsettings.json @@ -1,4 +1,4 @@ -{ +{ "Logging": { "LogLevel": { "Default": "Information", @@ -11,6 +11,7 @@ "Cluster": "", "Application": "", "ApplicationSecret": "", - "Location": "eastus" + "Location": "eastus", + "ManagedIdentityClientId": "" } } diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/Microsoft.UnifiedRedisPlatform.TestWebAppCore.SDK.csproj b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/Microsoft.UnifiedRedisPlatform.TestWebAppCore.SDK.csproj index b01788a..fb2445d 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/Microsoft.UnifiedRedisPlatform.TestWebAppCore.SDK.csproj +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/Microsoft.UnifiedRedisPlatform.TestWebAppCore.SDK.csproj @@ -1,11 +1,11 @@ - + netcoreapp3.1 - + diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/Program.cs b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/Program.cs index 5bd6cbc..237aefa 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/Program.cs +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/Program.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/Startup.cs b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/Startup.cs index eb8521c..5cf3de2 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/Startup.cs +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/Startup.cs @@ -1,4 +1,4 @@ -using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Caching.UnifiedRedisPlatform; using Microsoft.Extensions.Configuration; @@ -25,6 +25,7 @@ public void ConfigureServices(IServiceCollection services) options.Application = Configuration.GetValue("URP:Application"); options.AppSecret = Configuration.GetValue("URP:ApplicationSecret"); options.PreferredLocation = Configuration.GetValue("URP:Location"); + options.ManagedIdentityClientId = Configuration.GetValue("URP:ManagedIdentityClientId"); }); services.AddControllers(); } diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/appsettings.Development.json b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/appsettings.Development.json index 8983e0f..c9294ca 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/appsettings.Development.json +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/appsettings.Development.json @@ -1,4 +1,4 @@ -{ +{ "Logging": { "LogLevel": { "Default": "Information", diff --git a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/appsettings.json b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/appsettings.json index 8e88a64..22904c9 100644 --- a/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/appsettings.json +++ b/src/tests/Microsoft.UnifiedRedisPlatform.TestApps/TestWebAppCore.SDK/appsettings.json @@ -1,4 +1,4 @@ -{ +{ "Logging": { "LogLevel": { "Default": "Information", @@ -11,6 +11,7 @@ "Cluster": "PS-Prod-01", "Application": "OneProfile", "ApplicationSecret": "Data comes from key vault", - "Location": "eastus" + "Location": "eastus", + "ManagedIdentityClientId": "" } }