Skip to content

Commit 1689eda

Browse files
authored
feat: support checking if versions exist in RubyGems (#384)
This adds support for checking that Ruby gems versions exist in the repository Signed-off-by: Gareth Jones <[email protected]>
1 parent 8e9d581 commit 1689eda

File tree

4 files changed

+111
-3
lines changed

4 files changed

+111
-3
lines changed

tools/osv-linter/internal/pkgchecker/ecosystems.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ var EcosystemBaseURLs = map[string]string{
2525
"crates.io": "https://crates.io/api/v1/crates",
2626
"npm": "https://registry.npmjs.org",
2727
"NuGet": "https://api.nuget.org/v3-flatcontainer",
28-
"RubyGems": "https://rubygems.org/api/v1/gems",
28+
"RubyGems": "https://rubygems.org/api/v1",
2929
"Packagist": "https://repo.packagist.org/p2",
3030
"Pub": "https://pub.dev/api/packages",
3131
"Hackage": "https://hackage.haskell.org/package",
@@ -173,7 +173,7 @@ func VersionsExistInEcosystem(pkg string, versions []string, ecosystem string) e
173173
case "Rocky Linux":
174174
return nil
175175
case "RubyGems":
176-
return nil
176+
return versionsExistInRubyGems(pkg, versions)
177177
case "SUSE":
178178
return nil
179179
case "SwiftURL":

tools/osv-linter/internal/pkgchecker/ecosystems_test.go

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,62 @@ package pkgchecker
22

33
import "testing"
44

5+
func Test_versionsExistInRubyGems(t *testing.T) {
6+
t.Parallel()
7+
8+
type args struct {
9+
pkg string
10+
versions []string
11+
}
12+
tests := []struct {
13+
name string
14+
args args
15+
wantErr bool
16+
}{
17+
{
18+
name: "multiple_versions_which_all_exist",
19+
args: args{
20+
pkg: "capistrano",
21+
versions: []string{"2.5.7", "3.0.0.pre4", "3.11.1"},
22+
},
23+
wantErr: false,
24+
},
25+
{
26+
name: "multiple_versions_with_one_that_does_not_exist",
27+
args: args{
28+
pkg: "capistrano",
29+
versions: []string{"1.1.1", "2.3rc9", "3.1.5", "5.1rc1"},
30+
},
31+
wantErr: true,
32+
},
33+
{
34+
name: "an_invalid_version",
35+
args: args{
36+
pkg: "capistrano",
37+
versions: []string{"!"},
38+
},
39+
wantErr: true,
40+
},
41+
{
42+
name: "a_package_that_does_not_exit",
43+
args: args{
44+
pkg: "not-a-real-package",
45+
versions: []string{"1.0.0"},
46+
},
47+
wantErr: true,
48+
},
49+
}
50+
for _, tt := range tests {
51+
t.Run(tt.name, func(t *testing.T) {
52+
t.Parallel()
53+
54+
if err := versionsExistInRubyGems(tt.args.pkg, tt.args.versions); (err != nil) != tt.wantErr {
55+
t.Errorf("versionsExistInRubyGems() error = %v, wantErr %v", err, tt.wantErr)
56+
}
57+
})
58+
}
59+
}
60+
561
func Test_versionsExistInPackagist(t *testing.T) {
662
t.Parallel()
763

tools/osv-linter/internal/pkgchecker/package_check.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func existsInNuget(pkg string) bool {
7474

7575
// Validate the existence of a package in RubyGems.
7676
func existsInRubyGems(pkg string) bool {
77-
packageInstanceURL := fmt.Sprintf("%s/%s.json", EcosystemBaseURLs["RubyGems"], pkg)
77+
packageInstanceURL := fmt.Sprintf("%s/gems/%s.json", EcosystemBaseURLs["RubyGems"], pkg)
7878

7979
return checkPackageExists(packageInstanceURL)
8080
}

tools/osv-linter/internal/pkgchecker/version_check.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,58 @@ import (
1515
"golang.org/x/mod/semver"
1616
)
1717

18+
// Confirm that all specified versions of a package exist in RubyGems.
19+
func versionsExistInRubyGems(pkg string, versions []string) error {
20+
packageInstanceURL := fmt.Sprintf("%s/versions/%s.json", EcosystemBaseURLs["RubyGems"], pkg)
21+
22+
resp, err := faulttolerant.Get(packageInstanceURL)
23+
if err != nil {
24+
return fmt.Errorf("unable to validate package: %v", err)
25+
}
26+
defer resp.Body.Close()
27+
if resp.StatusCode != http.StatusOK {
28+
return fmt.Errorf("unable to validate package: %q for %s", resp.Status, packageInstanceURL)
29+
}
30+
31+
// Parse the known versions from the JSON.
32+
respJSON, err := io.ReadAll(resp.Body)
33+
if err != nil {
34+
return fmt.Errorf("unable to retrieve JSON for %q: %v", pkg, err)
35+
}
36+
// Fetch all known versions of package.
37+
versionsInRepository := []string{}
38+
releases := gjson.GetBytes(respJSON, "@this")
39+
releases.ForEach(func(key, value gjson.Result) bool {
40+
versionsInRepository = append(versionsInRepository, value.Get("number").String())
41+
return true // keep iterating.
42+
})
43+
// Determine which referenced versions are missing.
44+
versionsMissing := []string{}
45+
for _, versionToCheckFor := range versions {
46+
versionFound := false
47+
vc, err := semantic.Parse(versionToCheckFor, "RubyGems")
48+
if err != nil {
49+
versionsMissing = append(versionsMissing, versionToCheckFor)
50+
continue
51+
}
52+
for _, pkgversion := range versionsInRepository {
53+
if r, err := vc.CompareStr(pkgversion); r == 0 && err == nil {
54+
versionFound = true
55+
break
56+
}
57+
}
58+
if versionFound {
59+
continue
60+
}
61+
versionsMissing = append(versionsMissing, versionToCheckFor)
62+
}
63+
if len(versionsMissing) > 0 {
64+
return &MissingVersionsError{Package: pkg, Ecosystem: "RubyGems", Missing: versionsMissing, Known: versionsInRepository}
65+
}
66+
67+
return nil
68+
}
69+
1870
// Confirm that all specified versions of a package exist in Packagist.
1971
func versionsExistInPackagist(pkg string, versions []string) error {
2072
packageInstanceURL := fmt.Sprintf("%s/%s.json", EcosystemBaseURLs["Packagist"], pkg)

0 commit comments

Comments
 (0)