From 5789dbf655f34d947b637eadb9589a473585e40d Mon Sep 17 00:00:00 2001 From: Predrag Knezevic Date: Thu, 2 Jul 2026 11:28:03 +0200 Subject: [PATCH] fix: remove HelmChartSupport feature The HelmChartSupport feature gate (alpha, default off) and all code for detecting, pulling, caching, and loading native Helm chart bundles from OCI registries is removed. The path forward on the next bundle format is unclear and may not be Helm, so maintaining this code is not justified. The Helm backend that converts registry+v1 bundles into Helm chart objects for installation (RegistryV1HelmChartProvider) is unaffected. Co-Authored-By: Claude --- docs/draft/howto/enable-helm-chart-support.md | 415 ----------- helm/experimental.yaml | 1 - helm/olmv1/values.yaml | 1 - helm/tilt.yaml | 1 - internal/catalogd/graphql/graphql.go | 2 +- internal/operator-controller/applier/helm.go | 12 - .../operator-controller/features/features.go | 9 - internal/shared/util/image/cache.go | 79 +- internal/shared/util/image/cache_test.go | 186 ----- internal/shared/util/image/helm.go | 175 ----- internal/shared/util/image/helm_test.go | 683 ------------------ internal/shared/util/image/pull.go | 7 - manifests/experimental-e2e.yaml | 1 - manifests/experimental.yaml | 1 - manifests/standard-e2e.yaml | 1 - manifests/standard.yaml | 1 - test/e2e/steps/hooks.go | 1 - 17 files changed, 3 insertions(+), 1573 deletions(-) delete mode 100644 docs/draft/howto/enable-helm-chart-support.md delete mode 100644 internal/shared/util/image/helm.go delete mode 100644 internal/shared/util/image/helm_test.go diff --git a/docs/draft/howto/enable-helm-chart-support.md b/docs/draft/howto/enable-helm-chart-support.md deleted file mode 100644 index 44d083707c..0000000000 --- a/docs/draft/howto/enable-helm-chart-support.md +++ /dev/null @@ -1,415 +0,0 @@ -# How to Enable Helm Chart Support Feature Gate - -## Description - -This document outlines the steps to enable the Helm Chart support feature gate in the OLMv1 and subsequently deploy a Helm Chart to a Kubernetes cluster. It involves patching the `operator-controller-controller-manager` deployment to enable the `HelmChartSupport` feature, setting up a network policy for the registry, deploying an OCI registry, and finally creating a ClusterExtension to deploy the metrics server helm chart. - -The feature allows developers and end-users to deploy Helm charts from OCI registries through the `ClusterExtension` API. - -## Demos - -[![Helm Chart Support Demo](https://asciinema.org/a/wEzsqXLDAflJvzSX7QP47GvLw.svg)](https://asciinema.org/a/wEzsqXLDAflJvzSX7QP47GvLw) - - -## Enabling the Feature Gate - -To enable the Helm Chart support feature gate, you need to patch the `operator-controller-controller-manager` deployment in the `olmv1-system` namespace. This will add the `--feature-gates=HelmChartSupport=true` argument to the manager container. - -1. **Create a patch file:** - - ```bash - $ kubectl patch deployment -n olmv1-system operator-controller-controller-manager --type='json' -p='[{"op": "add", "path": "/spec/template/spec/containers/0/args/-", "value": "--feature-gates=HelmChartSupport=true"}]' - ``` - -2. **Wait for the controller manager pods to be ready:** - - ```bash - $ kubectl -n olmv1-system wait --for condition=ready pods -l apps.kubernetes.io/name=operator-controller - ``` - -Once the above wait condition is met, the `HelmChartSupport` feature gate should be enabled in operator controller. - -## Deploy an OCI Chart registry for testing - -With the operator-controller pod running with the `HelmChartSupport` feature gate enabled, you would need access to a Helm charts -hosted in an OCI registry. For this demo, the instructions will walk you through steps to deploy a registry in the `olmv1-system` -project. - -In addition to the OCI registry, you will need a ClusterCatalog in the Kubernetes cluster which will reference Helm charts in the OCI registry. - -1. **Configure network policy for the registry:** - - ```bash - $ cat << EOF | kubectl -n olmv1-system apply -f - - apiVersion: networking.k8s.io/v1 - kind: NetworkPolicy - metadata: - name: registry - spec: - egress: - - {} - ingress: - - ports: - - port: 8443 - protocol: TCP - podSelector: - matchLabels: - app: registry - policyTypes: - - Ingress - - Egress - EOF - ``` - -2. **Create certificates for the OCI registry:** - - ```bash - $ cat << EOF | kubectl -n olmv1-system apply -f - - --- - apiVersion: cert-manager.io/v1 - kind: Certificate - metadata: - name: registry-cert - namespace: olmv1-system - spec: - dnsNames: - - registry.olmv1-system.svc - - registry.olmv1-system.svc.cluster.local - issuerRef: - group: cert-manager.io - kind: ClusterIssuer - name: olmv1-ca - privateKey: - algorithm: RSA - encoding: PKCS1 - size: 2048 - secretName: registry-cert - status: {} - EOF - ``` - -3. **Deploy an OCI registry:** - - ```bash - $ cat << EOF | kubectl -n olmv1-system apply -f - - --- - apiVersion: apps/v1 - kind: Deployment - metadata: - creationTimestamp: null - labels: - app: registry - name: registry - spec: - replicas: 1 - selector: - matchLabels: - app: registry - strategy: {} - template: - metadata: - creationTimestamp: null - labels: - app: registry - spec: - containers: - - name: registry - image: docker.io/library/registry:3.0.0 - env: - - name: REGISTRY_HTTP_ADDR - value: "0.0.0.0:8443" - - name: REGISTRY_HTTP_TLS_CERTIFICATE - value: "/certs/tls.crt" - - name: REGISTRY_HTTP_TLS_KEY - value: "/certs/tls.key" - - name: OTEL_TRACES_EXPORTER - value: "none" - ports: - - name: registry - protocol: TCP - containerPort: 8443 - securityContext: - runAsUser: 999 - allowPrivilegeEscalation: false - runAsNonRoot: true - seccompProfile: - type: "RuntimeDefault" - capabilities: - drop: - - ALL - volumeMounts: - - name: blobs - mountPath: /var/lib/registry/docker - - name: certs - mountPath: /certs - resources: {} - volumes: - - name: blobs - emptyDir: {} - - name: certs - secret: - secretName: registry-cert - status: {} - EOF - ``` - -4. **Expose the registry container:** - - ```bash - $ cat << EOF | kubectl -n olmv1-system apply -f - - --- - apiVersion: v1 - kind: Service - metadata: - creationTimestamp: null - labels: - app: registry - name: registry - namespace: olmv1-system - spec: - ports: - - port: 443 - protocol: TCP - targetPort: 8443 - selector: - app: registry - status: - loadBalancer: {} - EOF - ``` - -5. **Wait for the registry pod to be in a Running phase:** - - ```bash - $ kubectl -n olmv1-system wait --for=jsonpath='{.status.phase}'=Running pod -l app=registry - ``` - -6. **Deploy the cluster catalog:** - - ```bash - $ cat << EOF | kubectl apply -f - - --- - apiVersion: olm.operatorframework.io/v1 - kind: ClusterCatalog - metadata: - name: metrics-server-operators - namespace: olmv1-system - spec: - priority: -100 - source: - image: - pollIntervalMinutes: 5 - ref: quay.io/eochieng/metrics-server-catalog:latest - type: Image - EOF - ``` - -7. **Upload charts to the registry:** - - ```bash - $ cat << EOF | kubectl apply -f - - --- - apiVersion: batch/v1 - kind: Job - metadata: - creationTimestamp: null - name: chart-uploader - spec: - template: - metadata: - creationTimestamp: null - spec: - containers: - - image: quay.io/eochieng/uploader:latest - name: chart-uploader - resources: {} - restartPolicy: Never - status: {} - EOF - ``` - -8. **Deploy metrics server RBAC and metrics server:** - - ```bash - $ cat << EOF | kubectl apply -f - - --- - apiVersion: v1 - kind: Namespace - metadata: - creationTimestamp: null - name: metrics-server-system - --- - apiVersion: v1 - kind: ServiceAccount - metadata: - creationTimestamp: null - name: metrics-server-installer - namespace: metrics-server-system - --- - apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRoleBinding - metadata: - creationTimestamp: null - name: metrics-server-crb - roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: metrics-server-cr - subjects: - - kind: ServiceAccount - name: metrics-server-installer - namespace: metrics-server-system - --- - apiVersion: rbac.authorization.k8s.io/v1 - kind: ClusterRole - metadata: - creationTimestamp: null - name: metrics-server-cr - rules: - - apiGroups: - - "" - resources: - - serviceaccounts - verbs: - - create - - delete - - list - - watch - - get - - patch - - update - - apiGroups: - - rbac.authorization.k8s.io - resources: - - clusterroles - - clusterrolebindings - - rolebindings - verbs: - - create - - delete - - list - - watch - - get - - patch - - update - - apiGroups: - - "" - resources: - - services - - secrets - verbs: - - get - - list - - watch - - create - - delete - - patch - - update - - apiGroups: - - apps - resources: - - deployments - - deployments/finalizers - verbs: - - get - - list - - watch - - create - - delete - - patch - - update - - apiGroups: - - apiregistration.k8s.io - resources: - - apiservices - verbs: - - get - - list - - watch - - create - - delete - - patch - - update - - apiGroups: - - olm.operatorframework.io - resources: - - clusterextensions - - clusterextensions/finalizers - verbs: - - get - - list - - watch - - create - - delete - - update - - patch - - apiGroups: - - metrics.k8s.io - resources: - - nodes - - pods - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - configmaps - - namespaces - - nodes - - pods - verbs: - - get - - list - - watch - - apiGroups: - - "" - resources: - - nodes/metrics - verbs: - - get - - apiGroups: - - authentication.k8s.io - resources: - - tokenreviews - verbs: - - create - - apiGroups: - - authorization.k8s.io - resources: - - subjectaccessreviews - verbs: - - create - EOF - ``` - -9. **Deploy metrics server cluster extension:** - - ```bash - $ cat << EOF | kubectl apply -f - - --- - apiVersion: olm.operatorframework.io/v1 - kind: ClusterExtension - metadata: - name: metrics-server - namespace: metrics-server-system - spec: - namespace: metrics-server-system - serviceAccount: - name: metrics-server-installer - source: - sourceType: Catalog - catalog: - packageName: metrics-server - version: 3.12.0 - EOF - ``` - -10. **Confirm the Helm chart has been deployed:** - - ```bash - $ kubectl get clusterextensions metrics-server - NAME INSTALLED BUNDLE VERSION INSTALLED PROGRESSING AGE - metrics-server metrics-server.v3.12.0 3.12.0 True True 4m40s - ``` diff --git a/helm/experimental.yaml b/helm/experimental.yaml index 53c636fded..cd93d76f7c 100644 --- a/helm/experimental.yaml +++ b/helm/experimental.yaml @@ -14,7 +14,6 @@ options: - BoxcutterRuntime - BundleReleaseSupport - DeploymentConfig - - HelmChartSupport - PreflightPermissions - SingleOwnNamespaceInstallSupport - WebhookProviderCertManager diff --git a/helm/olmv1/values.yaml b/helm/olmv1/values.yaml index 1458be7d2b..940023d09d 100644 --- a/helm/olmv1/values.yaml +++ b/helm/olmv1/values.yaml @@ -17,7 +17,6 @@ options: - BoxcutterRuntime - BundleReleaseSupport - DeploymentConfig - - HelmChartSupport - PreflightPermissions - SingleOwnNamespaceInstallSupport - SyntheticPermissions diff --git a/helm/tilt.yaml b/helm/tilt.yaml index aaed7c71fb..3a5cfab42f 100644 --- a/helm/tilt.yaml +++ b/helm/tilt.yaml @@ -16,7 +16,6 @@ options: enabled: - SingleOwnNamespaceInstallSupport - PreflightPermissions - - HelmChartSupport disabled: - WebhookProviderOpenshiftServiceCA catalogd: diff --git a/internal/catalogd/graphql/graphql.go b/internal/catalogd/graphql/graphql.go index 3e9e436766..908982c098 100644 --- a/internal/catalogd/graphql/graphql.go +++ b/internal/catalogd/graphql/graphql.go @@ -549,7 +549,7 @@ func BuildDynamicGraphQLSchema(catalogSchema *CatalogSchema, metasBySchema map[s objectType := objectType // Capture loop variable // Generate GraphQL field name from schema name // Convention: remove dots/special chars, lowercase, append 's' for pluralization - // Examples: "olm.bundle" -> "olmbundles", "helm.chart" -> "helmcharts" + // Examples: "olm.bundle" -> "olmbundles" // LIMITATION: Simple 's' appending doesn't follow English grammar rules or support // non-English languages. Schemas should use names that pluralize well with 's'. sanitized := alphanumericOnlyRE.ReplaceAllString(schemaName, "") diff --git a/internal/operator-controller/applier/helm.go b/internal/operator-controller/applier/helm.go index ab6e58a363..d155edd163 100644 --- a/internal/operator-controller/applier/helm.go +++ b/internal/operator-controller/applier/helm.go @@ -30,9 +30,7 @@ import ( "github.com/operator-framework/operator-controller/internal/operator-controller/authorization" "github.com/operator-framework/operator-controller/internal/operator-controller/contentmanager" "github.com/operator-framework/operator-controller/internal/operator-controller/contentmanager/cache" - "github.com/operator-framework/operator-controller/internal/operator-controller/features" "github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util" - imageutil "github.com/operator-framework/operator-controller/internal/shared/util/image" ) // HelmChartProvider provides helm charts from bundle sources and cluster extensions @@ -248,16 +246,6 @@ func (h *Helm) buildHelmChart(bundleFS fs.FS, ext *ocv1.ClusterExtension) (*char if h.HelmChartProvider == nil { return nil, errors.New("HelmChartProvider is nil") } - if features.OperatorControllerFeatureGate.Enabled(features.HelmChartSupport) { - meta := new(chart.Metadata) - if ok, _ := imageutil.IsBundleSourceChart(bundleFS, meta); ok { - return imageutil.LoadChartFSWithOptions( - bundleFS, - fmt.Sprintf("%s-%s.tgz", meta.Name, meta.Version), - imageutil.WithInstallNamespace(ext.Spec.Namespace), - ) - } - } return h.HelmChartProvider.Get(bundleFS, ext) } diff --git a/internal/operator-controller/features/features.go b/internal/operator-controller/features/features.go index 01e4fe4486..57f76c13bb 100644 --- a/internal/operator-controller/features/features.go +++ b/internal/operator-controller/features/features.go @@ -16,7 +16,6 @@ const ( SyntheticPermissions featuregate.Feature = "SyntheticPermissions" WebhookProviderCertManager featuregate.Feature = "WebhookProviderCertManager" WebhookProviderOpenshiftServiceCA featuregate.Feature = "WebhookProviderOpenshiftServiceCA" - HelmChartSupport featuregate.Feature = "HelmChartSupport" BoxcutterRuntime featuregate.Feature = "BoxcutterRuntime" DeploymentConfig featuregate.Feature = "DeploymentConfig" BundleReleaseSupport featuregate.Feature = "BundleReleaseSupport" @@ -68,14 +67,6 @@ var operatorControllerFeatureGates = map[featuregate.Feature]featuregate.Feature LockToDefault: false, }, - // HelmChartSupport enables support for installing, - // updating and uninstalling Helm Charts via Cluster Extensions. - HelmChartSupport: { - Default: false, - PreRelease: featuregate.Alpha, - LockToDefault: false, - }, - // BoxcutterRuntime configures OLM to use the Boxcutter runtime for extension lifecycling BoxcutterRuntime: { Default: false, diff --git a/internal/shared/util/image/cache.go b/internal/shared/util/image/cache.go index a82505ed5e..d9982b5efc 100644 --- a/internal/shared/util/image/cache.go +++ b/internal/shared/util/image/cache.go @@ -1,7 +1,6 @@ package image import ( - "bytes" "context" "errors" "fmt" @@ -11,19 +10,14 @@ import ( "os" "path/filepath" "slices" - "testing" "time" "github.com/containerd/containerd/archive" - "github.com/google/renameio/v2" "github.com/opencontainers/go-digest" ocispecv1 "github.com/opencontainers/image-spec/specs-go/v1" "go.podman.io/image/v5/docker/reference" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/registry" "sigs.k8s.io/controller-runtime/pkg/log" - "github.com/operator-framework/operator-controller/internal/operator-controller/features" errorutil "github.com/operator-framework/operator-controller/internal/shared/util/error" fsutil "github.com/operator-framework/operator-controller/internal/shared/util/fs" ) @@ -113,40 +107,6 @@ func (a *diskCache) unpackPath(ownerID string, digest digest.Digest) string { return filepath.Join(a.ownerIDPath(ownerID), digest.String()) } -type LayerUnpacker interface { - Unpack(_ context.Context, path string, layer LayerData, opts ...archive.ApplyOpt) error -} - -type defaultLayerUnpacker struct{} - -type chartLayerUnpacker struct{} - -var _ LayerUnpacker = &defaultLayerUnpacker{} -var _ LayerUnpacker = &chartLayerUnpacker{} - -func imageLayerUnpacker(layer LayerData) LayerUnpacker { - if features.OperatorControllerFeatureGate.Enabled(features.HelmChartSupport) || testing.Testing() { - if layer.MediaType == registry.ChartLayerMediaType { - return &chartLayerUnpacker{} - } - } - return &defaultLayerUnpacker{} -} - -func (u *chartLayerUnpacker) Unpack(_ context.Context, path string, layer LayerData, _ ...archive.ApplyOpt) error { - if err := storeChartLayer(path, layer); err != nil { - return fmt.Errorf("error applying chart layer[%d]: %w", layer.Index, err) - } - return nil -} - -func (u *defaultLayerUnpacker) Unpack(ctx context.Context, path string, layer LayerData, opts ...archive.ApplyOpt) error { - if _, err := archive.Apply(ctx, path, layer.Reader, opts...); err != nil { - return fmt.Errorf("error applying layer[%d]: %w", layer.Index, err) - } - return nil -} - func (a *diskCache) Store(ctx context.Context, ownerID string, srcRef reference.Named, canonicalRef reference.Canonical, imgCfg ocispecv1.Image, layers iter.Seq[LayerData]) (fs.FS, time.Time, error) { var applyOpts []archive.ApplyOpt if a.filterFunc != nil { @@ -169,9 +129,8 @@ func (a *diskCache) Store(ctx context.Context, ownerID string, srcRef reference. if layer.Err != nil { return fmt.Errorf("error reading layer[%d]: %w", layer.Index, layer.Err) } - layerUnpacker := imageLayerUnpacker(layer) - if err := layerUnpacker.Unpack(ctx, dest, layer, applyOpts...); err != nil { - return fmt.Errorf("unpacking layer: %w", err) + if _, err := archive.Apply(ctx, dest, layer.Reader, applyOpts...); err != nil { + return fmt.Errorf("error applying layer[%d]: %w", layer.Index, err) } l.Info("applied layer", "layer", layer.Index) } @@ -189,40 +148,6 @@ func (a *diskCache) Store(ctx context.Context, ownerID string, srcRef reference. return os.DirFS(dest), modTime, nil } -func storeChartLayer(path string, layer LayerData) error { - if layer.Err != nil { - return fmt.Errorf("error found in layer data: %w", layer.Err) - } - data, err := io.ReadAll(layer.Reader) - if err != nil { - return fmt.Errorf("error reading layer[%d]: %w", layer.Index, err) - } - meta := new(chart.Metadata) - _, err = inspectChart(data, meta) - if err != nil { - return fmt.Errorf("inspecting chart layer: %w", err) - } - chart, err := renameio.TempFile("", - filepath.Join(path, - fmt.Sprintf("%s-%s.tgz", meta.Name, meta.Version), - ), - ) - if err != nil { - return fmt.Errorf("create temp file: %w", err) - } - defer func() { - _ = chart.Cleanup() - }() - if _, err := io.Copy(chart, bytes.NewReader(data)); err != nil { - return fmt.Errorf("copying chart archive: %w", err) - } - _, err = chart.Seek(0, io.SeekStart) - if err != nil { - return fmt.Errorf("seek chart archive start: %w", err) - } - return chart.CloseAtomicallyReplace() -} - func (a *diskCache) Delete(_ context.Context, ownerID string) error { return fsutil.DeleteReadOnlyRecursive(a.ownerIDPath(ownerID)) } diff --git a/internal/shared/util/image/cache_test.go b/internal/shared/util/image/cache_test.go index 5f5e51b50d..387e6a9a72 100644 --- a/internal/shared/util/image/cache_test.go +++ b/internal/shared/util/image/cache_test.go @@ -2,10 +2,8 @@ package image import ( "archive/tar" - "bytes" "context" "errors" - "fmt" "io" "io/fs" "iter" @@ -22,7 +20,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.podman.io/image/v5/docker/reference" - "helm.sh/helm/v3/pkg/registry" fsutil "github.com/operator-framework/operator-controller/internal/shared/util/fs" ) @@ -147,67 +144,6 @@ func TestDiskCacheFetch(t *testing.T) { } } -func TestDiskCacheStore_HelmChart(t *testing.T) { - const myOwner = "myOwner" - myCanonicalRef := mustParseCanonical(t, "my.registry.io/ns/chart@sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03") - myTaggedRef, err := reference.WithTag(reference.TrimNamed(myCanonicalRef), "test-tag") - require.NoError(t, err) - - testCases := []struct { - name string - ownerID string - srcRef reference.Named - canonicalRef reference.Canonical - imgConfig ocispecv1.Image - layers iter.Seq[LayerData] - filterFunc func(context.Context, reference.Named, ocispecv1.Image) (archive.Filter, error) - setup func(*testing.T, *diskCache) - expect func(*testing.T, *diskCache, fs.FS, time.Time, error) - }{ - { - name: "returns no error if layer read contains helm chart", - ownerID: myOwner, - srcRef: myTaggedRef, - canonicalRef: myCanonicalRef, - layers: func() iter.Seq[LayerData] { - testChart := mockHelmChartTgz(t, - []fileContent{ - { - name: "testchart/Chart.yaml", - content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), - }, - { - name: "testchart/templates/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - ) - return func(yield func(LayerData) bool) { - yield(LayerData{Reader: bytes.NewBuffer(testChart), MediaType: registry.ChartLayerMediaType}) - } - }(), - expect: func(t *testing.T, cache *diskCache, fsys fs.FS, modTime time.Time, err error) { - require.NoError(t, err) - }, - }, - } - for _, tc := range testCases { - t.Run(tc.name, func(t *testing.T) { - dc := &diskCache{ - basePath: t.TempDir(), - filterFunc: tc.filterFunc, - } - if tc.setup != nil { - tc.setup(t, dc) - } - fsys, modTime, err := dc.Store(context.Background(), tc.ownerID, tc.srcRef, tc.canonicalRef, tc.imgConfig, tc.layers) - require.NotNil(t, tc.expect, "test case must include an expect function") - tc.expect(t, dc, fsys, modTime, err) - require.NoError(t, fsutil.DeleteReadOnlyRecursive(dc.basePath)) - }) - } -} - func TestDiskCacheStore(t *testing.T) { const myOwner = "myOwner" myCanonicalRef := mustParseCanonical(t, "my.registry.io/ns/repo@sha256:5891b5b522d5df086d0ff0b110fbd9d21bb4fc7163af34d08286a2e846f6be03") @@ -649,120 +585,6 @@ func TestDiskCacheGarbageCollection(t *testing.T) { } } -func Test_storeChartLayer(t *testing.T) { - tmp := t.TempDir() - type args struct { - path string - data LayerData - } - type want struct { - errStr string - } - - tests := []struct { - name string - args args - want want - }{ - { - name: "store chart layer to given path", - args: args{ - path: tmp, - data: LayerData{ - Index: 0, - MediaType: registry.ChartLayerMediaType, - Reader: bytes.NewBuffer(mockHelmChartTgz(t, - []fileContent{ - { - name: "testchart/Chart.yaml", - content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), - }, - { - name: "testchart/templates/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - )), - }, - }, - }, - { - name: "store invalid chart layer", - args: args{ - path: tmp, - data: LayerData{ - Index: 0, - MediaType: registry.ChartLayerMediaType, - Reader: bytes.NewBuffer(mockHelmChartTgz(t, - []fileContent{ - { - name: "testchart/Chart.yaml", - content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), - }, - { - name: "testchart/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - )), - }, - }, - }, - { - name: "store existing from dummy reader", - args: args{ - path: tmp, - data: LayerData{ - Index: 0, - MediaType: registry.ChartLayerMediaType, - Reader: &dummyReader{}, - }, - }, - want: want{ - errStr: "error reading layer[0]: something went wrong", - }, - }, - { - name: "handle chart layer data", - args: args{ - path: tmp, - data: LayerData{ - Index: 0, - MediaType: registry.ChartLayerMediaType, - Err: fmt.Errorf("invalid layer data"), - Reader: bytes.NewBuffer(mockHelmChartTgz(t, - []fileContent{ - { - name: "testchart/Chart.yaml", - content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), - }, - { - name: "testchart/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - )), - }, - }, - want: want{ - errStr: "error found in layer data: invalid layer data", - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - err := storeChartLayer(tc.args.path, tc.args.data) - if tc.want.errStr != "" { - require.Error(t, err) - require.EqualError(t, err, tc.want.errStr, "chart store error") - } else { - require.NoError(t, err) - } - }) - } -} - func mustParseCanonical(t *testing.T, s string) reference.Canonical { n, err := reference.ParseNamed(s) require.NoError(t, err) @@ -797,11 +619,3 @@ func fsTarReader(fsys fs.FS) io.ReadCloser { }() return pr } - -type dummyReader struct{} - -var _ io.Reader = &dummyReader{} - -func (r *dummyReader) Read(p []byte) (int, error) { - return 0, errors.New("something went wrong") -} diff --git a/internal/shared/util/image/helm.go b/internal/shared/util/image/helm.go deleted file mode 100644 index 299f921df5..0000000000 --- a/internal/shared/util/image/helm.go +++ /dev/null @@ -1,175 +0,0 @@ -package image - -import ( - "bytes" - "context" - "encoding/json" - "errors" - "fmt" - "io/fs" - "iter" - "regexp" - "strings" - "time" - - ocispecv1 "github.com/opencontainers/image-spec/specs-go/v1" - "go.podman.io/image/v5/docker/reference" - "go.podman.io/image/v5/pkg/blobinfocache/none" - "go.podman.io/image/v5/types" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/chart/loader" - "helm.sh/helm/v3/pkg/registry" -) - -func hasChart(imgCloser types.ImageCloser) bool { - config := imgCloser.ConfigInfo() - return config.MediaType == registry.ConfigMediaType -} - -func pullChart(ctx context.Context, ownerID string, srcRef reference.Named, canonicalRef reference.Canonical, imgSrc types.ImageSource, cache Cache) (fs.FS, time.Time, error) { - imgDigest := canonicalRef.Digest() - raw, _, err := imgSrc.GetManifest(ctx, &imgDigest) - if err != nil { - return nil, time.Time{}, fmt.Errorf("get OCI helm chart manifest; %w", err) - } - - chartManifest := ocispecv1.Manifest{} - if err := json.Unmarshal(raw, &chartManifest); err != nil { - return nil, time.Time{}, fmt.Errorf("unmarshaling chart manifest; %w", err) - } - - layerIter := iter.Seq[LayerData](func(yield func(LayerData) bool) { - for i, layer := range chartManifest.Layers { - ld := LayerData{Index: i, MediaType: layer.MediaType} - if layer.MediaType == registry.ChartLayerMediaType { - ld.Reader, _, ld.Err = imgSrc.GetBlob(ctx, - types.BlobInfo{ - Annotations: layer.Annotations, - MediaType: layer.MediaType, - Digest: layer.Digest, - Size: layer.Size, - }, - none.NoCache) - } - // Ignore the Helm provenance data layer - if layer.MediaType == registry.ProvLayerMediaType { - continue - } - if !yield(ld) { - return - } - } - }) - - return cache.Store(ctx, ownerID, srcRef, canonicalRef, ocispecv1.Image{}, layerIter) -} - -func IsValidChart(chart *chart.Chart) error { - if chart.Metadata == nil { - return errors.New("chart metadata is missing") - } - if chart.Metadata.Name == "" { - return errors.New("chart name is required") - } - if chart.Metadata.Version == "" { - return errors.New("chart version is required") - } - return chart.Metadata.Validate() -} - -type chartInspectionResult struct { - // templatesExist is set to true if the templates - // directory exists in the chart archive - templatesExist bool - // chartfileExists is set to true if the Chart.yaml - // file exists in the chart archive - chartfileExists bool -} - -func inspectChart(data []byte, metadata *chart.Metadata) (chartInspectionResult, error) { - report := chartInspectionResult{} - chart, err := loader.LoadArchive(bytes.NewBuffer(data)) - if err != nil { - return report, fmt.Errorf("loading chart archive: %w", err) - } - - report.templatesExist = len(chart.Templates) > 0 - report.chartfileExists = chart.Metadata != nil - - if metadata != nil && chart.Metadata != nil { - *metadata = *chart.Metadata - } - - return report, nil -} - -func IsBundleSourceChart(bundleFS fs.FS, metadata *chart.Metadata) (bool, error) { - var chartPath string - files, _ := fs.ReadDir(bundleFS, ".") - for _, file := range files { - if strings.HasSuffix(file.Name(), ".tgz") || - strings.HasSuffix(file.Name(), ".tar.gz") { - chartPath = file.Name() - break - } - } - - chartData, err := fs.ReadFile(bundleFS, chartPath) - if err != nil { - return false, err - } - - result, err := inspectChart(chartData, metadata) - if err != nil { - return false, fmt.Errorf("reading %s from fs: %w", chartPath, err) - } - - return (result.templatesExist && result.chartfileExists), nil -} - -type ChartOption func(*chart.Chart) - -func WithInstallNamespace(namespace string) ChartOption { - re := regexp.MustCompile(`{{\W+\.Release\.Namespace\W+}}`) - - return func(chrt *chart.Chart) { - for i, template := range chrt.Templates { - chrt.Templates[i].Data = re.ReplaceAll(template.Data, []byte(namespace)) - } - } -} - -func LoadChartFSWithOptions(bundleFS fs.FS, filename string, options ...ChartOption) (*chart.Chart, error) { - ch, err := loadChartFS(bundleFS, filename) - if err != nil { - return nil, err - } - - return enrichChart(ch, options...) -} - -func enrichChart(chart *chart.Chart, options ...ChartOption) (*chart.Chart, error) { - if chart == nil { - return nil, fmt.Errorf("chart can not be nil") - } - for _, f := range options { - f(chart) - } - return chart, nil -} - -var LoadChartFS = loadChartFS - -// loadChartFS loads a chart archive from a filesystem of -// type fs.FS with the provided filename -func loadChartFS(bundleFS fs.FS, filename string) (*chart.Chart, error) { - if filename == "" { - return nil, fmt.Errorf("chart file name was not provided") - } - - tarball, err := fs.ReadFile(bundleFS, filename) - if err != nil { - return nil, fmt.Errorf("reading chart %s; %+v", filename, err) - } - return loader.LoadArchive(bytes.NewBuffer(tarball)) -} diff --git a/internal/shared/util/image/helm_test.go b/internal/shared/util/image/helm_test.go deleted file mode 100644 index 0bebc2a4e9..0000000000 --- a/internal/shared/util/image/helm_test.go +++ /dev/null @@ -1,683 +0,0 @@ -package image - -import ( - "archive/tar" - "bytes" - "compress/gzip" - "context" - "fmt" - "io/fs" - "net/http/httptest" - "net/url" - "os" - "path/filepath" - "reflect" - "testing" - "time" - - "github.com/containerd/containerd/archive" - goregistry "github.com/google/go-containerregistry/pkg/registry" - "github.com/opencontainers/go-digest" - ocispecv1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - "go.podman.io/image/v5/docker" - "go.podman.io/image/v5/docker/reference" - "go.podman.io/image/v5/image" - "go.podman.io/image/v5/types" - "helm.sh/helm/v3/pkg/chart" - "helm.sh/helm/v3/pkg/registry" - - fsutil "github.com/operator-framework/operator-controller/internal/shared/util/fs" -) - -func Test_hasChart(t *testing.T) { - chartTagRef, _, cleanup := setupChartRegistry(t, - mockHelmChartTgz(t, - []fileContent{ - { - name: "testchart/Chart.yaml", - content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), - }, - { - name: "testchart/templates/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - ), - ) - defer cleanup() - - imgTagRef, _, shutdown := setupRegistry(t) - defer shutdown() - - type args struct { - srcRef string - contextFunc func(context.Context) (*types.SystemContext, error) - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "returns true when image contains chart", - args: args{ - srcRef: chartTagRef.String(), - contextFunc: buildSourceContextFunc(t, chartTagRef), - }, - want: true, - }, - { - name: "returns false when image is not chart", - args: args{ - srcRef: imgTagRef.String(), - contextFunc: buildSourceContextFunc(t, imgTagRef), - }, - want: false, - }, - } - - ctx := context.Background() - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - srcRef, err := reference.ParseNamed(tc.args.srcRef) - require.NoError(t, err) - - srcImgRef, err := docker.NewReference(srcRef) - require.NoError(t, err) - - sysCtx, err := tc.args.contextFunc(ctx) - require.NoError(t, err) - - imgSrc, err := srcImgRef.NewImageSource(ctx, sysCtx) - require.NoError(t, err) - - img, err := image.FromSource(ctx, sysCtx, imgSrc) - require.NoError(t, err) - - defer func() { - if err := img.Close(); err != nil { - panic(err) - } - }() - - got := hasChart(img) - require.Equal(t, tc.want, got) - }) - } -} - -func Test_pullChart(t *testing.T) { - const myOwner = "myOwner" - myChartName := "testchart-0.1.0.tgz" - testChart := mockHelmChartTgz(t, - []fileContent{ - { - name: "testchart/Chart.yaml", - content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), - }, - { - name: "testchart/templates/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - ) - - myTagRef, myCanonicalRef, cleanup := setupChartRegistry(t, testChart) - defer cleanup() - - tests := []struct { - name string - ownerID string - srcRef string - cache Cache - contextFunc func(context.Context) (*types.SystemContext, error) - expect func(*testing.T, fs.FS, time.Time) - }{ - { - name: "pull helm chart from OCI registry", - ownerID: myOwner, - srcRef: myTagRef.String(), - cache: &diskCache{ - basePath: t.TempDir(), - filterFunc: func(ctx context.Context, named reference.Named, image ocispecv1.Image) (archive.Filter, error) { - return forceOwnershipRWX(), nil - }, - }, - contextFunc: buildSourceContextFunc(t, myTagRef), - expect: func(t *testing.T, fsys fs.FS, modTime time.Time) { - now := time.Now() - require.LessOrEqual(t, now.Sub(modTime), 3*time.Second, "modified time should less than 3 seconds") - - actualChartData, err := fs.ReadFile(fsys, myChartName) - require.NoError(t, err) - - assert.Equal(t, testChart, actualChartData) - }, - }, - } - - ctx := context.Background() - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - srcRef, err := reference.ParseNamed(tc.srcRef) - require.NoError(t, err) - - srcImgRef, err := docker.NewReference(srcRef) - require.NoError(t, err) - - sysCtx, err := tc.contextFunc(ctx) - require.NoError(t, err) - - imgSrc, err := srcImgRef.NewImageSource(ctx, sysCtx) - require.NoError(t, err) - - fsys, modTime, err := pullChart(ctx, tc.ownerID, srcRef, myCanonicalRef, imgSrc, tc.cache) - require.NotNil(t, tc.expect, "expect function must be defined") - require.NoError(t, err) - - tc.expect(t, fsys, modTime) - - if dc, ok := tc.cache.(*diskCache); ok && dc.basePath != "" { - require.NoError(t, fsutil.DeleteReadOnlyRecursive(dc.basePath)) - } - }) - } -} - -func TestIsValidChart(t *testing.T) { - tt := []struct { - name string - target *chart.Chart - wantErr bool - errMsg string - }{ - { - name: "helm chart with required metadata", - target: &chart.Chart{ - Metadata: &chart.Metadata{ - APIVersion: "v2", - Name: "sample-chart", - Version: "0.1.2", - }, - }, - wantErr: false, - }, - { - name: "helm chart without name", - target: &chart.Chart{ - Metadata: &chart.Metadata{ - APIVersion: "v2", - Name: "", - Version: "0.1.2", - }, - }, - wantErr: true, - errMsg: "chart name is required", - }, - { - name: "helm chart with missing version", - target: &chart.Chart{ - Metadata: &chart.Metadata{ - APIVersion: "v2", - Name: "sample-chart", - Version: "", - }, - }, - wantErr: true, - errMsg: "chart version is required", - }, - { - name: "helm chart with missing metadata", - target: &chart.Chart{ - Metadata: nil, - }, - wantErr: true, - errMsg: "chart metadata is missing", - }, - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - err := IsValidChart(tc.target) - if tc.wantErr && assert.Error(t, err, "checking valid chart") { - assert.EqualError(t, err, tc.errMsg, "validating chart") - } - }) - } -} - -func TestIsBundleSourceChart(t *testing.T) { - type args struct { - meta *chart.Metadata - files []fileContent - } - type want struct { - value bool - errStr string - } - tt := []struct { - name string - args args - want want - }{ - { - name: "complete helm chart with nil *chart.Metadata", - args: args{ - meta: nil, - files: []fileContent{ - { - name: "testchart/Chart.yaml", - content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), - }, - { - name: "testchart/templates/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - }, - want: want{ - value: true, - }, - }, - { - name: "complete helm chart", - args: args{ - meta: &chart.Metadata{}, - files: []fileContent{ - { - name: "testchart/Chart.yaml", - content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), - }, - { - name: "testchart/templates/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - }, - want: want{ - value: true, - }, - }, - { - name: "helm chart without templates", - args: args{ - meta: nil, - files: []fileContent{ - { - name: "testchart/Chart.yaml", - content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), - }, - }, - }, - want: want{ - value: false, - }, - }, - { - name: "helm chart without a Chart.yaml", - args: args{ - meta: nil, - files: []fileContent{ - { - name: "testchart/templates/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - }, - want: want{ - value: false, - errStr: "reading testchart-0.1.0.tgz from fs: loading chart archive: Chart.yaml file is missing", - }, - }, - { - name: "invalid chart archive", - args: args{ - meta: nil, - files: []fileContent{ - { - name: "testchart/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - }, - want: want{ - value: false, - errStr: "reading testchart-0.1.0.tgz from fs: loading chart archive: Chart.yaml file is missing", - }, - }, - } - - for _, tc := range tt { - t.Run(tc.name, func(t *testing.T) { - chartFS, _ := createTempFS(t, mockHelmChartTgz(t, tc.args.files)) - got, err := IsBundleSourceChart(chartFS, tc.args.meta) - if tc.want.errStr != "" { - require.Error(t, err, "chart validation error required") - require.EqualError(t, err, tc.want.errStr, "chart error") - } - require.Equal(t, tc.want.value, got, "validata helm chart") - }) - } -} - -func Test_loadChartFS(t *testing.T) { - type args struct { - filename string - files []fileContent - } - type want struct { - name string - version string - errMsg string - } - tests := []struct { - name string - args args - want want - expect func(*chart.Chart, want, error) - }{ - { - name: "empty filename is provided", - args: args{ - filename: "", - files: []fileContent{ - { - name: "testchart/Chart.yaml", - content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), - }, - { - name: "testchart/templates/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - }, - want: want{ - name: "", - errMsg: "chart file name was not provided", - }, - expect: func(chart *chart.Chart, want want, err error) { - require.EqualError(t, err, want.errMsg) - assert.Nil(t, chart, "no chart would be returned") - }, - }, - { - name: "load sample chart", - args: args{ - filename: "testchart-0.1.0.tgz", - files: []fileContent{ - { - name: "testchart/Chart.yaml", - content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), - }, - { - name: "testchart/templates/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - }, - want: want{ - name: "testchart", - version: "0.1.0", - }, - expect: func(chart *chart.Chart, want want, err error) { - require.NoError(t, err, "chart should load successfully") - assert.Equal(t, want.name, chart.Metadata.Name, "verify chart name") - assert.Equal(t, want.version, chart.Metadata.Version, "verify chart version") - }, - }, - { - name: "load nonexistent chart", - args: args{ - filename: "nonexistent-chart-0.1.0.tgz", - files: []fileContent{ - { - name: "testchart/Chart.yaml", - content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), - }, - { - name: "testchart/templates/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - }, - want: want{ - name: "nonexistent-chart", - version: "0.1.0", - }, - expect: func(chart *chart.Chart, want want, err error) { - assert.Nil(t, chart, "chart does not exist on filesystem") - require.Error(t, err, "reading chart nonexistent-chart-0.1.0.tgz; open nonexistent-chart-0.1.0.tgz: no such file or directory") - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - chartFS, _ := createTempFS(t, mockHelmChartTgz(t, tc.args.files)) - - got, err := loadChartFS(chartFS, tc.args.filename) - assert.NotNil(t, tc.expect, "validation function") - tc.expect(got, tc.want, err) - }) - } -} - -func TestLoadChartFSWithOptions(t *testing.T) { - type args struct { - filename string - files []fileContent - } - type want struct { - name string - version string - errMsg string - } - tests := []struct { - name string - args args - want want - expect func(*chart.Chart, want, error) - }{ - { - name: "empty filename is provided", - args: args{ - filename: "", - files: []fileContent{ - { - name: "testchart/Chart.yaml", - content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), - }, - { - name: "testchart/templates/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - }, - want: want{ - errMsg: "chart file name was not provided", - }, - expect: func(chart *chart.Chart, want want, err error) { - require.Error(t, err, want.errMsg) - }, - }, - { - name: "load sample chart", - args: args{ - filename: "testchart-0.1.0.tgz", - files: []fileContent{ - { - name: "testchart/Chart.yaml", - content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), - }, - { - name: "testchart/templates/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - }, - want: want{ - name: "testchart", - version: "0.1.0", - }, - expect: func(chart *chart.Chart, want want, err error) { - require.NoError(t, err) - assert.Equal(t, want.name, chart.Metadata.Name, "chart name") - assert.Equal(t, want.version, chart.Metadata.Version, "chart version") - }, - }, - { - name: "load nonexistent chart", - args: args{ - filename: "nonexistent-chart-0.1.0.tgz", - files: []fileContent{ - { - name: "testchart/Chart.yaml", - content: []byte("apiVersion: v2\nname: testchart\nversion: 0.1.0"), - }, - { - name: "testchart/templates/deployment.yaml", - content: []byte("kind: Deployment\napiVersion: apps/v1"), - }, - }, - }, - want: want{ - errMsg: "reading chart nonexistent-chart-0.1.0.tgz; open nonexistent-chart-0.1.0.tgz: no such file or directory", - }, - expect: func(chart *chart.Chart, want want, err error) { - require.Error(t, err, want.errMsg) - assert.Nil(t, chart) - }, - }, - } - - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - chartFS, _ := createTempFS(t, mockHelmChartTgz(t, tc.args.files)) - got, err := LoadChartFSWithOptions(chartFS, tc.args.filename, WithInstallNamespace("metrics-server-system")) - require.NotNil(t, tc.expect) - tc.expect(got, tc.want, err) - }) - } -} - -func Test_enrichChart(t *testing.T) { - type args struct { - chart *chart.Chart - options []ChartOption - } - tests := []struct { - name string - args args - want *chart.Chart - wantErr bool - }{ - { - name: "enrich empty chart object", - args: args{ - chart: nil, - options: []ChartOption{ - WithInstallNamespace("test-namespace-system"), - }, - }, - wantErr: true, - want: nil, - }, - } - for _, tc := range tests { - t.Run(tc.name, func(t *testing.T) { - got, err := enrichChart(tc.args.chart, tc.args.options...) - if (err != nil) != tc.wantErr { - t.Errorf("enrichChart() error = %v, wantErr %v", err, tc.wantErr) - return - } - if !reflect.DeepEqual(got, tc.want) { - t.Errorf("enrichChart() = %v, want %v", got, tc.want) - } - }) - } -} - -func setupChartRegistry(t *testing.T, chart []byte) (reference.NamedTagged, reference.Canonical, func()) { - server := httptest.NewServer(goregistry.New()) - serverURL, err := url.Parse(server.URL) - require.NoError(t, err) - - clientOpts := []registry.ClientOption{ - registry.ClientOptDebug(true), - registry.ClientOptEnableCache(true), - registry.ClientOptPlainHTTP(), - } - client, err := registry.NewClient(clientOpts...) - require.NoError(t, err) - - testCreationTime := "2020-09-22T22:04:05Z" - ref := fmt.Sprintf("%s/testrepo/testchart:%s", serverURL.Host, "0.1.0") - result, err := client.Push(chart, ref, registry.PushOptCreationTime(testCreationTime)) - require.NoError(t, err) - - imageTagRef, err := newReference(serverURL.Host, "testrepo/testchart", "0.1.0") - require.NoError(t, err) - - imageDigestRef, err := reference.WithDigest( - reference.TrimNamed(imageTagRef), - digest.Digest(result.Manifest.Digest), - ) - require.NoError(t, err) - - return imageTagRef, imageDigestRef, func() { - server.Close() - } -} - -type fileContent struct { - name string - content []byte -} - -func mockHelmChartTgz(t *testing.T, contents []fileContent) []byte { - require.NotEmpty(t, contents, "chart content required") - var buf bytes.Buffer - tw := tar.NewWriter(&buf) - - // Add files to the chart archive - for _, file := range contents { - require.NoError(t, tw.WriteHeader(&tar.Header{ - Name: file.name, - Mode: 0600, - Size: int64(len(file.content)), - })) - _, _ = tw.Write(file.content) - } - - require.NoError(t, tw.Close()) - - var gzBuf bytes.Buffer - gz := gzip.NewWriter(&gzBuf) - _, err := gz.Write(buf.Bytes()) - require.NoError(t, err) - require.NoError(t, gz.Close()) - - return gzBuf.Bytes() -} - -func createTempFS(t *testing.T, data []byte) (fs.FS, error) { - require.NotEmpty(t, data, "chart data") - tmpDir, _ := os.MkdirTemp(t.TempDir(), "bundlefs-") - if len(data) == 0 { - return os.DirFS(tmpDir), nil - } - - dest, err := os.Create(filepath.Join(tmpDir, "testchart-0.1.0.tgz")) - if err != nil { - return nil, err - } - defer dest.Close() - - if _, err := dest.Write(data); err != nil { - return nil, err - } - - return os.DirFS(tmpDir), nil -} diff --git a/internal/shared/util/image/pull.go b/internal/shared/util/image/pull.go index 1fff90809b..5b3bfd6dce 100644 --- a/internal/shared/util/image/pull.go +++ b/internal/shared/util/image/pull.go @@ -24,7 +24,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/reconcile" - "github.com/operator-framework/operator-controller/internal/operator-controller/features" "github.com/operator-framework/operator-controller/internal/shared/util/http" ) @@ -225,12 +224,6 @@ func (p *ContainersImagePuller) applyImage(ctx context.Context, ownerID string, } }() - if features.OperatorControllerFeatureGate.Enabled(features.HelmChartSupport) { - if hasChart(img) { - return pullChart(ctx, ownerID, srcRef, canonicalRef, imgSrc, cache) - } - } - ociImg, err := img.OCIConfig(ctx) if err != nil { return nil, time.Time{}, err diff --git a/manifests/experimental-e2e.yaml b/manifests/experimental-e2e.yaml index 664484d852..f8ea9c41ac 100644 --- a/manifests/experimental-e2e.yaml +++ b/manifests/experimental-e2e.yaml @@ -2827,7 +2827,6 @@ spec: - --feature-gates=BoxcutterRuntime=true - --feature-gates=BundleReleaseSupport=true - --feature-gates=DeploymentConfig=true - - --feature-gates=HelmChartSupport=true - --feature-gates=PreflightPermissions=true - --feature-gates=SingleOwnNamespaceInstallSupport=true - --feature-gates=WebhookProviderCertManager=true diff --git a/manifests/experimental.yaml b/manifests/experimental.yaml index 267570d6fa..ca57727cac 100644 --- a/manifests/experimental.yaml +++ b/manifests/experimental.yaml @@ -2733,7 +2733,6 @@ spec: - --feature-gates=BoxcutterRuntime=true - --feature-gates=BundleReleaseSupport=true - --feature-gates=DeploymentConfig=true - - --feature-gates=HelmChartSupport=true - --feature-gates=PreflightPermissions=true - --feature-gates=SingleOwnNamespaceInstallSupport=true - --feature-gates=WebhookProviderCertManager=true diff --git a/manifests/standard-e2e.yaml b/manifests/standard-e2e.yaml index 6f55abc173..88acbe91d4 100644 --- a/manifests/standard-e2e.yaml +++ b/manifests/standard-e2e.yaml @@ -1960,7 +1960,6 @@ spec: - --feature-gates=BoxcutterRuntime=false - --feature-gates=BundleReleaseSupport=false - --feature-gates=DeploymentConfig=false - - --feature-gates=HelmChartSupport=false - --feature-gates=PreflightPermissions=false - --feature-gates=SingleOwnNamespaceInstallSupport=false - --feature-gates=SyntheticPermissions=false diff --git a/manifests/standard.yaml b/manifests/standard.yaml index 870f10e3fc..c02dd7e618 100644 --- a/manifests/standard.yaml +++ b/manifests/standard.yaml @@ -1866,7 +1866,6 @@ spec: - --feature-gates=BoxcutterRuntime=false - --feature-gates=BundleReleaseSupport=false - --feature-gates=DeploymentConfig=false - - --feature-gates=HelmChartSupport=false - --feature-gates=PreflightPermissions=false - --feature-gates=SingleOwnNamespaceInstallSupport=false - --feature-gates=SyntheticPermissions=false diff --git a/test/e2e/steps/hooks.go b/test/e2e/steps/hooks.go index ef56b99a6e..d9d51146a3 100644 --- a/test/e2e/steps/hooks.go +++ b/test/e2e/steps/hooks.go @@ -91,7 +91,6 @@ var ( features.SingleOwnNamespaceInstallSupport: false, features.SyntheticPermissions: false, features.WebhookProviderOpenshiftServiceCA: false, - features.HelmChartSupport: false, features.BoxcutterRuntime: false, features.DeploymentConfig: false, catalogdHAFeature: false,