Skip to content
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
2 changes: 1 addition & 1 deletion NuGet.config
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<clear />
<!--Begin: Package sources managed by Dependency Flow automation. Do not edit the sources below.-->
<!-- Begin: Package sources from dotnet-dotnet -->
<add key="darc-pub-dotnet-dotnet-493580a" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-dotnet-493580a5/nuget/v3/index.json" />
<add key="darc-pub-dotnet-dotnet-2c07be8" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/darc-pub-dotnet-dotnet-2c07be8a/nuget/v3/index.json" />
<!-- End: Package sources from dotnet-dotnet -->
<!--End: Package sources managed by Dependency Flow automation. Do not edit the sources above.-->
<add key="dotnet-public" value="https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-public/nuget/v3/index.json" />
Expand Down
6 changes: 3 additions & 3 deletions eng/Version.Details.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ This file should be imported by eng/Versions.props
<Project>
<PropertyGroup>
<!-- dotnet-dotnet dependencies -->
<MicrosoftDotNetArcadeSdkPackageVersion>10.0.0-beta.26412.103</MicrosoftDotNetArcadeSdkPackageVersion>
<SystemCommandLinePackageVersion>2.0.12</SystemCommandLinePackageVersion>
<SystemSecurityCryptographyXmlPackageVersion>10.0.12</SystemSecurityCryptographyXmlPackageVersion>
<MicrosoftDotNetArcadeSdkPackageVersion>10.0.0-beta.26427.124</MicrosoftDotNetArcadeSdkPackageVersion>
<SystemCommandLinePackageVersion>2.0.13</SystemCommandLinePackageVersion>
<SystemSecurityCryptographyXmlPackageVersion>10.0.13</SystemSecurityCryptographyXmlPackageVersion>
</PropertyGroup>
<!--Property group for alternate package version names-->
<PropertyGroup>
Expand Down
14 changes: 7 additions & 7 deletions eng/Version.Details.xml
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Dependencies>
<Source Uri="https://github.com/dotnet/dotnet" Mapping="sourcelink" Sha="493580a515a01970cfe1da2c7dd589efbaf36996" BarId="326844" />
<Source Uri="https://github.com/dotnet/dotnet" Mapping="sourcelink" Sha="2c07be8ab38eb69e43aedf74ed288079c839b1dc" BarId="329108" />
<ProductDependencies>
<Dependency Name="System.CommandLine" Version="2.0.12">
<Dependency Name="System.CommandLine" Version="2.0.13">
<Uri>https://github.com/dotnet/dotnet</Uri>
<Sha>493580a515a01970cfe1da2c7dd589efbaf36996</Sha>
<Sha>2c07be8ab38eb69e43aedf74ed288079c839b1dc</Sha>
</Dependency>
<Dependency Name="System.Security.Cryptography.Xml" Version="10.0.12">
<Dependency Name="System.Security.Cryptography.Xml" Version="10.0.13">
<Uri>https://github.com/dotnet/dotnet</Uri>
<Sha>493580a515a01970cfe1da2c7dd589efbaf36996</Sha>
<Sha>2c07be8ab38eb69e43aedf74ed288079c839b1dc</Sha>
</Dependency>
</ProductDependencies>
<ToolsetDependencies>
<Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="10.0.0-beta.26412.103">
<Dependency Name="Microsoft.DotNet.Arcade.Sdk" Version="10.0.0-beta.26427.124">
<Uri>https://github.com/dotnet/dotnet</Uri>
<Sha>493580a515a01970cfe1da2c7dd589efbaf36996</Sha>
<Sha>2c07be8ab38eb69e43aedf74ed288079c839b1dc</Sha>
</Dependency>
</ToolsetDependencies>
</Dependencies>
2 changes: 1 addition & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<Import Project="Version.Details.props" />

