diff --git a/.gitignore b/.gitignore index 4f59a06..d49f84e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,7 +8,7 @@ *.user *.userosscache *.sln.docstates - +*/**/appsettings.local.json # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs @@ -221,7 +221,7 @@ ClientBin/ *.publishsettings orleans.codegen.cs -# Including strong name files can present a security risk +# Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk @@ -317,7 +317,7 @@ __pycache__/ # OpenCover UI analysis results OpenCover/ -# Azure Stream Analytics local run output +# Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log @@ -326,6 +326,6 @@ ASALocalRun/ # NVidia Nsight GPU debugger configuration file *.nvuser -# MFractors (Xamarin productivity tool) working folder +# MFractors (Xamarin productivity tool) working folder .mfractor/ -/src/docker-compose.override.yml \ No newline at end of file +/src/docker-compose.override.yml diff --git a/deploy/connection-string/deploy-app.yaml b/deploy/connection-string/deploy-app.yaml index 2f02f23..113a230 100644 --- a/deploy/connection-string/deploy-app.yaml +++ b/deploy/connection-string/deploy-app.yaml @@ -17,14 +17,14 @@ spec: - name: order-processor image: ghcr.io/kedacore/sample-dotnet-worker-servicebus-queue:latest env: - - name: KEDA_SERVICEBUS_AUTH_MODE + - name: OrderQueueOptions__AuthMode value: ConnectionString - - name: KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING + - name: OrderQueueOptions__ConnectionString valueFrom: secretKeyRef: name: secrets-order-consumer key: servicebus-connectionstring - - name: KEDA_SERVICEBUS_QUEUE_NAME + - name: OrderQueueOptions__QueueName value: orders --- apiVersion: v1 @@ -34,4 +34,4 @@ metadata: labels: app: order-processor data: - servicebus-connectionstring: \ No newline at end of file + servicebus-connectionstring: diff --git a/deploy/deploy-web.yaml b/deploy/deploy-web.yaml index 1e5c82f..978ebb7 100644 --- a/deploy/deploy-web.yaml +++ b/deploy/deploy-web.yaml @@ -17,18 +17,23 @@ spec: - name: order-web image: ghcr.io/kedacore/sample-dotnet-worker-servicebus-portal env: - - name: KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING + - name: OrderQueueOptions__AuthMode + value: ConnectionString + env: + - name: OrderQueueOptions__ConnectionString valueFrom: secretKeyRef: name: secrets-order-portal key: servicebus-connectionstring + - name: OrderQueueOptions__QueueName + value: orders --- apiVersion: v1 kind: Service metadata: name: kedasampleweb labels: - app: order-web + app: order-web annotations: # Expose service on ..cloudapp.azure.com service.beta.kubernetes.io/azure-dns-label-name: keda-orders-portal diff --git a/deploy/pod-identity/deploy-app-with-pod-identity.yaml b/deploy/pod-identity/deploy-app-with-pod-identity.yaml index 0a44a14..2ff8fe8 100644 --- a/deploy/pod-identity/deploy-app-with-pod-identity.yaml +++ b/deploy/pod-identity/deploy-app-with-pod-identity.yaml @@ -37,11 +37,11 @@ spec: - name: order-processor image: ghcr.io/kedacore/sample-dotnet-worker-servicebus-queue:latest env: - - name: KEDA_SERVICEBUS_AUTH_MODE + - name: OrderQueueOptions__AuthMode value: PodIdentity - - name: KEDA_SERVICEBUS_HOST_NAME + - name: OrderQueueOptions__FullyQualifiedNamespace value: .servicebus.windows.net - - name: KEDA_SERVICEBUS_QUEUE_NAME + - name: OrderQueueOptions__QueueName value: orders - - name: KEDA_SERVICEBUS_IDENTITY_USERASSIGNEDID - value: \ No newline at end of file + - name: OrderQueueOptions__ClientId + value: diff --git a/deploy/workload-identity/deploy-app-with-workload-identity.yaml b/deploy/workload-identity/deploy-app-with-workload-identity.yaml index 42aefcd..3ddb9ca 100644 --- a/deploy/workload-identity/deploy-app-with-workload-identity.yaml +++ b/deploy/workload-identity/deploy-app-with-workload-identity.yaml @@ -28,9 +28,9 @@ spec: - name: order-processor image: ghcr.io/kedacore/sample-dotnet-worker-servicebus-queue:latest env: - - name: KEDA_SERVICEBUS_AUTH_MODE + - name: OrderQueueOptions__AuthMode value: WorkloadIdentity - - name: KEDA_SERVICEBUS_HOST_NAME + - name: OrderQueueOptions__FullyQualifiedNamespace value: .servicebus.windows.net - - name: KEDA_SERVICEBUS_QUEUE_NAME + - name: OrderQueueOptions__QueueName value: orders diff --git a/pod-identity.md b/pod-identity.md index 6430e55..4850e01 100644 --- a/pod-identity.md +++ b/pod-identity.md @@ -339,7 +339,7 @@ To build and run the web app locally, add the service bus connection string to a There is also a docker image available, so you can also run it locally with the following command: ```cli -docker run -p 8080:80 -d -e OrderQueue__ConnectionString="KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING" kedasamples/sample-dotnet-web +docker run -p 8080:80 -d -e OrderQueue__ConnectionString="" kedasamples/sample-dotnet-web ``` To deploy the web application to your Kubernetes cluster: diff --git a/src/Keda.Samples.DotNet.Web/Controllers/OrdersController.cs b/src/Keda.Samples.DotNet.Web/Controllers/OrdersController.cs deleted file mode 100644 index f480db1..0000000 --- a/src/Keda.Samples.DotNet.Web/Controllers/OrdersController.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System.ComponentModel.DataAnnotations; -using System.Text; -using System.Threading.Tasks; -using GuardNet; -using Keda.Samples.Dotnet.Contracts; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Azure.ServiceBus; -using Newtonsoft.Json; - -namespace Keda.Samples.DotNet.Web.Controllers -{ - /// - /// API endpoint to manage orders - /// - [ApiController] - [Route("api/v1/orders")] - public class OrdersController : ControllerBase - { - private readonly QueueClient _queueClient; - - /// - /// Constructor - /// - /// Client to send messages to queue with - public OrdersController(QueueClient queueClient) - { - Guard.NotNull(queueClient, nameof(queueClient)); - - _queueClient = queueClient; - } - - /// - /// Create Order - /// - [HttpPost(Name = "Order_Create")] - [ProducesResponseType(StatusCodes.Status201Created)] - public async Task Create([FromBody, Required] Order order) - { - var rawOrder = JsonConvert.SerializeObject(order); - var orderMessage = new Message(Encoding.UTF8.GetBytes(rawOrder)); - await _queueClient.SendAsync(orderMessage); - - return Accepted(); - } - } -} \ No newline at end of file diff --git a/src/Keda.Samples.DotNet.Web/Controllers/QueueController.cs b/src/Keda.Samples.DotNet.Web/Controllers/QueueController.cs deleted file mode 100644 index f1907b0..0000000 --- a/src/Keda.Samples.DotNet.Web/Controllers/QueueController.cs +++ /dev/null @@ -1,37 +0,0 @@ -using System.Threading.Tasks; -using Keda.Samples.Dotnet.Contracts; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Azure.ServiceBus; -using Microsoft.Azure.ServiceBus.Management; -using Microsoft.Extensions.Configuration; - -namespace Keda.Samples.DotNet.Web.Controllers -{ - [ApiController] - [Route("api/v1/[controller]")] - public class QueueController : ControllerBase - { - protected IConfiguration Configuration { get; } - - public QueueController(IConfiguration configuration) - { - Configuration = configuration; - } - - [HttpGet] - [ApiExplorerSettings(IgnoreApi = true)] - public async Task Get() - { - var connectionString = Configuration.GetValue("KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING"); - - // Check current queue length - var client = new ManagementClient(new ServiceBusConnectionStringBuilder(connectionString)); - var queueInfo = await client.GetQueueRuntimeInfoAsync("orders"); - - return new QueueStatus - { - MessageCount = queueInfo.MessageCount - }; - } - } -} \ No newline at end of file diff --git a/src/Keda.Samples.DotNet.Web/Dockerfile b/src/Keda.Samples.DotNet.Web/Dockerfile index b155571..d6e4a4a 100644 --- a/src/Keda.Samples.DotNet.Web/Dockerfile +++ b/src/Keda.Samples.DotNet.Web/Dockerfile @@ -1,9 +1,9 @@ -FROM mcr.microsoft.com/dotnet/core/aspnet:3.1.1-alpine AS base +FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base WORKDIR /app EXPOSE 80 EXPOSE 443 -FROM mcr.microsoft.com/dotnet/core/sdk:3.1.101-alpine AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY ["Keda.Samples.DotNet.Web/Keda.Samples.DotNet.Web.csproj", "Keda.Samples.DotNet.Web/"] COPY ["Keda.Samples.Dotnet.Contracts/Keda.Samples.Dotnet.Contracts.csproj", "Keda.Samples.Dotnet.Contracts/"] diff --git a/src/Keda.Samples.DotNet.Web/Extensions/IServiceCollectionExtensions.cs b/src/Keda.Samples.DotNet.Web/Extensions/IServiceCollectionExtensions.cs deleted file mode 100644 index eabd0d3..0000000 --- a/src/Keda.Samples.DotNet.Web/Extensions/IServiceCollectionExtensions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.IO; -using Microsoft.OpenApi.Models; - -namespace Microsoft.Extensions.DependencyInjection -{ - public static class IServiceCollectionExtensions - { - public static IServiceCollection AddSwagger(this IServiceCollection services) - { - var openApiInformation = new OpenApiInfo - { - Title = "KEDA API", - Version = "v1" - }; - - services.AddSwaggerGen(swaggerGenerationOptions => - { - swaggerGenerationOptions.SwaggerDoc("v1", openApiInformation); - swaggerGenerationOptions.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, - "Keda.Samples.DotNet.Web.Open-Api.xml")); - }); - - return services; - } - } -} \ No newline at end of file diff --git a/src/Keda.Samples.DotNet.Web/Keda.Samples.DotNet.Web.Open-Api.xml b/src/Keda.Samples.DotNet.Web/Keda.Samples.DotNet.Web.Open-Api.xml index f970cc4..9bb585c 100644 --- a/src/Keda.Samples.DotNet.Web/Keda.Samples.DotNet.Web.Open-Api.xml +++ b/src/Keda.Samples.DotNet.Web/Keda.Samples.DotNet.Web.Open-Api.xml @@ -9,13 +9,7 @@ API endpoint to manage orders - - - Constructor - - Client to send messages to queue with - - + Create Order diff --git a/src/Keda.Samples.DotNet.Web/Keda.Samples.DotNet.Web.csproj b/src/Keda.Samples.DotNet.Web/Keda.Samples.DotNet.Web.csproj index 2b825b9..dfad7ce 100644 --- a/src/Keda.Samples.DotNet.Web/Keda.Samples.DotNet.Web.csproj +++ b/src/Keda.Samples.DotNet.Web/Keda.Samples.DotNet.Web.csproj @@ -1,28 +1,22 @@  - netcoreapp3.1 + net8.0 + 12 ..\docker-compose.dcproj 858adc6c-c378-4698-a3c4-1112a29d6561 true - Keda.Samples.DotNet.Web.Open-Api.xml Linux - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + + + + diff --git a/src/Keda.Samples.DotNet.Web/OrderQueueSettings.cs b/src/Keda.Samples.DotNet.Web/OrderQueueSettings.cs deleted file mode 100644 index 9712444..0000000 --- a/src/Keda.Samples.DotNet.Web/OrderQueueSettings.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace Keda.Samples.DotNet.Web -{ - public class OrderQueueSettings - { - public string ConnectionString { get; set; } - } -} diff --git a/src/Keda.Samples.DotNet.Web/Pages/Error.cshtml b/src/Keda.Samples.DotNet.Web/Pages/Error.cshtml index 6f92b95..317d91e 100644 --- a/src/Keda.Samples.DotNet.Web/Pages/Error.cshtml +++ b/src/Keda.Samples.DotNet.Web/Pages/Error.cshtml @@ -1,5 +1,4 @@ @page -@model ErrorModel @{ ViewData["Title"] = "Error"; } @@ -7,13 +6,6 @@

