Add `oci://` prefix

Signed-off-by: Stefan Prodan <stefan.prodan@gmail.com>
This commit is contained in:
Stefan Prodan 2022-07-05 15:27:45 +03:00
parent c9f5af7ddc
commit ded0c2d78b
No known key found for this signature in database
GPG Key ID: 3299AEB0E4085BAF
5 changed files with 41 additions and 13 deletions

View File

@ -25,12 +25,16 @@ import (
const (
// OCIRepositoryKind is the string representation of a OCIRepository.
OCIRepositoryKind = "OCIRepository"
// OCIRepositoryPrefix is the prefix used for OCIRepository URLs.
OCIRepositoryPrefix = "oci://"
)
// OCIRepositorySpec defines the desired state of OCIRepository
type OCIRepositorySpec struct {
// URL is a reference to an OCI artifact repository hosted
// on a remote container registry.
// +kubebuilder:validation:Pattern="^oci://"
// +required
URL string `json:"url"`

View File

@ -120,6 +120,7 @@ spec:
url:
description: URL is a reference to an OCI artifact repository hosted
on a remote container registry.
pattern: ^oci://
type: string
verify:
description: Verification specifies the configuration to verify the

View File

@ -4,6 +4,6 @@ metadata:
name: ocirepository-sample
spec:
interval: 1m
url: ghcr.io/stefanprodan/manifests/podinfo
url: oci://ghcr.io/stefanprodan/manifests/podinfo
ref:
tag: 6.1.6

View File

@ -20,8 +20,10 @@ import (
"context"
"errors"
"fmt"
"github.com/google/go-containerregistry/pkg/name"
"os"
"sort"
"strings"
"time"
"github.com/Masterminds/semver/v3"
@ -363,12 +365,31 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, obj *sour
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.
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.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 != "" {
@ -376,11 +397,11 @@ func (r *OCIRepositoryReconciler) getArtifactURL(ctx context.Context, obj *sourc
if err != nil {
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 != "" {
return fmt.Sprintf("%s:%s", obj.Spec.URL, obj.Spec.Reference.Tag), nil
return fmt.Sprintf("%s:%s", url, obj.Spec.Reference.Tag), nil
}
}

View File

@ -239,6 +239,7 @@ func TestOCIRepository_SecretRef(t *testing.T) {
g.Expect(err).ToNot(HaveOccurred())
repositoryURL := fmt.Sprintf("%s/podinfo", regServer.registryHost)
ociURL := fmt.Sprintf("oci://%s", repositoryURL)
// Push Test Image
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",
url: repositoryURL,
url: ociURL,
digest: podinfoImageDigest,
includeSecretRef: true,
includeServiceAccount: false,
},
{
name: "private-registry-access-via-serviceaccount",
url: repositoryURL,
url: ociURL,
digest: podinfoImageDigest,
includeSecretRef: false,
includeServiceAccount: true,
@ -289,7 +290,7 @@ func TestOCIRepository_SecretRef(t *testing.T) {
},
Type: corev1.SecretTypeDockerConfigJson,
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())
@ -435,6 +436,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
g.Expect(err).ToNot(HaveOccurred())
repositoryURL := fmt.Sprintf("%s/podinfo", regServer.registryHost)
ociURL := fmt.Sprintf("oci://%s", repositoryURL)
// Push Test Image
err = crane.Push(image, repositoryURL, crane.WithAuth(&authn.Basic{
@ -458,7 +460,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
}{
{
name: "missing-auth",
url: repositoryURL,
url: ociURL,
repoUsername: "",
repoPassword: "",
digest: podinfoImageDigest,
@ -467,7 +469,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
},
{
name: "invalid-auth-via-secret",
url: repositoryURL,
url: ociURL,
repoUsername: "InvalidUser",
repoPassword: "InvalidPassword",
digest: podinfoImageDigest,
@ -476,7 +478,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
},
{
name: "invalid-auth-via-service-account",
url: repositoryURL,
url: ociURL,
repoUsername: "InvalidUser",
repoPassword: "InvalidPassword",
digest: podinfoImageDigest,
@ -500,7 +502,7 @@ func TestOCIRepository_FailedAuth(t *testing.T) {
},
Type: corev1.SecretTypeDockerConfigJson,
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())
@ -623,7 +625,7 @@ func createPodinfoImageFromTar(tarFileName, tag string, imageServer *httptest.Se
}
return &podinfoImage{
url: repositoryURL,
url: "oci://" + repositoryURL,
tag: tag,
digest: podinfoImageDigest,
}, nil