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

Add deprecation info decorator #29

Merged
merged 5 commits into from
Sep 12, 2023
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
15 changes: 2 additions & 13 deletions src/Common/DependencyNodeIdentity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,11 @@ public class DependencyNodeIdentity : PackageIdentity

public bool Vulnerable { get; set; }

public bool Deprecated { get; set; }

public DependencyNodeIdentity(string id, NuGetVersion version, DependencyType type) : base(id, version)
{
Type = type;
}

public override int GetHashCode()
{
return HashCode.Combine(base.GetHashCode(), Type, Vulnerable);
}

public override bool Equals(object? obj)
{
return obj is DependencyNodeIdentity identity &&
base.Equals(obj) &&
Type == identity.Type &&
Vulnerable == identity.Vulnerable;
}
}
}
106 changes: 106 additions & 0 deletions src/Common/DeprecationInfoDecorator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
using Logging;
using Microsoft.Extensions.Logging;
using NuGet.Packaging.Core;
using NuGet.Protocol.Core.Types;

namespace Common
{
public class DeprecationInfoDecorator : IPackageDependencyNodeDecorator
{
private readonly List<SourceRepository> _sourceRepositories;
private readonly SourceCacheContext _sourceCacheContext;
private readonly Dictionary<PackageIdentity, bool> PackageDeprecationData = new();
private readonly List<PackageMetadataResource> _packageMetadataResources = new();
private bool _packageMetadataResourcesAcquired;

public DeprecationInfoDecorator(List<SourceRepository> sourceRepositories, SourceCacheContext sourceCacheContext)
{
_sourceRepositories = sourceRepositories ?? throw new ArgumentNullException(nameof(sourceRepositories));
_sourceCacheContext = sourceCacheContext ?? throw new ArgumentNullException(nameof(sourceCacheContext));
}

public async Task DecorateAsync(PackageDependencyNode dependencyNode, CancellationToken cancellationToken)
{
if (PackageDeprecationData.TryGetValue(dependencyNode.Identity, out var isPackageDeprecated))
{
dependencyNode.Identity.Deprecated = isPackageDeprecated;
return;
}

if (_packageMetadataResourcesAcquired)
{
await InitializeMetadataResource(cancellationToken);
_packageMetadataResourcesAcquired = true;
}

bool isDeprecated = await IsPackageDeprecatedAsync(dependencyNode.Identity, cancellationToken);
PackageDeprecationData.Add(dependencyNode.Identity, isDeprecated);
dependencyNode.Identity.Deprecated = isDeprecated;
}

private async Task<bool> IsPackageDeprecatedAsync(PackageIdentity packageIdentity, CancellationToken cancellationToken)
{
List<Task<IPackageSearchMetadata>>? results = new(_packageMetadataResources.Count);

bool isDeprecated = false;
foreach (PackageMetadataResource packageMetadataResource in _packageMetadataResources)
{
var packageMetadata = packageMetadataResource.GetMetadataAsync(packageIdentity, _sourceCacheContext, NuGet.Common.NullLogger.Instance, cancellationToken);
if (packageMetadata != null)
{
results.Add(packageMetadata);
}
}
await Task.WhenAll(results);
if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
foreach (var result in results)
{
if (result?.Result != null)
{
isDeprecated = isDeprecated || await result.Result.GetDeprecationMetadataAsync() != null;
}
}

return isDeprecated;
}

private async Task InitializeMetadataResource(CancellationToken cancellationToken)
{
List<Task<PackageMetadataResource?>>? results = new(_sourceRepositories.Count);

foreach (SourceRepository source in _sourceRepositories)
{
var metadataResource = GetMetadataResourceAsync(source, _sourceCacheContext, cancellationToken);
if (metadataResource != null)
{
results.Add(metadataResource);
}
}

await Task.WhenAll(results);

if (cancellationToken.IsCancellationRequested)
{
cancellationToken.ThrowIfCancellationRequested();
}
foreach (var result in results)
{
if (result?.Result != null)
{
_packageMetadataResources.Add(result.Result);
}
}
}

static async Task<PackageMetadataResource?> GetMetadataResourceAsync(SourceRepository source, SourceCacheContext cacheContext, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
PackageMetadataResource metadataResource =
await source.GetResourceAsync<PackageMetadataResource>(cancellationToken);
return metadataResource;
}
}
}
2 changes: 1 addition & 1 deletion src/Common/IPackageDependencyNodeDecorator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ public interface IPackageDependencyNodeDecorator
{
Task DecorateAsync(PackageDependencyNode dependencyNode, CancellationToken cancellationToken);
}
}
}
43 changes: 35 additions & 8 deletions src/DependencyVisualizerTool/DGMLDependencyVisualizerTool.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public static XDocument TransGraphToDGMLXDocument(PackageDependencyGraph graph,
id: firstNode.Identity.ToString(),
label: firstNode.Identity.ToString(),
type: firstNode.Identity.Type,
isVulnerable: firstNode.Identity.Vulnerable);
isVulnerable: firstNode.Identity.Vulnerable,
isDeprecated: firstNode.Identity.Deprecated);
nodes.Add(firstNode.Identity.ToString(), firstNodeDGML);

