Skip to content

Commit

Permalink
Merge branch 'release/7.0.3xx' => 'main' (#31511)
Browse files Browse the repository at this point in the history
  • Loading branch information
v-wuzhai authored Apr 4, 2023
2 parents 326fa05 + e1cd792 commit b488cef
Show file tree
Hide file tree
Showing 120 changed files with 12,702 additions and 17 deletions.
2 changes: 1 addition & 1 deletion .vsts-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ stages:
name: $(DncEngInternalBuildPool)
demands: ImageOverride -equals 1es-ubuntu-2204
${{ if eq(variables['System.TeamProject'], 'public') }}:
helixTargetQueue: 'ubuntu.2204.amd64.open@mcr.microsoft.com/dotnet-buildtools/prereqs:ubuntu-22.04-helix-amd64'
helixTargetQueue: ubuntu.2204.amd64.open
${{ if ne(variables['System.TeamProject'], 'public') }}:
helixTargetQueue: Ubuntu.2204.Amd64
strategy:
Expand Down
14 changes: 10 additions & 4 deletions CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,18 @@

# ApiCompat tools owned by runtime team
# Area-ApiCompat
/src/ApiCompat/ @dotnet/area-infrastructure-libraries
/src/Tests/Microsoft.DotNet.ApiCompatibility*/ @dotnet/area-infrastructure-libraries
/src/Tests/Microsoft.DotNet.ApiCompat*/ @dotnet/area-infrastructure-libraries
/src/Tests/Microsoft.DotNet.PackageValidation*/ @dotnet/area-infrastructure-libraries
/src/ApiCompat/ @ericstj @dotnet/area-infrastructure-libraries @joperezr
/src/Tests/Microsoft.DotNet.ApiCompatibility*/ @ericstj @dotnet/area-infrastructure-libraries @joperezr
/src/Tests/Microsoft.DotNet.ApiCompat*/ @ericstj @dotnet/area-infrastructure-libraries @joperezr
/src/Tests/Microsoft.DotNet.PackageValidation*/ @ericstj @dotnet/area-infrastructure-libraries @joperezr

# Area-GenAPI
/src/GenAPI/ @andriipatsula @dotnet/area-infrastructure-libraries
/src/Tests/Microsoft.DotNet.GenAPI/ @andriipatsula @dotnet/area-infrastructure-libraries
/src/Microsoft.DotNet.ApiSymbolExtensions/ @andriipatsula @dotnet/area-infrastructure-libraries

# Area: dotnet containers
/src/Cli/Containers @dotnet/sdk-container-builds-maintainers
/src/Tests/containerize.UnitTests @dotnet/sdk-container-builds-maintainers
/src/Tests/Microsoft.NET.Build.Containers.IntegrationTests @dotnet/sdk-container-builds-maintainers
/src/Tests/Microsoft.NET.Build.Containers.UnitTests @dotnet/sdk-container-builds-maintainers
16 changes: 16 additions & 0 deletions containers.slnf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"solution": {
"path": "sdk.sln",
"projects": [
"src\\Cli\\Microsoft.DotNet.Cli.Utils\\Microsoft.DotNet.Cli.Utils.csproj",
"src\\Cli\\Microsoft.DotNet.InternalAbstractions\\Microsoft.DotNet.InternalAbstractions.csproj",
"src\\Containers\\Microsoft.NET.Build.Containers\\Microsoft.NET.Build.Containers.csproj",
"src\\Containers\\containerize\\containerize.csproj",
"src\\Containers\\packaging\\package.csproj",
"src\\Tests\\Microsoft.NET.Build.Containers.IntegrationTests\\Microsoft.NET.Build.Containers.IntegrationTests.csproj",
"src\\Tests\\Microsoft.NET.Build.Containers.UnitTests\\Microsoft.NET.Build.Containers.UnitTests.csproj",
"src\\Tests\\Microsoft.NET.TestFramework\\Microsoft.NET.TestFramework.csproj",
"src\\Tests\\containerize.UnitTests\\containerize.UnitTests.csproj"
]
}
}
1 change: 1 addition & 0 deletions eng/Signing.props
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
<FileSignInfo Include="xunit.performance.core.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="xunit.performance.execution.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="xunit.performance.metrics.dll" CertificateName="3PartySHA2" />
<FileSignInfo Include="Valleysoft.DockerCredsProvider.dll" CertificateName="3PartySHA2" />
</ItemGroup>

