Add `oci://` prefix
Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
parent
c9f5af7ddc
commit
ded0c2d78b
|
@ -25,12 +25,16 @@ import (
|
||||||
const (
|
const (
|
||||||
// OCIRepositoryKind is the string representation of a OCIRepository.
|
// OCIRepositoryKind is the string representation of a OCIRepository.
|
||||||
OCIRepositoryKind = "OCIRepository"
|
OCIRepositoryKind = "OCIRepository"
|
||||||
|
|
||||||
|
// OCIRepositoryPrefix is the prefix used for OCIRepository URLs.
|
||||||
|
OCIRepositoryPrefix = "oci://"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OCIRepositorySpec defines the desired state of OCIRepository
|
// OCIRepositorySpec defines the desired state of OCIRepository
|
||||||
type OCIRepositorySpec struct {
|
type OCIRepositorySpec struct {
|
||||||
// URL is a reference to an OCI artifact repository hosted
|
// URL is a reference to an OCI artifact repository hosted
|
||||||
// on a remote container registry.
|
// on a remote container registry.
|
||||||
|
// +kubebuilder:validation:Pattern="^oci://"
|
||||||
// +required
|
// +required
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
|
|
||||||
|
|
|
@ -120,6 +120,7 @@ spec:
|
||||||
url:
|
url:
|
||||||
description: URL is a reference to an OCI artifact repository hosted
|
description: URL is a reference to an OCI artifact repository hosted
|
||||||
on a remote container registry.
|
on a remote container registry.
|
||||||
|
pattern: ^oci://
|
||||||
type: string
|
type: string
|
||||||
verify:
|
verify:
|
||||||
description: Verification specifies the configuration to verify the
|
description: Verification specifies the configuration to verify the
|
||||||
|
|
|
@ -4,6 +4,6 @@ metadata:
|
||||||
name: ocirepository-sample
|
name: ocirepository-sample
|
||||||
spec:
|
spec:
|
||||||
interval: 1m
|
interval: 1m
|
||||||
url: ghcr.io/stefanprodan/manifests/podinfo
|
url: oci://ghcr.io/stefanprodan/manifests/podinfo
|
||||||
ref:
|
ref:
|
||||||
tag: 6.1.6
|
tag: 6.1.6
|
||||||
|
|
|
@ -20,8 +20,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/google/go-containerregistry/pkg/name"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/Masterminds/semver/v3"
|
"github.com/Masterminds/semver/v3"
|
||||||
|
@ -363,12 +365,31 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
|
||||||
return sreconcile.ResultSuccess, nil
|
return sreconcile.ResultSuccess, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// parseRepositoryURL extracts the repository URL.
|
||||||
|
func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *sourcev1.OCIRepository) (string, error) {
|
||||||
|
if !strings.HasPrefix(obj.Spec.URL, sourcev1.OCIRepositoryPrefix) {
|
||||||
|
return "", fmt.Errorf("URL must be in format 'oci://<domain>/<org>/<repo>'")
|
||||||
|
}
|
||||||
|
|
||||||
|
url := strings.TrimPrefix(obj.Spec.URL, sourcev1.OCIRepositoryPrefix)
|
||||||
|
ref, err := name.ParseReference(url)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("'%s' invalid URL: %w", obj.Spec.URL, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref.Context().Name(), nil
|
||||||
|
}
|
||||||
|
|
||||||
// getArtifactURL determines which tag or digest should be used and returns the OCI artifact FQN.
|
// getArtifactURL determines which tag or digest should be used and returns the OCI artifact FQN.
|
||||||
func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourcev1.OCIRepository, keychain authn.Keychain) (string, error) {
|
func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourcev1.OCIRepository, keychain authn.Keychain) (string, error) {
|
||||||
url := obj.Spec.URL
|
url, err := r.parseRepositoryURL(obj)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
if obj.Spec.Reference != nil {
|
if obj.Spec.Reference != nil {
|
||||||
if obj.Spec.Reference.Digest != "" {
|
if obj.Spec.Reference.Digest != "" {
|
||||||
return fmt.Sprintf("%s@%s", obj.Spec.URL, obj.Spec.Reference.Digest), nil
|
return fmt.Sprintf("%s@%s", url, obj.Spec.Reference.Digest), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj.Spec.Reference.SemVer != "" {
|
if obj.Spec.Reference.SemVer != "" {
|
||||||
|
@ -376,11 +397,11 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%s:%s", obj.Spec.URL, tag), nil
|
return fmt.Sprintf("%s:%s", url, tag), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if obj.Spec.Reference.Tag != "" {
|
if obj.Spec.Reference.Tag != "" {
|
||||||
return fmt.Sprintf("%s:%s", obj.Spec.URL, obj.Spec.Reference.Tag), nil
|
return fmt.Sprintf("%s:%s", url, obj.Spec.Reference.Tag), nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -239,6 +239,7 @@ func TestOCIRepository_SecretRef(t *testing.T) {
|
||||||
g.Expect(err).ToNot(HaveOccurred())
|
g.Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
repositoryURL := fmt.Sprintf("%s/podinfo", regServer.registryHost)
|
repositoryURL := fmt.Sprintf("%s/podinfo", regServer.registryHost)
|
||||||
|
ociURL := fmt.Sprintf("oci://%s", repositoryURL)
|
||||||
|
|
||||||
// Push Test Image
|
// Push Test Image
|
||||||
err = crane.Push(image, repositoryURL, crane.WithAuth(&authn.Basic{
|
err = crane.Push(image, repositoryURL, crane.WithAuth(&authn.Basic{
|
||||||
|
@ -260,14 +261,14 @@ func TestOCIRepository_SecretRef(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "private-registry-access-via-secretref",
|
name: "private-registry-access-via-secretref",
|
||||||
url: repositoryURL,
|
url: ociURL,
|
||||||
digest: podinfoImageDigest,
|
digest: podinfoImageDigest,
|
||||||
includeSecretRef: true,
|
includeSecretRef: true,
|
||||||
includeServiceAccount: false,
|
includeServiceAccount: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "private-registry-access-via-serviceaccount",
|
name: "private-registry-access-via-serviceaccount",
|
||||||
url: repositoryURL,
|
url: ociURL,
|
||||||
digest: podinfoImageDigest,
|
digest: podinfoImageDigest,
|
||||||
includeSecretRef: false,
|
includeSecretRef: false,
|
||||||
includeServiceAccount: true,
|
includeServiceAccount: true,
|
||||||
|
@ -289,7 +290,7 @@ func TestOCIRepository_SecretRef(t *testing.T) {
|
||||||
},
|
},
|
||||||
Type: corev1.SecretTypeDockerConfigJson,
|
Type: corev1.SecretTypeDockerConfigJson,
|
||||||
StringData: map[string]string{
|
StringData: map[string]string{
|
||||||
".dockerconfigjson": fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`, tt.url, testRegistryUsername, testRegistryPassword),
|
".dockerconfigjson": fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`, repositoryURL, testRegistryUsername, testRegistryPassword),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
g.Expect(testEnv.CreateAndWait(ctx, secret)).To(Succeed())
|
g.Expect(testEnv.CreateAndWait(ctx, secret)).To(Succeed())
|
||||||
|
@ -435,6 +436,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
|
||||||
g.Expect(err).ToNot(HaveOccurred())
|
g.Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
repositoryURL := fmt.Sprintf("%s/podinfo", regServer.registryHost)
|
repositoryURL := fmt.Sprintf("%s/podinfo", regServer.registryHost)
|
||||||
|
ociURL := fmt.Sprintf("oci://%s", repositoryURL)
|
||||||
|
|
||||||
// Push Test Image
|
// Push Test Image
|
||||||
err = crane.Push(image, repositoryURL, crane.WithAuth(&authn.Basic{
|
err = crane.Push(image, repositoryURL, crane.WithAuth(&authn.Basic{
|
||||||
|
@ -458,7 +460,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "missing-auth",
|
name: "missing-auth",
|
||||||
url: repositoryURL,
|
url: ociURL,
|
||||||
repoUsername: "",
|
repoUsername: "",
|
||||||
repoPassword: "",
|
repoPassword: "",
|
||||||
digest: podinfoImageDigest,
|
digest: podinfoImageDigest,
|
||||||
|
@ -467,7 +469,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid-auth-via-secret",
|
name: "invalid-auth-via-secret",
|
||||||
url: repositoryURL,
|
url: ociURL,
|
||||||
repoUsername: "InvalidUser",
|
repoUsername: "InvalidUser",
|
||||||
repoPassword: "InvalidPassword",
|
repoPassword: "InvalidPassword",
|
||||||
digest: podinfoImageDigest,
|
digest: podinfoImageDigest,
|
||||||
|
@ -476,7 +478,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid-auth-via-service-account",
|
name: "invalid-auth-via-service-account",
|
||||||
url: repositoryURL,
|
url: ociURL,
|
||||||
repoUsername: "InvalidUser",
|
repoUsername: "InvalidUser",
|
||||||
repoPassword: "InvalidPassword",
|
repoPassword: "InvalidPassword",
|
||||||
digest: podinfoImageDigest,
|
digest: podinfoImageDigest,
|
||||||
|
@ -500,7 +502,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
|
||||||
},
|
},
|
||||||
Type: corev1.SecretTypeDockerConfigJson,
|
Type: corev1.SecretTypeDockerConfigJson,
|
||||||
StringData: map[string]string{
|
StringData: map[string]string{
|
||||||
".dockerconfigjson": fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`, tt.url, tt.repoUsername, tt.repoPassword),
|
".dockerconfigjson": fmt.Sprintf(`{"auths": {%q: {"username": %q, "password": %q}}}`, repositoryURL, tt.repoUsername, tt.repoPassword),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
g.Expect(testEnv.CreateAndWait(ctx, secret)).To(Succeed())
|
g.Expect(testEnv.CreateAndWait(ctx, secret)).To(Succeed())
|
||||||
|
@ -623,7 +625,7 @@ func createPodinfoImageFromTar(tarFileName, tag string, imageServer *httptest.Se
|
||||||
}
|
}
|
||||||
|
|
||||||
return &podinfoImage{
|
return &podinfoImage{
|
||||||
url: repositoryURL,
|
url: "oci://" + repositoryURL,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
digest: podinfoImageDigest,
|
digest: podinfoImageDigest,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
Loading…
Reference in New Issue