Skip to content

Commit

Permalink
fix: return ecosyste.ms license based on package version
Browse files Browse the repository at this point in the history
  • Loading branch information
paulrosca-snyk committed Dec 17, 2024
1 parent 5e753b3 commit e6b1d52
Show file tree
Hide file tree
Showing 7 changed files with 146 additions and 35 deletions.
10 changes: 10 additions & 0 deletions internal/utils/spdx.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package utils

import (
"fmt"
"strings"

"github.com/package-url/packageurl-go"
spdx_2_3 "github.com/spdx/tools-golang/spdx/v2/v2_3"

"github.com/snyk/parlay/ecosystems/packages"
)

func GetPurlFromSPDXPackage(pkg *spdx_2_3.Package) (*packageurl.PackageURL, error) {
Expand All @@ -28,3 +31,10 @@ func GetPurlFromSPDXPackage(pkg *spdx_2_3.Package) (*packageurl.PackageURL, erro

return &purl, nil
}

func GetSPDXLicenseExpressionFromEcosystemsLicense(data *packages.Version) string {
if data == nil || data.Licenses == nil || *data.Licenses == "" {
return ""
}
return fmt.Sprintf("(%s)", strings.Join(strings.Split(*data.Licenses, ","), " OR "))
}
39 changes: 39 additions & 0 deletions internal/utils/spdx_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package utils_test

import (
"testing"

"github.com/snyk/parlay/ecosystems/packages"
"github.com/snyk/parlay/internal/utils"

"github.com/stretchr/testify/assert"
)

func TestGetSPDXLicenseExpressionFromEcosystemsLicense(t *testing.T) {
assert := assert.New(t)
licenses := "GPLv2,MIT"
data := packages.Version{Licenses: &licenses}
expression := utils.GetSPDXLicenseExpressionFromEcosystemsLicense(&data)
assert.Equal("(GPLv2 OR MIT)", expression)
}

func TestGetSPDXLicenseExpressionFromEcosystemsLicense_NoData(t *testing.T) {
assert := assert.New(t)
expression := utils.GetSPDXLicenseExpressionFromEcosystemsLicense(nil)
assert.Equal("", expression)
}

func TestGetSPDXLicenseExpressionFromEcosystemsLicense_NoLicenses(t *testing.T) {
assert := assert.New(t)
data := packages.Version{}
expression := utils.GetSPDXLicenseExpressionFromEcosystemsLicense(&data)
assert.Equal("", expression)
}

func TestGetSPDXLicenseExpressionFromEcosystemsLicense_EmptyLicenses(t *testing.T) {
assert := assert.New(t)
licenses := ""
data := packages.Version{Licenses: &licenses}
expression := utils.GetSPDXLicenseExpressionFromEcosystemsLicense(&data)
assert.Equal("", expression)
}
50 changes: 36 additions & 14 deletions lib/ecosystems/enrich_cyclonedx.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ import (
"github.com/snyk/parlay/internal/utils"
)

type cdxEnricher = func(*cdx.Component, *packages.Package)
type cdxPackageEnricher = func(*cdx.Component, *packages.Package)
type cdxPackageVersionEnricher = func(*cdx.Component, *packages.Version)

var cdxEnrichers = []cdxEnricher{
var cdxPackageEnrichers = []cdxPackageEnricher{
enrichCDXDescription,
enrichCDXLicense,
enrichCDXHomepage,
enrichCDXRegistryURL,
enrichCDXRepositoryURL,
Expand All @@ -47,19 +47,21 @@ var cdxEnrichers = []cdxEnricher{
enrichCDXSupplier,
}

var cdxPackageVersionEnrichers = []cdxPackageVersionEnricher{
enrichCDXLicense,
}

func enrichCDXDescription(comp *cdx.Component, data *packages.Package) {
if data.Description != nil {
comp.Description = *data.Description
}
}

func enrichCDXLicense(comp *cdx.Component, data *packages.Package) {
if data.NormalizedLicenses != nil {
if len(data.NormalizedLicenses) > 0 {
expression := data.NormalizedLicenses[0]
licenses := cdx.LicenseChoice{Expression: expression}
comp.Licenses = &cdx.Licenses{licenses}
}
func enrichCDXLicense(comp *cdx.Component, data *packages.Version) {
expression := utils.GetSPDXLicenseExpressionFromEcosystemsLicense(data)
if expression != "" {
licenses := cdx.LicenseChoice{Expression: expression}
comp.Licenses = &cdx.Licenses{licenses}
}
}

Expand Down Expand Up @@ -207,24 +209,44 @@ func enrichCDX(bom *cdx.BOM, logger *zerolog.Logger) {
return
}

resp, err := GetPackageData(purl)
packageResp, err := GetPackageData(purl)
if err != nil {
l.Debug().
Err(err).
Msg("Skipping package: failed to get package data")
return
}

if resp.JSON200 == nil {
if packageResp.JSON200 == nil {
l.Debug().
Err(err).
Msg("Skipping package: no data on ecosyste.ms response")
return
}

for _, enrichFunc := range cdxEnrichers {
enrichFunc(comp, resp.JSON200)
for _, enrichFunc := range cdxPackageEnrichers {
enrichFunc(comp, packageResp.JSON200)
}

packageVersionResp, err := GetPackageVersionData(purl)
if err != nil {
l.Debug().
Err(err).
Msg("Skipping package version enrichment: failed to get package version data")
return
}

if packageVersionResp.JSON200 == nil {
l.Debug().
Err(err).
Msg("Skipping package version enrichment: no data on ecosyste.ms response")
return
}

for _, enrichFunc := range cdxPackageVersionEnrichers {
enrichFunc(comp, packageVersionResp.JSON200)
}

}(comps[i])
}

Expand Down
22 changes: 16 additions & 6 deletions lib/ecosystems/enrich_cyclonedx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,20 @@ func TestEnrichSBOM_CycloneDX(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

httpmock.RegisterResponder("GET", `=~^https://packages.ecosyste.ms/api/v1/registries/.*/packages/.*/versions`,
func(r *http.Request) (*http.Response, error) {
return httpmock.NewJsonResponse(200, map[string]interface{}{
// This is the license we expect to see for the specific package version
"licenses": "MIT",
})
},
)
httpmock.RegisterResponder("GET", `=~^https://packages.ecosyste.ms/api/v1/registries`,
func(req *http.Request) (*http.Response, error) {
return httpmock.NewJsonResponse(200, map[string]interface{}{
"description": "description",
"normalized_licenses": []string{
// This license should be ignored as it corresponds to the latest version of the package
"BSD-3-Clause",
},
})
Expand Down Expand Up @@ -73,7 +82,7 @@ func TestEnrichSBOM_CycloneDX(t *testing.T) {
component := components[0]
licenses := *component.Licenses

comp := cdx.LicenseChoice(cdx.LicenseChoice{Expression: "BSD-3-Clause"})
comp := cdx.LicenseChoice(cdx.LicenseChoice{Expression: "(MIT)"})

assert.Equal(t, "description", components[0].Description)
assert.Equal(t, comp, licenses[0])
Expand Down Expand Up @@ -119,7 +128,7 @@ func TestEnrichSBOM_CycloneDX_NestedComps(t *testing.T) {

httpmock.GetTotalCallCount()
calls := httpmock.GetCallCountInfo()
assert.Equal(t, 2, calls[`GET =~^https://packages.ecosyste.ms/api/v1/registries`])
assert.Equal(t, 4, calls[`GET =~^https://packages.ecosyste.ms/api/v1/registries`])
}

func TestEnrichSBOMWithoutLicense(t *testing.T) {
Expand Down Expand Up @@ -156,7 +165,7 @@ func TestEnrichSBOMWithoutLicense(t *testing.T) {

httpmock.GetTotalCallCount()
calls := httpmock.GetCallCountInfo()
assert.Equal(t, len(components), calls[`GET =~^https://packages.ecosyste.ms/api/v1/registries`])
assert.Equal(t, 2*len(components), calls[`GET =~^https://packages.ecosyste.ms/api/v1/registries`])
}

func TestEnrichDescription(t *testing.T) {
Expand All @@ -181,14 +190,15 @@ func TestEnrichLicense(t *testing.T) {
Name: "cyclonedx-go",
Version: "v0.3.0",
}
pack := &packages.Package{
NormalizedLicenses: []string{"BSD-3-Clause"},
lic := "BSD-3-Clause"
pack := &packages.Version{
Licenses: &lic,
}

enrichCDXLicense(component, pack)

licenses := *component.Licenses
comp := cdx.LicenseChoice(cdx.LicenseChoice{Expression: "BSD-3-Clause"})
comp := cdx.LicenseChoice(cdx.LicenseChoice{Expression: "(BSD-3-Clause)"})
assert.Equal(t, comp, licenses[0])
}

Expand Down
29 changes: 19 additions & 10 deletions lib/ecosystems/enrich_spdx.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@ package ecosystems

import (
"errors"
"fmt"
"strings"

"github.com/package-url/packageurl-go"
"github.com/rs/zerolog"
Expand All @@ -28,6 +26,7 @@ import (
"github.com/spdx/tools-golang/spdx/v2/v2_3"

"github.com/snyk/parlay/ecosystems/packages"
"github.com/snyk/parlay/internal/utils"
)

func enrichSPDX(bom *spdx.Document, logger *zerolog.Logger) {
Expand All @@ -41,20 +40,31 @@ func enrichSPDX(bom *spdx.Document, logger *zerolog.Logger) {
continue
}

resp, err := GetPackageData(*purl)
packageResp, err := GetPackageData(*purl)
if err != nil {
continue
}

pkgData := resp.JSON200
pkgData := packageResp.JSON200
if pkgData == nil {
continue
}

enrichSPDXDescription(pkg, pkgData)
enrichSPDXLicense(pkg, pkgData)
enrichSPDXHomepage(pkg, pkgData)
enrichSPDXSupplier(pkg, pkgData)

packageVersionResp, err := GetPackageVersionData(*purl)
if err != nil {
continue
}

pkgVersionData := packageVersionResp.JSON200
if pkgData == nil {
continue
}

enrichSPDXLicense(pkg, pkgVersionData)
}
}

Expand Down Expand Up @@ -86,11 +96,10 @@ func enrichSPDXSupplier(pkg *v2_3.Package, data *packages.Package) {
}
}

func enrichSPDXLicense(pkg *v2_3.Package, data *packages.Package) {
if len(data.NormalizedLicenses) == 1 {
pkg.PackageLicenseConcluded = data.NormalizedLicenses[0]
} else if len(data.NormalizedLicenses) > 1 {
pkg.PackageLicenseConcluded = fmt.Sprintf("(%s)", strings.Join(data.NormalizedLicenses, " OR "))
func enrichSPDXLicense(pkg *v2_3.Package, data *packages.Version) {
expression := utils.GetSPDXLicenseExpressionFromEcosystemsLicense(data)
if expression != "" {
pkg.PackageLicenseConcluded = *data.Licenses
}
}

Expand Down
11 changes: 10 additions & 1 deletion lib/ecosystems/enrich_spdx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,11 +33,20 @@ func TestEnrichSBOM_SPDX(t *testing.T) {
httpmock.Activate()
defer httpmock.DeactivateAndReset()

httpmock.RegisterResponder("GET", `=~^https://packages.ecosyste.ms/api/v1/registries/.*/packages/.*/versions`,
func(r *http.Request) (*http.Response, error) {
return httpmock.NewJsonResponse(200, map[string]interface{}{
// This is the license we expect to see for the specific package version
"licenses": "MIT",
})
},
)
httpmock.RegisterResponder("GET", `=~^https://packages.ecosyste.ms/api/v1/registries`,
func(req *http.Request) (*http.Response, error) {
return httpmock.NewJsonResponse(200, map[string]interface{}{
"description": "description",
"normalized_licenses": []string{
// This license should be ignored as it corresponds to the latest version of the package
"BSD-3-Clause",
},
"homepage": "https://github.com/spdx/tools-golang",
Expand Down Expand Up @@ -73,7 +82,7 @@ func TestEnrichSBOM_SPDX(t *testing.T) {
pkgs := bom.Packages

assert.Equal(t, "description", pkgs[0].PackageDescription)
assert.Equal(t, "BSD-3-Clause", pkgs[0].PackageLicenseConcluded)
assert.Equal(t, "MIT", pkgs[0].PackageLicenseConcluded)
assert.Equal(t, "https://github.com/spdx/tools-golang", pkgs[0].PackageHomePage)
assert.Equal(t, "Organization", pkgs[0].PackageSupplier.SupplierType)
assert.Equal(t, "Acme Corp", pkgs[0].PackageSupplier.Supplier)
Expand Down
20 changes: 16 additions & 4 deletions lib/ecosystems/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,6 @@ func GetPackageData(purl packageurl.PackageURL) (*packages.GetRegistryPackageRes
return nil, err
}

// Ecosyste.ms has a purl based API, but unfortunately slower
// so we break the purl down to registry and name values locally
// params := packages.LookupPackageParams{Purl: &p}
// resp, err := client.LookupPackageWithResponse(context.Background(), &params)
name := purlToEcosystemsName(purl)
registry := purlToEcosystemsRegistry(purl)
resp, err := client.GetRegistryPackageWithResponse(context.Background(), registry, name)
Expand All @@ -47,6 +43,22 @@ func GetPackageData(purl packageurl.PackageURL) (*packages.GetRegistryPackageRes
return resp, nil
}

func GetPackageVersionData(purl packageurl.PackageURL) (*packages.GetRegistryPackageVersionResponse, error) {
client, err := packages.NewClientWithResponses(server)
if err != nil {
return nil, err
}

name := purlToEcosystemsName(purl)
registry := purlToEcosystemsRegistry(purl)
resp, err := client.GetRegistryPackageVersionWithResponse(context.Background(), registry, name, purl.Version)

if err != nil {
return nil, err
}
return resp, nil
}

func purlToEcosystemsRegistry(purl packageurl.PackageURL) string {
return map[string]string{
packageurl.TypeApk: "alpine-edge",
Expand Down

0 comments on commit e6b1d52

Please sign in to comment.