Skip to content

Commit 2d404a2

Browse files
[Feature] Add package source mapping to package download (#6885)
1 parent 6d3aa38 commit 2d404a2

19 files changed

Lines changed: 448 additions & 14 deletions

File tree

src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Package/Download/PackageDownloadRunner.cs

Lines changed: 110 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) .NET Foundation. All rights reserved.
22
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
33

4+
#nullable enable
5+
46
using System;
57
using System.Collections.Generic;
68
using System.Globalization;
@@ -48,28 +50,62 @@ public static async Task<int> RunAsync(PackageDownloadArgs args, CancellationTok
4850

4951
public static async Task<int> RunAsync(PackageDownloadArgs args, ILoggerWithColor logger, IReadOnlyList<PackageSource> packageSources, ISettings settings, CancellationToken token)
5052
{
51-
// Check for insecure sources
52-
if (DetectAndReportInsecureSources(args.AllowInsecureConnections, packageSources, logger))
53+
bool hasSourcesArg = args.Sources?.Count > 0;
54+
PackageSourceMapping? packageSourceMapping = null;
55+
if (!hasSourcesArg)
56+
{
57+
packageSourceMapping = PackageSourceMapping.GetPackageSourceMapping(settings);
58+
}
59+
60+
bool ignorePackageSourceMapping =
61+
hasSourcesArg
62+
|| packageSourceMapping is null
63+
|| !packageSourceMapping.IsEnabled;
64+
65+
// When package source mapping is disabled, validate all configured sources upfront.
66+
// When mapping is enabled, source validation is deferred to the per-package resolution step,
67+
// since each package may map to a different subset of sources.
68+
if (ignorePackageSourceMapping && DetectAndReportInsecureSources(args.AllowInsecureConnections, packageSources, logger))
5369
{
5470
return ExitCodeError;
5571
}
5672

5773
string outputDirectory = args.OutputDirectory ?? Directory.GetCurrentDirectory();
5874
var cache = new SourceCacheContext();
59-
IReadOnlyList<SourceRepository> sourceRepositories = GetSourceRepositories(packageSources);
75+
IReadOnlyList<SourceRepository> allRepositories = GetSourceRepositories(packageSources);
6076
bool downloadedAllSuccessfully = true;
6177

62-
foreach (var package in args.Packages)
78+
foreach (var package in args.Packages ?? [])
6379
{
6480
logger.LogMinimal(string.Format(
6581
CultureInfo.CurrentCulture,
6682
Strings.PackageDownloadCommand_Starting,
6783
package.Id,
6884
string.IsNullOrEmpty(package.NuGetVersion?.ToNormalizedString()) ? Strings.PackageDownloadCommand_LatestVersion : package.NuGetVersion.ToNormalizedString()));
6985

86+
// Resolve which repositories to use for this package
87+
IReadOnlyList<SourceRepository> sourceRepositories;
88+
if (ignorePackageSourceMapping)
89+
{
90+
sourceRepositories = allRepositories;
91+
}
92+
else
93+
{
94+
if (!TryGetRepositoriesForPackage(
95+
package.Id,
96+
args,
97+
packageSourceMapping!,
98+
allRepositories,
99+
logger,
100+
out sourceRepositories))
101+
{
102+
return ExitCodeError;
103+
}
104+
}
105+
70106
try
71107
{
72-
(NuGetVersion version, SourceRepository downloadRepository) =
108+
(NuGetVersion? version, SourceRepository? downloadRepository) =
73109
await ResolvePackageDownloadVersion(
74110
package,
75111
sourceRepositories,
@@ -88,7 +124,7 @@ await ResolvePackageDownloadVersion(
88124
bool success = await DownloadPackageAsync(
89125
package.Id,
90126
version,
91-
downloadRepository,
127+
downloadRepository!,
92128
cache,
93129
settings,
94130
outputDirectory,
@@ -127,16 +163,16 @@ await ResolvePackageDownloadVersion(
127163
return downloadedAllSuccessfully ? ExitCodeSuccess : ExitCodeError;
128164
}
129165

130-
internal static async Task<(NuGetVersion, SourceRepository)> ResolvePackageDownloadVersion(
166+
internal static async Task<(NuGetVersion?, SourceRepository?)> ResolvePackageDownloadVersion(
131167
PackageWithNuGetVersion packageWithNuGetVersion,
132-
IEnumerable<SourceRepository> sourceRepositories,
168+
IReadOnlyList<SourceRepository> sourceRepositories,
133169
SourceCacheContext cache,
134170
ILoggerWithColor logger,
135171
bool includePrerelease,
136172
CancellationToken token)
137173
{
138-
NuGetVersion versionToDownload = null;
139-
SourceRepository downloadSourceRepository = null;
174+
NuGetVersion? versionToDownload = null;
175+
SourceRepository? downloadSourceRepository = null;
140176
bool versionSpecified = packageWithNuGetVersion.NuGetVersion != null;
141177

142178
foreach (var repo in sourceRepositories)
@@ -188,6 +224,69 @@ await ResolvePackageDownloadVersion(
188224
return (versionToDownload, downloadSourceRepository);
189225
}
190226

227+
/// <summary>
228+
/// Builds the set of SourceRepository objects to use for a given package,
229+
/// applying package source mapping
230+
/// validating HTTP usage only on the *effective* sources.
231+
/// </summary>
232+
private static bool TryGetRepositoriesForPackage(
233+
string packageId,
234+
PackageDownloadArgs args,
235+
PackageSourceMapping packageSourceMapping,
236+
IReadOnlyList<SourceRepository> allRepos,
237+
ILoggerWithColor logger,
238+
out IReadOnlyList<SourceRepository> repositories)
239+
{
240+
var mappedNames = packageSourceMapping.GetConfiguredPackageSources(packageId);
241+
242+
// Only validate insecure sources when mapping produced something
243+
if (mappedNames.Count > 0)
244+
{
245+
var mappedRepos = new List<SourceRepository>(mappedNames.Count);
246+
foreach (var mappedName in mappedNames)
247+
{
248+
SourceRepository? repo = null;
249+
for (int i = 0; i < allRepos.Count; i++)
250+
{
251+
if (string.Equals(allRepos[i].PackageSource.Name, mappedName, StringComparison.OrdinalIgnoreCase))
252+
{
253+
repo = allRepos[i];
254+
break;
255+
}
256+
}
257+
258+
if (repo != null)
259+
{
260+
mappedRepos.Add(repo);
261+
}
262+
else
263+
{
264+
logger.LogVerbose(
265+
string.Format(
266+
CultureInfo.CurrentCulture,
267+
Strings.PackageDownloadCommand_PackageSourceMapping_NoSuchSource,
268+
mappedName,
269+
packageId));
270+
}
271+
}
272+
273+
if (DetectAndReportInsecureSources(args.AllowInsecureConnections, mappedRepos.Select(repo => repo.PackageSource), logger))
274+
{
275+
repositories = [];
276+
return false;
277+
}
278+
279+
repositories = mappedRepos;
280+
return true;
281+
}
282+
else
283+
{
284+
// No mapping for this package: fall back to all sources
285+
repositories = allRepos;
286+
return true;
287+
}
288+
}
289+
191290
private static async Task<bool> DownloadPackageAsync(
192291
string id,
193292
NuGetVersion version,
@@ -239,7 +338,7 @@ private static async Task<bool> DownloadPackageAsync(
239338
return success;
240339
}
241340

242-
private static IReadOnlyList<PackageSource> GetPackageSources(IList<string> sources, IPackageSourceProvider sourceProvider)
341+
private static IReadOnlyList<PackageSource> GetPackageSources(IList<string>? sources, IPackageSourceProvider sourceProvider)
243342
{
244343
IEnumerable<PackageSource> configuredSources = sourceProvider.LoadPackageSources()
245344
.Where(s => s.IsEnabled);

src/NuGet.Core/NuGet.CommandLine.XPlat/Strings.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/NuGet.Core/NuGet.CommandLine.XPlat/Strings.resx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1157,4 +1157,9 @@ Do not translate "PackageVersion"</comment>
11571157
<data name="Error_PackageDownload_VersionNotFound" xml:space="preserve">
11581158
<value>Unable to find a valid package version</value>
11591159
</data>
1160+
<data name="PackageDownloadCommand_PackageSourceMapping_NoSuchSource" xml:space="preserve">
1161+
<value>The mapped source '{0}' for package '{1}' was not found among the configured sources.</value>
1162+
<comment>0 - package source name
1163+
1 - package name</comment>
1164+
</data>
11601165
</root>

src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.cs.xlf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,12 @@ Další informace najdete tady: https://docs.nuget.org/docs/reference/command-li
929929
<target state="new">Package identifier (e.g. 'Newtonsoft.Json').</target>
930930
<note />
931931
</trans-unit>
932+
<trans-unit id="PackageDownloadCommand_PackageSourceMapping_NoSuchSource">
933+
<source>The mapped source '{0}' for package '{1}' was not found among the configured sources.</source>
934+
<target state="new">The mapped source '{0}' for package '{1}' was not found among the configured sources.</target>
935+
<note>0 - package source name
936+
1 - package name</note>
937+
</trans-unit>
932938
<trans-unit id="PackageDownloadCommand_SourcesDescription">
933939
<source>Specifies one or more NuGet package sources to use.</source>
934940
<target state="new">Specifies one or more NuGet package sources to use.</target>

src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.de.xlf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,12 @@ Weitere Informationen finden Sie unter: https://docs.nuget.org/docs/reference/co
929929
<target state="new">Package identifier (e.g. 'Newtonsoft.Json').</target>
930930
<note />
931931
</trans-unit>
932+
<trans-unit id="PackageDownloadCommand_PackageSourceMapping_NoSuchSource">
933+
<source>The mapped source '{0}' for package '{1}' was not found among the configured sources.</source>
934+
<target state="new">The mapped source '{0}' for package '{1}' was not found among the configured sources.</target>
935+
<note>0 - package source name
936+
1 - package name</note>
937+
</trans-unit>
932938
<trans-unit id="PackageDownloadCommand_SourcesDescription">
933939
<source>Specifies one or more NuGet package sources to use.</source>
934940
<target state="new">Specifies one or more NuGet package sources to use.</target>

src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.es.xlf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,12 @@ Para obtener más información, visite https://docs.nuget.org/docs/reference/com
929929
<target state="new">Package identifier (e.g. 'Newtonsoft.Json').</target>
930930
<note />
931931
</trans-unit>
932+
<trans-unit id="PackageDownloadCommand_PackageSourceMapping_NoSuchSource">
933+
<source>The mapped source '{0}' for package '{1}' was not found among the configured sources.</source>
934+
<target state="new">The mapped source '{0}' for package '{1}' was not found among the configured sources.</target>
935+
<note>0 - package source name
936+
1 - package name</note>
937+
</trans-unit>
932938
<trans-unit id="PackageDownloadCommand_SourcesDescription">
933939
<source>Specifies one or more NuGet package sources to use.</source>
934940
<target state="new">Specifies one or more NuGet package sources to use.</target>

src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.fr.xlf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,12 @@ Pour plus d'informations, visitez https://docs.nuget.org/docs/reference/command-
929929
<target state="new">Package identifier (e.g. 'Newtonsoft.Json').</target>
930930
<note />
931931
</trans-unit>
932+
<trans-unit id="PackageDownloadCommand_PackageSourceMapping_NoSuchSource">
933+
<source>The mapped source '{0}' for package '{1}' was not found among the configured sources.</source>
934+
<target state="new">The mapped source '{0}' for package '{1}' was not found among the configured sources.</target>
935+
<note>0 - package source name
936+
1 - package name</note>
937+
</trans-unit>
932938
<trans-unit id="PackageDownloadCommand_SourcesDescription">
933939
<source>Specifies one or more NuGet package sources to use.</source>
934940
<target state="new">Specifies one or more NuGet package sources to use.</target>

src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.it.xlf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,12 @@ Per altre informazioni, vedere https://docs.nuget.org/docs/reference/command-lin
929929
<target state="new">Package identifier (e.g. 'Newtonsoft.Json').</target>
930930
<note />
931931
</trans-unit>
932+
<trans-unit id="PackageDownloadCommand_PackageSourceMapping_NoSuchSource">
933+
<source>The mapped source '{0}' for package '{1}' was not found among the configured sources.</source>
934+
<target state="new">The mapped source '{0}' for package '{1}' was not found among the configured sources.</target>
935+
<note>0 - package source name
936+
1 - package name</note>
937+
</trans-unit>
932938
<trans-unit id="PackageDownloadCommand_SourcesDescription">
933939
<source>Specifies one or more NuGet package sources to use.</source>
934940
<target state="new">Specifies one or more NuGet package sources to use.</target>

src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.ja.xlf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,12 @@ For more information, visit https://docs.nuget.org/docs/reference/command-line-r
929929
<target state="new">Package identifier (e.g. 'Newtonsoft.Json').</target>
930930
<note />
931931
</trans-unit>
932+
<trans-unit id="PackageDownloadCommand_PackageSourceMapping_NoSuchSource">
933+
<source>The mapped source '{0}' for package '{1}' was not found among the configured sources.</source>
934+
<target state="new">The mapped source '{0}' for package '{1}' was not found among the configured sources.</target>
935+
<note>0 - package source name
936+
1 - package name</note>
937+
</trans-unit>
932938
<trans-unit id="PackageDownloadCommand_SourcesDescription">
933939
<source>Specifies one or more NuGet package sources to use.</source>
934940
<target state="new">Specifies one or more NuGet package sources to use.</target>

src/NuGet.Core/NuGet.CommandLine.XPlat/xlf/Strings.ko.xlf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,12 @@ For more information, visit https://docs.nuget.org/docs/reference/command-line-r
929929
<target state="new">Package identifier (e.g. 'Newtonsoft.Json').</target>
930930
<note />
931931
</trans-unit>
932+
<trans-unit id="PackageDownloadCommand_PackageSourceMapping_NoSuchSource">
933+
<source>The mapped source '{0}' for package '{1}' was not found among the configured sources.</source>
934+
<target state="new">The mapped source '{0}' for package '{1}' was not found among the configured sources.</target>
935+
<note>0 - package source name
936+
1 - package name</note>
937+
</trans-unit>
932938
<trans-unit id="PackageDownloadCommand_SourcesDescription">
933939
<source>Specifies one or more NuGet package sources to use.</source>
934940
<target state="new">Specifies one or more NuGet package sources to use.</target>

0 commit comments

Comments
 (0)