11package image
22
33import (
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
2531type 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
3138type 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+
109150func (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+
150226func (a * diskCache ) Delete (_ context.Context , ownerID string ) error {
151227 return fsutil .DeleteReadOnlyRecursive (a .ownerIDPath (ownerID ))
152228}
0 commit comments