Skip to content

Commit d08cbda

Browse files
authored
Fix issue of filtering sig,att,sbom image tags when searching SHA and Tag/SemVer, enable regex filtering with SHA searching too, for additional exclusions (#349)
1 parent 3dcfc81 commit d08cbda

File tree

8 files changed

+393
-53
lines changed

8 files changed

+393
-53
lines changed

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ require (
108108
github.com/prometheus/client_model v0.6.1 // indirect
109109
github.com/prometheus/common v0.63.0 // indirect
110110
github.com/prometheus/procfs v0.16.0 // indirect
111+
github.com/stretchr/objx v0.5.2 // indirect
111112
github.com/vbatts/tar-split v0.12.1 // indirect
112113
github.com/x448/float16 v0.8.4 // indirect
113114
github.com/xlab/treeprint v1.2.0 // indirect

pkg/client/client.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ import (
2020
"github.com/jetstack/version-checker/pkg/client/selfhosted"
2121
)
2222

23+
// Used for testing/mocking purposes
24+
type ClientHandler interface {
25+
Tags(ctx context.Context, imageURL string) ([]api.ImageTag, error)
26+
}
27+
2328
// Client is a container image registry client to list tags of given image
2429
// URLs.
2530
type Client struct {

pkg/client/ghcr/ghcr.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,6 @@ func (c *Client) extractImageTags(versions []*github.PackageVersion) []api.Image
119119
}
120120

121121
for _, tag := range meta.Container.Tags {
122-
if c.shouldSkipTag(tag) {
123-
continue
124-
}
125-
126122
tags = append(tags, api.ImageTag{
127123
Tag: tag,
128124
SHA: sha,
@@ -134,12 +130,6 @@ func (c *Client) extractImageTags(versions []*github.PackageVersion) []api.Image
134130
return tags
135131
}
136132

137-
func (c *Client) shouldSkipTag(tag string) bool {
138-
return strings.HasSuffix(tag, ".att") ||
139-
strings.HasSuffix(tag, ".sig") ||
140-
strings.HasSuffix(tag, ".sbom")
141-
}
142-
143133
func (c *Client) ownerType(ctx context.Context, owner string) (string, error) {
144134
if ownerType, ok := c.ownerTypes[owner]; ok {
145135
return ownerType, nil

pkg/version/errors/errors.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ func NewVersionErrorNotFound(format string, a ...interface{}) *ErrorVersionNotFo
1717
return &ErrorVersionNotFound{fmt.Errorf(format, a...)}
1818
}
1919

20+
// The function IsNoVersionFound checks if the error is of type
21+
// ErrorVersionNotFound.
2022
func IsNoVersionFound(err error) bool {
2123
var notFound *ErrorVersionNotFound
2224
return errors.As(err, &notFound)

pkg/version/errors/errors_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package errors
2+
3+
import (
4+
"errors"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestIsNoVersionFound(t *testing.T) {
11+
tests := []struct {
12+
name string
13+
err error
14+
expectRes bool
15+
}{
16+
{
17+
name: "error is of type ErrorVersionNotFound",
18+
err: NewVersionErrorNotFound("version not found"),
19+
expectRes: true,
20+
},
21+
{
22+
name: "error is not of type ErrorVersionNotFound",
23+
err: errors.New("some other error"),
24+
expectRes: false,
25+
},
26+
{
27+
name: "error is nil",
28+
err: nil,
29+
expectRes: false,
30+
},
31+
}
32+
33+
for _, test := range tests {
34+
t.Run(test.name, func(t *testing.T) {
35+
result := IsNoVersionFound(test.err)
36+
assert.Equal(t, result, test.expectRes)
37+
})
38+
}
39+
}

pkg/version/filters.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package version
2+
3+
import (
4+
"strings"
5+
6+
"github.com/jetstack/version-checker/pkg/api"
7+
"github.com/jetstack/version-checker/pkg/version/semver"
8+
)
9+
10+
func isSBOMAttestationOrSig(tag string) bool {
11+
return strings.HasSuffix(tag, ".att") ||
12+
strings.HasSuffix(tag, ".sig") ||
13+
strings.HasSuffix(tag, ".sbom")
14+
}
15+
16+
// Used when filtering Tags as a SemVer
17+
func shouldSkipTag(opts *api.Options, v *semver.SemVer) bool {
18+
// Handle Regex matching
19+
if opts.RegexMatcher != nil {
20+
return !opts.RegexMatcher.MatchString(v.String())
21+
}
22+
23+
// Handle metadata and version pinning
24+
return (!opts.UseMetaData && v.HasMetaData()) ||
25+
(opts.PinMajor != nil && *opts.PinMajor != v.Major()) ||
26+
(opts.PinMinor != nil && *opts.PinMinor != v.Minor()) ||
27+
(opts.PinPatch != nil && *opts.PinPatch != v.Patch())
28+
}
29+
30+
// Used when filtering SHA Tags
31+
func shouldSkipSHA(opts *api.Options, sha string) bool {
32+
// Filter out Sbom and Attestation/Signatures
33+
if isSBOMAttestationOrSig(sha) {
34+
return true
35+
}
36+
37+
// Allow for Regex Filtering
38+
if opts != nil && opts.RegexMatcher != nil {
39+
return !opts.RegexMatcher.MatchString(sha)
40+
}
41+
42+
return false
43+
}
44+
45+
// isBetterSemVer compares two semantic version numbers and
46+
// associated image tags to determine if one is considered better than the other.
47+
func isBetterSemVer(_ *api.Options, latestV, v *semver.SemVer, latestImageTag, currentImageTag *api.ImageTag) bool {
48+
// No latest version set yet
49+
if latestV == nil {
50+
return true
51+
}
52+
53+
// If the current version is greater than the latest
54+
if latestV.LessThan(v) {
55+
return true
56+
}
57+
58+
// If the versions are equal, prefer the one with a later timestamp
59+
if latestV.Equal(v) && currentImageTag.Timestamp.After(latestImageTag.Timestamp) {
60+
return true
61+
}
62+
63+
return false
64+
}

pkg/version/version.go

Lines changed: 16 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ import (
1919
type Version struct {
2020
log *logrus.Entry
2121

22-
client *client.Client
22+
client client.ClientHandler
2323
imageCache *cache.Cache
2424
}
2525

26-
func New(log *logrus.Entry, client *client.Client, cacheTimeout time.Duration) *Version {
26+
func New(log *logrus.Entry, client client.ClientHandler, cacheTimeout time.Duration) *Version {
2727
log = log.WithField("module", "version_getter")
2828

2929
v := &Version{
@@ -49,7 +49,7 @@ func (v *Version) LatestTagFromImage(ctx context.Context, imageURL string, opts
4949

5050
// If UseSHA then return early
5151
if opts.UseSHA {
52-
tag, err = latestSHA(tags)
52+
tag, err = latestSHA(opts, tags)
5353
if err != nil {
5454
return nil, err
5555
}
@@ -88,6 +88,7 @@ func (v *Version) Fetch(ctx context.Context, imageURL string, _ *api.Options) (i
8888
if len(tags) == 0 {
8989
return nil, versionerrors.NewVersionErrorNotFound("no tags found for given image URL: %q", imageURL)
9090
}
91+
v.log.WithField("image", imageURL).Debugf("fetched %v tags", len(tags))
9192

9293
return tags, nil
9394
}
@@ -102,13 +103,18 @@ func latestSemver(opts *api.Options, tags []api.ImageTag) (*api.ImageTag, error)
102103
)
103104

104105
for i := range tags {
106+
// Filter out SBOM and Attestation/Sig's
107+
if isSBOMAttestationOrSig(tags[i].Tag) || isSBOMAttestationOrSig(tags[i].SHA) {
108+
continue
109+
}
110+
105111
v := semver.Parse(tags[i].Tag)
106112

107113
if shouldSkipTag(opts, v) {
108114
continue
109115
}
110116

111-
if isBetterTag(opts, latestV, v, latestImageTag, &tags[i]) {
117+
if isBetterSemVer(opts, latestV, v, latestImageTag, &tags[i]) {
112118
latestV = v
113119
latestImageTag = &tags[i]
114120
}
@@ -121,43 +127,16 @@ func latestSemver(opts *api.Options, tags []api.ImageTag) (*api.ImageTag, error)
121127
return latestImageTag, nil
122128
}
123129

124-
func shouldSkipTag(opts *api.Options, v *semver.SemVer) bool {
125-
// Handle Regex matching
126-
if opts.RegexMatcher != nil {
127-
return !opts.RegexMatcher.MatchString(v.String())
128-
}
129-
130-
// Handle metadata and version pinning
131-
return (!opts.UseMetaData && v.HasMetaData()) ||
132-
(opts.PinMajor != nil && *opts.PinMajor != v.Major()) ||
133-
(opts.PinMinor != nil && *opts.PinMinor != v.Minor()) ||
134-
(opts.PinPatch != nil && *opts.PinPatch != v.Patch())
135-
}
136-
137-
func isBetterTag(_ *api.Options, latestV, v *semver.SemVer, latestImageTag, currentImageTag *api.ImageTag) bool {
138-
// No latest version set yet
139-
if latestV == nil {
140-
return true
141-
}
142-
143-
// If the current version is greater than the latest
144-
if latestV.LessThan(v) {
145-
return true
146-
}
147-
148-
// If the versions are equal, prefer the one with a later timestamp
149-
if latestV.Equal(v) && currentImageTag.Timestamp.After(latestImageTag.Timestamp) {
150-
return true
151-
}
152-
153-
return false
154-
}
155-
156130
// latestSHA will return the latest ImageTag based on image timestamps.
157-
func latestSHA(tags []api.ImageTag) (*api.ImageTag, error) {
131+
func latestSHA(opts *api.Options, tags []api.ImageTag) (*api.ImageTag, error) {
158132
var latestTag *api.ImageTag
159133

160134
for i := range tags {
135+
// Filter out SBOM and Attestation/Sig's...
136+
if shouldSkipSHA(opts, tags[i].Tag) {
137+
continue
138+
}
139+
161140
if latestTag == nil || tags[i].Timestamp.After(latestTag.Timestamp) {
162141
latestTag = &tags[i]
163142
}

0 commit comments

Comments
 (0)