Skip to content

Commit

Permalink
[Egress Extensibility] Convert S3 to An Egress Extension (Pt 1) (#3702)
Browse files Browse the repository at this point in the history
* Initial work on separating out S3 into an extension - untested

* Initial work on separating out S3 into an extension - untested

* Minor tweaks - extension can now start up, haven't been able to test that it actually works yet.

* In progress.

* Experimenting - having some issues.

* Applied same EXPERIMENTAL changes I made in the main repo, and was able to successfully egress using the extension.

* Some cleanup, getting ready for PR

* Tweaks

* Significant refactoring to share components between Azure and S3

* PR Feedback for Justin

* Converted projects from Sdk.Web to Sdk

* PR Feedback for Justin
  • Loading branch information
kkeirstead authored Feb 24, 2023
1 parent 8d95a7c commit 14b7525
Show file tree
Hide file tree
Showing 39 changed files with 767 additions and 299 deletions.
14 changes: 14 additions & 0 deletions dotnet-monitor.sln
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monit
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AzureBlobStorage", "src\Extensions\AzureBlobStorage\AzureBlobStorage.csproj", "{5ED61A7B-F0AA-45F2-9E9A-8972FF7F7278}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "S3Storage", "src\Extensions\S3Storage\S3Storage.csproj", "{55309C3E-1DD2-4B4A-9126-CFA28C97F1AD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monitoring.Extension.Common", "src\Extensions\Microsoft.Diagnostics.Monitoring.Extension.Common\Microsoft.Diagnostics.Monitoring.Extension.Common.csproj", "{65C25244-F80E-4D59-98D6-CBB3D04E2B3C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monitoring.Profiler.UnitTests", "src\Tests\Microsoft.Diagnostics.Monitoring.Profiler.UnitTests\Microsoft.Diagnostics.Monitoring.Profiler.UnitTests.csproj", "{A25AC517-F7C6-43C6-B892-4A447914C42C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Diagnostics.Monitoring.Profiler.UnitTestApp", "src\Tests\Microsoft.Diagnostics.Monitoring.Profiler.UnitTestApp\Microsoft.Diagnostics.Monitoring.Profiler.UnitTestApp.csproj", "{1CA2284B-A3A0-476A-9A93-A95E665E78BE}"
Expand Down Expand Up @@ -120,6 +124,14 @@ Global
{5ED61A7B-F0AA-45F2-9E9A-8972FF7F7278}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5ED61A7B-F0AA-45F2-9E9A-8972FF7F7278}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5ED61A7B-F0AA-45F2-9E9A-8972FF7F7278}.Release|Any CPU.Build.0 = Release|Any CPU
{55309C3E-1DD2-4B4A-9126-CFA28C97F1AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{55309C3E-1DD2-4B4A-9126-CFA28C97F1AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{55309C3E-1DD2-4B4A-9126-CFA28C97F1AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{55309C3E-1DD2-4B4A-9126-CFA28C97F1AD}.Release|Any CPU.Build.0 = Release|Any CPU
{65C25244-F80E-4D59-98D6-CBB3D04E2B3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{65C25244-F80E-4D59-98D6-CBB3D04E2B3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{65C25244-F80E-4D59-98D6-CBB3D04E2B3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{65C25244-F80E-4D59-98D6-CBB3D04E2B3C}.Release|Any CPU.Build.0 = Release|Any CPU
{A25AC517-F7C6-43C6-B892-4A447914C42C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A25AC517-F7C6-43C6-B892-4A447914C42C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A25AC517-F7C6-43C6-B892-4A447914C42C}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -173,6 +185,8 @@ Global
{0DBE362D-82F1-4740-AE6A-40C1A82EDCDB} = {C7568468-1C79-4944-8136-18812A7F9EA7}
{8f8a9a15-24d5-496c-b769-3caed25d1ba8} = {C7568468-1C79-4944-8136-18812A7F9EA7}
{5ED61A7B-F0AA-45F2-9E9A-8972FF7F7278} = {B62728C8-1267-4043-B46F-5537BBAEC692}
{55309C3E-1DD2-4B4A-9126-CFA28C97F1AD} = {B62728C8-1267-4043-B46F-5537BBAEC692}
{65C25244-F80E-4D59-98D6-CBB3D04E2B3C} = {B62728C8-1267-4043-B46F-5537BBAEC692}
{A25AC517-F7C6-43C6-B892-4A447914C42C} = {C7568468-1C79-4944-8136-18812A7F9EA7}
{1CA2284B-A3A0-476A-9A93-A95E665E78BE} = {C7568468-1C79-4944-8136-18812A7F9EA7}
{F324BAD6-C9C0-42DE-9150-D8307D972E9A} = {C7568468-1C79-4944-8136-18812A7F9EA7}
Expand Down
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
project references for command line builds. They should match the values in the diagnostics repo. -->
<MicrosoftBclAsyncInterfacesVersion>1.1.0</MicrosoftBclAsyncInterfacesVersion>
<MicrosoftDiagnosticsTracingTraceEventVersion>2.0.64</MicrosoftDiagnosticsTracingTraceEventVersion>
<MicrosoftExtensionsLoggingVersion>2.1.1</MicrosoftExtensionsLoggingVersion>
<MicrosoftExtensionsLoggingVersion>7.0.0</MicrosoftExtensionsLoggingVersion>
</PropertyGroup>
<PropertyGroup Label=".NET 6 Dependent" Condition=" '$(TargetFramework)' == 'net6.0' ">
<MicrosoftAspNetCoreAuthenticationJwtBearerVersion>$(MicrosoftAspNetCoreApp60Version)</MicrosoftAspNetCoreAuthenticationJwtBearerVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
using Azure.Storage.Blobs.Models;
using Azure.Storage.Blobs.Specialized;
using Azure.Storage.Queues;
using Microsoft.Diagnostics.Monitoring.Extension.Common;
using Microsoft.Extensions.Logging;
using System.Globalization;
using System.Net;

Expand All @@ -19,17 +21,15 @@ namespace Microsoft.Diagnostics.Monitoring.AzureBlobStorage
/// <remarks>
/// Blobs created through this provider will overwrite existing blobs if they have the same blob name.
/// </remarks>
internal partial class AzureBlobEgressProvider
internal partial class AzureBlobEgressProvider : EgressProvider<AzureBlobEgressProviderOptions>
{
private int BlobStorageBufferSize = 4 * 1024 * 1024;
private readonly ILogger _logger;

public AzureBlobEgressProvider(ILogger logger)
public AzureBlobEgressProvider(ILogger logger) : base(logger)
{
_logger = logger;
}

public async Task<string> EgressAsync(
public override async Task<string> EgressAsync(
AzureBlobEgressProviderOptions options,
Func<Stream, CancellationToken, Task> action,
EgressArtifactSettings artifactSettings,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;

namespace Microsoft.Diagnostics.Monitoring.AzureBlobStorage
Expand Down
13 changes: 11 additions & 2 deletions src/Extensions/AzureBlobStorage/AzureBlobStorage.csproj
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>$(ToolTargetFrameworks)</TargetFrameworks>
Expand All @@ -16,7 +16,10 @@
<PackageReference Include="Azure.Identity" Version="$(AzureIdentityVersion)" />
<PackageReference Include="Azure.Storage.Blobs" Version="$(AzureStorageBlobsVersion)" />
<PackageReference Include="Azure.Storage.Queues" Version="$(AzureStorageQueuesVersion)" />
<PackageReference Include="System.CommandLine" Version="$(SystemCommandLineVersion)" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Microsoft.Diagnostics.Monitoring.Extension.Common\Microsoft.Diagnostics.Monitoring.Extension.Common.csproj" />
</ItemGroup>

<ItemGroup>
Expand All @@ -34,4 +37,10 @@
</EmbeddedResource>
</ItemGroup>

<ItemGroup>
<None Update="extension.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>

</Project>
2 changes: 2 additions & 0 deletions src/Extensions/AzureBlobStorage/LoggingEventIds.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.Logging;

namespace Microsoft.Diagnostics.Monitoring.AzureBlobStorage
{
// The existing EventIds must not be duplicated, reused, or repurposed.
Expand Down
2 changes: 2 additions & 0 deletions src/Extensions/AzureBlobStorage/LoggingExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.Logging;

namespace Microsoft.Diagnostics.Monitoring.AzureBlobStorage
{
public static class LoggingExtensions
Expand Down
170 changes: 44 additions & 126 deletions src/Extensions/AzureBlobStorage/Program.cs
Original file line number Diff line number Diff line change
@@ -1,155 +1,73 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Diagnostics.Monitoring.Extension.Common;
using Microsoft.Extensions.Logging;
using System.CommandLine;
using System.CommandLine.Parsing;
using System.Text.Json;

namespace Microsoft.Diagnostics.Monitoring.AzureBlobStorage
{
internal sealed class Program
{
private static Stream StdInStream;
private static CancellationTokenSource CancelSource = new CancellationTokenSource();

public static ILogger Logger { get; set; }

static async Task<int> Main(string[] args)
{
using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
});

Logger = loggerFactory.CreateLogger<Program>();

// Expected command line format is: dotnet-monitor-egress-azureblobstorage.exe Egress
RootCommand rootCommand = new RootCommand("Egresses an artifact to Azure Storage.");

Command egressCmd = new Command("Egress", "The class of extension being invoked; Egress is for egressing an artifact.");

egressCmd.SetHandler(Egress);

rootCommand.Add(egressCmd);

return await rootCommand.InvokeAsync(args);
}
ILogger logger = Utilities.CreateLogger();

private static async Task<int> Egress()
{
EgressArtifactResult result = new();
try
{
string jsonConfig = Console.ReadLine();
ExtensionEgressPayload configPayload = JsonSerializer.Deserialize<ExtensionEgressPayload>(jsonConfig);
AzureBlobEgressProviderOptions options = BuildOptions(configPayload);

AzureBlobEgressProvider provider = new AzureBlobEgressProvider(Logger);

Console.CancelKeyPress += Console_CancelKeyPress;

result.ArtifactPath = await provider.EgressAsync(options,
GetStream,
configPayload.Settings,
CancelSource.Token);

result.Succeeded = true;
}
catch (Exception ex)
{
result.Succeeded = false;
result.FailureMessage = ex.Message;
}

string jsonBlob = JsonSerializer.Serialize<EgressArtifactResult>(result);
Console.Write(jsonBlob);

// return non-zero exit code when failed
return result.Succeeded ? 0 : 1;
}

private static AzureBlobEgressProviderOptions BuildOptions(ExtensionEgressPayload configPayload)
{
AzureBlobEgressProviderOptions options = GetOptions<AzureBlobEgressProviderOptions>(configPayload);
AzureBlobEgressProvider provider = new(logger);

// If account key was not provided but the name was provided,
// lookup the account key property value from EgressOptions.Properties
if (string.IsNullOrEmpty(options.AccountKey) && !string.IsNullOrEmpty(options.AccountKeyName))
Action<ExtensionEgressPayload, AzureBlobEgressProviderOptions> configureOptions = (configPayload, options) =>
{
if (configPayload.Properties.TryGetValue(options.AccountKeyName, out string accountKey))
// If account key was not provided but the name was provided,
// lookup the account key property value from EgressOptions.Properties
if (string.IsNullOrEmpty(options.AccountKey) && !string.IsNullOrEmpty(options.AccountKeyName))
{
options.AccountKey = accountKey;
if (configPayload.Properties.TryGetValue(options.AccountKeyName, out string accountKey))
{
options.AccountKey = accountKey;
}
else
{
logger.EgressProviderUnableToFindPropertyKey(Constants.AzureBlobStorageProviderName, options.AccountKeyName);
}
}
else
{
Logger.EgressProviderUnableToFindPropertyKey(Constants.AzureBlobStorageProviderName, options.AccountKeyName);
}
}

// If shared access signature (SAS) was not provided but the name was provided,
// lookup the SAS property value from EgressOptions.Properties
if (string.IsNullOrEmpty(options.SharedAccessSignature) && !string.IsNullOrEmpty(options.SharedAccessSignatureName))
{
if (configPayload.Properties.TryGetValue(options.SharedAccessSignatureName, out string sasSig))
// If shared access signature (SAS) was not provided but the name was provided,
// lookup the SAS property value from EgressOptions.Properties
if (string.IsNullOrEmpty(options.SharedAccessSignature) && !string.IsNullOrEmpty(options.SharedAccessSignatureName))
{
options.SharedAccessSignature = sasSig;
if (configPayload.Properties.TryGetValue(options.SharedAccessSignatureName, out string sasSig))
{
options.SharedAccessSignature = sasSig;
}
else
{
logger.EgressProviderUnableToFindPropertyKey(Constants.AzureBlobStorageProviderName, options.SharedAccessSignatureName);
}
}
else
{
Logger.EgressProviderUnableToFindPropertyKey(Constants.AzureBlobStorageProviderName, options.SharedAccessSignatureName);
}
}

// If queue shared access signature (SAS) was not provided but the name was provided,
// lookup the SAS property value from EgressOptions.Properties
if (string.IsNullOrEmpty(options.QueueSharedAccessSignature) && !string.IsNullOrEmpty(options.QueueSharedAccessSignatureName))
{
if (configPayload.Properties.TryGetValue(options.QueueSharedAccessSignatureName, out string signature))
// If queue shared access signature (SAS) was not provided but the name was provided,
// lookup the SAS property value from EgressOptions.Properties
if (string.IsNullOrEmpty(options.QueueSharedAccessSignature) && !string.IsNullOrEmpty(options.QueueSharedAccessSignatureName))
{
options.QueueSharedAccessSignature = signature;
if (configPayload.Properties.TryGetValue(options.QueueSharedAccessSignatureName, out string signature))
{
options.QueueSharedAccessSignature = signature;
}
else
{
logger.EgressProviderUnableToFindPropertyKey(Constants.AzureBlobStorageProviderName, options.QueueSharedAccessSignatureName);
}
}
else
{
Logger.EgressProviderUnableToFindPropertyKey(Constants.AzureBlobStorageProviderName, options.QueueSharedAccessSignatureName);
}
}

return options;
}

private static T GetOptions<T>(ExtensionEgressPayload payload) where T : class, new()
{
IConfigurationBuilder builder = new ConfigurationBuilder();

var configurationRoot = builder.AddInMemoryCollection(payload.Configuration).Build();
};

T options = new();

configurationRoot.Bind(options);

return options;
}
// Expected command line format is: dotnet-monitor-egress-azureblobstorage.exe Egress
RootCommand rootCommand = new RootCommand("Egresses an artifact to Azure storage.");

private static void Console_CancelKeyPress(object sender, ConsoleCancelEventArgs e)
{
CancelSource.Cancel();
StdInStream.Close();
}
Command egressCmd = EgressHelper.CreateEgressCommand(provider, configureOptions);

private static async Task GetStream(Stream outputStream, CancellationToken cancellationToken)
{
const int DefaultBufferSize = 0x10000;
rootCommand.Add(egressCmd);

StdInStream = Console.OpenStandardInput();
await StdInStream.CopyToAsync(outputStream, DefaultBufferSize, cancellationToken);
return await rootCommand.InvokeAsync(args);
}
}

internal sealed class ExtensionEgressPayload
{
public EgressArtifactSettings Settings { get; set; }
public Dictionary<string, string> Properties { get; set; }
public Dictionary<string, string> Configuration { get; set; }
public string ProviderName { get; set; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

using System.Diagnostics;

namespace Microsoft.Diagnostics.Monitoring.AzureBlobStorage
namespace Microsoft.Diagnostics.Monitoring.Extension.Common
{
[DebuggerDisplay("{Succeeded?\"Succeeded\":\"Failed\",nq}: {Succeeded?ArtifactPath:FailureMessage}")]
internal sealed class EgressArtifactResult
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Diagnostics.Monitoring.AzureBlobStorage
using System;
using System.Collections.Generic;

namespace Microsoft.Diagnostics.Monitoring.Extension.Common
{
internal sealed class EgressArtifactSettings
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace Microsoft.Diagnostics.Monitoring.AzureBlobStorage
using System;

namespace Microsoft.Diagnostics.Monitoring.Extension.Common
{
/// <summary>
/// Exception that egress providers can throw when an operational error occurs (e.g. failed to write the stream data).
Expand Down
Loading

0 comments on commit 14b7525

Please sign in to comment.