1
1
package image
2
2
3
3
import (
4
+ "bytes"
4
5
"context"
5
6
"errors"
6
7
"fmt"
@@ -10,22 +11,28 @@ import (
10
11
"os"
11
12
"path/filepath"
12
13
"slices"
14
+ "testing"
13
15
"time"
14
16
15
17
"github.com/containerd/containerd/archive"
16
18
"github.com/containers/image/v5/docker/reference"
19
+ "github.com/google/renameio/v2"
17
20
"github.com/opencontainers/go-digest"
18
21
ocispecv1 "github.com/opencontainers/image-spec/specs-go/v1"
22
+ "helm.sh/helm/v3/pkg/chart"
23
+ "helm.sh/helm/v3/pkg/registry"
19
24
"sigs.k8s.io/controller-runtime/pkg/log"
20
25
26
+ "github.com/operator-framework/operator-controller/internal/operator-controller/features"
21
27
errorutil "github.com/operator-framework/operator-controller/internal/shared/util/error"
22
28
fsutil "github.com/operator-framework/operator-controller/internal/shared/util/fs"
23
29
)
24
30
25
31
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
29
36
}
30
37
31
38
type Cache interface {
@@ -106,6 +113,40 @@ func (a *diskCache) unpackPath(ownerID string, digest digest.Digest) string {
106
113
return filepath .Join (a .ownerIDPath (ownerID ), digest .String ())
107
114
}
108
115
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
+
109
150
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 ) {
110
151
var applyOpts []archive.ApplyOpt
111
152
if a .filterFunc != nil {
@@ -128,8 +169,9 @@ func (a *diskCache) Store(ctx context.Context, ownerID string, srcRef reference.
128
169
if layer .Err != nil {
129
170
return fmt .Errorf ("error reading layer[%d]: %w" , layer .Index , layer .Err )
130
171
}
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 )
133
175
}
134
176
l .Info ("applied layer" , "layer" , layer .Index )
135
177
}
@@ -147,6 +189,40 @@ func (a *diskCache) Store(ctx context.Context, ownerID string, srcRef reference.
147
189
return os .DirFS (dest ), modTime , nil
148
190
}
149
191
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
+
150
226
func (a * diskCache ) Delete (_ context.Context , ownerID string ) error {
151
227
return fsutil .DeleteReadOnlyRecursive (a .ownerIDPath (ownerID ))
152
228
}
0 commit comments