Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rearrange NuGet error handling to one location in full runner #10692

Merged
merged 1 commit into from
Oct 1, 2024
Merged
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
1 change: 1 addition & 0 deletions nuget/helpers/lib/NuGetUpdater/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ tab_width = 4

# New line preferences
insert_final_newline = true
end_of_line = lf

#### .NET Coding Conventions ####
[*.{cs,vb}]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,98 @@ await RunAsync(
);
}

[Fact]
public async Task PrivateSourceAuthenticationFailureIsForwaredToApiHandler()
{
static (int, string) TestHttpHandler(string uriString)
{
var uri = new Uri(uriString, UriKind.Absolute);
var baseUrl = $"{uri.Scheme}://{uri.Host}:{uri.Port}";
return uri.PathAndQuery switch
{
// initial request is good
"/index.json" => (200, $$"""
{
"version": "3.0.0",
"resources": [
{
"@id": "{{baseUrl}}/download",
"@type": "PackageBaseAddress/3.0.0"
},
{
"@id": "{{baseUrl}}/query",
"@type": "SearchQueryService"
},
{
"@id": "{{baseUrl}}/registrations",
"@type": "RegistrationsBaseUrl"
}
]
}
"""),
// all other requests are unauthorized
_ => (401, "{}"),
};
}
using var http = TestHttpServer.CreateTestStringServer(TestHttpHandler);
await RunAsync(
packages:
[
],
job: new Job()
{
PackageManager = "nuget",
Source = new()
{
Provider = "github",
Repo = "test/repo",
Directory = "/",
},
AllowedUpdates =
[
new() { UpdateType = "all" }
]
},
files:
[
("NuGet.Config", $"""
<configuration>
<packageSources>
<clear />
<add key="private_feed" value="{http.BaseUrl.TrimEnd('/')}/index.json" allowInsecureConnections="true" />
</packageSources>
</configuration>
"""),
("project.csproj", """
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Some.Package" Version="1.0.0" />
</ItemGroup>
</Project>
""")
],
expectedResult: new RunResult()
{
Base64DependencyFiles = [],
BaseCommitSha = "TEST-COMMIT-SHA",
},
expectedApiMessages:
[
new PrivateSourceAuthenticationFailure()
{
Details = $"({http.BaseUrl.TrimEnd('/')}/index.json)"
},
new MarkAsProcessed()
{
BaseCommitSha = "TEST-COMMIT-SHA",
}
]
);
}

