Skip to content

Commit 46af89e

Browse files
committed
Add support for deploying OCI helm charts in OLM v1
* added support for deploying OCI helm charts which sits behind the HelmChartSupport feature gate * extend the Cache Store() method to allow storing of Helm charts * inspect chart archive contents * added MediaType to the LayerData struct Signed-off-by: Edmund Ochieng <[email protected]>
1 parent 00b965c commit 46af89e

File tree

7 files changed

+1154
-6
lines changed

7 files changed

+1154
-6
lines changed

internal/operator-controller/applier/helm.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@ import (
2626

2727
ocv1 "github.com/operator-framework/operator-controller/api/v1"
2828
"github.com/operator-framework/operator-controller/internal/operator-controller/authorization"
29+
"github.com/operator-framework/operator-controller/internal/operator-controller/features"
2930
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/bundle/source"
3031
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/preflights/crdupgradesafety"
3132
"github.com/operator-framework/operator-controller/internal/operator-controller/rukpak/util"
33+
imageutil "github.com/operator-framework/operator-controller/internal/shared/util/image"
3234
)
3335

3436
const (
@@ -209,6 +211,17 @@ func (h *Helm) buildHelmChart(bundleFS fs.FS, ext *ocv1.ClusterExtension) (*char
209211
if err != nil {
210212
return nil, err
211213
}
214+
if features.OperatorControllerFeatureGate.Enabled(features.HelmChartSupport) {
215+
meta := new(chart.Metadata)
216+
if ok, _ := imageutil.IsBundleSourceChart(bundleFS, meta); ok {
217+
return imageutil.LoadChartFSWithOptions(
218+
bundleFS,
219+
fmt.Sprintf("%s-%s.tgz", meta.Name, meta.Version),
220+
imageutil.WithInstallNamespace(ext.Spec.Namespace),
221+
)
222+
}
223+
}
224+
212225
return h.BundleToHelmChartConverter.ToHelmChart(source.FromFS(bundleFS), ext.Spec.Namespace, watchNamespace)
213226
}
214227

internal/operator-controller/features/features.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const (
1616
SyntheticPermissions featuregate.Feature = "SyntheticPermissions"
1717
WebhookProviderCertManager featuregate.Feature = "WebhookProviderCertManager"
1818
WebhookProviderOpenshiftServiceCA featuregate.Feature = "WebhookProviderOpenshiftServiceCA"
19+
HelmChartSupport featuregate.Feature = "HelmChartSupport"
1920
)
2021

2122
var operatorControllerFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
@@ -63,6 +64,14 @@ var operatorControllerFeatureGates = map[featuregate.Feature]featuregate.Feature
6364
PreRelease: featuregate.Alpha,
6465
LockToDefault: false,
6566
},
67+
68+
// HelmChartSupport enables support for installing,
69+
// updating and uninstalling Helm Charts via Cluster Extensions.
70+
HelmChartSupport: {
71+
Default: false,
72+
PreRelease: featuregate.Alpha,
73+
LockToDefault: false,
74+
},
6675
}
6776

6877
var OperatorControllerFeatureGate featuregate.MutableFeatureGate = featuregate.NewFeatureGate()

internal/shared/util/image/cache.go

Lines changed: 81 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package image
22

