-
Notifications
You must be signed in to change notification settings - Fork 90
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
feat: add a new detector MvnPomCliComponentDetector
#544
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
namespace Microsoft.ComponentDetection.Detectors.Maven; | ||
|
||
using Microsoft.ComponentDetection.Contracts.Internal; | ||
|
||
public interface IMavenFileParserService | ||
{ | ||
void ParseDependenciesFile(ProcessRequest processRequest); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
namespace Microsoft.ComponentDetection.Detectors.Maven; | ||
|
||
using System; | ||
using System.Xml; | ||
using Microsoft.ComponentDetection.Contracts; | ||
using Microsoft.ComponentDetection.Contracts.BcdeModels; | ||
using Microsoft.ComponentDetection.Contracts.Internal; | ||
using Microsoft.ComponentDetection.Contracts.TypedComponent; | ||
using Microsoft.Extensions.Logging; | ||
|
||
public class MavenFileParserService : IMavenFileParserService | ||
{ | ||
private readonly ILogger<MavenFileParserService> logger; | ||
|
||
public MavenFileParserService( | ||
ILogger<MavenFileParserService> logger) => this.logger = logger; | ||
|
||
public void ParseDependenciesFile(ProcessRequest processRequest) | ||
{ | ||
var singleFileComponentRecorder = processRequest.SingleFileComponentRecorder; | ||
var stream = processRequest.ComponentStream; | ||
|
||
try | ||
{ | ||
var doc = new XmlDocument(); | ||
doc.Load(stream.Location); | ||
|
||
var nsmgr = new XmlNamespaceManager(doc.NameTable); | ||
nsmgr.AddNamespace("ns", "http://maven.apache.org/POM/4.0.0"); | ||
|
||
var dependencies = doc.SelectSingleNode("//ns:project/ns:dependencies", nsmgr); | ||
if (dependencies == null) | ||
{ | ||
return; | ||
} | ||
|
||
foreach (XmlNode node in dependencies.ChildNodes) | ||
{ | ||
this.RegisterComponent(node, nsmgr, singleFileComponentRecorder); | ||
} | ||
} | ||
catch (Exception e) | ||
{ | ||
// If something went wrong, just ignore the component | ||
this.logger.LogError(e, "Error parsing pom maven component from {PomLocation}", stream.Location); | ||
singleFileComponentRecorder.RegisterPackageParseFailure(stream.Location); | ||
} | ||
} | ||
|
||
private void RegisterComponent(XmlNode node, XmlNamespaceManager nsmgr, ISingleFileComponentRecorder singleFileComponentRecorder) | ||
{ | ||
var groupIdNode = node.SelectSingleNode("ns:groupId", nsmgr); | ||
var artifactIdNode = node.SelectSingleNode("ns:artifactId", nsmgr); | ||
var versionNode = node.SelectSingleNode("ns:version", nsmgr); | ||
|
||
if (groupIdNode == null || artifactIdNode == null || versionNode == null) | ||
{ | ||
this.logger.LogInformation("{XmlNode} doesn't have groupId, artifactId or version information", node.InnerText); | ||
return; | ||
} | ||
|
||
var groupId = groupIdNode.InnerText; | ||
var artifactId = artifactIdNode.InnerText; | ||
var version = versionNode.InnerText; | ||
var dependencyScope = DependencyScope.MavenCompile; | ||
|
||
var component = new MavenComponent(groupId, artifactId, version); | ||
|
||
singleFileComponentRecorder.RegisterUsage( | ||
new DetectedComponent(component), | ||
isDevelopmentDependency: null, | ||
dependencyScope: dependencyScope); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
namespace Microsoft.ComponentDetection.Detectors.Maven; | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading.Tasks; | ||
using Microsoft.ComponentDetection.Contracts; | ||
using Microsoft.ComponentDetection.Contracts.Internal; | ||
using Microsoft.ComponentDetection.Contracts.TypedComponent; | ||
using Microsoft.Extensions.Logging; | ||
|
||
public class MavenPomComponentDetector : FileComponentDetector, IDefaultOffComponentDetector | ||
{ | ||
private readonly IMavenFileParserService mavenFileParserService; | ||
|
||
public MavenPomComponentDetector( | ||
IComponentStreamEnumerableFactory componentStreamEnumerableFactory, | ||
IObservableDirectoryWalkerFactory walkerFactory, | ||
IMavenFileParserService mavenFileParserService, | ||
ILogger<MavenPomComponentDetector> logger) | ||
{ | ||
this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory; | ||
this.Scanner = walkerFactory; | ||
this.mavenFileParserService = mavenFileParserService; | ||
this.Logger = logger; | ||
} | ||
|
||
public override string Id => "MvnPom"; | ||
|
||
public override IList<string> SearchPatterns => new List<string>() { "*.pom" }; | ||
|
||
public override IEnumerable<string> Categories => new[] { Enum.GetName(typeof(DetectorClass), DetectorClass.Maven) }; | ||
|
||
public override IEnumerable<ComponentType> SupportedComponentTypes => new[] { ComponentType.Maven }; | ||
|
||
public override int Version => 2; | ||
|
||
protected override async Task OnFileFoundAsync(ProcessRequest processRequest, IDictionary<string, string> detectorArgs) | ||
{ | ||
await this.ProcessFileAsync(processRequest); | ||
} | ||
|
||
private async Task ProcessFileAsync(ProcessRequest processRequest) | ||
{ | ||
this.mavenFileParserService.ParseDependenciesFile(processRequest); | ||
|
||
await Task.CompletedTask; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
namespace Microsoft.ComponentDetection.Detectors.Maven; | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Threading.Tasks; | ||
using System.Xml; | ||
using Microsoft.ComponentDetection.Contracts; | ||
using Microsoft.ComponentDetection.Contracts.BcdeModels; | ||
using Microsoft.ComponentDetection.Contracts.Internal; | ||
using Microsoft.ComponentDetection.Contracts.TypedComponent; | ||
using Microsoft.Extensions.Logging; | ||
|
||
public class MvnPomCliComponentDetector : FileComponentDetector | ||
{ | ||
public MvnPomCliComponentDetector( | ||
IComponentStreamEnumerableFactory componentStreamEnumerableFactory, | ||
IObservableDirectoryWalkerFactory walkerFactory, | ||
ILogger<MvnCliComponentDetector> logger) | ||
{ | ||
this.ComponentStreamEnumerableFactory = componentStreamEnumerableFactory; | ||
this.Scanner = walkerFactory; | ||
this.Logger = logger; | ||
} | ||
|
||
public override string Id => "MvnPomCli"; | ||
|
||
public override IList<string> SearchPatterns => new List<string>() { "*.pom" }; | ||
|
||
public override IEnumerable<string> Categories => new[] { Enum.GetName(typeof(DetectorClass), DetectorClass.Maven) }; | ||
|
||
public override IEnumerable<ComponentType> SupportedComponentTypes => new[] { ComponentType.Maven }; | ||
|
||
public override int Version => 2; | ||
|
||
protected override async Task OnFileFoundAsync(ProcessRequest processRequest, IDictionary<string, string> detectorArgs) | ||
{ | ||
await this.ProcessFileAsync(processRequest); | ||
} | ||
|
||
private async Task ProcessFileAsync(ProcessRequest processRequest) | ||
{ | ||
var singleFileComponentRecorder = processRequest.SingleFileComponentRecorder; | ||
var stream = processRequest.ComponentStream; | ||
|
||
try | ||
{ | ||
byte[] pomBytes = null; | ||
|
||
if ("*.pom".Equals(stream.Pattern, StringComparison.OrdinalIgnoreCase)) | ||
AbhinavAbhinav11 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
using (var contentStream = File.Open(stream.Location, FileMode.Open)) | ||
{ | ||
pomBytes = new byte[contentStream.Length]; | ||
await contentStream.ReadAsync(pomBytes.AsMemory(0, (int)contentStream.Length)); | ||
|
||
using var pomStream = new MemoryStream(pomBytes, false); | ||
var doc = new XmlDocument(); | ||
doc.Load(pomStream); | ||
|
||
var nsmgr = new XmlNamespaceManager(doc.NameTable); | ||
nsmgr.AddNamespace("ns", "http://maven.apache.org/POM/4.0.0"); | ||
|
||
var dependencies = doc.SelectSingleNode("//ns:project/ns:dependencies", nsmgr); | ||
if (dependencies == null) | ||
{ | ||
return; | ||
} | ||
|
||
foreach (XmlNode node in dependencies.ChildNodes) | ||
melotic marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
this.RegisterComponent(node, nsmgr, singleFileComponentRecorder); | ||
} | ||
} | ||
} | ||
else | ||
{ | ||
return; | ||
AbhinavAbhinav11 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
catch (Exception e) | ||
{ | ||
// If something went wrong, just ignore the component | ||
this.Logger.LogError(e, "Error parsing pom maven component from {PomLocation}", stream.Location); | ||
singleFileComponentRecorder.RegisterPackageParseFailure(stream.Location); | ||
} | ||
} | ||
|
||
private void RegisterComponent(XmlNode node, XmlNamespaceManager nsmgr, ISingleFileComponentRecorder singleFileComponentRecorder) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This method looks the same as |
||
{ | ||
var groupIdNode = node.SelectSingleNode("ns:groupId", nsmgr); | ||
var artifactIdNode = node.SelectSingleNode("ns:artifactId", nsmgr); | ||
var versionNode = node.SelectSingleNode("ns:version", nsmgr); | ||
|
||
if (groupIdNode == null || artifactIdNode == null || versionNode == null) | ||
{ | ||
return; | ||
AbhinavAbhinav11 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
var groupId = groupIdNode.InnerText; | ||
var artifactId = artifactIdNode.InnerText; | ||
var version = versionNode.InnerText; | ||
var dependencyScope = DependencyScope.MavenCompile; | ||
|
||
var component = new MavenComponent(groupId, artifactId, version); | ||
|
||
singleFileComponentRecorder.RegisterUsage( | ||
new DetectedComponent(component), | ||
isDevelopmentDependency: null, | ||
dependencyScope: dependencyScope); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
namespace Microsoft.ComponentDetection.Orchestrator.Extensions; | ||
namespace Microsoft.ComponentDetection.Orchestrator.Extensions; | ||
|
||
using Microsoft.ComponentDetection.Common; | ||
using Microsoft.ComponentDetection.Common.Telemetry; | ||
|
@@ -97,6 +97,8 @@ public static IServiceCollection AddComponentDetection(this IServiceCollection s | |
services.AddSingleton<IMavenCommandService, MavenCommandService>(); | ||
services.AddSingleton<IMavenStyleDependencyGraphParserService, MavenStyleDependencyGraphParserService>(); | ||
services.AddSingleton<IComponentDetector, MvnCliComponentDetector>(); | ||
services.AddSingleton<IMavenFileParserService, MavenFileParserService>(); | ||
services.AddSingleton<IComponentDetector, MavenPomComponentDetector>(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing |
||
|
||
// npm | ||
services.AddSingleton<IComponentDetector, NpmComponentDetector>(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
namespace Microsoft.ComponentDetection.Detectors.Tests; | ||
|
||
using System; | ||
using System.Linq; | ||
using System.Threading.Tasks; | ||
using Microsoft.ComponentDetection.Contracts; | ||
using Microsoft.ComponentDetection.Contracts.Internal; | ||
using Microsoft.ComponentDetection.Contracts.TypedComponent; | ||
using Microsoft.ComponentDetection.Detectors.Maven; | ||
using Microsoft.ComponentDetection.Detectors.Tests.Utilities; | ||
using Microsoft.ComponentDetection.TestsUtilities; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using Moq; | ||
|
||
[TestClass] | ||
[TestCategory("Governance/All")] | ||
[TestCategory("Governance/ComponentDetection")] | ||
public class MvnPomComponentDetectorTest : BaseDetectorTest<MavenPomComponentDetector> | ||
{ | ||
private readonly Mock<IMavenFileParserService> mavenFileParserServiceMock; | ||
|
||
public MvnPomComponentDetectorTest() | ||
{ | ||
this.mavenFileParserServiceMock = new Mock<IMavenFileParserService>(); | ||
this.DetectorTestUtility.AddServiceMock(this.mavenFileParserServiceMock); | ||
} | ||
|
||
[TestMethod] | ||
public async Task MavenRootsAsync() | ||
{ | ||
const string componentString = "org.apache.maven:maven-compat:jar:3.6.1-SNAPSHOT"; | ||
const string childComponentString = "org.apache.maven:maven-compat-child:jar:3.6.1-SNAPSHOT"; | ||
var content = $@"com.bcde.test:top-level:jar:1.0.0{Environment.NewLine}\- {componentString}{Environment.NewLine} \- {childComponentString}"; | ||
this.DetectorTestUtility.WithFile("pom.xml", content) | ||
.WithFile("pom.xml", content, searchPatterns: new[] { "pom.xml" }); | ||
|
||
this.mavenFileParserServiceMock.Setup(x => x.ParseDependenciesFile(It.IsAny<ProcessRequest>())) | ||
.Callback((ProcessRequest pr) => | ||
{ | ||
pr.SingleFileComponentRecorder.RegisterUsage( | ||
new DetectedComponent( | ||
new MavenComponent("com.bcde.test", "top-levelt", "1.0.0")), | ||
isExplicitReferencedDependency: true); | ||
pr.SingleFileComponentRecorder.RegisterUsage( | ||
new DetectedComponent( | ||
new MavenComponent("org.apache.maven", "maven-compat", "3.6.1-SNAPSHOT")), | ||
isExplicitReferencedDependency: true); | ||
pr.SingleFileComponentRecorder.RegisterUsage( | ||
new DetectedComponent( | ||
new MavenComponent("org.apache.maven", "maven-compat-child", "3.6.1-SNAPSHOT")), | ||
isExplicitReferencedDependency: false, | ||
parentComponentId: "org.apache.maven maven-compat 3.6.1-SNAPSHOT - Maven"); | ||
}); | ||
|
||
var (detectorResult, componentRecorder) = await this.DetectorTestUtility.ExecuteDetectorAsync(); | ||
|
||
var detectedComponents = componentRecorder.GetDetectedComponents(); | ||
Assert.AreEqual(detectedComponents.Count(), 3); | ||
Assert.AreEqual(detectorResult.ResultCode, ProcessingResultCode.Success); | ||
|
||
var splitComponent = componentString.Split(':'); | ||
var splitChildComponent = childComponentString.Split(':'); | ||
|
||
var mavenComponent = detectedComponents.FirstOrDefault(x => (x.Component as MavenComponent).ArtifactId == splitChildComponent[1]); | ||
Assert.IsNotNull(mavenComponent); | ||
|
||
componentRecorder.AssertAllExplicitlyReferencedComponents<MavenComponent>( | ||
mavenComponent.Component.Id, | ||
parentComponent => parentComponent.ArtifactId == splitComponent[1]); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you also implement
IDefaultOffComponentDetector
? We don't want a new detector to be enabled by default (only explicitly opted-in) before we've had the chance to fully verify it correctly works.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MavenPomComponentDetector
is markedIDefaultOffComponenDetector
, butMvnPomCliComponentDetector
is not