Skip to content

Commit

Permalink
Add Author/License to LinuxComponent (#934)
Browse files Browse the repository at this point in the history
* Add Author/License to LinuxComponent

* Add unit test. Remove comment

* Increase coverage.

* Feedback

* Fix. Only Author and License are nullable.

---------

Co-authored-by: Sebastian Gomez <segomez@microsoft.com>
  • Loading branch information
sebasgomez238 and sebasgomez238 authored Dec 19, 2023
1 parent ce76f5d commit 293fc60
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 8 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
namespace Microsoft.ComponentDetection.Contracts.TypedComponent;
namespace Microsoft.ComponentDetection.Contracts.TypedComponent;

using PackageUrl;

Expand All @@ -9,12 +9,14 @@ private LinuxComponent()
/* Reserved for deserialization */
}

public LinuxComponent(string distribution, string release, string name, string version)
public LinuxComponent(string distribution, string release, string name, string version, string license = null, string author = null)
{
this.Distribution = this.ValidateRequiredInput(distribution, nameof(this.Distribution), nameof(ComponentType.Linux));
this.Release = this.ValidateRequiredInput(release, nameof(this.Release), nameof(ComponentType.Linux));
this.Name = this.ValidateRequiredInput(name, nameof(this.Name), nameof(ComponentType.Linux));
this.Version = this.ValidateRequiredInput(version, nameof(this.Version), nameof(ComponentType.Linux));
this.License = license;
this.Author = author;
}

public string Distribution { get; set; }
Expand All @@ -25,6 +27,12 @@ public LinuxComponent(string distribution, string release, string name, string v

public string Version { get; set; }

#nullable enable
public string? License { get; set; }

public string? Author { get; set; }
#nullable disable

public override ComponentType Type => ComponentType.Linux;

public override string Id => $"{this.Distribution} {this.Release} {this.Name} {this.Version} - {this.Type}";
Expand Down
36 changes: 35 additions & 1 deletion src/Microsoft.ComponentDetection.Detectors/linux/LinuxScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ public async Task<IEnumerable<LayerMappedLinuxComponents>> ScanLinuxAsync(string
.DistinctBy(artifact => (artifact.Name, artifact.Version))
.Where(artifact => AllowedArtifactTypes.Contains(artifact.Type))
.Select(artifact =>
(Component: new LinuxComponent(syftOutput.Distro.Id, syftOutput.Distro.VersionId, artifact.Name, artifact.Version), layerIds: artifact.Locations.Select(location => location.LayerId).Distinct()));
(Component: new LinuxComponent(syftOutput.Distro.Id, syftOutput.Distro.VersionId, artifact.Name, artifact.Version, this.GetLicenseFromArtifactElement(artifact), this.GetSupplierFromArtifactElement(artifact)), layerIds: artifact.Locations.Select(location => location.LayerId).Distinct()));

foreach (var (component, layers) in linuxComponentsWithLayers)
{
Expand Down Expand Up @@ -137,6 +137,40 @@ public async Task<IEnumerable<LayerMappedLinuxComponents>> ScanLinuxAsync(string
}
}

private string GetSupplierFromArtifactElement(ArtifactElement artifact)
{
var supplier = artifact.Metadata?.Author;
if (!string.IsNullOrEmpty(supplier))
{
return supplier;
}

supplier = artifact.Metadata?.Maintainer;
if (!string.IsNullOrEmpty(supplier))
{
return supplier;
}

return null;
}

private string GetLicenseFromArtifactElement(ArtifactElement artifact)
{
var license = artifact.Metadata?.License?.String;
if (license != null)
{
return license.ToString();
}

var licenses = artifact.Licenses;
if (licenses != null && licenses.Any())
{
return string.Join(", ", licenses.Select(l => l.ToString()));
}

return null;
}

internal sealed class LinuxComponentRecord
{
public string Name { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Microsoft.ComponentDetection.Detectors.Tests;
[TestCategory("Governance/ComponentDetection")]
public class LinuxScannerTests
{
private const string SyftOutput = @"{
private const string SyftOutputLicensesFieldAndAuthor = @"{
""distro"": {
""id"":""test-distribution"",
""versionId"":""1.0.0""
Expand All @@ -32,7 +32,59 @@ public class LinuxScannerTests
""path"": ""/var/lib/dpkg/status"",
""layerID"": ""sha256:f95fc50d21d981f1efe1f04109c2c3287c271794f5d9e4fdf9888851a174a971""
}
]
],
""metadata"": {
""author"": ""John Doe""
},
""licenses"": [
""MIT"",
""GPLv2"",
""GPLv3""
]
}
]
}";