33
import (
4+
"bytes"
45
"context"
56
"errors"
67
"fmt"
@@ -10,22 +11,28 @@ import (
1011
"os"
1112
"path/filepath"
1213
"slices"
14+
"testing"
1315
"time"
1416

1517
"github.com/containerd/containerd/archive"
1618
"github.com/containers/image/v5/docker/reference"
19+
"github.com/google/renameio/v2"
1720
"github.com/opencontainers/go-digest"
1821
ocispecv1 "github.com/opencontainers/image-spec/specs-go/v1"
22+
"helm.sh/helm/v3/pkg/chart"
23+
"helm.sh/helm/v3/pkg/registry"
1924
"sigs.k8s.io/controller-runtime/pkg/log"
2025

26+
"github.com/operator-framework/operator-controller/internal/operator-controller/features"
2127
errorutil "github.com/operator-framework/operator-controller/internal/shared/util/error"
2228
fsutil "github.com/operator-framework/operator-controller/internal/shared/util/fs"
2329
)
2430

2531
type LayerData struct {
26-
Reader io.Reader
27-
Index int
28-
Err error
32+
MediaType string
33+
Reader io.Reader
34+
Index int
35+
Err error
2936
}
3037

3138
type Cache interface {
@@ -106,6 +113,40 @@ func (a *diskCache) unpackPath(ownerID string, digest digest.Digest) string {
106113
return filepath.Join(a.ownerIDPath(ownerID), digest.String())
107114
}
108115

116+
type LayerUnpacker interface {
117+
Unpack(_ context.Context, path string, layer LayerData, opts ...archive.ApplyOpt) error
118+
}
119+
120+
type defaultLayerUnpacker struct{}
121+
122+
type chartLayerUnpacker struct{}
123+
124+
var _ LayerUnpacker = &defaultLayerUnpacker{}
125+
var _ LayerUnpacker = &chartLayerUnpacker{}
126+
127+
func imageLayerUnpacker(layer LayerData) LayerUnpacker {
128+
if features.OperatorControllerFeatureGate.Enabled(features.HelmChartSupport) || testing.Testing() {
129+
if layer.MediaType == registry.ChartLayerMediaType {
130+
return &chartLayerUnpacker{}
131+
}
132+
}
133+
return &defaultLayerUnpacker{}
134+
}
135+
136+
func (u *chartLayerUnpacker) Unpack(_ context.Context, path string, layer LayerData, _ ...archive.ApplyOpt) error {
137+
if err := storeChartLayer(path, layer); err != nil {
138+
return fmt.Errorf("error applying chart layer[%d]: %w", layer.Index, err)
139+
}
140+
return nil
141+
}
142+
143+
func (u *defaultLayerUnpacker) Unpack(ctx context.Context, path string, layer LayerData, opts ...archive.ApplyOpt) error {
144+
if _, err := archive.Apply(ctx, path, layer.Reader, opts...); err != nil {
145+
return fmt.Errorf("error applying layer[%d]: %w", layer.Index, err)
146+
}
147+
return nil
148+
}
149+
109150
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) {
110151
var applyOpts []archive.ApplyOpt
111152
if a.filterFunc != nil {
@@ -128,8 +169,9 @@ func (a *diskCache) Store(ctx context.Context, ownerID string, srcRef reference.
128169
if layer.Err != nil {
129170
return fmt.Errorf("error reading layer[%d]: %w", layer.Index, layer.Err)
130171
}
131-
if _, err := archive.Apply(ctx, dest, layer.Reader, applyOpts...); err != nil {
132-
return fmt.Errorf("error applying layer[%d]: %w", layer.Index, err)
172+
layerUnpacker := imageLayerUnpacker(layer)
173+
if err := layerUnpacker.Unpack(ctx, dest, layer, applyOpts...); err != nil {
174+
return fmt.Errorf("unpacking layer: %w", err)
133175
}
134176
l.Info("applied layer", "layer", layer.Index)
135177
}
@@ -147,6 +189,40 @@ func (a *diskCache) Store(ctx context.Context, ownerID string, srcRef reference.
147189
return os.DirFS(dest), modTime, nil
148190
}
149191

192+
func storeChartLayer(path string, layer LayerData) error {
193+
if layer.Err != nil {
194+
return fmt.Errorf("error found in layer data: %w", layer.Err)
195+
}
196+
data, err := io.ReadAll(layer.Reader)
197+
if err != nil {
198+
return fmt.Errorf("error reading layer[%d]: %w", layer.Index, err)
199+
}
200+
meta := new(chart.Metadata)
201+
_, err = inspectChart(data, meta)
202+
if err != nil {
203+
return fmt.Errorf("inspecting chart layer: %w", err)
204+
}
205+
chart, err := renameio.TempFile("",
206+
filepath.Join(path,
207+
fmt.Sprintf("%s-%s.tgz", meta.Name, meta.Version),
208+
),
209+
)
210+
if err != nil {
211+
return fmt.Errorf("create temp file: %w", err)
212+
}
213+
defer func() {
214+
_ = chart.Cleanup()
215+
}()
216+
if _, err := io.Copy(chart, bytes.NewReader(data)); err != nil {
217+
return fmt.Errorf("copying chart archive: %w", err)
218+
}
219+
_, err = chart.Seek(0, io.SeekStart)
220+
if err != nil {
221+
return fmt.Errorf("seek chart archive start: %w", err)
222+
}
223+
return chart.CloseAtomicallyReplace()
224+
}
225+
150226
func (a *diskCache) Delete(_ context.Context, ownerID string) error {
151227
return fsutil.DeleteReadOnlyRecursive(a.ownerIDPath(ownerID))
152228
}

0 commit comments

Comments
 (0)