From d6a4b3261838e1246cf4d4b08bfc6cc2e7328de9 Mon Sep 17 00:00:00 2001 From: Riya <108456994+rg3122@users.noreply.github.com> Date: Fri, 23 Oct 2026 22:46:29 +0530 Subject: [PATCH 1/3] fix: select Helm chart layer after provenance Signed-off-by: Riya --- internal/controller/ocirepository_controller.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/internal/controller/ocirepository_controller.go b/internal/controller/ocirepository_controller.go index 5329432e3..f9a156ea2 100644 --- a/internal/controller/ocirepository_controller.go +++ b/internal/controller/ocirepository_controller.go @@ -677,6 +677,23 @@ func (r *OCIRepositoryReconciler) selectLayer(obj *sourcev1.OCIRepository, image } default: layer = layers[0] + // Helm OCI charts may put the provenance file before the chart layer. + // When no explicit selector is configured, prefer the chart content + // layer in that case so extraction does not try to untar the .prov file. + if mediaType, err := layer.MediaType(); err != nil { + return nil, fmt.Errorf("failed to determine the media type of first layer from artifact: %w", err) + } else if string(mediaType) == "application/vnd.cncf.helm.chart.provenance.v1.prov" { + for i, candidate := range layers[1:] { + md, err := candidate.MediaType() + if err != nil { + return nil, fmt.Errorf("failed to determine the media type of layer[%v] from artifact: %w", i+1, err) + } + if string(md) == "application/vnd.cncf.helm.chart.content.v1.tar+gzip" { + layer = candidate + break + } + } + } } blob, err := layer.Compressed() From 60ff2050a0c18bceda1857edd10a6c05c3700c0f Mon Sep 17 00:00:00 2001 From: Riya <108456994+rg3122@users.noreply.github.com> Date: Fri, 23 Oct 2026 22:46:36 +0530 Subject: [PATCH 2/3] test: cover provenance-first Helm OCI layers Signed-off-by: Riya --- ...repository_controller_select_layer_test.go | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 internal/controller/ocirepository_controller_select_layer_test.go diff --git a/internal/controller/ocirepository_controller_select_layer_test.go b/internal/controller/ocirepository_controller_select_layer_test.go new file mode 100644 index 000000000..d455be90c --- /dev/null +++ b/internal/controller/ocirepository_controller_select_layer_test.go @@ -0,0 +1,77 @@ +/* +Copyright 2026 The Flux authors + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "io" + "testing" + + sourcev1 "github.com/fluxcd/source-controller/api/v1" + "github.com/google/go-containerregistry/pkg/v1/empty" + "github.com/google/go-containerregistry/pkg/v1/mutate" + "github.com/google/go-containerregistry/pkg/v1/static" + gcrv1 "github.com/google/go-containerregistry/pkg/v1" + "github.com/google/go-containerregistry/pkg/v1/types" + . "github.com/onsi/gomega" +) + +const ( + helmChartLayerMediaType = "application/vnd.cncf.helm.chart.content.v1.tar+gzip" + helmProvenanceLayerMediaType = "application/vnd.cncf.helm.chart.provenance.v1.prov" +) + +func TestSelectLayerHelmProvenanceFirst(t *testing.T) { + g := NewWithT(t) + + provenance := []byte("provenance file") + chart := []byte("chart archive") + provenanceLayer := static.NewLayer(provenance, types.MediaType(helmProvenanceLayerMediaType)) + chartLayer := static.NewLayer(chart, types.MediaType(helmChartLayerMediaType)) + img, err := mutate.AppendLayers(empty.Image, provenanceLayer, chartLayer) + g.Expect(err).NotTo(HaveOccurred()) + + reconciler := &OCIRepositoryReconciler{} + obj := &sourcev1.OCIRepository{} + blob, err := reconciler.selectLayer(obj, gcrv1.Image(img)) + g.Expect(err).NotTo(HaveOccurred()) + defer blob.Close() + + got, err := io.ReadAll(blob) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(got).To(Equal(chart)) +} + +func TestSelectLayerHelmChartFirstRemainsUnchanged(t *testing.T) { + g := NewWithT(t) + + chart := []byte("chart archive") + provenance := []byte("provenance file") + chartLayer := static.NewLayer(chart, types.MediaType(helmChartLayerMediaType)) + provenanceLayer := static.NewLayer(provenance, types.MediaType(helmProvenanceLayerMediaType)) + img, err := mutate.AppendLayers(empty.Image, chartLayer, provenanceLayer) + g.Expect(err).NotTo(HaveOccurred()) + + reconciler := &OCIRepositoryReconciler{} + obj := &sourcev1.OCIRepository{} + blob, err := reconciler.selectLayer(obj, gcrv1.Image(img)) + g.Expect(err).NotTo(HaveOccurred()) + defer blob.Close() + + got, err := io.ReadAll(blob) + g.Expect(err).NotTo(HaveOccurred()) + g.Expect(got).To(Equal(chart)) +} From 69330f664259e49a0e39048bec68ee208325459a Mon Sep 17 00:00:00 2001 From: Riya <108456994+rg3122@users.noreply.github.com> Date: Tue, 3 Nov 2026 18:43:03 +0530 Subject: [PATCH 3/3] test: gofmt OCI layer selection tests Signed-off-by: Riya --- .../controller/ocirepository_controller_select_layer_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/controller/ocirepository_controller_select_layer_test.go b/internal/controller/ocirepository_controller_select_layer_test.go index d455be90c..c37c084d1 100644 --- a/internal/controller/ocirepository_controller_select_layer_test.go +++ b/internal/controller/ocirepository_controller_select_layer_test.go @@ -21,10 +21,10 @@ import ( "testing" sourcev1 "github.com/fluxcd/source-controller/api/v1" + gcrv1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/empty" "github.com/google/go-containerregistry/pkg/v1/mutate" "github.com/google/go-containerregistry/pkg/v1/static" - gcrv1 "github.com/google/go-containerregistry/pkg/v1" "github.com/google/go-containerregistry/pkg/v1/types" . "github.com/onsi/gomega" )