while (queue.Count > 0)
Expand All @@ -49,7 +50,8 @@ public static XDocument TransGraphToDGMLXDocument(PackageDependencyGraph graph,
id: child.Item1.Identity.ToString(),
label: child.Item1.Identity.ToString(),
type: child.Item1.Identity.Type,
child.Item1.Identity.Vulnerable);
child.Item1.Identity.Vulnerable,
child.Item1.Identity.Deprecated);
nodes.Add(child.Item1.Identity.ToString(), currentDGML);
}
}
Expand Down Expand Up @@ -88,8 +90,18 @@ from item in links
new XAttribute("StrokeThickness", "1")),
new XElement(XName.Get("Category", DGMLxmlns),
new XAttribute("Id", "VulnerablePackage"),
new XAttribute("Background", "Red"),
new XAttribute("StrokeThickness", "1"))))
new XAttribute("Background", "None"),
new XAttribute("StrokeThickness", "4"),
new XAttribute("Stroke", "Red")),
new XElement(XName.Get("Category", DGMLxmlns),
new XAttribute("Id", "DeprecatedPackage"),
new XAttribute("Background", "Yellow"),
new XAttribute("StrokeThickness", "1")),
new XElement(XName.Get("Category", DGMLxmlns),
new XAttribute("Id", "VulnerableAndDeprecatedPackage"),
new XAttribute("Background", "Yellow"),
new XAttribute("StrokeThickness", "4"),
new XAttribute("Stroke", "Red"))))
);
return document;
}
Expand All @@ -102,16 +114,31 @@ private class DGMLNode : IEquatable<DGMLNode>

public string Category { get; set; }

public DGMLNode(string id, string label, DependencyType type, bool isVulnerable)
public DGMLNode(string id, string label, DependencyType type, bool isVulnerable, bool isDeprecated)
{
this.Id = id;
this.Label = label;
this.Category = isVulnerable ? "VulnerablePackage" : type.ToString();
this.Category = GetCategory(type, isVulnerable, isDeprecated);
}

public bool Equals(DGMLNode other)
private static string GetCategory(DependencyType type, bool isVulnerable, bool isDeprecated)
{
return Id.Equals(other.Id, StringComparison.OrdinalIgnoreCase);
return isVulnerable && isDeprecated ?
"VulnerableAndDeprecatedPackage" :
isVulnerable ?
"VulnerablePackage" :
isDeprecated ?
"DeprecatedPackage" :
type.ToString();
}

public bool Equals(DGMLNode? other)
{
if (other != null)
{
return Id.Equals(other.Id, StringComparison.OrdinalIgnoreCase);
}
return false;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

<ItemGroup>
<None Remove="compiler\resources\diamonddependency.assets.json" />
<None Remove="compiler\resources\diamonddependency.withvulnerabilitiesanddeprecations.dgml" />
<None Remove="compiler\resources\diamonddependencywithtoplevel.assets.json" />
<None Remove="compiler\resources\missingpackageversion.assets.json" />
<None Remove="compiler\resources\multipleversions.assets.json" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
using System.Reflection.Emit;
using System.Text.RegularExpressions;
using Common;
using FluentAssertions;
using FluentAssertions.Equivalency;
using NuGet.Packaging.Core;
using NuGet.ProjectModel;
using NuGet.Versioning;
using SharedUtility;

namespace DependencyVisualizerTool.Test
Expand Down Expand Up @@ -143,22 +147,67 @@
actualDGML.Should().Be(expectedDGML, because: actualDGML);
}