<!-- Filter out any test packages from ItemsToSign -->
Expand Down
45 changes: 45 additions & 0 deletions sdk.sln
Original file line number Diff line number Diff line change
Expand Up @@ -452,6 +452,20 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tasks", "Tasks", "{F720D695
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Sdk.WebAssembly.Tasks", "src\WasmSdk\Tasks\Microsoft.NET.Sdk.WebAssembly.Tasks.csproj", "{754C18B9-AEDB-4455-BAF4-844C6CEEF8F7}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Containers", "Containers", "{EC4A66C5-E2E2-4201-9E8D-BAC6B630D12A}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "containerize", "src\Containers\containerize\containerize.csproj", "{ECCDB04A-A365-4656-989D-4E258D286AE4}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Build.Containers", "src\Containers\Microsoft.NET.Build.Containers\Microsoft.NET.Build.Containers.csproj", "{9E8C7C3B-B8B3-490C-BA11-E1F60ED4E21D}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "package", "src\Containers\packaging\package.csproj", "{DEA8FE40-0AE9-4CE6-9430-089C985217CA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "containerize.UnitTests", "src\Tests\containerize.UnitTests\containerize.UnitTests.csproj", "{F04DB812-7278-47F2-913E-225CE2EF150C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Build.Containers.IntegrationTests", "src\Tests\Microsoft.NET.Build.Containers.IntegrationTests\Microsoft.NET.Build.Containers.IntegrationTests.csproj", "{7DCA2BEC-B1E1-4F2B-952A-A26B5110FDA5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Build.Containers.UnitTests", "src\Tests\Microsoft.NET.Build.Containers.UnitTests\Microsoft.NET.Build.Containers.UnitTests.csproj", "{E54506B8-0B81-4FC4-99B5-5C67E19D4B09}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -846,6 +860,30 @@ Global
{754C18B9-AEDB-4455-BAF4-844C6CEEF8F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{754C18B9-AEDB-4455-BAF4-844C6CEEF8F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{754C18B9-AEDB-4455-BAF4-844C6CEEF8F7}.Release|Any CPU.Build.0 = Release|Any CPU
{ECCDB04A-A365-4656-989D-4E258D286AE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{ECCDB04A-A365-4656-989D-4E258D286AE4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ECCDB04A-A365-4656-989D-4E258D286AE4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ECCDB04A-A365-4656-989D-4E258D286AE4}.Release|Any CPU.Build.0 = Release|Any CPU
{9E8C7C3B-B8B3-490C-BA11-E1F60ED4E21D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9E8C7C3B-B8B3-490C-BA11-E1F60ED4E21D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9E8C7C3B-B8B3-490C-BA11-E1F60ED4E21D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9E8C7C3B-B8B3-490C-BA11-E1F60ED4E21D}.Release|Any CPU.Build.0 = Release|Any CPU
{DEA8FE40-0AE9-4CE6-9430-089C985217CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DEA8FE40-0AE9-4CE6-9430-089C985217CA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DEA8FE40-0AE9-4CE6-9430-089C985217CA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DEA8FE40-0AE9-4CE6-9430-089C985217CA}.Release|Any CPU.Build.0 = Release|Any CPU
{F04DB812-7278-47F2-913E-225CE2EF150C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F04DB812-7278-47F2-913E-225CE2EF150C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F04DB812-7278-47F2-913E-225CE2EF150C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F04DB812-7278-47F2-913E-225CE2EF150C}.Release|Any CPU.Build.0 = Release|Any CPU
{7DCA2BEC-B1E1-4F2B-952A-A26B5110FDA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7DCA2BEC-B1E1-4F2B-952A-A26B5110FDA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7DCA2BEC-B1E1-4F2B-952A-A26B5110FDA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7DCA2BEC-B1E1-4F2B-952A-A26B5110FDA5}.Release|Any CPU.Build.0 = Release|Any CPU
{E54506B8-0B81-4FC4-99B5-5C67E19D4B09}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E54506B8-0B81-4FC4-99B5-5C67E19D4B09}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E54506B8-0B81-4FC4-99B5-5C67E19D4B09}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E54506B8-0B81-4FC4-99B5-5C67E19D4B09}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1001,6 +1039,13 @@ Global
{B2CE3F28-8FEC-4715-B483-5B442E3136CE} = {21835A9E-D667-4761-8675-B2BC105CC2FE}
{F720D695-F35A-4700-9463-C359163D14EE} = {21835A9E-D667-4761-8675-B2BC105CC2FE}
{754C18B9-AEDB-4455-BAF4-844C6CEEF8F7} = {F720D695-F35A-4700-9463-C359163D14EE}
{EC4A66C5-E2E2-4201-9E8D-BAC6B630D12A} = {22AB674F-ED91-4FBC-BFEE-8A1E82F9F05E}
{ECCDB04A-A365-4656-989D-4E258D286AE4} = {EC4A66C5-E2E2-4201-9E8D-BAC6B630D12A}
{9E8C7C3B-B8B3-490C-BA11-E1F60ED4E21D} = {EC4A66C5-E2E2-4201-9E8D-BAC6B630D12A}
{DEA8FE40-0AE9-4CE6-9430-089C985217CA} = {EC4A66C5-E2E2-4201-9E8D-BAC6B630D12A}
{F04DB812-7278-47F2-913E-225CE2EF150C} = {580D1AE7-AA8F-4912-8B76-105594E00B3B}
{7DCA2BEC-B1E1-4F2B-952A-A26B5110FDA5} = {580D1AE7-AA8F-4912-8B76-105594E00B3B}
{E54506B8-0B81-4FC4-99B5-5C67E19D4B09} = {580D1AE7-AA8F-4912-8B76-105594E00B3B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {FB8F26CE-4DE6-433F-B32A-79183020BBD6}
Expand Down
3 changes: 3 additions & 0 deletions source-build.slnf
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
"src\\Cli\\Microsoft.DotNet.InternalAbstractions\\Microsoft.DotNet.InternalAbstractions.csproj",
"src\\Cli\\Microsoft.TemplateEngine.Cli\\Microsoft.TemplateEngine.Cli.csproj",
"src\\Cli\\dotnet\\dotnet.csproj",
"src\\Containers\\Microsoft.NET.Build.Containers\\Microsoft.NET.Build.Containers.csproj",
"src\\Containers\\containerize\\containerize.csproj",
"src\\Containers\\packaging\\package.csproj",
"src\\Layout\\redist\\redist.csproj",
"src\\Layout\\tool_fsharp\\tool_fsc.csproj",
"src\\Layout\\tool_msbuild\\tool_msbuild.csproj",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.NET.Build.Containers.Resources;

namespace Microsoft.NET.Build.Containers;

/// <summary>
/// A delegating handler that handles the special error handling needed for Amazon ECR.
///
/// When pushing images to ECR if the target container repository does not exist ECR ends
/// the connection causing an IOException with a generic "The response ended prematurely."
/// error message. The handler catches the generic error and provides a more informed error
/// message to let the user know they need to create the repository.
/// </summary>
internal sealed class AmazonECRMessageHandler : DelegatingHandler
{
public AmazonECRMessageHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
try
{
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
catch (HttpRequestException e) when (e.InnerException is IOException ioe && ioe.Message.Equals("The response ended prematurely.", StringComparison.OrdinalIgnoreCase))
{
string message = Resource.GetString(nameof(Strings.AmazonRegistryFailed));
throw new ContainerHttpException(message, request.RequestUri?.ToString(), null);
}
catch
{
throw;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;

using Valleysoft.DockerCredsProvider;

using Microsoft.NET.Build.Containers.Credentials;
using System.Net.Sockets;
using Microsoft.NET.Build.Containers.Resources;

namespace Microsoft.NET.Build.Containers;

/// <summary>
/// A delegating handler that performs the Docker auth handshake as described <see href="https://docs.docker.com/registry/spec/auth/token/">in their docs</see> if a request isn't authenticated
/// </summary>
internal sealed partial class AuthHandshakeMessageHandler : DelegatingHandler
{
private const int MaxRequestRetries = 5; // Arbitrary but seems to work ok for chunked uploads to ghcr.io

private sealed record AuthInfo(Uri Realm, string Service, string? Scope);

/// <summary>
/// the www-authenticate header must have realm, service, and scope information, so this method parses it into that shape if present
/// </summary>
/// <param name="msg"></param>
/// <param name="authInfo"></param>
/// <returns></returns>
private static bool TryParseAuthenticationInfo(HttpResponseMessage msg, [NotNullWhen(true)] out string? scheme, [NotNullWhen(true)] out AuthInfo? authInfo)
{
authInfo = null;
scheme = null;

var authenticateHeader = msg.Headers.WwwAuthenticate;
if (!authenticateHeader.Any())
{
return false;
}

AuthenticationHeaderValue header = authenticateHeader.First();
if (header is { Scheme: "Bearer" or "Basic", Parameter: string bearerArgs })
{
scheme = header.Scheme;
Dictionary<string, string> keyValues = new();
foreach (Match match in BearerParameterSplitter().Matches(bearerArgs))
{
keyValues.Add(match.Groups["key"].Value, match.Groups["value"].Value);
}

if (keyValues.TryGetValue("realm", out string? realm) && keyValues.TryGetValue("service", out string? service))
{
string? scope = null;
keyValues.TryGetValue("scope", out scope);
authInfo = new AuthInfo(new Uri(realm), service, scope);
return true;
}
}

return false;
}

public AuthHandshakeMessageHandler(HttpMessageHandler innerHandler) : base(innerHandler) { }

/// <summary>
/// Response to a request to get a token using some auth.
/// </summary>
/// <remarks>
/// <see href="https://docs.docker.com/registry/spec/auth/token/#token-response-fields"/>
/// </remarks>
private sealed record TokenResponse(string? token, string? access_token, int? expires_in, DateTimeOffset? issued_at)
{
public string ResolvedToken => token ?? access_token ?? throw new ArgumentException(Resource.GetString(nameof(Strings.InvalidTokenResponse)));
}

/// <summary>
/// Uses the authentication information from a 401 response to perform the authentication dance for a given registry.
/// Credentials for the request are retrieved from the credential provider, then used to acquire a token.
/// That token is cached for some duration on a per-host basis.
/// </summary>
/// <param name="uri"></param>
/// <param name="service"></param>
/// <param name="scope"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task<AuthenticationHeaderValue?> GetAuthenticationAsync(string registry, string scheme, Uri realm, string service, string? scope, CancellationToken cancellationToken)
{
// Allow overrides for auth via environment variables
string? credU = Environment.GetEnvironmentVariable(ContainerHelpers.HostObjectUser);
string? credP = Environment.GetEnvironmentVariable(ContainerHelpers.HostObjectPass);

// fetch creds for the host
DockerCredentials? privateRepoCreds;

if (!string.IsNullOrEmpty(credU) && !string.IsNullOrEmpty(credP))
{
privateRepoCreds = new DockerCredentials(credU, credP);
}
else
{
try
{
privateRepoCreds = await CredsProvider.GetCredentialsAsync(registry).ConfigureAwait(false);
}
catch (Exception e)
{
throw new CredentialRetrievalException(registry, e);
}
}

if (scheme is "Basic")
{
var basicAuth = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{privateRepoCreds.Username}:{privateRepoCreds.Password}")));
return AuthHeaderCache.AddOrUpdate(realm, basicAuth);
}
else if (scheme is "Bearer")
{
// use those creds when calling the token provider
var header = privateRepoCreds.Username == "<token>"
? new AuthenticationHeaderValue("Bearer", privateRepoCreds.Password)
: new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($"{privateRepoCreds.Username}:{privateRepoCreds.Password}")));
var builder = new UriBuilder(realm);
var queryDict = System.Web.HttpUtility.ParseQueryString("");
queryDict["service"] = service;
if (scope is string s)
{
queryDict["scope"] = s;
}
builder.Query = queryDict.ToString();
var message = new HttpRequestMessage(HttpMethod.Get, builder.ToString());
message.Headers.Authorization = header;

var tokenResponse = await base.SendAsync(message, cancellationToken).ConfigureAwait(false);
tokenResponse.EnsureSuccessStatusCode();

TokenResponse? token = JsonSerializer.Deserialize<TokenResponse>(tokenResponse.Content.ReadAsStream(cancellationToken));
if (token is null)
{
throw new ArgumentException(Resource.GetString(nameof(Strings.CouldntDeserializeJsonToken)));
}

// save the retrieved token in the cache
var bearerAuth = new AuthenticationHeaderValue("Bearer", token.ResolvedToken);
return AuthHeaderCache.AddOrUpdate(realm, bearerAuth);
}
else
{
return null;
}
}

protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
if (request.RequestUri is null)
{
throw new ArgumentException(Resource.GetString(nameof(Strings.NoRequestUriSpecified)), nameof(request));
}

// attempt to use cached token for the request if available
if (AuthHeaderCache.TryGet(request.RequestUri, out AuthenticationHeaderValue? cachedAuthentication))
{
request.Headers.Authorization = cachedAuthentication;
}

int retryCount = 0;

while (retryCount < MaxRequestRetries)
{
try
{
var response = await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
if (response is { StatusCode: HttpStatusCode.OK })
{
return response;
}
else if (response is { StatusCode: HttpStatusCode.Unauthorized } && TryParseAuthenticationInfo(response, out string? scheme, out AuthInfo? authInfo))
{
if (await GetAuthenticationAsync(request.RequestUri.Host, scheme, authInfo.Realm, authInfo.Service, authInfo.Scope, cancellationToken).ConfigureAwait(false) is AuthenticationHeaderValue authentication)
{
request.Headers.Authorization = AuthHeaderCache.AddOrUpdate(request.RequestUri, authentication);
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
}
return response;
}
else
{
return response;
}
}
catch (HttpRequestException e) when (e.InnerException is IOException ioe && ioe.InnerException is SocketException se)
{
retryCount += 1;

// TODO: log in a way that is MSBuild-friendly
Console.WriteLine($"Encountered a SocketException with message \"{se.Message}\". Pausing before retry.");

await Task.Delay(TimeSpan.FromSeconds(1.0 * Math.Pow(2, retryCount)), cancellationToken).ConfigureAwait(false);

// retry
continue;
}
}

throw new ApplicationException(Resource.GetString(nameof(Strings.TooManyRetries)));
}

[GeneratedRegex("(?<key>\\w+)=\"(?<value>[^\"]*)\"(?:,|$)")]
private static partial Regex BearerParameterSplitter();
}
Loading

0 comments on commit b488cef

Please sign in to comment.