Skip to content

Commit

Permalink
Introduce a semver filter in OCIRepository
Browse files Browse the repository at this point in the history
If implemented a semver filter regex can be declared in conjuction with
a semver range in the OCIRepository `spec.Reference`

Signed-off-by: Soule BA <[email protected]>
  • Loading branch information
souleb committed Mar 19, 2024
1 parent 565f6ee commit adfc46d
Show file tree
Hide file tree
Showing 8 changed files with 69 additions and 4 deletions.
4 changes: 4 additions & 0 deletions api/v1beta2/ocirepository_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,10 @@ type OCIRepositoryRef struct {
// +optional
SemVer string `json:"semver,omitempty"`

// SemverFilter is a regex pattern to filter the tags within the SemVer range.
// +optional
SemverFilter string `json:"semverFilter,omitempty"`

// Tag is the image tag to pull, defaults to latest.
// +optional
Tag string `json:"tag,omitempty"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,10 @@ spec:
SemVer is the range of tags to pull selecting the latest within
the range, takes precedence over Tag.
type: string
semverFilter:
description: SemverFilter is a regex pattern to filter the tags
within the SemVer range.
type: string
tag:
description: Tag is the image tag to pull, defaults to latest.
type: string
Expand Down
12 changes: 12 additions & 0 deletions docs/api/v1beta2/source.md
Original file line number Diff line number Diff line change
Expand Up @@ -2938,6 +2938,18 @@ the range, takes precedence over Tag.</p>
</tr>
<tr>
<td>
<code>semverFilter</code><br>
<em>
string
</em>
</td>
<td>
<em>(Optional)</em>
<p>SemverFilter is a regex pattern to filter the tags within the SemVer range.</p>
</td>
</tr>
<tr>
<td>
<code>tag</code><br>
<em>
string
Expand Down
35 changes: 32 additions & 3 deletions internal/controller/ocirepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"net/http"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"time"
Expand Down Expand Up @@ -112,6 +113,8 @@ var ociRepositoryFailConditions = []string{
sourcev1.StorageOperationFailedCondition,
}

type filterFunc func(tags []string) ([]string, error)

type invalidOCIURLError struct {
err error
}
Expand Down Expand Up @@ -732,7 +735,7 @@ func (r *OCIRepositoryReconciler) getArtifactRef(obj *ociv1.OCIRepository, optio
}

if obj.Spec.Reference.SemVer != "" {
return r.getTagBySemver(repo, obj.Spec.Reference.SemVer, options)
return r.getTagBySemver(repo, obj.Spec.Reference.SemVer, filterTags(obj.Spec.Reference.SemverFilter), options)
}

if obj.Spec.Reference.Tag != "" {
Expand All @@ -745,19 +748,24 @@ func (r *OCIRepositoryReconciler) getArtifactRef(obj *ociv1.OCIRepository, optio

// getTagBySemver call the remote container registry, fetches all the tags from the repository,
// and returns the latest tag according to the semver expression.
func (r *OCIRepositoryReconciler) getTagBySemver(repo name.Repository, exp string, options []remote.Option) (name.Reference, error) {
func (r *OCIRepositoryReconciler) getTagBySemver(repo name.Repository, exp string, filter filterFunc, options []remote.Option) (name.Reference, error) {
tags, err := remote.List(repo, options...)
if err != nil {
return nil, err
}

validTags, err := filter(tags)
if err != nil {
return nil, err
}

constraint, err := semver.NewConstraint(exp)
if err != nil {
return nil, fmt.Errorf("semver '%s' parse error: %w", exp, err)
}

var matchingVersions []*semver.Version
for _, t := range tags {
for _, t := range validTags {
v, err := version.ParseVersion(t)
if err != nil {
continue
Expand Down Expand Up @@ -1209,3 +1217,24 @@ func layerSelectorEqual(a, b *ociv1.OCILayerSelector) bool {
}
return *a == *b
}

func filterTags(filter string) filterFunc {
return func(tags []string) ([]string, error) {
if filter == "" {
return tags, nil
}

match, err := regexp.Compile(filter)
if err != nil {
return nil, err
}

validTags := []string{}
for _, tag := range tags {
if match.MatchString(tag) {
validTags = append(validTags, tag)
}
}
return validTags, nil
}
}
18 changes: 17 additions & 1 deletion internal/controller/ocirepository_controller_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2039,7 +2039,14 @@ func TestOCIRepository_getArtifactRef(t *testing.T) {
server.Close()
})

imgs, err := pushMultiplePodinfoImages(server.registryHost, true, "6.1.4", "6.1.5", "6.1.6")
imgs, err := pushMultiplePodinfoImages(server.registryHost, true,
"6.1.4",
"6.1.5-beta.1",
"6.1.5-rc.1",
"6.1.5",
"6.1.6-rc.1",
"6.1.6",
)
g.Expect(err).ToNot(HaveOccurred())

tests := []struct {
Expand Down Expand Up @@ -2083,6 +2090,15 @@ func TestOCIRepository_getArtifactRef(t *testing.T) {
url: "ghcr.io/stefanprodan/charts",
wantErr: true,
},
{
name: "valid url with semver filter",
url: fmt.Sprintf("oci://%s/podinfo", server.registryHost),
reference: &ociv1.OCIRepositoryRef{
SemVer: ">= 6.1.x-0",
SemverFilter: ".*-rc.*",
},
want: server.registryHost + "/podinfo:6.1.6-rc.1",
},
}

clientBuilder := fakeclient.NewClientBuilder().
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.

0 comments on commit adfc46d

Please sign in to comment.