private static string RemoveWhitespace(string s)
[Fact]
public async Task TransGraphToDGMLXDocument_WithVulnerableAndDeprecatedPackages_CreateDGMLCorrectly()
{
return Regex.Replace(s, @"\s+", string.Empty);
var packageA = new PackageIdentity("A", new NuGetVersion(1, 0, 0));
var packageB = new PackageIdentity("B", new NuGetVersion(1, 0, 0));
var packageC = new PackageIdentity("C", new NuGetVersion(1, 1, 0));
var decorator = new VulnerabilityAndDeprecationDecorator(
vulnerablePackages: new HashSet<PackageIdentity> { packageA, packageC },
deprecatedPackages: new HashSet<PackageIdentity> { packageB, packageC });

var graph = await GetOnlyDependencyGraphAsync("DependencyVisualizerTool.Test.compiler.resources.diamonddependency.assets.json", new() { decorator });

string actualDGML = RemoveWhitespace(DGMLDependencyVisualizerTool.TransGraphToDGMLXDocument(graph).ToString());

string expectedDGML = RemoveWhitespace(TestHelpers.GetResource("DependencyVisualizerTool.Test.compiler.resources.diamonddependency.withvulnerabilitiesanddeprecations.dgml", GetType()));

actualDGML.Should().Be(expectedDGML, because: actualDGML);
}

private async Task<PackageDependencyGraph> GetOnlyDependencyGraphAsync(string resourceName)
private async Task<PackageDependencyGraph> GetOnlyDependencyGraphAsync(string resourceName, List<IPackageDependencyNodeDecorator> decorators = null)

Check warning on line 169 in src/DependencyVisualizerTool/DependencyVisualizerTool.Test/PackageDependencyVisualizerToolTests.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 169 in src/DependencyVisualizerTool/DependencyVisualizerTool.Test/PackageDependencyVisualizerToolTests.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
{
var assetsFileText = TestHelpers.GetResource(resourceName, GetType());

var assetsFile = new LockFileFormat().Parse(assetsFileText, Path.GetTempPath());
var dependencyGraphSpec = new DependencyGraphSpec();
dependencyGraphSpec.AddProject(assetsFile.PackageSpec);
Dictionary<string, PackageDependencyGraph> graphs = await PackageDependencyGraph.GenerateAllDependencyGraphsFromAssetsFileAsync(assetsFile, dependencyGraphSpec, projectsOnly: false, new(), CancellationToken.None);
Dictionary<string, PackageDependencyGraph> graphs = await PackageDependencyGraph.GenerateAllDependencyGraphsFromAssetsFileAsync(assetsFile, dependencyGraphSpec, projectsOnly: false, decorators ?? new(), CancellationToken.None);
graphs.Should().HaveCount(1);
var graph = graphs.Single().Value;
return graph;
}

private static string RemoveWhitespace(string s)
{
return Regex.Replace(s, @"\s+", string.Empty);
}

