Skip to content
Merged
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ require (
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.63.0 // indirect
github.com/prometheus/procfs v0.16.0 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/vbatts/tar-split v0.12.1 // indirect
github.com/x448/float16 v0.8.4 // indirect
github.com/xlab/treeprint v1.2.0 // indirect
Expand Down
5 changes: 5 additions & 0 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ import (
"github.com/jetstack/version-checker/pkg/client/selfhosted"
)

// Used for testing/mocking purposes
type ClientHandler interface {
Tags(ctx context.Context, imageURL string) ([]api.ImageTag, error)
}

// Client is a container image registry client to list tags of given image
// URLs.
type Client struct {
Expand Down
10 changes: 0 additions & 10 deletions pkg/client/ghcr/ghcr.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,6 @@ func (c *Client) extractImageTags(versions []*github.PackageVersion) []api.Image
}

for _, tag := range meta.Container.Tags {
if c.shouldSkipTag(tag) {
continue
}

tags = append(tags, api.ImageTag{
Tag: tag,
SHA: sha,
Expand All @@ -134,12 +130,6 @@ func (c *Client) extractImageTags(versions []*github.PackageVersion) []api.Image
return tags
}

func (c *Client) shouldSkipTag(tag string) bool {
return strings.HasSuffix(tag, ".att") ||
strings.HasSuffix(tag, ".sig") ||
strings.HasSuffix(tag, ".sbom")
}

func (c *Client) ownerType(ctx context.Context, owner string) (string, error) {
if ownerType, ok := c.ownerTypes[owner]; ok {
return ownerType, nil
Expand Down
2 changes: 2 additions & 0 deletions pkg/version/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ func NewVersionErrorNotFound(format string, a ...interface{}) *ErrorVersionNotFo
return &ErrorVersionNotFound{fmt.Errorf(format, a...)}
}

// The function IsNoVersionFound checks if the error is of type
// ErrorVersionNotFound.
func IsNoVersionFound(err error) bool {
var notFound *ErrorVersionNotFound
return errors.As(err, &notFound)
Expand Down
39 changes: 39 additions & 0 deletions pkg/version/errors/errors_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package errors

import (
"errors"
"testing"

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

func TestIsNoVersionFound(t *testing.T) {
tests := []struct {
name string
err error
expectRes bool
}{
{
name: "error is of type ErrorVersionNotFound",
err: NewVersionErrorNotFound("version not found"),
expectRes: true,
},
{
name: "error is not of type ErrorVersionNotFound",
err: errors.New("some other error"),
expectRes: false,
},
{
name: "error is nil",
err: nil,
expectRes: false,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
result := IsNoVersionFound(test.err)
assert.Equal(t, result, test.expectRes)
})
}
}
64 changes: 64 additions & 0 deletions pkg/version/filters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package version

import (
"strings"

"github.com/jetstack/version-checker/pkg/api"
"github.com/jetstack/version-checker/pkg/version/semver"
)

func isSBOMAttestationOrSig(tag string) bool {
return strings.HasSuffix(tag, ".att") ||
strings.HasSuffix(tag, ".sig") ||
strings.HasSuffix(tag, ".sbom")
}

// Used when filtering Tags as a SemVer
func shouldSkipTag(opts *api.Options, v *semver.SemVer) bool {
// Handle Regex matching
if opts.RegexMatcher != nil {
return !opts.RegexMatcher.MatchString(v.String())
}

// Handle metadata and version pinning
return (!opts.UseMetaData && v.HasMetaData()) ||
(opts.PinMajor != nil && *opts.PinMajor != v.Major()) ||
(opts.PinMinor != nil && *opts.PinMinor != v.Minor()) ||
(opts.PinPatch != nil && *opts.PinPatch != v.Patch())
}

// Used when filtering SHA Tags
func shouldSkipSHA(opts *api.Options, sha string) bool {
// Filter out Sbom and Attestation/Signatures
if isSBOMAttestationOrSig(sha) {
return true
}

// Allow for Regex Filtering
if opts != nil && opts.RegexMatcher != nil {
return !opts.RegexMatcher.MatchString(sha)
}

return false
}

// isBetterSemVer compares two semantic version numbers and
// associated image tags to determine if one is considered better than the other.
func isBetterSemVer(_ *api.Options, latestV, v *semver.SemVer, latestImageTag, currentImageTag *api.ImageTag) bool {
// No latest version set yet
if latestV == nil {
return true
}

// If the current version is greater than the latest
if latestV.LessThan(v) {
return true
}

// If the versions are equal, prefer the one with a later timestamp
if latestV.Equal(v) && currentImageTag.Timestamp.After(latestImageTag.Timestamp) {
return true
}

return false
}
53 changes: 16 additions & 37 deletions pkg/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ import (
type Version struct {
log *logrus.Entry

client *client.Client
client client.ClientHandler
imageCache *cache.Cache
}

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

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

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

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

for i := range tags {
// Filter out SBOM and Attestation/Sig's
if isSBOMAttestationOrSig(tags[i].Tag) || isSBOMAttestationOrSig(tags[i].SHA) {
continue
}

v := semver.Parse(tags[i].Tag)

if shouldSkipTag(opts, v) {
continue
}

if isBetterTag(opts, latestV, v, latestImageTag, &tags[i]) {
if isBetterSemVer(opts, latestV, v, latestImageTag, &tags[i]) {
latestV = v
latestImageTag = &tags[i]
}
Expand All @@ -121,43 +127,16 @@ func latestSemver(opts *api.Options, tags []api.ImageTag) (*api.ImageTag, error)
return latestImageTag, nil
}

func shouldSkipTag(opts *api.Options, v *semver.SemVer) bool {
// Handle Regex matching
if opts.RegexMatcher != nil {
return !opts.RegexMatcher.MatchString(v.String())
}

// Handle metadata and version pinning
return (!opts.UseMetaData && v.HasMetaData()) ||
(opts.PinMajor != nil && *opts.PinMajor != v.Major()) ||
(opts.PinMinor != nil && *opts.PinMinor != v.Minor()) ||
(opts.PinPatch != nil && *opts.PinPatch != v.Patch())
}

func isBetterTag(_ *api.Options, latestV, v *semver.SemVer, latestImageTag, currentImageTag *api.ImageTag) bool {
// No latest version set yet
if latestV == nil {
return true
}

// If the current version is greater than the latest
if latestV.LessThan(v) {
return true
}

// If the versions are equal, prefer the one with a later timestamp
if latestV.Equal(v) && currentImageTag.Timestamp.After(latestImageTag.Timestamp) {
return true
}

return false
}

// latestSHA will return the latest ImageTag based on image timestamps.
func latestSHA(tags []api.ImageTag) (*api.ImageTag, error) {
func latestSHA(opts *api.Options, tags []api.ImageTag) (*api.ImageTag, error) {
var latestTag *api.ImageTag

for i := range tags {
// Filter out SBOM and Attestation/Sig's...
if shouldSkipSHA(opts, tags[i].Tag) {
continue
}

if latestTag == nil || tags[i].Timestamp.After(latestTag.Timestamp) {
latestTag = &tags[i]
}
Expand Down
Loading
Loading