Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

durable task scheduler auth extension support #362

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/workflows/validate-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,12 @@ jobs:
with:
submodules: true

- name: Setup .NET
- name: Setup .NET 6.0
uses: actions/setup-dotnet@v3
with:
dotnet-version: '6.0.x'

- name: Setup .NET from global.json
uses: actions/setup-dotnet@v3
with:
global-json-file: global.json
Expand Down
24 changes: 24 additions & 0 deletions Microsoft.DurableTask.sln
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Analyzers.Tests", "test\Ana
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureFunctionsApp.Tests", "samples\AzureFunctionsUnitTests\AzureFunctionsApp.Tests.csproj", "{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Extensions", "Extensions", "{5227C712-2355-403F-90D6-51D0BCAE4D38}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure", "src\Extensions\Azure\Azure.csproj", "{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Azure.Tests", "test\Extensions\Azure\Azure.Tests.csproj", "{DBB5DB4E-A1B0-4C86-A233-213789C46929}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AspNetWebApp", "samples\portable-sdk\dotnet\AspNetWebApp\AspNetWebApp.csproj", "{869D2D51-9372-4764-B059-C43B6C1180A3}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -185,6 +193,18 @@ Global
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9}.Release|Any CPU.Build.0 = Release|Any CPU
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Release|Any CPU.ActiveCfg = Release|Any CPU
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC}.Release|Any CPU.Build.0 = Release|Any CPU
{DBB5DB4E-A1B0-4C86-A233-213789C46929}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DBB5DB4E-A1B0-4C86-A233-213789C46929}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBB5DB4E-A1B0-4C86-A233-213789C46929}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBB5DB4E-A1B0-4C86-A233-213789C46929}.Release|Any CPU.Build.0 = Release|Any CPU
{869D2D51-9372-4764-B059-C43B6C1180A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{869D2D51-9372-4764-B059-C43B6C1180A3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{869D2D51-9372-4764-B059-C43B6C1180A3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{869D2D51-9372-4764-B059-C43B6C1180A3}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -220,6 +240,10 @@ Global
{998E9D97-BD36-4A9D-81FC-5DAC1CE40083} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
{541FCCCE-1059-4691-B027-F761CD80DE92} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
{FC2692E7-79AE-400E-A50F-8E0BCC8C9BD9} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
{5227C712-2355-403F-90D6-51D0BCAE4D38} = {8AFC9781-F6F1-4696-BB4A-9ED7CA9D612B}
{662BF73D-A4DD-4910-8625-7C12F1ACDBEC} = {5227C712-2355-403F-90D6-51D0BCAE4D38}
{DBB5DB4E-A1B0-4C86-A233-213789C46929} = {E5637F81-2FB9-4CD7-900D-455363B142A7}
{869D2D51-9372-4764-B059-C43B6C1180A3} = {EFF7632B-821E-4CFC-B4A0-ED4B24296B17}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {AB41CB55-35EA-4986-A522-387AB3402E71}
Expand Down
27 changes: 27 additions & 0 deletions samples/portable-sdk/dotnet/AspNetWebApp/AspNetWebApp.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<CompilerGeneratedFilesOutputPath>$(BaseIntermediateOutputPath)Generated</CompilerGeneratedFilesOutputPath>

<!-- Disable automatic assembly attribute generation -->
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.13.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.67.0" />
<PackageReference Include="Microsoft.DurableTask.Client.Grpc" Version="1.5.0" />
<PackageReference Include="Microsoft.DurableTask.Worker.Grpc" Version="1.5.0" />
<PackageReference Include="Microsoft.DurableTask.Generators" Version="1.0.0-preview.1" OutputItemType="Analyzer" />
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.1" />

<!-- Reference to the Azure extensions project -->
<ProjectReference Include="../../../../src/Extensions/Azure/Azure.csproj" />
</ItemGroup>

</Project>
22 changes: 22 additions & 0 deletions samples/portable-sdk/dotnet/AspNetWebApp/DockerFile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 8080

FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["AspNetWebApp.csproj", "."]
RUN dotnet restore "./AspNetWebApp.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "AspNetWebApp.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "AspNetWebApp.csproj" -c Release -o /app/publish /p:UseAppHost=false

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENV ASPNETCORE_ENVIRONMENT=Production
ENTRYPOINT ["dotnet", "AspNetWebApp.dll"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;

namespace AspNetWebApp.Scenarios;

[DurableTask]
class HelloCities : TaskOrchestrator<string, List<string>>
{
public override async Task<List<string>> RunAsync(TaskOrchestrationContext context, string input)
{
List<string> results =
[
await context.CallSayHelloAsync("Seattle"),
await context.CallSayHelloAsync("Amsterdam"),
await context.CallSayHelloAsync("Hyderabad"),
await context.CallSayHelloAsync("Shanghai"),
await context.CallSayHelloAsync("Tokyo"),
];
return results;
}
}

[DurableTask]
class SayHello : TaskActivity<string, string>
{
public override Task<string> RunAsync(TaskActivityContext context, string cityName)
{
return Task.FromResult($"Hello, {cityName}!");
}
}
55 changes: 55 additions & 0 deletions samples/portable-sdk/dotnet/AspNetWebApp/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
using System.Text.Json.Serialization;
using Azure.Core;
using Azure.Identity;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Worker;
using Microsoft.DurableTask.Extensions.Azure;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

string endpointAddress = builder.Configuration["DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS"]
?? throw new InvalidOperationException("Missing required configuration 'DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS'");

string taskHubName = builder.Configuration["DURABLE_TASK_SCHEDULER_TASK_HUB_NAME"]
?? throw new InvalidOperationException("Missing required configuration 'DURABLE_TASK_SCHEDULER_TASK_HUB_NAME'");

TokenCredential credential = builder.Environment.IsProduction()
? new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId = builder.Configuration["CONTAINER_APP_UMI_CLIENT_ID"] })
: new DefaultAzureCredential();

// Add all the generated orchestrations and activities automatically
builder.Services.AddDurableTaskWorker(builder =>
{
builder.AddTasks(r => r.AddAllGeneratedTasks());
builder.UseDurableTaskScheduler(endpointAddress, taskHubName, credential);
});

// Register the client, which can be used to start orchestrations
builder.Services.AddDurableTaskClient(builder =>
{
builder.UseDurableTaskScheduler(endpointAddress, taskHubName, credential);
});

// Configure console logging using the simpler, more compact format
builder.Services.AddLogging(logging =>
{
logging.AddSimpleConsole(options =>
{
options.SingleLine = true;
options.UseUtcTimestamp = true;
options.TimestampFormat = "yyyy-MM-ddTHH:mm:ss.fffZ ";
});
});

// Configure the HTTP request pipeline
builder.Services.AddControllers().AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull;
});