private class VulnerabilityAndDeprecationDecorator : IPackageDependencyNodeDecorator
{
private readonly HashSet<PackageIdentity> _vulnerablePackages;
private readonly HashSet<PackageIdentity> _deprecatedPackages;

public VulnerabilityAndDeprecationDecorator(HashSet<PackageIdentity> vulnerablePackages, HashSet<PackageIdentity> deprecatedPackages)
{
_vulnerablePackages = vulnerablePackages ?? throw new ArgumentNullException(nameof(vulnerablePackages));
_deprecatedPackages = deprecatedPackages ?? throw new ArgumentNullException(nameof(deprecatedPackages));
}

public Task DecorateAsync(PackageDependencyNode dependencyNode, CancellationToken cancellationToken)
{
PackageIdentity identity = dependencyNode.Identity;
if (_vulnerablePackages.Contains(identity))
{
dependencyNode.Identity.Vulnerable = true;
}
if (_deprecatedPackages.Contains(identity))
{
dependencyNode.Identity.Deprecated = true;
}
return Task.CompletedTask;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
<Categories>
<Category Id="Project" Background="Lightblue" StrokeThickness="2" />
<Category Id="Package" Background="None" StrokeThickness="1" />
<CategoryId="VulnerablePackage"Background="Red"StrokeThickness="1"/>
<Category Id="VulnerablePackage" Background="None" StrokeThickness="4" Stroke="Red" />
<Category Id="DeprecatedPackage" Background="Yellow" StrokeThickness="1" />
<Category Id="VulnerableAndDeprecatedPackage" Background="Yellow" StrokeThickness="4" Stroke="Red" />
</Categories>

</DirectedGraph>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<DirectedGraph xmlns="http://schemas.microsoft.com/vs/2009/dgml">
<Nodes>
<Node Id="TestProject.1.0.0" Label="TestProject.1.0.0" Category="Project" />
<Node Id="A.1.0.0" Label="A.1.0.0" Category="VulnerablePackage" />
<Node Id="B.1.0.0" Label="B.1.0.0" Category="DeprecatedPackage" />
<Node Id="C.1.1.0" Label="C.1.1.0" Category="VulnerableAndDeprecatedPackage" />
</Nodes>
<Links>
<Link Source="TestProject.1.0.0" Target="A.1.0.0" Label="[1.0.0, )" />
<Link Source="TestProject.1.0.0" Target="B.1.0.0" Label="[1.0.0, )" />
<Link Source="A.1.0.0" Target="C.1.1.0" Label="[1.0.0, )" />
<Link Source="B.1.0.0" Target="C.1.1.0" Label="[1.1.0, )" />
</Links>
<Categories>
<Category Id="Project" Background="Lightblue" StrokeThickness="2" />
<Category Id="Package" Background="None" StrokeThickness="1" />
<Category Id="VulnerablePackage" Background="None" StrokeThickness="4" Stroke="Red" />
<Category Id="DeprecatedPackage" Background="Yellow" StrokeThickness="1" />
<Category Id="VulnerableAndDeprecatedPackage" Background="Yellow" StrokeThickness="4" Stroke="Red" />
</Categories>

</DirectedGraph>
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@
<Categories>
<Category Id="Project" Background="Lightblue" StrokeThickness="2" />
<Category Id="Package" Background="None" StrokeThickness="1" />
<CategoryId="VulnerablePackage"Background="Red"StrokeThickness="1"/>
<Category Id="VulnerablePackage" Background="None" StrokeThickness="4" Stroke="Red" />
<Category Id="DeprecatedPackage" Background="Yellow" StrokeThickness="1" />
<Category Id="VulnerableAndDeprecatedPackage" Background="Yellow" StrokeThickness="4" Stroke="Red" />
</Categories>

</DirectedGraph>
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@
<Categories>
<Category Id="Project" Background="Lightblue" StrokeThickness="2" />
<Category Id="Package" Background="None" StrokeThickness="1" />
<CategoryId="VulnerablePackage"Background="Red"StrokeThickness="1"/>
<Category Id="VulnerablePackage" Background="None" StrokeThickness="4" Stroke="Red" />
<Category Id="DeprecatedPackage" Background="Yellow" StrokeThickness="1" />
<Category Id="VulnerableAndDeprecatedPackage" Background="Yellow" StrokeThickness="4" Stroke="Red" />
</Categories>

</DirectedGraph>
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
<Categories>
<Category Id="Project" Background="Lightblue" StrokeThickness="2" />
<Category Id="Package" Background="None" StrokeThickness="1" />
<CategoryId="VulnerablePackage"Background="Red"StrokeThickness="1"/>
<Category Id="VulnerablePackage" Background="None" StrokeThickness="4" Stroke="Red" />
<Category Id="DeprecatedPackage" Background="Yellow" StrokeThickness="1" />
<Category Id="VulnerableAndDeprecatedPackage" Background="Yellow" StrokeThickness="4" Stroke="Red" />
</Categories>

</DirectedGraph>
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
<Categories>
<Category Id="Project" Background="Lightblue" StrokeThickness="2" />
<Category Id="Package" Background="None" StrokeThickness="1" />
<CategoryId="VulnerablePackage"Background="Red"StrokeThickness="1"/>
<Category Id="VulnerablePackage" Background="None" StrokeThickness="4" Stroke="Red" />
<Category Id="DeprecatedPackage" Background="Yellow" StrokeThickness="1" />
<Category Id="VulnerableAndDeprecatedPackage" Background="Yellow" StrokeThickness="4" Stroke="Red" />
</Categories>

</DirectedGraph>
Loading