diff --git a/DurableTask.sln b/DurableTask.sln index a82b8ec8e..bfb8afce6 100644 --- a/DurableTask.sln +++ b/DurableTask.sln @@ -64,12 +64,18 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DurableTask.ApplicationInsights", "src\DurableTask.ApplicationInsights\DurableTask.ApplicationInsights.csproj", "{331D783C-C3AF-43DD-9270-6CF22459B2C1}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DistributedTraceSample", "DistributedTraceSample", "{240FA679-D5A7-41CA-BA22-70B45A966088}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DistributedTrace", "DistributedTrace", "{240FA679-D5A7-41CA-BA22-70B45A966088}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationInsightsSample", "samples\DistributedTraceSample\ApplicationInsights\ApplicationInsightsSample.csproj", "{C831792B-00EE-4030-988F-F4492DA9BCE7}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenTelemetrySample", "samples\DistributedTraceSample\OpenTelemetry\OpenTelemetrySample.csproj", "{D818ED4C-29B9-431F-8D09-EE8C82510E25}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ManagedIdentity", "ManagedIdentity", "{8B797A00-0F43-46F9-8F1A-C945FD4F304F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagedIdentity.AzStorageV1", "samples\ManagedIdentitySample\DTFx.AzureStorage v1.x\ManagedIdentity.AzStorageV1.csproj", "{CFFC5AD7-5B82-48CF-879D-295D327B5CA6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ManagedIdentity.AzStorageV2", "samples\ManagedIdentitySample\DTFx.AzureStorage v2.x\ManagedIdentity.AzStorageV2.csproj", "{87CA84DE-A0FE-443C-8B2B-AB89F5DF5C24}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -246,6 +252,22 @@ Global {D818ED4C-29B9-431F-8D09-EE8C82510E25}.Release|Any CPU.Build.0 = Release|Any CPU {D818ED4C-29B9-431F-8D09-EE8C82510E25}.Release|x64.ActiveCfg = Release|Any CPU {D818ED4C-29B9-431F-8D09-EE8C82510E25}.Release|x64.Build.0 = Release|Any CPU + {CFFC5AD7-5B82-48CF-879D-295D327B5CA6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CFFC5AD7-5B82-48CF-879D-295D327B5CA6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CFFC5AD7-5B82-48CF-879D-295D327B5CA6}.Debug|x64.ActiveCfg = Debug|Any CPU + {CFFC5AD7-5B82-48CF-879D-295D327B5CA6}.Debug|x64.Build.0 = Debug|Any CPU + {CFFC5AD7-5B82-48CF-879D-295D327B5CA6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CFFC5AD7-5B82-48CF-879D-295D327B5CA6}.Release|Any CPU.Build.0 = Release|Any CPU + {CFFC5AD7-5B82-48CF-879D-295D327B5CA6}.Release|x64.ActiveCfg = Release|Any CPU + {CFFC5AD7-5B82-48CF-879D-295D327B5CA6}.Release|x64.Build.0 = Release|Any CPU + {87CA84DE-A0FE-443C-8B2B-AB89F5DF5C24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {87CA84DE-A0FE-443C-8B2B-AB89F5DF5C24}.Debug|Any CPU.Build.0 = Debug|Any CPU + {87CA84DE-A0FE-443C-8B2B-AB89F5DF5C24}.Debug|x64.ActiveCfg = Debug|Any CPU + {87CA84DE-A0FE-443C-8B2B-AB89F5DF5C24}.Debug|x64.Build.0 = Debug|Any CPU + {87CA84DE-A0FE-443C-8B2B-AB89F5DF5C24}.Release|Any CPU.ActiveCfg = Release|Any CPU + {87CA84DE-A0FE-443C-8B2B-AB89F5DF5C24}.Release|Any CPU.Build.0 = Release|Any CPU + {87CA84DE-A0FE-443C-8B2B-AB89F5DF5C24}.Release|x64.ActiveCfg = Release|Any CPU + {87CA84DE-A0FE-443C-8B2B-AB89F5DF5C24}.Release|x64.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -274,9 +296,12 @@ Global {240FA679-D5A7-41CA-BA22-70B45A966088} = {AF4E71A6-B16E-4488-B22D-2761101A601A} {C831792B-00EE-4030-988F-F4492DA9BCE7} = {240FA679-D5A7-41CA-BA22-70B45A966088} {D818ED4C-29B9-431F-8D09-EE8C82510E25} = {240FA679-D5A7-41CA-BA22-70B45A966088} + {8B797A00-0F43-46F9-8F1A-C945FD4F304F} = {AF4E71A6-B16E-4488-B22D-2761101A601A} + {CFFC5AD7-5B82-48CF-879D-295D327B5CA6} = {8B797A00-0F43-46F9-8F1A-C945FD4F304F} + {87CA84DE-A0FE-443C-8B2B-AB89F5DF5C24} = {8B797A00-0F43-46F9-8F1A-C945FD4F304F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution - EnterpriseLibraryConfigurationToolBinariesPath = packages\TransientFaultHandling.Core.5.1.1209.1\lib\NET4 SolutionGuid = {2D63A120-9394-48D9-8CA9-1184364FB854} + EnterpriseLibraryConfigurationToolBinariesPath = packages\TransientFaultHandling.Core.5.1.1209.1\lib\NET4 EndGlobalSection EndGlobal diff --git a/samples/ManagedIdentitySample/DTFx.AzureStorage v1.x/ConsoleApp.csproj b/samples/ManagedIdentitySample/DTFx.AzureStorage v1.x/ConsoleApp.csproj deleted file mode 100644 index 509de1e63..000000000 --- a/samples/ManagedIdentitySample/DTFx.AzureStorage v1.x/ConsoleApp.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - Exe - net6.0 - enable - enable - - - - - - - - diff --git a/samples/ManagedIdentitySample/DTFx.AzureStorage v1.x/ManagedIdentity.AzStorageV1.csproj b/samples/ManagedIdentitySample/DTFx.AzureStorage v1.x/ManagedIdentity.AzStorageV1.csproj new file mode 100644 index 000000000..f9c44f4a9 --- /dev/null +++ b/samples/ManagedIdentitySample/DTFx.AzureStorage v1.x/ManagedIdentity.AzStorageV1.csproj @@ -0,0 +1,19 @@ + + + + Latest + enable + Exe + net6.0 + + + + + + + + + + + + diff --git a/samples/ManagedIdentitySample/DTFx.AzureStorage v1.x/Program.cs b/samples/ManagedIdentitySample/DTFx.AzureStorage v1.x/Program.cs index 57ebd6066..78bf4e524 100644 --- a/samples/ManagedIdentitySample/DTFx.AzureStorage v1.x/Program.cs +++ b/samples/ManagedIdentitySample/DTFx.AzureStorage v1.x/Program.cs @@ -1,72 +1,96 @@ -using Azure.Core; +// ---------------------------------------------------------------------------------- +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Threading; +using System.Threading.Tasks; +using Azure.Core; using Azure.Identity; using DurableTask.AzureStorage; using DurableTask.Core; +using Microsoft.Extensions.Azure; +using Microsoft.Extensions.Logging; using Microsoft.WindowsAzure.Storage.Auth; -internal class Program +// Create a DefaultAzureCredential used to access the Azure Storage Account. +// The identity will require the following roles on the resource: +// - Azure Blob Data Contributor +// - Azure Queue Data Contributor +// - Azure Table Data Contributor +DefaultAzureCredential credential = new(); + +// Create a diagnostic logger factory for reading telemetry +ILoggerFactory loggerFactory = LoggerFactory.Create(b => b + .AddConsole() + .AddFilter("Azure.Core", LogLevel.Warning) + .AddFilter("Azure.Identity", LogLevel.Warning)); + +// The Azure SDKs used by the Azure.Identity library write their telemetry via Event Sources +using AzureEventSourceLogForwarder logForwarder = new(loggerFactory); +logForwarder.Start(); + +NewTokenAndFrequency initialTokenInfo = await GetTokenInfoAsync(credential); +AzureStorageOrchestrationService service = new(new AzureStorageOrchestrationServiceSettings { - private static async Task Main(string[] args) + StorageAccountDetails = new StorageAccountDetails { - // Create credential based on the configuration - var credential = new DefaultAzureCredential(); - string[] scopes = new string[] { "https://storage.azure.com/.default" }; // Scope for Azure Storage - - static Task RenewTokenFuncAsync(object state, CancellationToken cancellationToken) - { - var credential = new DefaultAzureCredential(); - var initialToken = credential.GetToken(new TokenRequestContext(new[] { "https://storage.azure.com/.default" })); - var expiresAfter = initialToken.ExpiresOn - DateTimeOffset.UtcNow - TimeSpan.FromMinutes(10); - return Task.FromResult(new NewTokenAndFrequency(initialToken.Token, expiresAfter)); - } - - // Get the token - var accessToken = await credential.GetTokenAsync(new Azure.Core.TokenRequestContext(scopes)); - - var service = new AzureStorageOrchestrationService(new AzureStorageOrchestrationServiceSettings - { - StorageAccountDetails = new StorageAccountDetails - { - AccountName = "YourStorageAccount", - EndpointSuffix = "core.windows.net", - StorageCredentials = new StorageCredentials(new Microsoft.WindowsAzure.Storage.Auth.TokenCredential( - accessToken.Token, - RenewTokenFuncAsync, - null, - TimeSpan.FromMinutes(5))) - } - }); - - var client = new TaskHubClient(service); - var worker = new TaskHubWorker(service); - - worker.AddTaskOrchestrations(typeof(SampleOrchestration)); - worker.AddTaskActivities(typeof(SampleActivity)); - - await worker.StartAsync(); - - var instance = await client.CreateOrchestrationInstanceAsync(typeof(SampleOrchestration), "World"); - - var result = await client.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(1)); - - Console.WriteLine($"Orchestration result : {result.Output}"); - - await worker.StopAsync(); - } + AccountName = "YourStorageAccount", + EndpointSuffix = "core.windows.net", + StorageCredentials = new StorageCredentials(new Microsoft.WindowsAzure.Storage.Auth.TokenCredential( + initialTokenInfo.Token, + GetTokenInfoAsync, + credential, + initialTokenInfo.Frequency.GetValueOrDefault())) + }, + LoggerFactory = loggerFactory, +}); + +TaskHubClient client = new(service, loggerFactory: loggerFactory); +TaskHubWorker worker = new(service, loggerFactory); + +worker.AddTaskOrchestrations(typeof(SampleOrchestration)); +worker.AddTaskActivities(typeof(SampleActivity)); + +await worker.StartAsync(); + +OrchestrationInstance instance = await client.CreateOrchestrationInstanceAsync(typeof(SampleOrchestration), "World"); +OrchestrationState state = await client.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(1)); + +ILogger logger = loggerFactory.CreateLogger(nameof(Program)); +logger.LogInformation("Orchestration output: {Output}", state.Output); + +await worker.StopAsync(); + +static async Task GetTokenInfoAsync(object state, CancellationToken cancellationToken = default) +{ + const string AzureStorageScope = "https://storage.azure.com/.default"; + + if (state is not DefaultAzureCredential credential) + throw new InvalidOperationException(); + + AccessToken accessToken = await credential.GetTokenAsync(new TokenRequestContext([AzureStorageScope]), cancellationToken); + TimeSpan refreshFrequency = accessToken.ExpiresOn - DateTimeOffset.UtcNow - TimeSpan.FromMinutes(10); // 10 minutes before expiration + return new NewTokenAndFrequency(accessToken.Token, refreshFrequency); } -public class SampleOrchestration : TaskOrchestration +internal sealed class SampleOrchestration : TaskOrchestration { - public override async Task RunTask(OrchestrationContext context, string input) - { - return await context.ScheduleTask(typeof(SampleActivity), input); - } + public override Task RunTask(OrchestrationContext context, string input) => + context.ScheduleTask(typeof(SampleActivity), input); } -public class SampleActivity : TaskActivity +internal sealed class SampleActivity : TaskActivity { - protected override string Execute(TaskContext context, string input) - { - return "Hello, " + input + "!"; - } + protected override string Execute(TaskContext context, string input) => + "Hello, " + input + "!"; } diff --git a/samples/ManagedIdentitySample/DTFx.AzureStorage v2.x/ConsoleApp.csproj b/samples/ManagedIdentitySample/DTFx.AzureStorage v2.x/ConsoleApp.csproj deleted file mode 100644 index 4238bec7b..000000000 --- a/samples/ManagedIdentitySample/DTFx.AzureStorage v2.x/ConsoleApp.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - Exe - net8.0 - enable - enable - - - - - - - - diff --git a/samples/ManagedIdentitySample/DTFx.AzureStorage v2.x/ManagedIdentity.AzStorageV2.csproj b/samples/ManagedIdentitySample/DTFx.AzureStorage v2.x/ManagedIdentity.AzStorageV2.csproj new file mode 100644 index 000000000..956fc14c2 --- /dev/null +++ b/samples/ManagedIdentitySample/DTFx.AzureStorage v2.x/ManagedIdentity.AzStorageV2.csproj @@ -0,0 +1,19 @@ + + + + Latest + enable + Exe + net8.0 + + + + + + + + + + + + diff --git a/samples/ManagedIdentitySample/DTFx.AzureStorage v2.x/Program.cs b/samples/ManagedIdentitySample/DTFx.AzureStorage v2.x/Program.cs index 8450a0bc1..7b072452a 100644 --- a/samples/ManagedIdentitySample/DTFx.AzureStorage v2.x/Program.cs +++ b/samples/ManagedIdentitySample/DTFx.AzureStorage v2.x/Program.cs @@ -1,53 +1,71 @@ -using DurableTask.AzureStorage; -using DurableTask.Core; +// ---------------------------------------------------------------------------------- +// Copyright Microsoft Corporation +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// ---------------------------------------------------------------------------------- + +using System; +using System.Threading.Tasks; using Azure.Identity; +using DurableTask.AzureStorage; +using DurableTask.Core; +using Microsoft.Extensions.Azure; +using Microsoft.Extensions.Logging; + +// Create a DefaultAzureCredential used to access the Azure Storage Account. +// The identity will require the following roles on the resource: +// - Azure Blob Data Contributor +// - Azure Queue Data Contributor +// - Azure Table Data Contributor +DefaultAzureCredential credential = new(); + +// Create a diagnostic logger factory for reading telemetry +ILoggerFactory loggerFactory = LoggerFactory.Create(b => b + .AddConsole() + .AddFilter("Azure.Core", LogLevel.Warning) + .AddFilter("Azure.Identity", LogLevel.Warning)); -internal class Program +// The Azure SDKs used by the Azure.Identity and Azure Storage client libraries write their telemetry via Event Sources +using AzureEventSourceLogForwarder logForwarder = new(loggerFactory); +logForwarder.Start(); + +AzureStorageOrchestrationService service = new(new AzureStorageOrchestrationServiceSettings { - private static async Task Main(string[] args) - { - var credential = new DefaultAzureCredential(); - - // Pass the credential created to the StorageAccountClientProvider to start an AzureStorageOrchestrationService - var service = new AzureStorageOrchestrationService(new AzureStorageOrchestrationServiceSettings - { - StorageAccountClientProvider = new StorageAccountClientProvider("AccountName", credential), - }); + LoggerFactory = loggerFactory, + StorageAccountClientProvider = new StorageAccountClientProvider("YourStorageAccount", credential), +}); - var client = new TaskHubClient(service); - var worker = new TaskHubWorker(service); +TaskHubClient client = new(service, loggerFactory: loggerFactory); +TaskHubWorker worker = new(service, loggerFactory); - worker.AddTaskOrchestrations(typeof(SampleOrchestration)); - worker.AddTaskActivities(typeof(SampleActivity)); +worker.AddTaskOrchestrations(typeof(SampleOrchestration)); +worker.AddTaskActivities(typeof(SampleActivity)); - await worker.StartAsync(); +await worker.StartAsync(); - var instance = await client.CreateOrchestrationInstanceAsync(typeof(SampleOrchestration), "World"); +OrchestrationInstance instance = await client.CreateOrchestrationInstanceAsync(typeof(SampleOrchestration), "World"); +OrchestrationState state = await client.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(1)); - var result = await client.WaitForOrchestrationAsync(instance, TimeSpan.FromMinutes(1)); +ILogger logger = loggerFactory.CreateLogger(nameof(Program)); +logger.LogInformation("Orchestration output: {Output}", state.Output); - Console.WriteLine($"Orchestration result : {result.Output}"); - - await worker.StopAsync(); - } -} +await worker.StopAsync(); -public class SampleOrchestration : TaskOrchestration +internal sealed class SampleOrchestration : TaskOrchestration { - public override async Task RunTask(OrchestrationContext context, string input) - { - await context.ScheduleTask(typeof(SampleActivity), input); - - return "Orchestrator Finished!"; - } + public override Task RunTask(OrchestrationContext context, string input) => + context.ScheduleTask(typeof(SampleActivity), input); } -public class SampleActivity : TaskActivity +internal sealed class SampleActivity : TaskActivity { - protected override string Execute(TaskContext context, string input) - { - Console.WriteLine("saying hello to " + input); - return "Hello " + input + "!"; - } + protected override string Execute(TaskContext context, string input) => + "Hello, " + input + "!"; } -