Skip to content
Open
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
22 changes: 10 additions & 12 deletions go/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,10 @@ require (
google.golang.org/api v0.273.0
google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7
google.golang.org/protobuf v1.36.11
gopkg.in/dnaeon/go-vcr.v4 v4.0.6
k8s.io/apimachinery v0.35.3
)

require (
github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
)

require (
cel.dev/expr v0.25.1 // indirect
cloud.google.com/go v0.123.0 // indirect
Expand All @@ -55,8 +47,13 @@ require (
github.com/ProtonMail/go-crypto v1.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/charmbracelet/colorprofile v0.4.2 // indirect
github.com/charmbracelet/ultraviolet v0.0.0-20251205161215-1948445e3318 // indirect
github.com/charmbracelet/x/ansi v0.11.6 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/charmbracelet/x/termios v0.1.1 // indirect
github.com/charmbracelet/x/windows v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.11.0 // indirect
github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
github.com/cloudflare/circl v1.6.3 // indirect
github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
Expand All @@ -79,14 +76,15 @@ require (
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/sergi/go-diff v1.4.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect; indirecta
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
go.opencensus.io v0.24.0 // indirect
Expand All @@ -95,8 +93,8 @@ require (
go.opentelemetry.io/otel/metric v1.42.0 // indirect
go.opentelemetry.io/otel/sdk/metric v1.42.0 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.3 // indirect
golang.org/x/crypto v0.49.0 // indirect
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.42.0 // indirect
Expand Down
11 changes: 7 additions & 4 deletions go/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -218,9 +218,8 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c=
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
Expand Down Expand Up @@ -257,13 +256,15 @@ go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
go.yaml.in/yaml/v4 v4.0.0-rc.3 h1:3h1fjsh1CTAPjW7q/EMe+C8shx5d8ctzZTrLcs/j8Go=
go.yaml.in/yaml/v4 v4.0.0-rc.3/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0=
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 h1:R84qjqJb5nVJMxqWYb3np9L5ZsaDtB+a39EqjV0JSUM=
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0/go.mod h1:S9Xr4PYopiDyqSyp5NjCrhFrqg6A5zA2E/iPHPhqnS8=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
Expand Down Expand Up @@ -340,6 +341,8 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/dnaeon/go-vcr.v4 v4.0.6 h1:PiJkrakkmzc5s7EfBnZOnyiLwi7o7A9fwPzN0X2uwe0=
gopkg.in/dnaeon/go-vcr.v4 v4.0.6/go.mod h1:sbq5oMEcM4PXngbcNbHhzfCP9OdZodLhrbRYoyg09HY=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
74 changes: 71 additions & 3 deletions go/osv/ecosystem/bioconductor.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,80 @@

package ecosystem

import (
"errors"
"fmt"
"net/url"
)

type bioconductorEcosystem struct {
semverLikeEcosystem
}

var _ Enumerable = bioconductorEcosystem{}
var _ Ecosystem = bioconductorEcosystem{}

// FIXME(michaelkedar): Newer releases (3.22+) of bioconductor seem to be returning 500s for package queries.
// 500 seems to be the response when the bioc_version is invalid (i.e. it's also 500 if bioc_version is e.g. 12.3).
// I am guessing the API has changed or is broken for newer bioc versions.
//
// OSV.dev currently has zero Bioconductor packages, so I'm not going to spend time debugging this.
// Removing the Enumerable interface for now (but keeping the code for reference).
// var _ Enumerable = bioconductorEcosystem{}

func apiPackageURLPositBioconductor(pkg, biocVersion string) string {
// Use the Posit Public Package Manager API to pull both the current and
// older versions for a specific package since Bioconductor doesn't natively
// support this functionality.
return fmt.Sprintf("https://packagemanager.posit.co/__api__/repos/4/packages/%s?bioc_version=%s",
url.PathEscape(pkg),
url.QueryEscape(biocVersion),
)
}

const apiBiocVersionsURL = "https://packagemanager.posit.co/__api__/status"

func (e bioconductorEcosystem) getVersions(pkg string) ([]string, error) {
biocVersions, err := e.getBiocVersions()
if err != nil {
return nil, err
}
var versions []string
for _, biocVersion := range biocVersions {
var data struct {
Version string `json:"version"`
}
if err := fetchJSON(apiPackageURLPositBioconductor(pkg, biocVersion), &data); err != nil {
if errors.Is(err, ErrPackageNotFound) {
continue
}

return nil, fmt.Errorf("failed to get Bioconductor versions for %s: %w", pkg, err)
}
if data.Version != "" {
versions = append(versions, data.Version)
}
}

if len(versions) == 0 {
return nil, ErrPackageNotFound
}

return sortVersions(e, versions)
}

func (e bioconductorEcosystem) getBiocVersions() ([]string, error) {
var data struct {
BiocVersions []struct {
BiocVersion string `json:"bioc_version"`
} `json:"bioc_versions"`
}
if err := fetchJSON(apiBiocVersionsURL, &data); err != nil {
return nil, fmt.Errorf("failed to get Bioconductor versions: %w", err)
}
versions := make([]string, 0, len(data.BiocVersions))
for _, biocVersion := range data.BiocVersions {
versions = append(versions, biocVersion.BiocVersion)
}

func (e bioconductorEcosystem) GetVersions(_ string) ([]string, error) {
panic("not yet implemented")
return versions, nil
}
55 changes: 55 additions & 0 deletions go/osv/ecosystem/bioconductor_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package ecosystem

import (
"errors"
"testing"

"github.com/google/go-cmp/cmp"
)

// TODO(michaelkedar): See bioconductor.go for why these are skipped.

func TestBioconductor_GetBiocVersions(t *testing.T) {
t.SkipNow()
setupHTTPClientForTest(t)
versions, err := bioconductorEcosystem{}.getBiocVersions()
if err != nil {
t.Errorf("getBiocVersions() error = %v", err)
return
}
if len(versions) == 0 {
t.Errorf("getBiocVersions() returned no versions")
return
}
expectedVersions := []string{"3.23", "3.22", "3.21", "3.20", "3.19", "3.18", "3.17", "3.16", "3.15", "3.14", "3.13", "3.12", "3.11", "3.10", "3.9", "3.8", "3.7", "3.6", "3.5", "3.4", "3.3", "3.2", "3.1"}
if diff := cmp.Diff(expectedVersions, versions); diff != "" {
t.Errorf("getBiocVersions() diff: %s", diff)
}
}

func TestBioconductor_GetVersions(t *testing.T) {
t.SkipNow()
setupHTTPClientForTest(t)
versions, err := bioconductorEcosystem{}.getVersions("a4") // TODO(michaelkedar): getVersions -> GetVersions
if err != nil {
t.Errorf("GetVersions() error = %v", err)
return
}
if len(versions) == 0 {
t.Errorf("GetVersions() returned no versions")
return
}
expectedVersions := []string{} // ???
if diff := cmp.Diff(expectedVersions, versions); diff != "" {
t.Errorf("GetVersions() diff: %s", diff)
}
}

func TestBioconductor_GetVersionsNotFound(t *testing.T) {
t.SkipNow()
setupHTTPClientForTest(t)
_, err := bioconductorEcosystem{}.getVersions("doesnotexist123456") // TODO(michaelkedar): getVersions -> GetVersions
if !errors.Is(err, ErrPackageNotFound) {
t.Errorf("GetVersions() error = %v, want %v", err, ErrPackageNotFound)
}
}
38 changes: 35 additions & 3 deletions go/osv/ecosystem/cran.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,25 @@

package ecosystem

import "github.com/google/osv-scalibr/semantic"
import (
"fmt"
"net/url"

"github.com/google/osv-scalibr/semantic"
)

type cranEcosystem struct{}

var _ Enumerable = cranEcosystem{}

func cranAPIURL(pkg string) string {
// Use the Posit Public Package Manager API to pull both the current
// and archived versions for a specific package since CRAN doesn't
// natively support this functionality.
path, _ := url.JoinPath("https://packagemanager.posit.co/__api__/repos/2/packages/", url.PathEscape(pkg))
return path
}

func (e cranEcosystem) Parse(version string) (Version, error) {
ver, err := semantic.ParseCRANVersion(version)
if err != nil {
Expand All @@ -37,6 +50,25 @@ func (e cranEcosystem) IsSemver() bool {
return false
}

func (e cranEcosystem) GetVersions(_ string) ([]string, error) {
panic("not yet implemented")
func (e cranEcosystem) GetVersions(pkg string) ([]string, error) {
var data struct {
Version string `json:"version"`
Archived []struct {
Version string `json:"version"`
} `json:"archived"`
}
if err := fetchJSON(cranAPIURL(pkg), &data); err != nil {
return nil, fmt.Errorf("failed to get CRAN versions for %s: %w", pkg, err)
}
var versions []string
if data.Version != "" {
versions = append(versions, data.Version)
}
for _, v := range data.Archived {
if v.Version != "" {
versions = append(versions, v.Version)
}
}

return sortVersions(e, versions)
}
39 changes: 39 additions & 0 deletions go/osv/ecosystem/cran_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package ecosystem

import (
"errors"
"testing"
)

func TestCRAN_GetVersions(t *testing.T) {
setupHTTPClientForTest(t)
ecosystem := cranEcosystem{}

t.Run("readxl", func(t *testing.T) {
versions, err := ecosystem.GetVersions("readxl")
if err != nil {
t.Fatalf("failed to get CRAN versions for readxl: %v", err)
}
// Test typical semver X.Y.Z version
checkNextVersion(t, versions, "0.1.0", "0.1.1")
checkNextVersion(t, versions, "0.1.1", "1.0.0")
})

t.Run("aqp", func(t *testing.T) {
// Test atypical versioned package
versions, err := ecosystem.GetVersions("aqp")
if err != nil {
t.Fatalf("failed to get CRAN versions for aqp: %v", err)
}
checkNextVersion(t, versions, "0.99-8.1", "0.99-8.47")
})
}

func TestCRAN_GetVersions_NotFound(t *testing.T) {
setupHTTPClientForTest(t)
ecosystem := cranEcosystem{}
_, err := ecosystem.GetVersions("doesnotexist123456")
if !errors.Is(err, ErrPackageNotFound) {
t.Errorf("expected ErrPackageNotFound, got %v", err)
}
}
Loading
Loading