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

Stage 1 of Package Reference Compatibility Validation #346

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 8 additions & 1 deletion NuGet.Jobs.sln
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.27121.1
VisualStudioVersion = 15.0.27130.2027
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NuGet.Jobs.Common", "src\NuGet.Jobs.Common\NuGet.Jobs.Common.csproj", "{4B4B1EFB-8F33-42E6-B79F-54E7F3293D31}"
EndProject
Expand Down Expand Up @@ -113,6 +113,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validation.Common.Job", "sr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PackageHash", "src\PackageHash\PackageHash.csproj", "{40843020-6F0A-48F0-AC28-42FFE3A5C21E}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Validation.PackageCompatibility.Core", "src\Validation.PackageCompatibility.Core\Validation.PackageCompatibility.Core.csproj", "{3958ECDD-DE42-401B-8BC6-553E49F2D42A}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -289,6 +291,10 @@ Global
{40843020-6F0A-48F0-AC28-42FFE3A5C21E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{40843020-6F0A-48F0-AC28-42FFE3A5C21E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{40843020-6F0A-48F0-AC28-42FFE3A5C21E}.Release|Any CPU.Build.0 = Release|Any CPU
{3958ECDD-DE42-401B-8BC6-553E49F2D42A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3958ECDD-DE42-401B-8BC6-553E49F2D42A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3958ECDD-DE42-401B-8BC6-553E49F2D42A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3958ECDD-DE42-401B-8BC6-553E49F2D42A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -336,6 +342,7 @@ Global
{B4B7564A-965B-447B-927F-6749E2C08880} = {6A776396-02B1-475D-A104-26940ADB04AB}
{FA87D075-A934-4443-8D0B-5DB32640B6D7} = {678D7B14-F8BC-4193-99AF-2EE8AA390A02}
{40843020-6F0A-48F0-AC28-42FFE3A5C21E} = {FA5644B5-4F08-43F6-86B3-039374312A47}
{3958ECDD-DE42-401B-8BC6-553E49F2D42A} = {678D7B14-F8BC-4193-99AF-2EE8AA390A02}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {284A7AC3-FB43-4F1F-9C9C-2AF0E1F46C2B}
Expand Down
55 changes: 52 additions & 3 deletions src/NuGet.Services.Validation.Orchestrator/Job.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Reflection;
using System.Threading.Tasks;
using AnglicanGeek.MarkdownMailer;
using Autofac;
Expand All @@ -18,18 +19,20 @@
using Microsoft.WindowsAzure.Storage;
using NuGet.Jobs;
using NuGet.Jobs.Configuration;
using NuGet.Jobs.Validation;
using NuGet.Jobs.Validation.Common;
using NuGet.Jobs.Validation.PackageSigning.Messages;
using NuGet.Jobs.Validation.PackageSigning.Storage;
using NuGet.Services.Configuration;
using NuGet.Services.KeyVault;
using NuGet.Services.Logging;
using NuGet.Services.ServiceBus;
using NuGet.Services.Validation.Orchestrator.Telemetry;
using NuGet.Services.Validation.PackageCertificates;
using NuGet.Services.Validation.PackageCompatibility;
using NuGet.Services.Validation.PackageSigning;
using NuGet.Services.Validation.Vcs;
using NuGetGallery.Services;
using Validation.PackageCompatibility.Core.Storage;

namespace NuGet.Services.Validation.Orchestrator
{
Expand All @@ -42,17 +45,20 @@ public class Job : JobBase
private const string VcsSectionName = "Vcs";
private const string PackageSigningSectionName = "PackageSigning";
private const string PackageCertificatesSectionName = "PackageCertificates";
private const string PackageCompatibilitySectionName = "PackageCompatibility";
private const string RunnerConfigurationSectionName = "RunnerConfiguration";
private const string GalleryDbConfigurationSectionName = "GalleryDb";
private const string ValidationDbConfigurationSectionName = "ValidationDb";
private const string ServiceBusConfigurationSectionName = "ServiceBus";
private const string SmtpConfigurationSectionName = "Smtp";
private const string EmailConfigurationSectionName = "Email";
private const string PackageDownloadTimeoutName = "PackageDownloadTimeout";

private const string VcsBindingKey = VcsSectionName;
private const string PackageVerificationTopicClientBindingKey = "PackageVerificationTopicClient";
private const string PackageSigningBindingKey = PackageSigningSectionName;
private const string PackageCertificatesBindingKey = PackageCertificatesSectionName;
private const string PackageCompatibilityBindingKey = PackageCompatibilitySectionName;
private const string ValidationStorageBindingKey = "ValidationStorage";
private const string OrchestratorBindingKey = "Orchestrator";

Expand Down Expand Up @@ -182,6 +188,7 @@ private void ConfigureJobServices(IServiceCollection services, IConfigurationRoo
services.AddTransient<IBrokeredMessageSerializer<PackageValidationMessageData>, PackageValidationMessageDataSerializationAdapter>();
services.AddTransient<IPackageCriteriaEvaluator, PackageCriteriaEvaluator>();
services.AddTransient<VcsValidator>();
services.AddTransient<PackageCompatibilityValidator>();
services.AddTransient<IPackageSignatureVerificationEnqueuer, PackageSignatureVerificationEnqueuer>();
services.AddTransient<IBrokeredMessageSerializer<SignatureValidationMessage>, SignatureValidationMessageSerializer>();
services.AddTransient<IValidatorStateService, ValidatorStateService>();
Expand All @@ -207,18 +214,36 @@ private void ConfigureJobServices(IServiceCollection services, IConfigurationRoo
? (IMailSender)new DiskMailSender()
: (IMailSender)new MailSender(mailSenderConfiguration);
});

services.AddSingleton(p =>
{
var assembly = Assembly.GetEntryAssembly();
var assemblyName = assembly.GetName().Name;
var assemblyVersion = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? "0.0.0";

var client = new HttpClient(new WebRequestHandler
{
AllowPipelining = true,
AutomaticDecompression = (DecompressionMethods.GZip | DecompressionMethods.Deflate),
});

client.Timeout = configurationRoot.GetValue<TimeSpan>(PackageDownloadTimeoutName);
client.DefaultRequestHeaders.Add("User-Agent", $"{assemblyName}/{assemblyVersion}");

return client;
});
services.AddTransient<ICoreMessageServiceConfiguration, CoreMessageServiceConfiguration>();
services.AddTransient<ICoreMessageService, CoreMessageService>();
services.AddTransient<IMessageService, MessageService>();
services.AddTransient<ITelemetryService, TelemetryService>();
services.AddTransient<IPackageDownloader, PackageDownloader>();
services.AddSingleton(new TelemetryClient());
}

private static IServiceProvider CreateProvider(IServiceCollection services)
{
var containerBuilder = new ContainerBuilder();
containerBuilder.Populate(services);

/// Initialize dependencies for the <see cref="VcsValidator"/>. There is some additional complexity here
/// because the implementations require ambiguous types (such as a <see cref="string"/> and a
/// <see cref="CloudStorageAccount"/> which there may be more than one configuration of).
Expand Down Expand Up @@ -309,6 +334,7 @@ private static IServiceProvider CreateProvider(IServiceCollection services)

ConfigurePackageSigningValidator(containerBuilder);
ConfigurePackageCertificatesValidator(containerBuilder);
ConfigurePackageCompatibilityValidator(containerBuilder);

return new AutofacServiceProvider(containerBuilder.Build());
}
Expand Down Expand Up @@ -345,6 +371,29 @@ private static void ConfigurePackageSigningValidator(ContainerBuilder builder)
.As<PackageSigningValidator>();
}


private static void ConfigurePackageCompatibilityValidator(ContainerBuilder builder)
{
// Configure the validator state service for the package compatibility validator.
builder
.RegisterType<ValidatorStateService>()
.WithParameter(
(pi, ctx) => pi.ParameterType == typeof(Type),
(pi, ctx) => typeof(PackageCompatibilityValidator))
.Keyed<IValidatorStateService>(PackageCompatibilityBindingKey);

// Configure the package compatibility service
builder
.RegisterType<PackageCompatibilityService>()
.As<IPackageCompatibilityService>();

// Configure the package compatibility validator
builder
.RegisterType<PackageCompatibilityValidator>()
.WithKeyedParameter(typeof(IValidatorStateService), PackageCompatibilityBindingKey)
.As<PackageCompatibilityValidator>();
}

private static void ConfigurePackageCertificatesValidator(ContainerBuilder builder)
{
// Configure the validator state service for the package certificates validator.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
<Compile Include="MessageService.cs" />
<Compile Include="OrchestrationRunner.cs" />
<Compile Include="Configuration\OrchestrationRunnerConfiguration.cs" />
<Compile Include="PackageCompatibility\PackageCompatibilityValidator.cs" />
<Compile Include="PackageCertificates\CertificateVerificationEnqueuer.cs" />
<Compile Include="PackageCertificates\ICertificateVerificationEnqueuer.cs" />
<Compile Include="PackageCertificates\PackageCertificatesConfiguration.cs" />
Expand Down Expand Up @@ -120,6 +121,10 @@
<Project>{2539ddf3-0cc5-4a03-b5f9-39b47744a7bd}</Project>
<Name>Validation.Common</Name>
</ProjectReference>
<ProjectReference Include="..\Validation.PackageCompatibility.Core\Validation.PackageCompatibility.Core.csproj">
<Project>{3958ECDD-DE42-401B-8BC6-553E49F2D42A}</Project>
<Name>Validation.PackageCompatibility.Core</Name>
</ProjectReference>
<ProjectReference Include="..\Validation.PackageSigning.Core\Validation.PackageSigning.Core.csproj">
<Project>{91C060DA-736F-4DA9-A57F-CB3AC0E6CB10}</Project>
<Name>Validation.PackageSigning.Core</Name>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using NuGet.Common;
using NuGet.Jobs.Validation;
using NuGet.Jobs.Validation.PackageSigning.Storage;
using Validation.PackageCompatibility.Core.Messages;
using Validation.PackageCompatibility.Core.Storage;

namespace NuGet.Services.Validation.PackageCompatibility
{
/// <summary>
/// Configuration for initializing the <see cref="PackageCompatibilityValidator"/>.
/// </summary>
public class PackageCompatibilityValidator : IValidator
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PackageCompatibilityValidator [](start = 17, length = 29)

Validators that are outside of the NuGet.Services.Validation.Orchestrator project are not supported today. This is a low-priority "bug" in the Orchestrator that we will fix in the future. The type that extends IValidator will need to be moved, the implementation can be kept here.

See this:

IEnumerable<Type> candidateTypes = GetCandidateTypes(Assembly.GetCallingAssembly());

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's useful feedback.

This is currently in the Orchestrator project though, just making sure you noticed that.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh. I can't read :)

{
private IValidatorStateService _validatorStateService;
private IPackageCompatibilityService _packageCompatibilityService;
private readonly ILogger<PackageCompatibilityValidator> _logger;

private IPackageDownloader _packageDownloader;

public PackageCompatibilityValidator(
IValidatorStateService validatorStateService,
IPackageCompatibilityService packageCompatibilityService,
IPackageDownloader packageDownloader,
ILogger<PackageCompatibilityValidator> logger)
{
_validatorStateService = validatorStateService;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null checks

_packageCompatibilityService = packageCompatibilityService;
_packageDownloader = packageDownloader;
_logger = logger;
}

public async Task<IValidationResult> GetResultAsync(IValidationRequest request)
{
var validatorStatus = await _validatorStateService.GetStatusAsync(request);

return validatorStatus.ToValidationResult();
}

public async Task<IValidationResult> StartAsync(IValidationRequest request)
{
try
{
var validatorStatus = await _validatorStateService.GetStatusAsync(request);

if (validatorStatus.State != ValidationStatus.NotStarted)
{
_logger.LogWarning(
"Package Compatibility validation with validationId {ValidationId} ({PackageId} {PackageVersion}) has already started.",
request.ValidationId,
request.PackageId,
request.PackageVersion);

return validatorStatus.ToValidationResult();
}

try
{
await Validate(request, CancellationToken.None);
}
catch (Exception e)
{
_logger.LogWarning(0, e, "Validation failed in the validator for the following ValidationId {ValidationId}", request.ValidationId);
}

// Treat every validation as succeeded, as we don't want to block
validatorStatus.State = ValidationStatus.Succeeded;

await _validatorStateService.SaveStatusAsync(validatorStatus);

return validatorStatus.ToValidationResult();
}
catch (Exception e)
{
_logger.LogWarning(0, e, "Validation failed for the following ValidationId {ValidationId}", request.ValidationId);
}

return new ValidationResult(ValidationStatus.Succeeded);
}

private async Task Validate(IValidationRequest request, CancellationToken cancellationToken)
{
using (var packageStream = await _packageDownloader.DownloadAsync(new Uri(request.NupkgUrl), cancellationToken))
using (var package = new Packaging.PackageArchiveReader(packageStream))
{
var warnings = new List<PackLogMessage>();

foreach (var rule in Packaging.Rules.DefaultPackageRuleSet.Rules)
{
warnings.AddRange(rule.Validate(package));
}

await _packageCompatibilityService.SetPackageCompatibilityState(request.ValidationId, warnings);
}
}

public Task CleanUpAsync(IValidationRequest request)
{
return Task.CompletedTask;
}
}
}
8 changes: 8 additions & 0 deletions src/NuGet.Services.Validation.Orchestrator/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@
"SubscriptionName": ""
}
},
"PackageCompatibility": {
"ServiceBus": {
"ConnectionString": "",
"TopicPath": "",
"SubscriptionName": ""
}
},
"RunnerConfiguration": {
"ProcessRecycleInterval": "1:00:00:00",
"ShutdownWaitInterval": "00:01:00"
Expand Down Expand Up @@ -86,6 +93,7 @@
"AnnouncementsUrl": "https://github.com/NuGet/Announcements/issues",
"TwitterUrl": "https://twitter.com/nuget"
},
"PackageDownloadTimeout": "10:00",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if this is specific to PackageCompatibility validator, please move the config there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only here, because the validator is currently running in-proc.
Once the validator is in it's own process it will get moved out of here.

"KeyVault_VaultName": "",
"KeyVault_ClientId": "",
"KeyVault_CertificateThumbprint": "",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using NuGet.Common;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Validation.PackageCompatibility.Core.Storage
{
public interface IPackageCompatibilityService
{
Task SetPackageCompatibilityState(
Guid validationId,
IEnumerable<PackLogMessage> messages);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;

namespace Validation.PackageCompatibility.Core.Messages
{
public class PackageCompatibilityValidationMessage
{
public PackageCompatibilityValidationMessage(string packageId, string packageVersion, Uri nupkgUri, Guid validationId)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PackageCompatibilityValidationMessage [](start = 15, length = 37)

Is this just used for the PackageCompatibilityValidator.Validate method? A public class seems like over kill, maybe a private class in PackageCompatibilityValidator would be better. Also, are all of these parameters used? It seems like PackageId is not.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In phase 2, this validation will run in it's own proc, and it'll be used there for logging and better visibility, analogous to the way the package id/version are used in the signing validation.

{
if (validationId == Guid.Empty)
{
throw new ArgumentOutOfRangeException(nameof(validationId));
}
ValidationId = validationId;
PackageId = packageId ?? throw new ArgumentNullException(nameof(packageId));
PackageVersion = packageVersion ?? throw new ArgumentNullException(nameof(packageVersion));
NupkgUri = nupkgUri ?? throw new ArgumentNullException(nameof(nupkgUri));
}

public string PackageId { get; }
public string PackageVersion { get; }
public Uri NupkgUri { get; }
public Guid ValidationId { get; }
}
}