private const string SyftOutputLicenseFieldAndMaintainer = @"{
""distro"": {
""id"":""test-distribution"",
""versionId"":""1.0.0""
},
""artifacts"": [
{
""name"":""test"",
""version"":""1.0.0"",
""type"":""deb"",
""locations"": [
{
""path"": ""/var/lib/dpkg/status"",
""layerID"": ""sha256:f95fc50d21d981f1efe1f04109c2c3287c271794f5d9e4fdf9888851a174a971""
}
],
""metadata"": {
""maintainer"": ""John Doe"",
""license"": ""MIT, GPLv2, GPLv3""
}
}
]
}";

private const string SyftOutputNoAuthorOrLicense = @"{
""distro"": {
""id"":""test-distribution"",
""versionId"":""1.0.0""
},
""artifacts"": [
{
""name"":""test"",
""version"":""1.0.0"",
""type"":""deb"",
""locations"": [
{
""path"": ""/var/lib/dpkg/status"",
""layerID"": ""sha256:f95fc50d21d981f1efe1f04109c2c3287c271794f5d9e4fdf9888851a174a971""
}
],
}
]
}";
Expand All @@ -47,17 +99,39 @@ public LinuxScannerTests()
this.mockDockerService.Setup(service => service.CanPingDockerAsync(It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
this.mockDockerService.Setup(service => service.TryPullImageAsync(It.IsAny<string>(), It.IsAny<CancellationToken>()));
this.mockDockerService.Setup(service => service.CreateAndRunContainerAsync(It.IsAny<string>(), It.IsAny<List<string>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((SyftOutput, string.Empty));

this.mockLogger = new Mock<ILogger<LinuxScanner>>();

this.linuxScanner = new LinuxScanner(this.mockDockerService.Object, this.mockLogger.Object);
}

[TestMethod]
public async Task TestLinuxScannerAsync()
[DataRow(SyftOutputLicensesFieldAndAuthor)]
[DataRow(SyftOutputLicenseFieldAndMaintainer)]
public async Task TestLinuxScannerAsync(string syftOutput)
{
this.mockDockerService.Setup(service => service.CreateAndRunContainerAsync(It.IsAny<string>(), It.IsAny<List<string>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((syftOutput, string.Empty));

var result = (await this.linuxScanner.ScanLinuxAsync("fake_hash", new[] { new DockerLayer { LayerIndex = 0, DiffId = "sha256:f95fc50d21d981f1efe1f04109c2c3287c271794f5d9e4fdf9888851a174a971" } }, 0)).First().LinuxComponents;

result.Should().ContainSingle();
var package = result.First();
package.Name.Should().Be("test");
package.Version.Should().Be("1.0.0");
package.Release.Should().Be("1.0.0");
package.Distribution.Should().Be("test-distribution");
package.Author.Should().Be("John Doe");
package.License.Should().Be("MIT, GPLv2, GPLv3");
}

[TestMethod]
[DataRow(SyftOutputNoAuthorOrLicense)]
public async Task TestLinuxScanner_ReturnsNullAuthorAndLicense_Async(string syftOutput)
{
this.mockDockerService.Setup(service => service.CreateAndRunContainerAsync(It.IsAny<string>(), It.IsAny<List<string>>(), It.IsAny<CancellationToken>()))
.ReturnsAsync((syftOutput, string.Empty));

var result = (await this.linuxScanner.ScanLinuxAsync("fake_hash", new[] { new DockerLayer { LayerIndex = 0, DiffId = "sha256:f95fc50d21d981f1efe1f04109c2c3287c271794f5d9e4fdf9888851a174a971" } }, 0)).First().LinuxComponents;

result.Should().ContainSingle();
Expand All @@ -66,5 +140,7 @@ public async Task TestLinuxScannerAsync()
package.Version.Should().Be("1.0.0");
package.Release.Should().Be("1.0.0");
package.Distribution.Should().Be("test-distribution");
package.Author.Should().Be(null);
package.License.Should().Be(null);
}
}

0 comments on commit 293fc60

Please sign in to comment.