Error.

An error occurred while processing your request.

-@if (Model.ShowRequestId) -{ -

- Request ID: @Model.RequestId -

-} -

Development Mode

Swapping to the Development environment displays detailed information about the error that occurred. diff --git a/src/Keda.Samples.DotNet.Web/Pages/Error.cshtml.cs b/src/Keda.Samples.DotNet.Web/Pages/Error.cshtml.cs deleted file mode 100644 index 864ca9b..0000000 --- a/src/Keda.Samples.DotNet.Web/Pages/Error.cshtml.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.Extensions.Logging; - -namespace Keda.Samples.DotNet.Web.Pages -{ - [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] - public class ErrorModel : PageModel - { - public string RequestId { get; set; } - - public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); - - private readonly ILogger _logger; - - public ErrorModel(ILogger logger) - { - _logger = logger; - } - - public void OnGet() - { - RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; - } - } -} diff --git a/src/Keda.Samples.DotNet.Web/Pages/Index.cshtml.cs b/src/Keda.Samples.DotNet.Web/Pages/Index.cshtml.cs deleted file mode 100644 index 59ee267..0000000 --- a/src/Keda.Samples.DotNet.Web/Pages/Index.cshtml.cs +++ /dev/null @@ -1,19 +0,0 @@ -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.Extensions.Logging; - -namespace Keda.Samples.DotNet.Web.Pages -{ - public class IndexModel : PageModel - { - private readonly ILogger _logger; - - public IndexModel(ILogger logger) - { - _logger = logger; - } - - public void OnGet() - { - } - } -} diff --git a/src/Keda.Samples.DotNet.Web/Pages/Privacy.cshtml b/src/Keda.Samples.DotNet.Web/Pages/Privacy.cshtml index 46ba966..5c36d09 100644 --- a/src/Keda.Samples.DotNet.Web/Pages/Privacy.cshtml +++ b/src/Keda.Samples.DotNet.Web/Pages/Privacy.cshtml @@ -1,5 +1,4 @@ @page -@model PrivacyModel @{ ViewData["Title"] = "Privacy Policy"; } diff --git a/src/Keda.Samples.DotNet.Web/Pages/Privacy.cshtml.cs b/src/Keda.Samples.DotNet.Web/Pages/Privacy.cshtml.cs deleted file mode 100644 index 3c6aa4f..0000000 --- a/src/Keda.Samples.DotNet.Web/Pages/Privacy.cshtml.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Microsoft.Extensions.Logging; - -namespace Keda.Samples.DotNet.Web.Pages -{ - public class PrivacyModel : PageModel - { - private readonly ILogger _logger; - - public PrivacyModel(ILogger logger) - { - _logger = logger; - } - - public void OnGet() - { - } - } -} diff --git a/src/Keda.Samples.DotNet.Web/Program.cs b/src/Keda.Samples.DotNet.Web/Program.cs index dde44e4..122256c 100644 --- a/src/Keda.Samples.DotNet.Web/Program.cs +++ b/src/Keda.Samples.DotNet.Web/Program.cs @@ -1,25 +1,51 @@ -using Microsoft.AspNetCore.Hosting; +using System.ComponentModel.DataAnnotations; +using System.Text.Json; +using System.Threading; +using Azure.Messaging.ServiceBus; +using Keda.Samples.Dotnet.Contracts; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -namespace Keda.Samples.DotNet.Web -{ - public class Program +var builder = WebApplication.CreateBuilder(); +builder.Configuration.AddJsonFile("appsettings.local.json", optional: true); +builder.Services.AddOptions().BindConfiguration(nameof(OrderQueueOptions)); +builder.Services.AddLogging(lb => lb.AddConsole()); +builder.Services.AddControllers(); +builder.Services.AddRazorPages(); +builder.Services.AddSignalR(); +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); +builder.Services.AddOrderQueueServices(); + + +var app = builder.Build(); +if (app.Environment.IsDevelopment()) app.UseDeveloperExceptionPage(); +else app.UseExceptionHandler("/Error"); + +app.UseStaticFiles(); +app.UseRouting(); +app.UseAuthorization(); +app.MapRazorPages(); +app.UseSwagger(); +app.UseSwaggerUI(); + +app.MapGet("/api/v1/queue", + async ([FromServices] ServiceBusReceiver receiver, CancellationToken ct) => + new QueueStatus((await receiver.PeekMessagesAsync(100, null, ct)).Count)); + +app.MapPost("api/v1/orders", + async ([FromBody, Required] Order order, [FromServices] ServiceBusSender sender, CancellationToken ct) => { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } - - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.ConfigureLogging((hostBuilderContext, loggingBuilder) => - { - loggingBuilder.AddConsole(consoleLoggerOptions => consoleLoggerOptions.TimestampFormat = "[HH:mm:ss]"); - }); - webBuilder.UseStartup(); - }); - } -} + var jsonString = JsonSerializer.Serialize(order); + var orderMessage = new ServiceBusMessage(jsonString); + await sender.SendMessageAsync(orderMessage, ct); + + return TypedResults.Accepted(order.Id); + }).WithName("Order_Create"); + +await app.RunAsync(); diff --git a/src/Keda.Samples.DotNet.Web/Startup.cs b/src/Keda.Samples.DotNet.Web/Startup.cs deleted file mode 100644 index 381a80e..0000000 --- a/src/Keda.Samples.DotNet.Web/Startup.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using System.IO; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Azure.ServiceBus; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.OpenApi.Models; - -namespace Keda.Samples.DotNet.Web -{ - public class Startup - { - public Startup(IConfiguration configuration) - { - Configuration = configuration; - } - - public IConfiguration Configuration { get; } - - // This method gets called by the runtime. Use this method to add services to the container. - public void ConfigureServices(IServiceCollection services) - { - services.AddControllers(); - services.AddRazorPages(); - services.AddSignalR(); - - services.AddOptions(); - var orderQueueSection = Configuration.GetSection("OrderQueue"); - services.Configure(orderQueueSection); - - services.AddSwagger(); - services.AddScoped(serviceProvider => - { - var connectionString = Configuration.GetValue("KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING"); - var serviceBusConnectionStringBuilder = new ServiceBusConnectionStringBuilder(connectionString); - return new QueueClient(serviceBusConnectionStringBuilder.GetNamespaceConnectionString(), serviceBusConnectionStringBuilder.EntityPath); - }); - } - - // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - if (env.IsDevelopment()) - { - app.UseDeveloperExceptionPage(); - } - else - { - app.UseExceptionHandler("/Error"); - // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. - app.UseHsts(); - } - - app.UseHttpsRedirection(); - app.UseStaticFiles(); - - app.UseRouting(); - - app.UseAuthorization(); - - app.UseEndpoints(endpoints => - { - endpoints.MapRazorPages(); - endpoints.MapControllers(); - }); - - app.UseSwagger(); - app.UseSwaggerUI(swaggerUiOptions => - { - swaggerUiOptions.SwaggerEndpoint("v1/swagger.json", "Keda.Samples.Dotnet.API"); - swaggerUiOptions.DocumentTitle = "KEDA API"; - }); - } - } -} diff --git a/src/Keda.Samples.DotNet.Web/appsettings.json b/src/Keda.Samples.DotNet.Web/appsettings.json index 8ed479d..dcca987 100644 --- a/src/Keda.Samples.DotNet.Web/appsettings.json +++ b/src/Keda.Samples.DotNet.Web/appsettings.json @@ -7,8 +7,13 @@ } }, "AllowedHosts": "*", - "OrderQueue": { - "ConnectionString": "KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING" + "OrderQueueOptions": { + "QueueName": "orders", + "AuthMode": "ConnectionString", + "FullyQualifiedNamespace": ".servicebus.windows.net", + "ConnectionString": "", + "TenantId": "", + "ClientId": "", + "ClientSecret": "" } - } diff --git a/src/Keda.Samples.Dotnet.Contracts/AddServiceBusClientExtension.cs b/src/Keda.Samples.Dotnet.Contracts/AddServiceBusClientExtension.cs new file mode 100644 index 0000000..de9d09b --- /dev/null +++ b/src/Keda.Samples.Dotnet.Contracts/AddServiceBusClientExtension.cs @@ -0,0 +1,65 @@ +using System; +using Azure.Identity; +using Azure.Messaging.ServiceBus; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Logging; + +namespace Keda.Samples.Dotnet.Contracts; + +public static class AddServiceBusClientExtension +{ + public static IServiceCollection AddOrderQueueServices(this IServiceCollection services) + { + services.AddSingleton(svc => + { + var logger = svc.GetRequiredService>(); + var options = svc.GetRequiredService>(); + return AuthenticateToAzureServiceBus(options.Value, logger); + }); + services.AddScoped(svc => + { + var client = svc.GetRequiredService(); + var options = svc.GetRequiredService>(); + return client.CreateReceiver(options.Value.GetEntityPath()); + }); + services.AddScoped(svc => + { + var client = svc.GetRequiredService(); + var options = svc.GetRequiredService>(); + return client.CreateSender(options.Value.GetEntityPath()); + }); + services.AddScoped(svc => + { + var client = svc.GetRequiredService(); + var options = svc.GetRequiredService>(); + return client.CreateProcessor(options.Value.GetEntityPath()); + }); + + return services; + } + + private static ServiceBusClient AuthenticateToAzureServiceBus(OrderQueueOptions options, ILogger logger) + { + switch (options.AuthMode) + { + case OrderQueueOptions.AuthenticationMode.AzureDefaultCredential: + logger.LogInformation($"Authentication with Azure Default Credential"); + return new ServiceBusClient(options.FullyQualifiedNamespace, new DefaultAzureCredential()); + case OrderQueueOptions.AuthenticationMode.ConnectionString: + logger.LogInformation($"Authentication by using connection string"); + return new ServiceBusClient(options.ConnectionString); + case OrderQueueOptions.AuthenticationMode.ServicePrinciple: + logger.LogInformation("Authentication by using service principle {ClientId}", options.ClientId); + return new ServiceBusClient(options.FullyQualifiedNamespace, new ClientSecretCredential(options.TenantId, options.ClientId, options.ClientSecret)); + case OrderQueueOptions.AuthenticationMode.PodIdentity: + logger.LogInformation("Authentication by using pod identity {ClientId}", options.ClientId); + return new ServiceBusClient(options.FullyQualifiedNamespace, new ManagedIdentityCredential(options.ClientId)); + case OrderQueueOptions.AuthenticationMode.WorkloadIdentity: + logger.LogInformation("Authentication by using workload identity {ClientId}", options.ClientId); + return new ServiceBusClient(options.FullyQualifiedNamespace, new ManagedIdentityCredential(options.ClientId)); + default: + throw new ArgumentOutOfRangeException("AuthMode","AuthMode not supported"); + } + } +} diff --git a/src/Keda.Samples.Dotnet.Contracts/Customer.cs b/src/Keda.Samples.Dotnet.Contracts/Customer.cs index b54e674..ac543db 100644 --- a/src/Keda.Samples.Dotnet.Contracts/Customer.cs +++ b/src/Keda.Samples.Dotnet.Contracts/Customer.cs @@ -1,9 +1,3 @@ -namespace Keda.Samples.Dotnet.Contracts -{ - public class Customer - { - public string FirstName { get; set; } +namespace Keda.Samples.Dotnet.Contracts; - public string LastName { get; set; } - } -} \ No newline at end of file +public record Customer(string FirstName, string LastName); diff --git a/src/Keda.Samples.Dotnet.Contracts/Keda.Samples.Dotnet.Contracts.csproj b/src/Keda.Samples.Dotnet.Contracts/Keda.Samples.Dotnet.Contracts.csproj index a20524d..bc69f45 100644 --- a/src/Keda.Samples.Dotnet.Contracts/Keda.Samples.Dotnet.Contracts.csproj +++ b/src/Keda.Samples.Dotnet.Contracts/Keda.Samples.Dotnet.Contracts.csproj @@ -1,11 +1,19 @@ - - netcoreapp3.1 - - - - - + + net8.0 + 12 + + + + + + + + + + + + diff --git a/src/Keda.Samples.Dotnet.Contracts/Order.cs b/src/Keda.Samples.Dotnet.Contracts/Order.cs index f08de1c..0d98a54 100644 --- a/src/Keda.Samples.Dotnet.Contracts/Order.cs +++ b/src/Keda.Samples.Dotnet.Contracts/Order.cs @@ -1,10 +1,4 @@ -namespace Keda.Samples.Dotnet.Contracts -{ - public class Order - { - public string Id { get; set; } - public int Amount { get; set; } - public string ArticleNumber { get; set; } - public Customer Customer { get; set; } - } -} \ No newline at end of file +namespace Keda.Samples.Dotnet.Contracts; + + +public record Order(string Id, int Amount, string ArticleNumber, Customer Customer); diff --git a/src/Keda.Samples.Dotnet.Contracts/OrderQueueOptions.cs b/src/Keda.Samples.Dotnet.Contracts/OrderQueueOptions.cs new file mode 100644 index 0000000..e3c6684 --- /dev/null +++ b/src/Keda.Samples.Dotnet.Contracts/OrderQueueOptions.cs @@ -0,0 +1,37 @@ +using System.ComponentModel.DataAnnotations; +using Azure.Messaging.ServiceBus; + +namespace Keda.Samples.Dotnet.Contracts; + +public class OrderQueueOptions +{ + + public enum AuthenticationMode + { + ConnectionString, + ServicePrinciple, + PodIdentity, + WorkloadIdentity, + AzureDefaultCredential, + } + + [Required] + public AuthenticationMode AuthMode { get; set; } + [Required] + public string QueueName { get; set; } + + public string ConnectionString { get; set; } + [Required] + public string FullyQualifiedNamespace { get; set;} + public string TenantId { get; set;} + public string ClientId { get; set;} + public string ClientSecret { get; set;} + + public string GetEntityPath() + { + if (AuthMode != AuthenticationMode.ConnectionString) return QueueName; + + var sb = ServiceBusConnectionStringProperties.Parse(ConnectionString); + return sb.EntityPath; + } +} diff --git a/src/Keda.Samples.Dotnet.Contracts/QueueStatus.cs b/src/Keda.Samples.Dotnet.Contracts/QueueStatus.cs index 378644b..1d78639 100644 --- a/src/Keda.Samples.Dotnet.Contracts/QueueStatus.cs +++ b/src/Keda.Samples.Dotnet.Contracts/QueueStatus.cs @@ -1,10 +1,3 @@ -using Newtonsoft.Json; +namespace Keda.Samples.Dotnet.Contracts; -namespace Keda.Samples.Dotnet.Contracts -{ - public class QueueStatus - { - [JsonProperty] - public long MessageCount { get; set; } - } -} +public record QueueStatus(long MessageCount); diff --git a/src/Keda.Samples.Dotnet.OrderGenerator/Keda.Samples.Dotnet.OrderGenerator.csproj b/src/Keda.Samples.Dotnet.OrderGenerator/Keda.Samples.Dotnet.OrderGenerator.csproj index d8ba517..afe2f68 100644 --- a/src/Keda.Samples.Dotnet.OrderGenerator/Keda.Samples.Dotnet.OrderGenerator.csproj +++ b/src/Keda.Samples.Dotnet.OrderGenerator/Keda.Samples.Dotnet.OrderGenerator.csproj @@ -1,18 +1,21 @@  + net8.0 + 12 Exe - netcoreapp3.1 - - - + - + + + + + diff --git a/src/Keda.Samples.Dotnet.OrderGenerator/Program.cs b/src/Keda.Samples.Dotnet.OrderGenerator/Program.cs index e21fdca..5b09ae5 100644 --- a/src/Keda.Samples.Dotnet.OrderGenerator/Program.cs +++ b/src/Keda.Samples.Dotnet.OrderGenerator/Program.cs @@ -1,68 +1,48 @@ using Bogus; using Keda.Samples.Dotnet.Contracts; -using Newtonsoft.Json; using System; -using System.Threading.Tasks; +using System.Text.Json; using Azure.Messaging.ServiceBus; - -namespace Keda.Samples.Dotnet.OrderGenerator +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Options; + +var builder = Host.CreateApplicationBuilder(); +builder.Configuration.AddJsonFile("appsettings.local.json", optional: true); +builder.Services.AddOptions().Bind(builder.Configuration.GetSection(nameof(OrderQueueOptions))); +builder.Services.AddOrderQueueServices(); +var app = builder.Build(); +var sender = app.Services.GetRequiredService(); + +Console.WriteLine("Let's queue some orders, how many do you want?"); +var orderCount = ReadNumber(); + +for (var i = 0; i < orderCount; i++) { - class Program - { - private const string QueueName = ""; - private const string ConnectionString = ""; - - static async Task Main(string[] args) - { - Console.WriteLine("Let's queue some orders, how many do you want?"); - - var requestedAmount = DetermineOrderAmount(); - await QueueOrders(requestedAmount); - - Console.WriteLine("That's it, see you later!"); - } - - private static async Task QueueOrders(int requestedAmount) - { - var serviceBusClient = new ServiceBusClient(ConnectionString); - var serviceBusSender = serviceBusClient.CreateSender(QueueName); + var f = new Faker(); + var customer = new Customer(f.Name.FirstName(), f.Name.LastName()); + var order = new Order(Guid.NewGuid().ToString(),f.Random.Int(),f.Commerce.Product(), customer); - for (int currentOrderAmount = 0; currentOrderAmount < requestedAmount; currentOrderAmount++) - { - var order = GenerateOrder(); - var rawOrder = JsonConvert.SerializeObject(order); - var orderMessage = new ServiceBusMessage(rawOrder); + var rawOrder = JsonSerializer.Serialize(order); + var orderMessage = new ServiceBusMessage(rawOrder); - Console.WriteLine($"Queuing order {order.Id} - A {order.ArticleNumber} for {order.Customer.FirstName} {order.Customer.LastName}"); - await serviceBusSender.SendMessageAsync(orderMessage); - } - } - - private static Order GenerateOrder() - { - var customerGenerator = new Faker() - .RuleFor(u => u.FirstName, (f, u) => f.Name.FirstName()) - .RuleFor(u => u.LastName, (f, u) => f.Name.LastName()); - - var orderGenerator = new Faker() - .RuleFor(u => u.Customer, () => customerGenerator) - .RuleFor(u => u.Id, f => Guid.NewGuid().ToString()) - .RuleFor(u => u.Amount, f => f.Random.Int()) - .RuleFor(u => u.ArticleNumber, f => f.Commerce.Product()); + Console.WriteLine($"Queuing order {order.Id} - A {order.ArticleNumber} for {order.Customer.FirstName} {order.Customer.LastName}"); + await sender.SendMessageAsync(orderMessage); +} - return orderGenerator.Generate(); - } +Console.WriteLine("That's it, see you later!"); +return; - private static int DetermineOrderAmount() - { - var rawAmount = Console.ReadLine(); - if (int.TryParse(rawAmount, out int amount)) - { - return amount; - } - Console.WriteLine("That's not a valid amount, let's try that again"); - return DetermineOrderAmount(); - } +static int ReadNumber() +{ + var rawAmount = Console.ReadLine(); + if (int.TryParse(rawAmount, out var amount)) + { + return amount; } + + Console.WriteLine("That's not a valid amount, let's try that again"); + return ReadNumber(); } diff --git a/src/Keda.Samples.Dotnet.OrderGenerator/appsettings.json b/src/Keda.Samples.Dotnet.OrderGenerator/appsettings.json new file mode 100644 index 0000000..dcca987 --- /dev/null +++ b/src/Keda.Samples.Dotnet.OrderGenerator/appsettings.json @@ -0,0 +1,19 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*", + "OrderQueueOptions": { + "QueueName": "orders", + "AuthMode": "ConnectionString", + "FullyQualifiedNamespace": ".servicebus.windows.net", + "ConnectionString": "", + "TenantId": "", + "ClientId": "", + "ClientSecret": "" + } +} diff --git a/src/Keda.Samples.Dotnet.OrderProcessor/Dockerfile b/src/Keda.Samples.Dotnet.OrderProcessor/Dockerfile index db4760b..29ccfdd 100644 --- a/src/Keda.Samples.Dotnet.OrderProcessor/Dockerfile +++ b/src/Keda.Samples.Dotnet.OrderProcessor/Dockerfile @@ -1,7 +1,7 @@ -FROM mcr.microsoft.com/dotnet/core/runtime:3.1.1-alpine AS base +FROM mcr.microsoft.com/dotnet/runtime:8.0 AS base WORKDIR /app -FROM mcr.microsoft.com/dotnet/core/sdk:3.1.101-alpine AS build +FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build WORKDIR /src COPY ["Keda.Samples.Dotnet.OrderProcessor/Keda.Samples.Dotnet.OrderProcessor.csproj", "Keda.Samples.Dotnet.OrderProcessor/"] COPY ["Keda.Samples.Dotnet.Contracts/Keda.Samples.Dotnet.Contracts.csproj", "Keda.Samples.Dotnet.Contracts/"] diff --git a/src/Keda.Samples.Dotnet.OrderProcessor/Keda.Samples.Dotnet.OrderProcessor.csproj b/src/Keda.Samples.Dotnet.OrderProcessor/Keda.Samples.Dotnet.OrderProcessor.csproj index 1da0eba..7711a9a 100644 --- a/src/Keda.Samples.Dotnet.OrderProcessor/Keda.Samples.Dotnet.OrderProcessor.csproj +++ b/src/Keda.Samples.Dotnet.OrderProcessor/Keda.Samples.Dotnet.OrderProcessor.csproj @@ -1,16 +1,16 @@  - netcoreapp3.1 + net8.0 + 12 Linux - - - - - + + + + diff --git a/src/Keda.Samples.Dotnet.OrderProcessor/OrdersQueueProcessor.cs b/src/Keda.Samples.Dotnet.OrderProcessor/OrdersQueueProcessor.cs index 8cca951..79e3f4c 100644 --- a/src/Keda.Samples.Dotnet.OrderProcessor/OrdersQueueProcessor.cs +++ b/src/Keda.Samples.Dotnet.OrderProcessor/OrdersQueueProcessor.cs @@ -2,26 +2,23 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using Azure.Messaging.ServiceBus; using Keda.Samples.Dotnet.Contracts; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace Keda.Samples.Dotnet.OrderProcessor { - public class OrdersQueueProcessor : QueueWorker + public class OrdersQueueProcessor(ServiceBusProcessor processor, ILogger logger) : QueueWorker(processor, logger) { - public OrdersQueueProcessor(IConfiguration configuration, ILogger logger) - : base(configuration, logger) - { - } - protected override async Task ProcessMessage(Order order, string messageId, IReadOnlyDictionary userProperties, CancellationToken cancellationToken) { - Logger.LogInformation("Processing order {OrderId} for {OrderAmount} units of {OrderArticle} bought by {CustomerFirstName} {CustomerLastName}", order.Id, order.Amount, order.ArticleNumber, order.Customer.FirstName, order.Customer.LastName); + logger.LogInformation("Processing order {OrderId} for {OrderAmount} units of {OrderArticle} bought by {CustomerFirstName} {CustomerLastName}", order.Id, order.Amount, order.ArticleNumber, order.Customer.FirstName, order.Customer.LastName); await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken); - Logger.LogInformation("Order {OrderId} processed", order.Id); + logger.LogInformation("Order {OrderId} processed", order.Id); } } } diff --git a/src/Keda.Samples.Dotnet.OrderProcessor/Program.cs b/src/Keda.Samples.Dotnet.OrderProcessor/Program.cs index 74f9869..0cbc84c 100644 --- a/src/Keda.Samples.Dotnet.OrderProcessor/Program.cs +++ b/src/Keda.Samples.Dotnet.OrderProcessor/Program.cs @@ -1,30 +1,13 @@ +using Keda.Samples.Dotnet.Contracts; +using Keda.Samples.Dotnet.OrderProcessor; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -namespace Keda.Samples.Dotnet.OrderProcessor -{ - public class Program - { - public static void Main(string[] args) - { - CreateHostBuilder(args).Build().Run(); - } +var builder = Host.CreateApplicationBuilder(); +builder.Configuration.AddJsonFile("appsettings.local.json", optional: true); +builder.Services.AddOptions().BindConfiguration(nameof(OrderQueueOptions)); +builder.Services.AddOrderQueueServices(); +builder.Services.AddHostedService(); - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) - .ConfigureAppConfiguration((hostingContext, config) => - { - config.AddEnvironmentVariables(); - }) - .ConfigureLogging((hostBuilderContext, loggingBuilder) => - { - loggingBuilder.AddConsole(consoleLoggerOptions => consoleLoggerOptions.TimestampFormat = "[HH:mm:ss]"); - }) - .ConfigureServices(services => - { - services.AddHostedService(); - }); - } -} +builder.Build().Run(); diff --git a/src/Keda.Samples.Dotnet.OrderProcessor/QueueWorker.cs b/src/Keda.Samples.Dotnet.OrderProcessor/QueueWorker.cs index 2e17dcc..b7bd81f 100644 --- a/src/Keda.Samples.Dotnet.OrderProcessor/QueueWorker.cs +++ b/src/Keda.Samples.Dotnet.OrderProcessor/QueueWorker.cs @@ -1,124 +1,76 @@ using System; using System.Collections.Generic; using System.Text; +using System.Text.Json; using System.Threading; using System.Threading.Tasks; using Azure.Messaging.ServiceBus; -using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; -using Newtonsoft.Json; -namespace Keda.Samples.Dotnet.OrderProcessor +namespace Keda.Samples.Dotnet.OrderProcessor; + +public abstract class QueueWorker(ServiceBusProcessor processor, ILogger> logger) : BackgroundService { - public abstract class QueueWorker : BackgroundService + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - protected ILogger> Logger { get; } - protected IConfiguration Configuration { get; } + processor.ProcessMessageAsync += HandleMessageAsync; + processor.ProcessErrorAsync += HandleReceivedExceptionAsync; - protected QueueWorker(IConfiguration configuration, ILogger> logger) - { - Configuration = configuration; - Logger = logger; - } + logger.LogInformation($"Starting message pump on queue {processor.EntityPath} in namespace {processor.FullyQualifiedNamespace}"); + await processor.StartProcessingAsync(stoppingToken); + logger.LogInformation("Message pump started"); - protected override async Task ExecuteAsync(CancellationToken stoppingToken) + while (!stoppingToken.IsCancellationRequested) { - var queueName = Configuration.GetValue("KEDA_SERVICEBUS_QUEUE_NAME"); - var messageProcessor = CreateServiceBusProcessor(queueName); - messageProcessor.ProcessMessageAsync += HandleMessageAsync; - messageProcessor.ProcessErrorAsync += HandleReceivedExceptionAsync; - - Logger.LogInformation($"Starting message pump on queue {queueName} in namespace {messageProcessor.FullyQualifiedNamespace}"); - await messageProcessor.StartProcessingAsync(stoppingToken); - Logger.LogInformation("Message pump started"); - - while (!stoppingToken.IsCancellationRequested) - { - await Task.Delay(TimeSpan.FromSeconds(1)); - } - - Logger.LogInformation("Closing message pump"); - await messageProcessor.CloseAsync(cancellationToken: stoppingToken); - Logger.LogInformation("Message pump closed : {Time}", DateTimeOffset.UtcNow); + await Task.Delay(TimeSpan.FromSeconds(1)); } - private ServiceBusProcessor CreateServiceBusProcessor(string queueName) - { - var serviceBusClient = AuthenticateToAzureServiceBus(); - var messageProcessor = serviceBusClient.CreateProcessor(queueName); - return messageProcessor; - } + logger.LogInformation("Closing message pump"); + await processor.CloseAsync(CancellationToken.None); + await processor.DisposeAsync(); + logger.LogInformation("Message pump closed : {Time}", DateTimeOffset.UtcNow); + } - private ServiceBusClient AuthenticateToAzureServiceBus() + + private async Task HandleMessageAsync (ProcessMessageEventArgs processMessageEventArgs) + { + try { - var authenticationMode = Configuration.GetValue("KEDA_SERVICEBUS_AUTH_MODE"); - - ServiceBusClient serviceBusClient; + var rawMessageBody = Encoding.UTF8.GetString(processMessageEventArgs.Message.Body); + logger.LogInformation("Received message {MessageId} with body {MessageBody}", + processMessageEventArgs.Message.MessageId, rawMessageBody); - switch (authenticationMode) + var order = JsonSerializer.Deserialize(rawMessageBody); + if (order != null) { - case AuthenticationMode.ConnectionString: - Logger.LogInformation($"Authentication by using connection string"); - serviceBusClient = ServiceBusClientFactory.CreateWithConnectionStringAuthentication(Configuration); - break; - case AuthenticationMode.ServicePrinciple: - Logger.LogInformation("Authentication by using service principle"); - serviceBusClient = ServiceBusClientFactory.CreateWithServicePrincipleAuthentication(Configuration); - break; - case AuthenticationMode.PodIdentity: - Logger.LogInformation("Authentication by using pod identity"); - serviceBusClient = ServiceBusClientFactory.CreateWithPodIdentityAuthentication(Configuration, Logger); - break; - case AuthenticationMode.WorkloadIdentity: - Logger.LogInformation("Authentication by using workload identity"); - serviceBusClient = ServiceBusClientFactory.CreateWithWorkloadIdentityAuthentication(Configuration, Logger); - break; - default: - throw new ArgumentOutOfRangeException(); + await ProcessMessage(order, processMessageEventArgs.Message.MessageId, + processMessageEventArgs.Message.ApplicationProperties, + processMessageEventArgs.CancellationToken); } - - return serviceBusClient; - } - - private async Task HandleMessageAsync (ProcessMessageEventArgs processMessageEventArgs) - { - try + else { - var rawMessageBody = Encoding.UTF8.GetString(processMessageEventArgs.Message.Body.ToBytes().ToArray()); - Logger.LogInformation("Received message {MessageId} with body {MessageBody}", - processMessageEventArgs.Message.MessageId, rawMessageBody); - - var order = JsonConvert.DeserializeObject(rawMessageBody); - if (order != null) - { - await ProcessMessage(order, processMessageEventArgs.Message.MessageId, - processMessageEventArgs.Message.ApplicationProperties, - processMessageEventArgs.CancellationToken); - } - else - { - Logger.LogError( - "Unable to deserialize to message contract {ContractName} for message {MessageBody}", - typeof(TMessage), rawMessageBody); - } + logger.LogError( + "Unable to deserialize to message contract {ContractName} for message {MessageBody}", + typeof(TMessage), rawMessageBody); + } - Logger.LogInformation("Message {MessageId} processed", processMessageEventArgs.Message.MessageId); + logger.LogInformation("Message {MessageId} processed", processMessageEventArgs.Message.MessageId); - await processMessageEventArgs.CompleteMessageAsync(processMessageEventArgs.Message); - } - catch (Exception ex) - { - Logger.LogError(ex, "Unable to handle message"); - } + await processMessageEventArgs.CompleteMessageAsync(processMessageEventArgs.Message); } - - private Task HandleReceivedExceptionAsync(ProcessErrorEventArgs exceptionEvent) + catch (Exception ex) { - Logger.LogError(exceptionEvent.Exception, "Unable to process message"); - return Task.CompletedTask; + logger.LogError(ex, "Unable to handle message"); } + } - protected abstract Task ProcessMessage(TMessage order, string messageId, IReadOnlyDictionary userProperties, CancellationToken cancellationToken); + private Task HandleReceivedExceptionAsync(ProcessErrorEventArgs exceptionEvent) + { + logger.LogError(exceptionEvent.Exception, "Unable to process message"); + return Task.CompletedTask; } -} + + protected abstract Task ProcessMessage(TMessage order, string messageId, IReadOnlyDictionary userProperties, CancellationToken cancellationToken); +} \ No newline at end of file diff --git a/src/Keda.Samples.Dotnet.OrderProcessor/ServiceBusClientFactory.cs b/src/Keda.Samples.Dotnet.OrderProcessor/ServiceBusClientFactory.cs deleted file mode 100644 index 89cc542..0000000 --- a/src/Keda.Samples.Dotnet.OrderProcessor/ServiceBusClientFactory.cs +++ /dev/null @@ -1,46 +0,0 @@ -using Azure.Identity; -using Azure.Messaging.ServiceBus; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; - -namespace Keda.Samples.Dotnet.OrderProcessor -{ - public static class ServiceBusClientFactory - { - public static ServiceBusClient CreateWithPodIdentityAuthentication(IConfiguration configuration, ILogger logger) - { - var hostname = configuration.GetValue("KEDA_SERVICEBUS_HOST_NAME"); - - var clientIdentityId = configuration.GetValue("KEDA_SERVICEBUS_IDENTITY_USERASSIGNEDID", defaultValue: null); - if (string.IsNullOrWhiteSpace(clientIdentityId) == false) - { - logger.LogInformation("Using user-assigned identity with ID {UserAssignedIdentityId}", clientIdentityId); - } - - return new ServiceBusClient(hostname, new ManagedIdentityCredential(clientId: clientIdentityId)); - } - - public static ServiceBusClient CreateWithWorkloadIdentityAuthentication(IConfiguration configuration, ILogger logger) - { - var hostname = configuration.GetValue("KEDA_SERVICEBUS_HOST_NAME"); - - return new ServiceBusClient(hostname, new ManagedIdentityCredential()); - } - - public static ServiceBusClient CreateWithServicePrincipleAuthentication(IConfiguration configuration) - { - var hostname = configuration.GetValue("KEDA_SERVICEBUS_HOST_NAME"); - var tenantId = configuration.GetValue("KEDA_SERVICEBUS_TENANT_ID"); - var appIdentityId = configuration.GetValue("KEDA_SERVICEBUS_IDENTITY_APPID"); - var appIdentitySecret = configuration.GetValue("KEDA_SERVICEBUS_IDENTITY_SECRET"); - - return new ServiceBusClient(hostname, new ClientSecretCredential(tenantId, appIdentityId, appIdentitySecret)); - } - - public static ServiceBusClient CreateWithConnectionStringAuthentication(IConfiguration configuration) - { - var connectionString = configuration.GetValue("KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING"); - return new ServiceBusClient(connectionString); - } - } -} diff --git a/src/Keda.Samples.Dotnet.OrderProcessor/appsettings.Development.json b/src/Keda.Samples.Dotnet.OrderProcessor/appsettings.Development.json deleted file mode 100644 index e203e94..0000000 --- a/src/Keda.Samples.Dotnet.OrderProcessor/appsettings.Development.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "Logging": { - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - } -} diff --git a/src/Keda.Samples.Dotnet.OrderProcessor/appsettings.json b/src/Keda.Samples.Dotnet.OrderProcessor/appsettings.json index 8983e0f..dcca987 100644 --- a/src/Keda.Samples.Dotnet.OrderProcessor/appsettings.json +++ b/src/Keda.Samples.Dotnet.OrderProcessor/appsettings.json @@ -5,5 +5,15 @@ "Microsoft": "Warning", "Microsoft.Hosting.Lifetime": "Information" } + }, + "AllowedHosts": "*", + "OrderQueueOptions": { + "QueueName": "orders", + "AuthMode": "ConnectionString", + "FullyQualifiedNamespace": ".servicebus.windows.net", + "ConnectionString": "", + "TenantId": "", + "ClientId": "", + "ClientSecret": "" } } diff --git a/src/README.md b/src/README.md index 1e3ba0b..752d0f7 100644 --- a/src/README.md +++ b/src/README.md @@ -1,9 +1,9 @@ # .NET Order Processor This contains the sources of the sample: -- `Keda.Samples.Dotnet.Contracts` contains the message contracts +- `Keda.Samples.Dotnet.Contracts` contains the message contracts and ServiceBus DI helper - `Keda.Samples.Dotnet.OrderGenerator` is a utility that queues new orders to an Azure Service Bus Queue -- `Keda.Samples.Dotnet.OrderProcessor` is a .NET Core 3.0 worker that processes orders from an Azure Service Bus Queue -- `Keda.Samples.Dotnet.OrderWeb` is a ASP.NET Core 3.0 web app that visualizes the size of the service bus queue +- `Keda.Samples.Dotnet.OrderProcessor` is a .NET 8.0 worker that processes orders from an Azure Service Bus Queue +- `Keda.Samples.Dotnet.OrderWeb` is a ASP.NET 8.0 web app that visualizes the size of the service bus queue ## Building & Running the sample locally on Docker @@ -15,13 +15,13 @@ Build Docker container for order processor Run order processor locally ```shell -❯ docker run --detach --env KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING="" keda-sample-dotnet-worker-servicebus-queue +❯ docker run --detach --env OrderQueueOptions__ConnectionString="" keda-sample-dotnet-worker-servicebus-queue c6775c9383e56fc16da37b62ebbff0dc44d4019a53d282a1ef260a6d71022a32 ``` Let's use the test orders via our `OrderGenerator` tool: ```shell -❯ dotnet run --project .\Keda.Samples.Dotnet.OrderGenerator\Keda.Samples.Dotnet.OrderGenerator.csproj +❯ OrderQueueOptions__ConnectionString="" dotnet run --project .\Keda.Samples.Dotnet.OrderGenerator\Keda.Samples.Dotnet.OrderGenerator.csproj Let's queue some orders, how many do you want? 2 Queuing order 719a7b19-f1f7-4f46-a543-8da9bfaf843d - A Hat for Reilly Davis diff --git a/src/docker-compose.override.yml b/src/docker-compose.override.yml index 379eaa3..ed2229c 100644 --- a/src/docker-compose.override.yml +++ b/src/docker-compose.override.yml @@ -4,25 +4,26 @@ services: keda.samples.worker.servicebus: environment: - ASPNETCORE_ENVIRONMENT=Development - - KEDA_SERVICEBUS_QUEUE_NAME=orders - + - OrderQueueOptions__QueueName=orders + # Authentication mode: Connection string - - KEDA_SERVICEBUS_AUTH_MODE=ConnectionString - - KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING= - + - OrderQueueOptions__AuthMode=ConnectionString + - OrderQueueOptions__ConnectionString= + # Authentication mode: Service Principle - # - KEDA_SERVICEBUS_AUTH_MODE=ServicePrinciple - # - KEDA_SERVICEBUS_HOST_NAME=.servicebus.windows.net - # - KEDA_SERVICEBUS_TENANT_ID= - # - KEDA_SERVICEBUS_IDENTITY_APPID= - # - KEDA_SERVICEBUS_IDENTITY_SECRET= - + # - OrderQueueOptions__AuthMode=ServicePrinciple + # - OrderQueueOptions__FullyQualifiedNamespace=.servicebus.windows.net + # - OrderQueueOptions__TenantId= + # - OrderQueueOptions__ClientId= + # - OrderQueueOptions__ClientSecret= + # Authentication mode: Managed Identity - #- KEDA_SERVICEBUS_AUTH_MODE=ManagedIdentity - #- KEDA_SERVICEBUS_HOST_NAME=.servicebus.windows.net + #- OrderQueueOptions__AuthMode=ManagedIdentity + #- OrderQueueOptions__FullyQualifiedNamespace=.servicebus.windows.net # Optionally specify ID of user-assigned application identity - #- KEDA_SERVICEBUS_IDENTITY_USERASSIGNEDID= + #- OrderQueueOptions__ClientId= keda.samples.web: environment: - ASPNETCORE_ENVIRONMENT=Development - - KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING= \ No newline at end of file + - OrderQueueOptions__AuthMode=ConnectionString + - OrderQueueOptions__ConnectionString= diff --git a/workload-identity.md b/workload-identity.md index 7c9cc70..6469cec 100644 --- a/workload-identity.md +++ b/workload-identity.md @@ -367,7 +367,7 @@ To build and run the web app locally, add the service bus connection string to a There is also a docker image available, so you can also run it locally with the following command: ```cli -docker run -p 8080:80 -d -e OrderQueue__ConnectionString="KEDA_SERVICEBUS_QUEUE_CONNECTIONSTRING" kedasamples/sample-dotnet-web +docker run -p 8080:80 -d -e OrderQueue__ConnectionString="" kedasamples/sample-dotnet-web ``` To deploy the web application to your Kubernetes cluster: @@ -413,7 +413,7 @@ Run the following command with app ids of both created Azure AD applications. ```cli ❯ az ad app delete --id ``` - + ### Uninstall KEDA ```cli