4444 indexLock = sync .NewKeyLock ()
4545)
4646
47+ const (
48+ helmOCIConfigType = "application/vnd.cncf.helm.config.v1+json"
49+ helmOCILayerType = "application/vnd.cncf.helm.chart.content.v1.tar+gzip"
50+ )
51+
4752var _ Client = & nativeOCIClient {}
4853
4954type tagsCache interface {
@@ -260,6 +265,8 @@ func (c *nativeOCIClient) extract(ctx context.Context, digest string) (string, u
260265 return "" , nil , err
261266 }
262267
268+ var isHelmChart bool
269+
263270 if ! exists {
264271 ociManifest , err := getOCIManifest (ctx , digest , c .repo )
265272 if err != nil {
@@ -272,16 +279,25 @@ func (c *nativeOCIClient) extract(ctx context.Context, digest string) (string, u
272279 return "" , nil , fmt .Errorf ("expected no more than 10 oci layers, got %d" , len (ociManifest .Layers ))
273280 }
274281
282+ isHelmChart = ociManifest .Config .MediaType == helmOCIConfigType
283+
275284 contentLayers := 0
276285
277286 // Strictly speaking we only allow for a single content layer. There are images which contains extra layers, such
278287 // as provenance/attestation layers. Pending a better story to do this natively, we will skip such layers for now.
279288 for _ , layer := range ociManifest .Layers {
280- if isContentLayer (layer .MediaType ) {
289+ // For Helm charts, only look for the specific Helm chart content layer
290+ if isHelmChart {
291+ if isHelmOCI (layer .MediaType ) {
292+ if ! slices .Contains (c .allowedMediaTypes , layer .MediaType ) {
293+ return "" , nil , fmt .Errorf ("oci layer media type %s is not in the list of allowed media types" , layer .MediaType )
294+ }
295+ contentLayers ++
296+ }
297+ } else if isContentLayer (layer .MediaType ) {
281298 if ! slices .Contains (c .allowedMediaTypes , layer .MediaType ) {
282299 return "" , nil , fmt .Errorf ("oci layer media type %s is not in the list of allowed media types" , layer .MediaType )
283300 }
284-
285301 contentLayers ++
286302 }
287303 }
@@ -301,7 +317,15 @@ func (c *nativeOCIClient) extract(ctx context.Context, digest string) (string, u
301317 maxSize = math .MaxInt64
302318 }
303319
304- manifestsDir , err := extractContentToManifestsDir (ctx , cachedPath , digest , maxSize )
320+ if ! isHelmChart {
321+ // Get the manifest to determine if it's a Helm chart for extraction
322+ ociManifest , err := getOCIManifestFromCache (ctx , cachedPath , digest )
323+ if err != nil {
324+ return "" , nil , fmt .Errorf ("error getting oci manifest for extraction: %w" , err )
325+ }
326+ isHelmChart = ociManifest .Config .MediaType == helmOCIConfigType
327+ }
328+ manifestsDir , err := extractContentToManifestsDir (ctx , cachedPath , digest , maxSize , isHelmChart )
305329 if err != nil {
306330 return manifestsDir , nil , fmt .Errorf ("cannot extract contents of oci image with revision %s: %w" , digest , err )
307331 }
@@ -345,13 +369,7 @@ func (c *nativeOCIClient) digestMetadata(ctx context.Context, digest string) (*i
345369 if err != nil {
346370 return nil , fmt .Errorf ("error fetching oci metadata path for digest %s: %w" , digest , err )
347371 }
348-
349- repo , err := oci .NewFromTar (ctx , path )
350- if err != nil {
351- return nil , fmt .Errorf ("error extracting oci image for digest %s: %w" , digest , err )
352- }
353-
354- return getOCIManifest (ctx , digest , repo )
372+ return getOCIManifestFromCache (ctx , path , digest )
355373}
356374
357375func (c * nativeOCIClient ) ResolveRevision (ctx context.Context , revision string , noCache bool ) (string , error ) {
@@ -543,8 +561,8 @@ func saveCompressedImageToPath(ctx context.Context, digest string, repo oras.Rea
543561}
544562
545563// extractContentToManifestsDir looks up a locally stored OCI image, and extracts the embedded compressed layer which contains
546- // K8s manifests to a temporary directory
547- func extractContentToManifestsDir (ctx context.Context , cachedPath , digest string , maxSize int64 ) (string , error ) {
564+ // K8s manifests to a temp dir.
565+ func extractContentToManifestsDir (ctx context.Context , cachedPath , digest string , maxSize int64 , isHelmChart bool ) (string , error ) {
548566 manifestsDir , err := files .CreateTempDir (os .TempDir ())
549567 if err != nil {
550568 return manifestsDir , err
@@ -561,7 +579,7 @@ func extractContentToManifestsDir(ctx context.Context, cachedPath, digest string
561579 }
562580 defer os .RemoveAll (tempDir )
563581
564- fs , err := newCompressedLayerFileStore (manifestsDir , tempDir , maxSize )
582+ fs , err := newCompressedLayerFileStore (manifestsDir , tempDir , maxSize , isHelmChart )
565583 if err != nil {
566584 return manifestsDir , err
567585 }
@@ -574,26 +592,32 @@ func extractContentToManifestsDir(ctx context.Context, cachedPath, digest string
574592
575593type compressedLayerExtracterStore struct {
576594 * file.Store
577- dest string
578- maxSize int64
595+ dest string
596+ maxSize int64
597+ isHelmChart bool
579598}
580599
581- func newCompressedLayerFileStore (dest , tempDir string , maxSize int64 ) (* compressedLayerExtracterStore , error ) {
600+ func newCompressedLayerFileStore (dest , tempDir string , maxSize int64 , isHelmChart bool ) (* compressedLayerExtracterStore , error ) {
582601 f , err := file .New (tempDir )
583602 if err != nil {
584603 return nil , err
585604 }
586605
587- return & compressedLayerExtracterStore {f , dest , maxSize }, nil
606+ return & compressedLayerExtracterStore {f , dest , maxSize , isHelmChart }, nil
588607}
589608
590609func isHelmOCI (mediaType string ) bool {
591- return mediaType == "application/vnd.cncf.helm.chart.content.v1.tar+gzip"
610+ return mediaType == helmOCILayerType
592611}
593612
594613// Push looks in all the layers of an OCI image. Once it finds a layer that is compressed, it extracts the layer to a tempDir
595614// and then renames the temp dir to the directory where the repo-server expects to find k8s manifests.
596615func (s * compressedLayerExtracterStore ) Push (ctx context.Context , desc imagev1.Descriptor , content io.Reader ) error {
616+ // For Helm charts, only extract the Helm chart content layer, skip all other layers
617+ if s .isHelmChart && ! isHelmOCI (desc .MediaType ) {
618+ return s .Store .Push (ctx , desc , content )
619+ }
620+
597621 if isContentLayer (desc .MediaType ) {
598622 srcDir , err := files .CreateTempDir (os .TempDir ())
599623 if err != nil {
@@ -682,6 +706,15 @@ func getOCIManifest(ctx context.Context, digest string, repo oras.ReadOnlyTarget
682706 return & manifest , nil
683707}
684708
709+ // getOCIManifestFromCache retrieves an OCI manifest from a cached tar file
710+ func getOCIManifestFromCache (ctx context.Context , cachedPath , digest string ) (* imagev1.Manifest , error ) {
711+ repo , err := oci .NewFromTar (ctx , cachedPath )
712+ if err != nil {
713+ return nil , fmt .Errorf ("error creating oci store from cache: %w" , err )
714+ }
715+ return getOCIManifest (ctx , digest , repo )
716+ }
717+
685718// WithEventHandlers sets the git client event handlers
686719func WithEventHandlers (handlers EventHandlers ) ClientOpts {
687720 return func (c * nativeOCIClient ) {
0 commit comments