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,