private static async Task RunAsync(Job job, TestFile[] files, RunResult expectedResult, object[] expectedApiMessages, MockNuGetPackage[]? packages = null)
{
// arrange
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,33 @@ internal class TestApiHandler : IApiHandler

public IEnumerable<(Type Type, object Object)> ReceivedMessages => _receivedMessages;

public Task RecordUpdateJobError(JobErrorBase error)
{
_receivedMessages.Add((error.GetType(), error));
return Task.CompletedTask;
}

public Task UpdateDependencyList(UpdatedDependencyList updatedDependencyList)
{
_receivedMessages.Add((typeof(UpdatedDependencyList), updatedDependencyList));
_receivedMessages.Add((updatedDependencyList.GetType(), updatedDependencyList));
return Task.CompletedTask;
}

public Task IncrementMetric(IncrementMetric incrementMetric)
{
_receivedMessages.Add((typeof(IncrementMetric), incrementMetric));
_receivedMessages.Add((incrementMetric.GetType(), incrementMetric));
return Task.CompletedTask;
}

public Task CreatePullRequest(CreatePullRequest createPullRequest)
{
_receivedMessages.Add((typeof(CreatePullRequest), createPullRequest));
_receivedMessages.Add((createPullRequest.GetType(), createPullRequest));
return Task.CompletedTask;
}

public Task MarkAsProcessed(MarkAsProcessed markAsProcessed)
{
_receivedMessages.Add((typeof(MarkAsProcessed), markAsProcessed));
_receivedMessages.Add((markAsProcessed.GetType(), markAsProcessed));
return Task.CompletedTask;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -257,14 +257,6 @@ public static async Task MockNuGetPackagesInDirectory(MockNuGetPackage[]? packag
package.WriteToDirectory(localFeedPath);
}

// override various nuget locations
foreach (var envName in new[] { "NUGET_PACKAGES", "NUGET_HTTP_CACHE_PATH", "NUGET_SCRATCH", "NUGET_PLUGINS_CACHE_PATH" })
{
string dir = Path.Join(temporaryDirectory, envName);
Directory.CreateDirectory(dir);
Environment.SetEnvironmentVariable(envName, dir);
}

// ensure only the test feed is used
string relativeLocalFeedPath = Path.GetRelativePath(temporaryDirectory, localFeedPath);
await File.WriteAllTextAsync(Path.Join(temporaryDirectory, "NuGet.Config"), $"""
Expand All @@ -278,6 +270,14 @@ await File.WriteAllTextAsync(Path.Join(temporaryDirectory, "NuGet.Config"), $"""
"""
);
}

// override various nuget locations
foreach (var envName in new[] { "NUGET_PACKAGES", "NUGET_HTTP_CACHE_PATH", "NUGET_SCRATCH", "NUGET_PLUGINS_CACHE_PATH" })
{
string dir = Path.Join(temporaryDirectory, envName);
Directory.CreateDirectory(dir);
Environment.SetEnvironmentVariable(envName, dir);
}
}

protected static async Task<TestFile[]> RunUpdate(TestFile[] files, Func<string, Task> action)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,29 @@ public AnalyzeWorker(Logger logger)

public async Task RunAsync(string repoRoot, string discoveryPath, string dependencyPath, string analysisDirectory)
{
AnalysisResult analysisResult;
var discovery = await DeserializeJsonFileAsync<WorkspaceDiscoveryResult>(discoveryPath, nameof(WorkspaceDiscoveryResult));
var dependencyInfo = await DeserializeJsonFileAsync<DependencyInfo>(dependencyPath, nameof(DependencyInfo));
var analysisResult = await RunAsync(repoRoot, discovery, dependencyInfo);

try
{
analysisResult = await RunAsync(repoRoot, discovery, dependencyInfo);
}
catch (HttpRequestException ex)
when (ex.StatusCode == HttpStatusCode.Unauthorized || ex.StatusCode == HttpStatusCode.Forbidden)
{
var localPath = PathHelper.JoinPath(repoRoot, discovery.Path);
var nugetContext = new NuGetContext(localPath);
analysisResult = new AnalysisResult
{
ErrorType = ErrorType.AuthenticationFailure,
ErrorDetails = "(" + string.Join("|", nugetContext.PackageSources.Select(s => s.Source)) + ")",
UpdatedVersion = string.Empty,
CanUpdate = false,
UpdatedDependencies = [],
};
}

await WriteResultsAsync(analysisDirectory, dependencyInfo.Name, analysisResult, _logger);
}

Expand Down Expand Up @@ -68,100 +88,84 @@ public async Task<AnalysisResult> RunAsync(string repoRoot, WorkspaceDiscoveryRe
var isUpdateNecessary = isProjectUpdateNecessary || dotnetToolsHasDependency || globalJsonHasDependency;
using var nugetContext = new NuGetContext(startingDirectory);
AnalysisResult analysisResult;
try
if (isUpdateNecessary)
{
if (isUpdateNecessary)
_logger.Log($" Determining multi-dependency property.");
var multiDependencies = DetermineMultiDependencyDetails(
discovery,
dependencyInfo.Name,
propertyBasedDependencies);

usesMultiDependencyProperty = multiDependencies.Any(md => md.DependencyNames.Count > 1);
var dependenciesToUpdate = usesMultiDependencyProperty
? multiDependencies
.SelectMany(md => md.DependencyNames)
.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase)
: [dependencyInfo.Name];
var applicableTargetFrameworks = usesMultiDependencyProperty
? multiDependencies
.SelectMany(md => md.TargetFrameworks)
.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase)
.Select(NuGetFramework.Parse)
.ToImmutableArray()
: projectFrameworks;

_logger.Log($" Finding updated version.");
updatedVersion = await FindUpdatedVersionAsync(
startingDirectory,
dependencyInfo,
dependenciesToUpdate,
applicableTargetFrameworks,
nugetContext,
_logger,
CancellationToken.None);

_logger.Log($" Finding updated peer dependencies.");
if (updatedVersion is null)
{
updatedDependencies = [];
}
else if (isProjectUpdateNecessary)
{
_logger.Log($" Determining multi-dependency property.");
var multiDependencies = DetermineMultiDependencyDetails(
updatedDependencies = await FindUpdatedDependenciesAsync(
repoRoot,
discovery,
dependencyInfo.Name,
propertyBasedDependencies);

usesMultiDependencyProperty = multiDependencies.Any(md => md.DependencyNames.Count > 1);
var dependenciesToUpdate = usesMultiDependencyProperty
? multiDependencies
.SelectMany(md => md.DependencyNames)
.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase)
: [dependencyInfo.Name];
var applicableTargetFrameworks = usesMultiDependencyProperty
? multiDependencies
.SelectMany(md => md.TargetFrameworks)
.ToImmutableHashSet(StringComparer.OrdinalIgnoreCase)
.Select(NuGetFramework.Parse)
.ToImmutableArray()
: projectFrameworks;

_logger.Log($" Finding updated version.");
updatedVersion = await FindUpdatedVersionAsync(
startingDirectory,
dependencyInfo,
dependenciesToUpdate,
applicableTargetFrameworks,
updatedVersion,
nugetContext,
_logger,
CancellationToken.None);

_logger.Log($" Finding updated peer dependencies.");
if (updatedVersion is null)
{
updatedDependencies = [];
}
else if (isProjectUpdateNecessary)
{
updatedDependencies = await FindUpdatedDependenciesAsync(
repoRoot,
discovery,
dependenciesToUpdate,
updatedVersion,
nugetContext,
_logger,
CancellationToken.None);
}
else if (dotnetToolsHasDependency)
{
var infoUrl = await nugetContext.GetPackageInfoUrlAsync(dependencyInfo.Name, updatedVersion.ToNormalizedString(), CancellationToken.None);
updatedDependencies = [new Dependency(dependencyInfo.Name, updatedVersion.ToNormalizedString(), DependencyType.DotNetTool, IsDirect: true, InfoUrl: infoUrl)];
}
else if (globalJsonHasDependency)
{
var infoUrl = await nugetContext.GetPackageInfoUrlAsync(dependencyInfo.Name, updatedVersion.ToNormalizedString(), CancellationToken.None);
updatedDependencies = [new Dependency(dependencyInfo.Name, updatedVersion.ToNormalizedString(), DependencyType.MSBuildSdk, IsDirect: true, InfoUrl: infoUrl)];
}
else
{
throw new InvalidOperationException("Unreachable.");
}

//TODO: At this point we should add the peer dependencies to a queue where
// we will analyze them one by one to see if they themselves are part of a
// multi-dependency property. Basically looping this if-body until we have
// emptied the queue and have a complete list of updated dependencies. We
// should track the dependenciesToUpdate as they have already been analyzed.
}

analysisResult = new AnalysisResult
else if (dotnetToolsHasDependency)
{
UpdatedVersion = updatedVersion?.ToNormalizedString() ?? dependencyInfo.Version,
CanUpdate = updatedVersion is not null,
VersionComesFromMultiDependencyProperty = usesMultiDependencyProperty,
UpdatedDependencies = updatedDependencies,
};
}
catch (HttpRequestException ex)
when (ex.StatusCode == HttpStatusCode.Unauthorized || ex.StatusCode == HttpStatusCode.Forbidden)
{
// TODO: consolidate this error handling between AnalyzeWorker, DiscoveryWorker, and UpdateWorker
analysisResult = new AnalysisResult
var infoUrl = await nugetContext.GetPackageInfoUrlAsync(dependencyInfo.Name, updatedVersion.ToNormalizedString(), CancellationToken.None);
updatedDependencies = [new Dependency(dependencyInfo.Name, updatedVersion.ToNormalizedString(), DependencyType.DotNetTool, IsDirect: true, InfoUrl: infoUrl)];
}
else if (globalJsonHasDependency)
{
ErrorType = ErrorType.AuthenticationFailure,
ErrorDetails = "(" + string.Join("|", nugetContext.PackageSources.Select(s => s.Source)) + ")",
UpdatedVersion = string.Empty,
CanUpdate = false,
UpdatedDependencies = [],
};
var infoUrl = await nugetContext.GetPackageInfoUrlAsync(dependencyInfo.Name, updatedVersion.ToNormalizedString(), CancellationToken.None);
updatedDependencies = [new Dependency(dependencyInfo.Name, updatedVersion.ToNormalizedString(), DependencyType.MSBuildSdk, IsDirect: true, InfoUrl: infoUrl)];
}
else
{
throw new InvalidOperationException("Unreachable.");
}

//TODO: At this point we should add the peer dependencies to a queue where
// we will analyze them one by one to see if they themselves are part of a
// multi-dependency property. Basically looping this if-body until we have
// emptied the queue and have a complete list of updated dependencies. We
// should track the dependenciesToUpdate as they have already been analyzed.
}

analysisResult = new AnalysisResult
{
UpdatedVersion = updatedVersion?.ToNormalizedString() ?? dependencyInfo.Version,
CanUpdate = updatedVersion is not null,
VersionComesFromMultiDependencyProperty = usesMultiDependencyProperty,
UpdatedDependencies = updatedDependencies,
};

_logger.Log($"Analysis complete.");
return analysisResult;
}
Expand Down
Loading
Loading