@@ -40,6 +40,7 @@ import (
4040 "github.com/google/go-containerregistry/pkg/v1/remote"
4141 "github.com/notaryproject/notation-go/verifier/trustpolicy"
4242 "github.com/sigstore/cosign/v3/pkg/cosign"
43+ "helm.sh/helm/v4/pkg/registry"
4344 corev1 "k8s.io/api/core/v1"
4445 "k8s.io/apimachinery/pkg/runtime"
4546 "k8s.io/apimachinery/pkg/types"
@@ -871,7 +872,7 @@ func (r *OCIRepositoryReconciler) getArtifactRef(obj *sourcev1.OCIRepository, op
871872 }
872873
873874 if obj .Spec .Reference .SemVer != "" {
874- return r .getTagBySemver (repo , obj .Spec .Reference .SemVer , filterTags (obj .Spec .Reference .SemverFilter ), options )
875+ return r .getTagBySemver (repo , obj .Spec .Reference .SemVer , filterTags (obj .Spec .Reference .SemverFilter ), obj . GetLayerMediaType (), options )
875876 }
876877
877878 if obj .Spec .Reference .Tag != "" {
@@ -884,7 +885,7 @@ func (r *OCIRepositoryReconciler) getArtifactRef(obj *sourcev1.OCIRepository, op
884885
885886// getTagBySemver call the remote container registry, fetches all the tags from the repository,
886887// and returns the latest tag according to the semver expression.
887- func (r * OCIRepositoryReconciler ) getTagBySemver (repo name.Repository , exp string , filter filterFunc , options []remote.Option ) (name.Reference , error ) {
888+ func (r * OCIRepositoryReconciler ) getTagBySemver (repo name.Repository , exp string , filter filterFunc , mediaType string , options []remote.Option ) (name.Reference , error ) {
888889 tags , err := remote .List (repo , options ... )
889890 if err != nil {
890891 return nil , err
@@ -901,8 +902,9 @@ func (r *OCIRepositoryReconciler) getTagBySemver(repo name.Repository, exp strin
901902 }
902903
903904 var matchingVersions []* semver.Version
904- for _ , t := range validTags {
905- v , err := version .ParseVersion (t )
905+ for _ , ociTag := range validTags {
906+ semVerTag := convertOCIToSemVerTag (ociTag , mediaType )
907+ v , err := version .ParseVersion (semVerTag )
906908 if err != nil {
907909 continue
908910 }
@@ -916,8 +918,42 @@ func (r *OCIRepositoryReconciler) getTagBySemver(repo name.Repository, exp strin
916918 return nil , fmt .Errorf ("no match found for semver: %s" , exp )
917919 }
918920
921+ // Find the latest SemVer.
919922 sort .Sort (sort .Reverse (semver .Collection (matchingVersions )))
920- return repo .Tag (matchingVersions [0 ].Original ()), nil
923+ semVerTag := matchingVersions [0 ].Original ()
924+
925+ // Convert the latest SemVer to an OCI tag and return the reference.
926+ ociTag := convertSemVerToOCITag (semVerTag , mediaType )
927+ return repo .Tag (ociTag ), nil
928+ }
929+
930+ // convertSemVerToOCITag converts a SemVer tag to an OCI tag
931+ // according to rules defined by the media type.
932+ //
933+ // For OCI Helm charts, the conversion is mapping `+` to `_`,
934+ // because `+` is not permitted in OCI tags, while `_` is not
935+ // permitted in SemVer. Each character not being permitted in
936+ // one of the two sides establishes a perfect bijection between,
937+ // which then makes the mapping implemented by Helm (and honored
938+ // here) completely safe.
939+ func convertSemVerToOCITag (semVer , mediaType string ) string {
940+ if mediaType == registry .ChartLayerMediaType {
941+ return strings .ReplaceAll (semVer , "+" , "_" )
942+ }
943+ return semVer
944+ }
945+
946+ // convertOCIToSemVerTag converts an OCI tag to a SemVer tag
947+ // according to rules defined by the media type.
948+ //
949+ // For OCI Helm charts, the conversion is mapping `_` to `+`,
950+ // see the comment above on convertSemVerToOCITag for the
951+ // mapping in the opposite direction and rationale.
952+ func convertOCIToSemVerTag (ociTag , mediaType string ) string {
953+ if mediaType == registry .ChartLayerMediaType {
954+ return strings .ReplaceAll (ociTag , "_" , "+" )
955+ }
956+ return ociTag
921957}
922958
923959// keychain generates the credential keychain based on the resource
0 commit comments