<PropertyGroup>
<VersionPrefix>10.0.112</VersionPrefix>
<VersionPrefix>10.0.113</VersionPrefix>
<PreReleaseVersionLabel>servicing</PreReleaseVersionLabel>
<PreReleaseVersionIteration></PreReleaseVersionIteration>
<!-- Allowed values: '', 'prerelease', 'release'. Set to 'release' when stabilizing. -->
Expand Down
18 changes: 14 additions & 4 deletions eng/common/Get-GitHubAppToken.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,13 @@ try {
$installations = @()
$page = 1
do {
$pageInstallations = @(Invoke-RestMethod `
# Assign the response before wrapping it in @(). PowerShell otherwise
# preserves a top-level JSON array as one nested pipeline object.
$pageResponse = Invoke-RestMethod `
-Uri "https://api.github.com/app/installations?per_page=100&page=$page" `
-Headers $headers `
-Method Get)
-Method Get
$pageInstallations = @($pageResponse)
$installations += $pageInstallations
$page++
} while ($pageInstallations.Count -eq 100)
Expand All @@ -125,12 +128,19 @@ catch {
Write-PipelineTelemetryError -Category 'Build' -Message "Failed to list GitHub App installations: $_. The signed JWT may be invalid or the App's Client ID ('$AppClientId') may be incorrect."
exit 1
}
$installation = $installations | Where-Object { $_.account.login -ieq $InstallationOwner } | Select-Object -First 1
if (-not $installation) {
$matchingInstallations = @($installations | Where-Object { $_.account.login -ieq $InstallationOwner })
if ($matchingInstallations.Count -eq 0) {
$found = ($installations | ForEach-Object { $_.account.login }) -join ', '
Write-PipelineTelemetryError -Category 'Build' -Message "No installation found for '$InstallationOwner'. App is installed on: $found"
exit 1
}
if ($matchingInstallations.Count -ne 1) {
$matchingIds = ($matchingInstallations | ForEach-Object { $_.id }) -join ', '
Write-PipelineTelemetryError -Category 'Build' -Message "Found multiple installations for '$InstallationOwner': $matchingIds"
exit 1
}
$installation = $matchingInstallations[0]
Write-Host "Using installation $($installation.id) for '$($installation.account.login)'."

try {
$tokenResponse = Invoke-RestMethod `
Expand Down
36 changes: 19 additions & 17 deletions eng/common/core-templates/job/onelocbuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ parameters:
GithubPat: $(BotAccount-dotnet-bot-repo-PAT)

# Service connection for WIF-based Entra authentication to ceapex feeds (replaces CeapexPat).
# When set, dnceng/internal builds acquire a federated Entra token instead of using a PAT.
# All other projects (e.g. DevDiv, public), where this dnceng-scoped service connection does not
# exist, and any pipeline that sets this to '' fall back to PAT-based auth via the CeapexPat parameter.
# The `internal` and `DevDiv` System.TeamProject values have same-named, project-scoped
# connections. Other values, and any pipeline that sets this to '', use the CeapexPat parameter.
CeapexServiceConnection: 'dnceng-onelocbuild-ceapex'

# GitHub App authentication for the OneLoc check-in PR (dnceng/internal only).
# The infrastructure identifiers are centralized here and the App path is enabled by default.
# DevDiv requires its own project-scoped service connection before this path can be enabled there.
# GitHub App authentication for the OneLoc check-in PR.
# dnceng/internal and DevDiv/DevDiv are enabled by default with their project-scoped service
# connections. Other projects must explicitly opt in after provisioning equivalent infrastructure.
UseGitHubAppAuthentication: true
UseGitHubAppAuthenticationInOtherProjects: false
GitHubAppServiceConnection: 'dnceng-oneloc-githubapp'
GitHubAppClientId: 'Iv23lijBU8x3gc9lDOc9'
GitHubAppKeyVaultName: 'EngKeyVault'
Expand Down Expand Up @@ -88,22 +88,24 @@ jobs:
displayName: Generate LocProject.json
condition: ${{ parameters.condition }}

# Acquire an Entra token for ceapex feed access via WIF (dnceng/internal only).
# All other projects use PAT-based auth, since the ceapex service connection is scoped to dnceng/internal.
- ${{ if and(ne(parameters.CeapexServiceConnection, ''), eq(variables['System.TeamProject'], 'internal')) }}:
# Acquire an Entra token when System.TeamProject is `internal` or `DevDiv`.
- ${{ if and(ne(parameters.CeapexServiceConnection, ''), or(eq(variables['System.TeamProject'], 'internal'), eq(variables['System.TeamProject'], 'DevDiv'))) }}:
- template: /eng/common/templates/steps/get-federated-access-token.yml
parameters:
federatedServiceConnection: ${{ parameters.CeapexServiceConnection }}
outputVariableName: 'CeapexEntraToken'
condition: ${{ parameters.condition }}

# Mint a short-lived GitHub App installation token for the loc check-in PR (dnceng/internal only).
# All other projects fall back to PAT-based auth, since the app service connection is scoped to dnceng/internal.
- ${{ if and(eq(parameters.RepoType, 'gitHub'), eq(parameters.UseGitHubAppAuthentication, true), eq(variables['System.TeamProject'], 'internal')) }}:
# Mint a short-lived GitHub App installation token for the loc check-in PR. Use the connection
# provisioned in each supported project; other projects must explicitly opt in and override it.
- ${{ if and(eq(parameters.RepoType, 'gitHub'), eq(parameters.UseGitHubAppAuthentication, true), or(eq(variables['System.TeamProject'], 'internal'), eq(variables['System.TeamProject'], 'DevDiv'), eq(parameters.UseGitHubAppAuthenticationInOtherProjects, true))) }}:
- template: /eng/common/core-templates/steps/get-github-app-token.yml
parameters:
is1ESPipeline: ${{ parameters.is1ESPipeline }}
azureSubscription: ${{ parameters.GitHubAppServiceConnection }}
${{ if and(eq(variables['System.TeamProject'], 'DevDiv'), eq(parameters.GitHubAppServiceConnection, 'dnceng-oneloc-githubapp')) }}:
azureSubscription: 'devdiv-oneloc-githubapp'
${{ else }}:
azureSubscription: ${{ parameters.GitHubAppServiceConnection }}
keyVaultName: ${{ parameters.GitHubAppKeyVaultName }}
keyName: ${{ parameters.GitHubAppKeyName }}
appClientId: ${{ parameters.GitHubAppClientId }}
Expand All @@ -126,15 +128,15 @@ jobs:
isUseLfLineEndingsSelected: ${{ parameters.UseLfLineEndings }}
isShouldReusePrSelected: ${{ parameters.ReusePr }}
packageSourceAuth: patAuth
${{ if and(ne(parameters.CeapexServiceConnection, ''), eq(variables['System.TeamProject'], 'internal')) }}:
${{ if and(ne(parameters.CeapexServiceConnection, ''), or(eq(variables['System.TeamProject'], 'internal'), eq(variables['System.TeamProject'], 'DevDiv'))) }}:
patVariable: $(CeapexEntraToken)
${{ if or(eq(parameters.CeapexServiceConnection, ''), ne(variables['System.TeamProject'], 'internal')) }}:
${{ if or(eq(parameters.CeapexServiceConnection, ''), and(ne(variables['System.TeamProject'], 'internal'), ne(variables['System.TeamProject'], 'DevDiv'))) }}:
patVariable: ${{ parameters.CeapexPat }}
${{ if eq(parameters.RepoType, 'gitHub') }}:
repoType: ${{ parameters.RepoType }}
${{ if and(eq(parameters.UseGitHubAppAuthentication, true), eq(variables['System.TeamProject'], 'internal')) }}:
${{ if and(eq(parameters.UseGitHubAppAuthentication, true), or(eq(variables['System.TeamProject'], 'internal'), eq(variables['System.TeamProject'], 'DevDiv'), eq(parameters.UseGitHubAppAuthenticationInOtherProjects, true))) }}:
gitHubPatVariable: "$(GitHubAppInstallationToken)"
${{ if or(eq(parameters.UseGitHubAppAuthentication, false), ne(variables['System.TeamProject'], 'internal')) }}:
${{ else }}:
gitHubPatVariable: "${{ parameters.GithubPat }}"
${{ if ne(parameters.MirrorRepo, '') }}:
isMirrorRepoSelected: true
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"dotnet": "10.0.111"
},
"msbuild-sdks": {
"Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26412.103",
"Microsoft.DotNet.Arcade.Sdk": "10.0.0-beta.26427.124",
"Microsoft.Build.NoTargets": "3.7.0"
}
}
21 changes: 13 additions & 8 deletions src/Common/AzureDevOps/AzureDevOpsUrlParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,17 @@ public static bool TryParseOnPremHttp(string relativeUrl, string virtualDirector
return true;
}

public static bool TryParseHostedSsh(Uri uri, [NotNullWhen(true)]out string? account, [NotNullWhen(true)]out string? repositoryPath, [NotNullWhen(true)]out string? repositoryName)
public static bool TryParseHostedSsh(
Uri uri,
[NotNullWhen(true)] out string? account,
[NotNullWhen(true)] out string? repositoryPath,
[NotNullWhen(true)] out string? repositoryName,
out bool isUnsupportedFormat)
{
NullableDebug.Assert(uri != null);

account = repositoryPath = repositoryName = null;
isUnsupportedFormat = false;

// {"DefaultCollection"|""}/{repositoryPath}/"_ssh"/{"_full"|"_optimized"}/{repositoryName}
if (!UriUtilities.TrySplitRelativeUrl(uri.GetPath(), out var parts) || parts.Length == 0)
Expand All @@ -125,7 +131,7 @@ public static bool TryParseHostedSsh(Uri uri, [NotNullWhen(true)]out string? acc
// Check for v3 url format
if (parts[0] == "v3" &&
parts.Length >= 3 &&
TryParsePath(parts, 2, type: null, out repositoryPath, out repositoryName) &&
TryParsePath(parts, startIndex: 2, type: null, out repositoryPath, out repositoryName) &&
repositoryPath != "")
{
// ssh://{user}@{domain}:{port}/v3/{account}/{repositoryPath}/{'_full'|'_optimized'|''}/{repositoryName}
Expand All @@ -136,8 +142,6 @@ public static bool TryParseHostedSsh(Uri uri, [NotNullWhen(true)]out string? acc
// ssh v1/v2 url formats
// ssh://{account}@vs-ssh.visualstudio.com/

account = uri.UserInfo;

int index = 0;
if (StringComparer.OrdinalIgnoreCase.Equals(parts[0], "DefaultCollection"))
{
Expand All @@ -149,14 +153,15 @@ public static bool TryParseHostedSsh(Uri uri, [NotNullWhen(true)]out string? acc
// Failed to parse path
return false;
}
}

if (account.Length == 0)
{
// The format uses SSH connection user name as an account name.
// It is no longer supported since GitOperations.GetRepositoryUrl strips the user info
// to prevent leaking credentials.
isUnsupportedFormat = true;
return false;
}

return true;
return account.Length > 0;
}

public static bool TryParseOnPremSsh(Uri uri, [NotNullWhen(true)]out string? repositoryPath, [NotNullWhen(true)]out string? repositoryName)
Expand Down
49 changes: 26 additions & 23 deletions src/Microsoft.Build.Tasks.Git.UnitTests/GitOperationsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,23 +224,26 @@ public void GetRepositoryUrl_UnsupportedUrl(string kind)
}

[Theory]
[InlineData("https://github.com/org/repo")]
[InlineData("http://github.com/org/repo")]
[InlineData("http://github.com:102/org/repo")]
[InlineData("ssh://user@github.com/org/repo")]
[InlineData("abc://user@github.com/org/repo")]
public void NormalizeUrl_PlatformAgnostic1(string url)
[InlineData("https://github.com/org/repo", "https://github.com/org/repo")]
[InlineData("http://github.com/org/repo", "http://github.com/org/repo")]
[InlineData("http://github.com:102/org/repo", "http://github.com:102/org/repo")]
[InlineData("ssh://user@github.com/org/repo", "ssh://git@github.com/org/repo")] // "user" replaced with "git" in SSH URL
[InlineData("abc://user@github.com/org/repo", "abc://github.com/org/repo")]
public void NormalizeUrl_PlatformAgnostic1(string url, string expected)
{
AssertEx.AreEqual(url, GitOperations.NormalizeUrl(url, s_root)?.AbsoluteUri);
AssertEx.AreEqual(expected, GitOperations.NormalizeUrl(url, s_root)?.AbsoluteUri);
}

[Theory]
[InlineData("http://?", null)]
[InlineData("https://github.com/org/repo/./.", "https://github.com/org/repo/")]
[InlineData("http://github.com/org/" + TestStrings.RepoName, "http://github.com/org/" + TestStrings.RepoNameFullyEscaped)]
[InlineData("ssh://github.com/org/../repo", "ssh://github.com/repo")]
[InlineData("ssh://github.com/%32/repo", "ssh://github.com/2/repo")]
[InlineData("ssh://github.com/%3F/repo", "ssh://github.com/%3F/repo")]
[InlineData("ssh://github.com/org/../repo", "ssh://git@github.com/repo")]
[InlineData("ssh://github.com/%32/repo", "ssh://git@github.com/2/repo")]
[InlineData("ssh://github.com/%3F/repo", "ssh://git@github.com/%3F/repo")]
[InlineData(@"../.:./../../relative/path", null)]
[InlineData(@".:/../../relative/path", null)]
[InlineData(@"..:/../../relative/path", null)]
public void NormalizeUrl_PlatformAgnostic2(string url, string? expectedUrl)
{
AssertEx.AreEqual(expectedUrl, GitOperations.NormalizeUrl(url, s_root)?.AbsoluteUri);
Expand All @@ -259,24 +262,24 @@ public void NormalizeUrl_PlatformAgnostic2(string url, string? expectedUrl)
// [InlineData(@"../relative/path*<>|\0%00", @"file:///usr/src/a/relative/path*%3C%3E%7C/0%2500")] // https://github.com/dotnet/sourcelink/issues/439
[InlineData(@"../../../../relative/path", @"file:///relative/path")]
[InlineData(@"../.://../../relative/path", "file:///usr/src/a/relative/path")]
[InlineData(@"../.:./../../relative/path", "ssh://../relative/path")]
[InlineData(@".:/../../relative/path", "ssh://./relative/path")]
[InlineData(@"..:/../../relative/path", "ssh://../relative/path")]
[InlineData(@"../.:./../../relative/path", null)]
[InlineData(@".:/../../relative/path", null)]
[InlineData(@"..:/../../relative/path", null)]
[InlineData(@"@:org/repo", @"file:///usr/src/a/b/@:org/repo")]
public void NormalizeUrl_Unix(string url, string expectedUrl)
public void NormalizeUrl_Unix(string url, string? expectedUrl)
{
Assert.Equal(expectedUrl, GitOperations.NormalizeUrl(url, "/usr/src/a/b")?.AbsoluteUri);
}

[Theory]
[InlineData("abc:org/repo", "ssh://abc/org/repo")]
[InlineData("abc:org/x%20y", "ssh://abc/org/x%20y")]
[InlineData("ABC:ORG/REPO/X/Y", "ssh://abc/ORG/REPO/X/Y")]
[InlineData("github.com:org/repo", "ssh://github.com/org/repo")]
[InlineData("git@github.com:org/repo", "ssh://git@github.com/org/repo")]
[InlineData("@github.com:org/repo", "ssh://@github.com/org/repo")]
[InlineData("http:x//y", "ssh://http/x//y")]
public void GetRepositoryUrl_ScpSyntax(string url, string expectedUrl)
[InlineData("abc:org/repo", "ssh://git@abc/org/repo")]
[InlineData("abc:org/x%20y", "ssh://git@abc/org/x%20y")]
[InlineData("ABC:ORG/REPO/X/Y", "ssh://git@abc/ORG/REPO/X/Y")]
[InlineData("github.com:org/repo", "ssh://git@github.com/org/repo")]
[InlineData("user@github.com:org/repo", "ssh://git@github.com/org/repo")] // "user" replaced with "git" in SSH URL
[InlineData("@github.com:org/repo", "ssh://git@github.com/org/repo")]
[InlineData("http:x//y", "ssh://git@http/x//y")]
public void NormalizeUrl_ScpSyntax(string url, string expectedUrl)
{
Assert.Equal(expectedUrl, GitOperations.NormalizeUrl(url, s_root)?.AbsoluteUri);
}
Expand Down Expand Up @@ -398,7 +401,7 @@ public void GetSourceRoots_RepoWithoutCommitsWithSubmodules()
// URLs listed in .submodules are ignored (they are used by git submodule initialize to generate URLs stored in config).
AssertEx.Equal(new[]
{
$@"'{_workingDir}{s}sub{s}1{s}' SourceControl='git' RevisionId='1111111111111111111111111111111111111111' NestedRoot='sub/1/' ContainingRoot='{_workingDir}{s}' ScmRepositoryUrl='ssh://github.com/sub-1'",
$@"'{_workingDir}{s}sub{s}1{s}' SourceControl='git' RevisionId='1111111111111111111111111111111111111111' NestedRoot='sub/1/' ContainingRoot='{_workingDir}{s}' ScmRepositoryUrl='ssh://git@github.com/sub-1'",
$@"'{_workingDir}{s}sub{s}3{s}' SourceControl='git' RevisionId='3333333333333333333333333333333333333333' NestedRoot='sub/3/' ContainingRoot='{_workingDir}{s}' ScmRepositoryUrl='https://github.com/sub-3'",
$@"'{_workingDir}{s}sub{s}6{s}' SourceControl='git' RevisionId='6666666666666666666666666666666666666666' NestedRoot='sub/6/' ContainingRoot='{_workingDir}{s}' ScmRepositoryUrl='https://github.com/sub-6'",
}, items.Select(TestUtilities.InspectSourceRoot));
Expand Down
Loading
Loading