diff --git a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/LinuxComponent.cs b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/LinuxComponent.cs index f95501ec2..db286d69d 100644 --- a/src/Microsoft.ComponentDetection.Contracts/TypedComponent/LinuxComponent.cs +++ b/src/Microsoft.ComponentDetection.Contracts/TypedComponent/LinuxComponent.cs @@ -2,6 +2,7 @@ namespace Microsoft.ComponentDetection.Contracts.TypedComponent; using System; +using System.Collections.Generic; using System.Text.Json.Serialization; using PackageUrl; @@ -60,10 +61,20 @@ public override PackageUrl PackageUrl { packageType = "rpm"; } + else if (this.IsAlpine()) + { + packageType = "apk"; + } if (packageType != null) { - return new PackageUrl(packageType, this.Distribution, this.Name, this.Version, null, null); + var distroId = this.GetDistroId(); + var qualifiers = new SortedDictionary + { + { "distro", $"{distroId}-{this.Release}" }, + }; + + return new PackageUrl(packageType, distroId, this.Name, this.Version, qualifiers, null); } return null; @@ -96,4 +107,19 @@ private bool IsRHEL() { return this.Distribution.Equals("RED HAT ENTERPRISE LINUX", StringComparison.OrdinalIgnoreCase); } + + private bool IsAlpine() + { + return this.Distribution.Equals("ALPINE", StringComparison.OrdinalIgnoreCase); + } + + private string GetDistroId() + { + if (this.IsRHEL()) + { + return "redhat"; + } + + return this.Distribution.ToLowerInvariant(); + } } diff --git a/test/Microsoft.ComponentDetection.Contracts.Tests/PurlGenerationTests.cs b/test/Microsoft.ComponentDetection.Contracts.Tests/PurlGenerationTests.cs index 18b9f99c4..b2313bd21 100644 --- a/test/Microsoft.ComponentDetection.Contracts.Tests/PurlGenerationTests.cs +++ b/test/Microsoft.ComponentDetection.Contracts.Tests/PurlGenerationTests.cs @@ -47,6 +47,9 @@ public void DebianAndUbuntuAreDebType() ubuntuComponent.PackageUrl.Type.Should().Be("deb"); debianComponent.PackageUrl.Type.Should().Be("deb"); + + ubuntuComponent.PackageUrl.Qualifiers["distro"].Should().Be("ubuntu-18.04"); + debianComponent.PackageUrl.Qualifiers["distro"].Should().Be("debian-buster"); } [TestMethod] @@ -61,17 +64,29 @@ public void CentOsFedoraAndRHELAreRpmType() centosComponent.PackageUrl.Type.Should().Be("rpm"); fedoraComponent.PackageUrl.Type.Should().Be("rpm"); rhelComponent.PackageUrl.Type.Should().Be("rpm"); + + centosComponent.PackageUrl.Qualifiers["distro"].Should().Be("centos-18.04"); + fedoraComponent.PackageUrl.Qualifiers["distro"].Should().Be("fedora-18.04"); + rhelComponent.PackageUrl.Qualifiers["distro"].Should().Be("redhat-18.04"); } [TestMethod] - public void AlpineAndUnknownDoNotHavePurls() + public void AlpineIsApkType() { - // Alpine is not yet defined - // https://github.com/package-url/purl-spec/blame/180c46d266c45aa2bd81a2038af3f78e87bb4a25/README.rst#L711 + // Alpine uses "apk" purl type + // https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#apk var alpineComponent = new LinuxComponent("Alpine", "3.13", "bash", "1"); + + alpineComponent.PackageUrl.Type.Should().Be("apk"); + alpineComponent.PackageUrl.Namespace.Should().Be("alpine"); + alpineComponent.PackageUrl.Qualifiers["distro"].Should().Be("alpine-3.13"); + } + + [TestMethod] + public void UnknownDistroDoesNotHavePurl() + { var unknownLinuxComponent = new LinuxComponent("Linux", "0", "bash", "1'"); - alpineComponent.PackageUrl.Should().BeNull(); unknownLinuxComponent.PackageUrl.Should().BeNull(); } @@ -88,6 +103,16 @@ public void DistroNamesAreLowerCased() fedoraComponent.PackageUrl.Namespace.Should().Be("fedora"); } + [TestMethod] + public void RhelNamespaceIsRedhat() + { + // RHEL should use "redhat" as the namespace and distro id, matching Syft conventions + var rhelComponent = new LinuxComponent("Red Hat Enterprise Linux", "9.0", "bash", "1"); + + rhelComponent.PackageUrl.Namespace.Should().Be("redhat"); + rhelComponent.PackageUrl.Qualifiers["distro"].Should().Be("redhat-9.0"); + } + [TestMethod] public void CocoaPodNameShouldSupportPurl() {