// The actual listen URL can be configured in environment variables named "ASPNETCORE_URLS" or "ASPNETCORE_URLS_HTTPS"
WebApplication app = builder.Build();
app.MapControllers();
app.Run();
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:36209",
"sslPort": 0
}
},
"profiles": {
"http": {
"commandName": "Project",
"dotnetRunMessages": true,
"applicationUrl": "http://localhost:5008",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS": "https://localhost:8082",
"DURABLE_TASK_SCHEDULER_TASK_HUB_NAME": "samples"
}
}
}
}
147 changes: 147 additions & 0 deletions samples/portable-sdk/dotnet/AspNetWebApp/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# Hello World with the Durable Task SDK for .NET

In addition to [Durable Functions](https://learn.microsoft.com/azure/azure-functions/durable/durable-functions-overview), the [Durable Task SDK for .NET](https://github.com/microsoft/durabletask-dotnet) can also use the Durable Task Scheduler service for managing orchestration state.

This directory includes a sample .NET console app that demonstrates how to use the Durable Task Scheduler with the Durable Task SDK for .NET (without any Azure Functions dependency).

## Prerequisites

- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0)
- [PowerShell](https://docs.microsoft.com/powershell/scripting/install/installing-powershell)
- [Azure CLI](https://docs.microsoft.com/cli/azure/install-azure-cli)

## Creating a Durable Task Scheduler task hub

Before you can run the app, you need to create a Durable Task Scheduler task hub in Azure and produce a connection string that references it.

> **NOTE**: These are abbreviated instructions for simplicity. For a full set of instructions, see the Azure Durable Functions [QuickStart guide](../../../../quickstarts/HelloCities/README.md#create-a-durable-task-scheduler-namespace-and-task-hub).

1. Install the Durable Task Scheduler CLI extension:

```bash
az upgrade
az extension add --name durabletask --allow-preview true
```

1. Create a resource group:

```powershell
az group create --name my-resource-group --location northcentralus
```

1. Create a Durable Task Scheduler namespace:

```powershell
az durabletask namespace create -g my-resource-group --name my-namespace
```

1. Create a task hub within the namespace:

```powershell
az durabletask taskhub create -g my-resource-group --namespace my-namespace --name "portable-dotnet"
```

1. Grant the current user permission to connect to the `portable-dotnet` task hub:

```powershell
$subscriptionId = az account show --query "id" -o tsv
$loggedInUser = az account show --query "user.name" -o tsv

az role assignment create `
--assignee $loggedInUser `
--role "Durable Task Data Contributor" `
--scope "/subscriptions/$subscriptionId/resourceGroups/my-resource-group/providers/Microsoft.DurableTask/namespaces/my-namespace/taskHubs/portable-dotnet"
```

Note that it may take a minute for the role assignment to take effect.

1. Get the endpoint for the scheduler resource and save it to the `DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS` environment variable:

```powershell
$endpoint = az durabletask namespace show `
-g my-resource-group `
-n my-namespace `
--query "properties.url" `
-o tsv
$env:DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS = $endpoint
```

The `DURABLE_TASK_SCHEDULER_ENDPOINT_ADDRESS` environment variable is used by the sample app to connect to the Durable Task Scheduler resource.

1. Save the task hub name to the `DURABLE_TASK_SCHEDULER_TASK_HUB_NAME` environment variable:

```powershell
$env:DURABLE_TASK_SCHEDULER_TASK_HUB_NAME = "portable-dotnet"
```

The `DURABLE_TASK_SCHEDULER_TASK_HUB_NAME` environment variable is to configure the sample app with the correct task hub resource name.

## Running the sample

In the same terminal window as above, use the following steps to run the sample on your local machine.

1. Clone this repository.

1. Open a terminal window and navigate to the `samples/portable-sdk/dotnet/AspNetWebApp` directory.

1. Run the following command to build and run the sample:

```bash
dotnet run
```

You should see output similar to the following:

```plaintext
Building...
info: Microsoft.DurableTask[1]
Durable Task gRPC worker starting.
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5008
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: D:\projects\Azure-Functions-Durable-Task-Scheduler-Private-Preview\samples\portable-sdk\dotnet\AspNetWebApp
info: Microsoft.DurableTask[4]
Sidecar work-item streaming connection established.
```

## View orchestrations in the dashboard

You can view the orchestrations in the Durable Task Scheduler dashboard by navigating to the namespace-specific dashboard URL in your browser.

Use the following PowerShell command to get the dashboard URL:

```powershell
$baseUrl = az durabletask namespace show `
-g my-resource-group `
-n my-namespace `
--query "properties.dashboardUrl" `
-o tsv
$dashboardUrl = "$baseUrl/taskHubs/portable-dotnet"
$dashboardUrl
```

The URL should look something like the following:

```plaintext
https://my-namespace-atdngmgxfsh0-db.northcentralus.durabletask.io/taskHubs/portable-dotnet
```

Once logged in, you should see the orchestrations that were created by the sample app. Below is an example of what the dashboard might look like (note that some of the details will be different than the screenshot):

![Durable Task Scheduler dashboard](/media/images/dtfx-sample-dashboard.png)


## Optional: Deploy to Azure Container Apps
1. Create an container app following the instructions in the [Azure Container App documentation](https://learn.microsoft.com/azure/container-apps/get-started?tabs=bash).
2. During step 1, specify the deployed container app code folder at samples\portable-sdk\dotnet\AspNetWebApp
3. Follow the instructions to create a user managed identity and assign the `Durable Task Data Contributor` role then attach it to the container app you created in step 1 at [Azure-Functions-Durable-Task-Scheduler-Private-Preview](..\..\..\..\docs\configure-existing-app.md#run-the-app-on-azure-net). Please skip section "Add required environment variables to app" since these environment variables are not required for deploying to container app.
4. Call the container app endpoint at `http://sampleapi-<your-container-app-name>.azurecontainerapps.io/api/orchestrators/HelloCities`, Sample curl command:

```bash
curl -X POST "https://sampleapi-<your-container-app-name>.azurecontainerapps.io/api/orchestrators/HelloCities"
```
5. You should see the orchestration created in the Durable Task Scheduler dashboard.
Loading
Loading