Skip to content

Commit 33dd859

Browse files
authored
Merge pull request #1244 from errordeveloper/switch-crane-v1-remote
Switch from `crane` package to `remote`
2 parents 53ee3a3 + a5ec631 commit 33dd859

File tree

2 files changed

+107
-130
lines changed

2 files changed

+107
-130
lines changed

internal/controller/ocirepository_controller.go

+81-114
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package controller
1818

1919
import (
2020
"context"
21+
cryptotls "crypto/tls"
2122
"errors"
2223
"fmt"
2324
"io"
@@ -31,9 +32,9 @@ import (
3132
"github.com/Masterminds/semver/v3"
3233
"github.com/google/go-containerregistry/pkg/authn"
3334
"github.com/google/go-containerregistry/pkg/authn/k8schain"
34-
"github.com/google/go-containerregistry/pkg/crane"
3535
"github.com/google/go-containerregistry/pkg/name"
3636
gcrv1 "github.com/google/go-containerregistry/pkg/v1"
37+
v1 "github.com/google/go-containerregistry/pkg/v1"
3738
"github.com/google/go-containerregistry/pkg/v1/remote"
3839
corev1 "k8s.io/api/core/v1"
3940
"k8s.io/apimachinery/pkg/runtime"
@@ -369,10 +370,10 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
369370
return sreconcile.ResultEmpty, e
370371
}
371372

372-
opts := makeRemoteOptions(ctx, obj, transport, keychain, auth)
373+
opts := makeRemoteOptions(ctx, transport, keychain, auth)
373374

374375
// Determine which artifact revision to pull
375-
url, err := r.getArtifactURL(obj, opts.craneOpts)
376+
ref, err := r.getArtifactRef(obj, opts)
376377
if err != nil {
377378
if _, ok := err.(invalidOCIURLError); ok {
378379
e := serror.NewStalling(
@@ -390,7 +391,8 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
390391
}
391392

392393
// Get the upstream revision from the artifact digest
393-
revision, err := r.getRevision(url, opts.craneOpts)
394+
// TODO: getRevision resolves the digest, which may change before image is fetched, so it should probaly update ref
395+
revision, err := r.getRevision(ref, opts)
394396
if err != nil {
395397
e := serror.NewGeneric(
396398
fmt.Errorf("failed to determine artifact digest: %w", err),
@@ -405,7 +407,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
405407
// Mark observations about the revision on the object
406408
defer func() {
407409
if !obj.GetArtifact().HasRevision(revision) {
408-
message := fmt.Sprintf("new revision '%s' for '%s'", revision, url)
410+
message := fmt.Sprintf("new revision '%s' for '%s'", revision, ref)
409411
if obj.GetArtifact() != nil {
410412
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "NewRevision", message)
411413
}
@@ -428,7 +430,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
428430
conditions.GetObservedGeneration(obj, sourcev1.SourceVerifiedCondition) != obj.Generation ||
429431
conditions.IsFalse(obj, sourcev1.SourceVerifiedCondition) {
430432

431-
err := r.verifySignature(ctx, obj, url, opts.verifyOpts...)
433+
err := r.verifySignature(ctx, obj, ref, opts...)
432434
if err != nil {
433435
provider := obj.Spec.Verify.Provider
434436
if obj.Spec.Verify.SecretRef == nil {
@@ -453,7 +455,7 @@ func (r *OCIRepositoryReconciler) reconcileSource(ctx context.Context, sp *patch
453455
}
454456

455457
// Pull artifact from the remote container registry
456-
img, err := crane.Pull(url, opts.craneOpts...)
458+
img, err := remote.Image(ref, opts...)
457459
if err != nil {
458460
e := serror.NewGeneric(
459461
fmt.Errorf("failed to pull artifact from '%s': %w", obj.Spec.URL, err),
@@ -573,37 +575,31 @@ func (r *OCIRepositoryReconciler) selectLayer(obj *ociv1.OCIRepository, image gc
573575

574576
// getRevision fetches the upstream digest, returning the revision in the
575577
// format '<tag>@<digest>'.
576-
func (r *OCIRepositoryReconciler) getRevision(url string, options []crane.Option) (string, error) {
577-
ref, err := name.ParseReference(url)
578-
if err != nil {
579-
return "", err
580-
}
581-
582-
repoTag := ""
583-
repoName := strings.TrimPrefix(url, ref.Context().RegistryStr())
584-
if s := strings.Split(repoName, ":"); len(s) == 2 && !strings.Contains(repoName, "@") {
585-
repoTag = s[1]
586-
}
587-
588-
if repoTag == "" && !strings.Contains(repoName, "@") {
589-
repoTag = "latest"
590-
}
591-
592-
digest, err := crane.Digest(url, options...)
593-
if err != nil {
594-
return "", err
595-
}
596-
597-
digestHash, err := gcrv1.NewHash(digest)
598-
if err != nil {
599-
return "", err
600-
}
578+
func (r *OCIRepositoryReconciler) getRevision(ref name.Reference, options []remote.Option) (string, error) {
579+
switch ref := ref.(type) {
580+
case name.Digest:
581+
digest, err := v1.NewHash(ref.DigestStr())
582+
if err != nil {
583+
return "", err
584+
}
585+
return digest.String(), nil
586+
case name.Tag:
587+
var digest v1.Hash
601588

602-
revision := digestHash.String()
603-
if repoTag != "" {
604-
revision = fmt.Sprintf("%s@%s", repoTag, revision)
589+
desc, err := remote.Head(ref, options...)
590+
if err == nil {
591+
digest = desc.Digest
592+
} else {
593+
rdesc, err := remote.Get(ref, options...)
594+
if err != nil {
595+
return "", err
596+
}
597+
digest = rdesc.Descriptor.Digest
598+
}
599+
return fmt.Sprintf("%s@%s", ref.TagStr(), digest.String()), nil
600+
default:
601+
return "", fmt.Errorf("unsupported reference type: %T", ref)
605602
}
606-
return revision, nil
607603
}
608604

609605
// digestFromRevision extracts the digest from the revision string.
@@ -615,7 +611,7 @@ func (r *OCIRepositoryReconciler) digestFromRevision(revision string) string {
615611
// verifySignature verifies the authenticity of the given image reference URL.
616612
// First, it tries to use a key if a Secret with a valid public key is provided.
617613
// If not, it falls back to a keyless approach for verification.
618-
func (r *OCIRepositoryReconciler) verifySignature(ctx context.Context, obj *ociv1.OCIRepository, url string, opt ...remote.Option) error {
614+
func (r *OCIRepositoryReconciler) verifySignature(ctx context.Context, obj *ociv1.OCIRepository, ref name.Reference, opt ...remote.Option) error {
619615
ctxTimeout, cancel := context.WithTimeout(ctx, obj.Spec.Timeout.Duration)
620616
defer cancel()
621617

@@ -626,15 +622,6 @@ func (r *OCIRepositoryReconciler) verifySignature(ctx context.Context, obj *ociv
626622
soci.WithRemoteOptions(opt...),
627623
}
628624

629-
var nameOpts []name.Option
630-
if obj.Spec.Insecure {
631-
nameOpts = append(nameOpts, name.Insecure)
632-
}
633-
ref, err := name.ParseReference(url, nameOpts...)
634-
if err != nil {
635-
return err
636-
}
637-
638625
// get the public keys from the given secret
639626
if secretRef := obj.Spec.Verify.SecretRef; secretRef != nil {
640627
certSecretName := types.NamespacedName{
@@ -669,7 +656,7 @@ func (r *OCIRepositoryReconciler) verifySignature(ctx context.Context, obj *ociv
669656
}
670657

671658
if !signatureVerified {
672-
return fmt.Errorf("no matching signatures were found for '%s'", url)
659+
return fmt.Errorf("no matching signatures were found for '%s'", ref)
673660
}
674661

675662
return nil
@@ -691,71 +678,72 @@ func (r *OCIRepositoryReconciler) verifySignature(ctx context.Context, obj *ociv
691678
return nil
692679
}
693680

694-
return fmt.Errorf("no matching signatures were found for '%s'", url)
681+
return fmt.Errorf("no matching signatures were found for '%s'", ref)
695682
}
696683

697684
return nil
698685
}
699686

700-
// parseRepositoryURL validates and extracts the repository URL.
701-
func (r *OCIRepositoryReconciler) parseRepositoryURL(obj *ociv1.OCIRepository) (string, error) {
687+
// parseRepository validates and extracts the repository URL.
688+
func (r *OCIRepositoryReconciler) parseRepository(obj *ociv1.OCIRepository) (name.Repository, error) {
702689
if !strings.HasPrefix(obj.Spec.URL, ociv1.OCIRepositoryPrefix) {
703-
return "", fmt.Errorf("URL must be in format 'oci://<domain>/<org>/<repo>'")
690+
return name.Repository{}, fmt.Errorf("URL must be in format 'oci://<domain>/<org>/<repo>'")
704691
}
705692

706693
url := strings.TrimPrefix(obj.Spec.URL, ociv1.OCIRepositoryPrefix)
707-
ref, err := name.ParseReference(url)
694+
695+
options := []name.Option{}
696+
if obj.Spec.Insecure {
697+
options = append(options, name.Insecure)
698+
}
699+
repo, err := name.NewRepository(url, options...)
708700
if err != nil {
709-
return "", err
701+
return name.Repository{}, err
710702
}
711703

712-
imageName := strings.TrimPrefix(url, ref.Context().RegistryStr())
704+
imageName := strings.TrimPrefix(url, repo.RegistryStr())
713705
if s := strings.Split(imageName, ":"); len(s) > 1 {
714-
return "", fmt.Errorf("URL must not contain a tag; remove ':%s'", s[1])
706+
return name.Repository{}, fmt.Errorf("URL must not contain a tag; remove ':%s'", s[1])
715707
}
716708

717-
return ref.Context().Name(), nil
709+
return repo, nil
718710
}
719711

720-
// getArtifactURL determines which tag or revision should be used and returns the OCI artifact FQN.
721-
func (r *OCIRepositoryReconciler) getArtifactURL(obj *ociv1.OCIRepository, options []crane.Option) (string, error) {
722-
url, err := r.parseRepositoryURL(obj)
712+
// getArtifactRef determines which tag or revision should be used and returns the OCI artifact FQN.
713+
func (r *OCIRepositoryReconciler) getArtifactRef(obj *ociv1.OCIRepository, options []remote.Option) (name.Reference, error) {
714+
repo, err := r.parseRepository(obj)
723715
if err != nil {
724-
return "", invalidOCIURLError{err}
716+
return nil, invalidOCIURLError{err}
725717
}
726718

727719
if obj.Spec.Reference != nil {
728720
if obj.Spec.Reference.Digest != "" {
729-
return fmt.Sprintf("%s@%s", url, obj.Spec.Reference.Digest), nil
721+
return repo.Digest(obj.Spec.Reference.Digest), nil
730722
}
731723

732724
if obj.Spec.Reference.SemVer != "" {
733-
tag, err := r.getTagBySemver(url, obj.Spec.Reference.SemVer, options)
734-
if err != nil {
735-
return "", err
736-
}
737-
return fmt.Sprintf("%s:%s", url, tag), nil
725+
return r.getTagBySemver(repo, obj.Spec.Reference.SemVer, options)
738726
}
739727

740728
if obj.Spec.Reference.Tag != "" {
741-
return fmt.Sprintf("%s:%s", url, obj.Spec.Reference.Tag), nil
729+
return repo.Tag(obj.Spec.Reference.Tag), nil
742730
}
743731
}
744732

745-
return url, nil
733+
return repo.Tag(name.DefaultTag), nil
746734
}
747735

748736
// getTagBySemver call the remote container registry, fetches all the tags from the repository,
749737
// and returns the latest tag according to the semver expression.
750-
func (r *OCIRepositoryReconciler) getTagBySemver(url, exp string, options []crane.Option) (string, error) {
751-
tags, err := crane.ListTags(url, options...)
738+
func (r *OCIRepositoryReconciler) getTagBySemver(repo name.Repository, exp string, options []remote.Option) (name.Reference, error) {
739+
tags, err := remote.List(repo, options...)
752740
if err != nil {
753-
return "", err
741+
return nil, err
754742
}
755743

756744
constraint, err := semver.NewConstraint(exp)
757745
if err != nil {
758-
return "", fmt.Errorf("semver '%s' parse error: %w", exp, err)
746+
return nil, fmt.Errorf("semver '%s' parse error: %w", exp, err)
759747
}
760748

761749
var matchingVersions []*semver.Version
@@ -771,11 +759,11 @@ func (r *OCIRepositoryReconciler) getTagBySemver(url, exp string, options []cran
771759
}
772760

773761
if len(matchingVersions) == 0 {
774-
return "", fmt.Errorf("no match found for semver: %s", exp)
762+
return nil, fmt.Errorf("no match found for semver: %s", exp)
775763
}
776764

777765
sort.Sort(sort.Reverse(semver.Collection(matchingVersions)))
778-
return matchingVersions[0].Original(), nil
766+
return repo.Tag(matchingVersions[0].Original()), nil
779767
}
780768

781769
// keychain generates the credential keychain based on the resource
@@ -825,9 +813,16 @@ func (r *OCIRepositoryReconciler) keychain(ctx context.Context, obj *ociv1.OCIRe
825813

826814
// transport clones the default transport from remote and when a certSecretRef is specified,
827815
// the returned transport will include the TLS client and/or CA certificates.
828-
func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *ociv1.OCIRepository) (http.RoundTripper, error) {
816+
func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *ociv1.OCIRepository) (*http.Transport, error) {
817+
transport := remote.DefaultTransport.(*http.Transport).Clone()
818+
829819
if obj.Spec.CertSecretRef == nil || obj.Spec.CertSecretRef.Name == "" {
830-
return nil, nil
820+
if obj.Spec.Insecure {
821+
transport.TLSClientConfig = &cryptotls.Config{
822+
InsecureSkipVerify: true,
823+
}
824+
}
825+
return transport, nil
831826
}
832827

833828
certSecretName := types.NamespacedName{
@@ -839,7 +834,6 @@ func (r *OCIRepositoryReconciler) transport(ctx context.Context, obj *ociv1.OCIR
839834
return nil, err
840835
}
841836

842-
transport := remote.DefaultTransport.(*http.Transport).Clone()
843837
tlsConfig, _, err := tls.KubeTLSClientConfigFromSecret(certSecret, "")
844838
if err != nil {
845839
return nil, err
@@ -1155,55 +1149,28 @@ func (r *OCIRepositoryReconciler) notify(ctx context.Context, oldObj, newObj *oc
11551149
}
11561150
}
11571151

1158-
// craneOptions sets the auth headers, timeout and user agent
1159-
// for all operations against remote container registries.
1160-
func craneOptions(ctx context.Context, insecure bool) []crane.Option {
1161-
options := []crane.Option{
1162-
crane.WithContext(ctx),
1163-
crane.WithUserAgent(oci.UserAgent),
1164-
}
1165-
1166-
if insecure {
1167-
options = append(options, crane.Insecure)
1168-
}
1169-
1170-
return options
1171-
}
1172-
11731152
// makeRemoteOptions returns a remoteOptions struct with the authentication and transport options set.
11741153
// The returned struct can be used to interact with a remote registry using go-containerregistry based libraries.
1175-
func makeRemoteOptions(ctxTimeout context.Context, obj *ociv1.OCIRepository, transport http.RoundTripper,
1154+
func makeRemoteOptions(ctxTimeout context.Context, transport http.RoundTripper,
11761155
keychain authn.Keychain, auth authn.Authenticator) remoteOptions {
1177-
o := remoteOptions{
1178-
craneOpts: craneOptions(ctxTimeout, obj.Spec.Insecure),
1179-
verifyOpts: []remote.Option{},
1180-
}
1181-
1182-
if transport != nil {
1183-
o.craneOpts = append(o.craneOpts, crane.WithTransport(transport))
1184-
o.verifyOpts = append(o.verifyOpts, remote.WithTransport(transport))
1185-
}
11861156

1157+
authOption := remote.WithAuthFromKeychain(keychain)
11871158
if auth != nil {
11881159
// auth take precedence over keychain here as we expect the caller to set
11891160
// the auth only if it is required.
1190-
o.verifyOpts = append(o.verifyOpts, remote.WithAuth(auth))
1191-
o.craneOpts = append(o.craneOpts, crane.WithAuth(auth))
1192-
return o
1161+
authOption = remote.WithAuth(auth)
1162+
}
1163+
return remoteOptions{
1164+
remote.WithContext(ctxTimeout),
1165+
remote.WithUserAgent(oci.UserAgent),
1166+
remote.WithTransport(transport),
1167+
authOption,
11931168
}
1194-
1195-
o.verifyOpts = append(o.verifyOpts, remote.WithAuthFromKeychain(keychain))
1196-
o.craneOpts = append(o.craneOpts, crane.WithAuthFromKeychain(keychain))
1197-
1198-
return o
11991169
}
12001170

12011171
// remoteOptions contains the options to interact with a remote registry.
12021172
// It can be used to pass options to go-containerregistry based libraries.
1203-
type remoteOptions struct {
1204-
craneOpts []crane.Option
1205-
verifyOpts []remote.Option
1206-
}
1173+
type remoteOptions []remote.Option
12071174

12081175
// ociContentConfigChanged evaluates the current spec with the observations
12091176
// of the artifact in the status to determine if artifact content configuration

0 commit comments

Comments
 (0)