Skip to content

Commit adfc46d

Browse files
committed
Introduce a semver filter in OCIRepository
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 <bah.soule@gmail.com>
1 parent 565f6ee commit adfc46d

8 files changed

+69
-4
lines changed

api/v1beta2/ocirepository_types.go

+4
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,10 @@ type OCIRepositoryRef struct {
157157
// +optional
158158
SemVer string `json:"semver,omitempty"`
159159

160+
// SemverFilter is a regex pattern to filter the tags within the SemVer range.
161+
// +optional
162+
SemverFilter string `json:"semverFilter,omitempty"`
163+
160164
// Tag is the image tag to pull, defaults to latest.
161165
// +optional
162166
Tag string `json:"tag,omitempty"`

config/crd/bases/source.toolkit.fluxcd.io_ocirepositories.yaml

+4
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,10 @@ spec:
146146
SemVer is the range of tags to pull selecting the latest within
147147
the range, takes precedence over Tag.
148148
type: string
149+
semverFilter:
150+
description: SemverFilter is a regex pattern to filter the tags
151+
within the SemVer range.
152+
type: string
149153
tag:
150154
description: Tag is the image tag to pull, defaults to latest.
151155
type: string

docs/api/v1beta2/source.md

+12
Original file line numberDiff line numberDiff line change
@@ -2938,6 +2938,18 @@ the range, takes precedence over Tag.</p>
29382938
</tr>
29392939
<tr>
29402940
<td>
2941+
<code>semverFilter</code><br>
2942+
<em>
2943+
string
2944+
</em>
2945+
</td>
2946+
<td>
2947+
<em>(Optional)</em>
2948+
<p>SemverFilter is a regex pattern to filter the tags within the SemVer range.</p>
2949+
</td>
2950+
</tr>
2951+
<tr>
2952+
<td>
29412953
<code>tag</code><br>
29422954
<em>
29432955
string

internal/controller/ocirepository_controller.go

+32-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"net/http"
2626
"os"
2727
"path/filepath"
28+
"regexp"
2829
"sort"
2930
"strings"
3031
"time"
@@ -112,6 +113,8 @@ var ociRepositoryFailConditions = []string{
112113
sourcev1.StorageOperationFailedCondition,
113114
}
114115

116+
type filterFunc func(tags []string) ([]string, error)
117+
115118
type invalidOCIURLError struct {
116119
err error
117120
}
@@ -732,7 +735,7 @@ func (r *OCIRepositoryReconciler) getArtifactRef(obj *ociv1.OCIRepository, optio
732735
}
733736

734737
if obj.Spec.Reference.SemVer != "" {
735-
return r.getTagBySemver(repo, obj.Spec.Reference.SemVer, options)
738+
return r.getTagBySemver(repo, obj.Spec.Reference.SemVer, filterTags(obj.Spec.Reference.SemverFilter), options)
736739
}
737740

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

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

757+
validTags, err := filter(tags)
758+
if err != nil {
759+
return nil, err
760+
}
761+
754762
constraint, err := semver.NewConstraint(exp)
755763
if err != nil {
756764
return nil, fmt.Errorf("semver '%s' parse error: %w", exp, err)
757765
}
758766

759767
var matchingVersions []*semver.Version
760-
for _, t := range tags {
768+
for _, t := range validTags {
761769
v, err := version.ParseVersion(t)
762770
if err != nil {
763771
continue
@@ -1209,3 +1217,24 @@ func layerSelectorEqual(a, b *ociv1.OCILayerSelector) bool {
12091217
}
12101218
return *a == *b
12111219
}
1220+
1221+
func filterTags(filter string) filterFunc {
1222+
return func(tags []string) ([]string, error) {
1223+
if filter == "" {
1224+
return tags, nil
1225+
}
1226+
1227+
match, err := regexp.Compile(filter)
1228+
if err != nil {
1229+
return nil, err
1230+
}
1231+
1232+
validTags := []string{}
1233+
for _, tag := range tags {
1234+
if match.MatchString(tag) {
1235+
validTags = append(validTags, tag)
1236+
}
1237+
}
1238+
return validTags, nil
1239+
}
1240+
}

internal/controller/ocirepository_controller_test.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -2039,7 +2039,14 @@ func TestOCIRepository_getArtifactRef(t *testing.T) {
20392039
server.Close()
20402040
})
20412041

2042-
imgs, err := pushMultiplePodinfoImages(server.registryHost, true, "6.1.4", "6.1.5", "6.1.6")
2042+
imgs, err := pushMultiplePodinfoImages(server.registryHost, true,
2043+
"6.1.4",
2044+
"6.1.5-beta.1",
2045+
"6.1.5-rc.1",
2046+
"6.1.5",
2047+
"6.1.6-rc.1",
2048+
"6.1.6",
2049+
)
20432050
g.Expect(err).ToNot(HaveOccurred())
20442051

20452052
tests := []struct {
@@ -2083,6 +2090,15 @@ func TestOCIRepository_getArtifactRef(t *testing.T) {
20832090
url: "ghcr.io/stefanprodan/charts",
20842091
wantErr: true,
20852092
},
2093+
{
2094+
name: "valid url with semver filter",
2095+
url: fmt.Sprintf("oci://%s/podinfo", server.registryHost),
2096+
reference: &ociv1.OCIRepositoryRef{
2097+
SemVer: ">= 6.1.x-0",
2098+
SemverFilter: ".*-rc.*",
2099+
},
2100+
want: server.registryHost + "/podinfo:6.1.6-rc.1",
2101+
},
20862102
}
20872103

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

0 commit comments

Comments
 (0)