Merge pull request #1407 from fluxcd/add-semverfilter-to-ocirepo
Introduce a semVer filter in OCIRepository API
This commit is contained in:
commit
74c5f99948
|
|
@ -157,6 +157,10 @@ type OCIRepositoryRef struct {
|
||||||
// +optional
|
// +optional
|
||||||
SemVer string `json:"semver,omitempty"`
|
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.
|
// Tag is the image tag to pull, defaults to latest.
|
||||||
// +optional
|
// +optional
|
||||||
Tag string `json:"tag,omitempty"`
|
Tag string `json:"tag,omitempty"`
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,10 @@ spec:
|
||||||
SemVer is the range of tags to pull selecting the latest within
|
SemVer is the range of tags to pull selecting the latest within
|
||||||
the range, takes precedence over Tag.
|
the range, takes precedence over Tag.
|
||||||
type: string
|
type: string
|
||||||
|
semverFilter:
|
||||||
|
description: SemverFilter is a regex pattern to filter the tags
|
||||||
|
within the SemVer range.
|
||||||
|
type: string
|
||||||
tag:
|
tag:
|
||||||
description: Tag is the image tag to pull, defaults to latest.
|
description: Tag is the image tag to pull, defaults to latest.
|
||||||
type: string
|
type: string
|
||||||
|
|
|
||||||
|
|
@ -2938,6 +2938,18 @@ the range, takes precedence over Tag.</p>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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>
|
<code>tag</code><br>
|
||||||
<em>
|
<em>
|
||||||
string
|
string
|
||||||
|
|
|
||||||
|
|
@ -441,6 +441,37 @@ spec:
|
||||||
|
|
||||||
This field takes precedence over [`.tag`](#tag-example).
|
This field takes precedence over [`.tag`](#tag-example).
|
||||||
|
|
||||||
|
#### SemverFilter example
|
||||||
|
|
||||||
|
`.spec.ref.semverFilter` is an optional field to specify a SemVer filter to apply
|
||||||
|
when fetching tags from the OCI repository. The filter is a regular expression
|
||||||
|
that is applied to the tags fetched from the repository. Only tags that match
|
||||||
|
the filter are considered for the semver range resolution.
|
||||||
|
|
||||||
|
**Note:** The filter is only taken into account when the `.spec.ref.semver` field
|
||||||
|
is set.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
apiVersion: source.toolkit.fluxcd.io/v1beta2
|
||||||
|
kind: OCIRepository
|
||||||
|
metadata:
|
||||||
|
name: podinfo
|
||||||
|
namespace: default
|
||||||
|
spec:
|
||||||
|
interval: 5m0s
|
||||||
|
url: oci://ghcr.io/stefanprodan/manifests/podinfo
|
||||||
|
ref:
|
||||||
|
# SemVer comparisons using constraints without a prerelease comparator will skip prerelease versions.
|
||||||
|
# Adding a `-0` suffix to the semver range will include prerelease versions.
|
||||||
|
semver: ">= 6.1.x-0"
|
||||||
|
semverFilter: ".*-rc.*"
|
||||||
|
```
|
||||||
|
|
||||||
|
In the above example, the controller fetches tags from the `ghcr.io/stefanprodan/manifests/podinfo`
|
||||||
|
repository and filters them using the regular expression `.*-rc.*`. Only tags that
|
||||||
|
contain the `-rc` suffix are considered for the semver range resolution.
|
||||||
|
|
||||||
#### Digest example
|
#### Digest example
|
||||||
|
|
||||||
To pull a specific digest, use `.spec.ref.digest`:
|
To pull a specific digest, use `.spec.ref.digest`:
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -116,6 +117,8 @@ var ociRepositoryFailConditions = []string{
|
||||||
sourcev1.StorageOperationFailedCondition,
|
sourcev1.StorageOperationFailedCondition,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type filterFunc func(tags []string) ([]string, error)
|
||||||
|
|
||||||
type invalidOCIURLError struct {
|
type invalidOCIURLError struct {
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
@ -821,7 +824,7 @@ func (r *OCIRepositoryReconciler) getArtifactRef(obj *ociv1.OCIRepository, optio
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj.Spec.Reference.SemVer != "" {
|
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 != "" {
|
if obj.Spec.Reference.Tag != "" {
|
||||||
|
|
@ -834,19 +837,24 @@ func (r *OCIRepositoryReconciler) getArtifactRef(obj *ociv1.OCIRepository, optio
|
||||||
|
|
||||||
// getTagBySemver call the remote container registry, fetches all the tags from the repository,
|
// getTagBySemver call the remote container registry, fetches all the tags from the repository,
|
||||||
// and returns the latest tag according to the semver expression.
|
// 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...)
|
tags, err := remote.List(repo, options...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
validTags, err := filter(tags)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
constraint, err := semver.NewConstraint(exp)
|
constraint, err := semver.NewConstraint(exp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("semver '%s' parse error: %w", exp, err)
|
return nil, fmt.Errorf("semver '%s' parse error: %w", exp, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var matchingVersions []*semver.Version
|
var matchingVersions []*semver.Version
|
||||||
for _, t := range tags {
|
for _, t := range validTags {
|
||||||
v, err := version.ParseVersion(t)
|
v, err := version.ParseVersion(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
|
|
@ -1298,3 +1306,24 @@ func layerSelectorEqual(a, b *ociv1.OCILayerSelector) bool {
|
||||||
}
|
}
|
||||||
return *a == *b
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2757,7 +2757,14 @@ func TestOCIRepository_getArtifactRef(t *testing.T) {
|
||||||
server.Close()
|
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())
|
g.Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
|
|
@ -2801,6 +2808,24 @@ func TestOCIRepository_getArtifactRef(t *testing.T) {
|
||||||
url: "ghcr.io/stefanprodan/charts",
|
url: "ghcr.io/stefanprodan/charts",
|
||||||
wantErr: true,
|
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",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "valid url with semver filter and unexisting version",
|
||||||
|
url: fmt.Sprintf("oci://%s/podinfo", server.registryHost),
|
||||||
|
reference: &ociv1.OCIRepositoryRef{
|
||||||
|
SemVer: ">= 6.1.x-0",
|
||||||
|
SemverFilter: ".*-alpha.*",
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
clientBuilder := fakeclient.NewClientBuilder().
|
clientBuilder := fakeclient.NewClientBuilder().
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue