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 ( 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"`

View File

@ -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

View File

@ -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

View File

@ -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
} }
} }

View File

@ -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