6
6
"fmt"
7
7
"io"
8
8
"net/http"
9
- "os"
10
9
"runtime"
11
10
"strconv"
12
11
"strings"
@@ -22,7 +21,6 @@ import (
22
21
"github.com/google/go-containerregistry/pkg/v1/mutate"
23
22
"github.com/google/go-containerregistry/pkg/v1/remote"
24
23
"github.com/google/go-containerregistry/pkg/v1/remote/transport"
25
- "github.com/google/go-containerregistry/pkg/v1/tarball"
26
24
)
27
25
28
26
// ref: https://github.com/mudler/luet/blob/master/pkg/helpers/docker/docker.go#L117
@@ -97,30 +95,31 @@ func (pw *progressWriter) Write(p []byte) (int, error) {
97
95
}
98
96
99
97
// ExtractOCIImage will extract a given targetImage into a given targetDestination
100
- func ExtractOCIImage (img v1.Image , imageRef string , targetDestination string , downloadStatus func (string , string , string , float64 )) error {
101
- // Create a temporary tar file
102
- tmpTarFile , err := os .CreateTemp ("" , "localai-oci-*.tar" )
103
- if err != nil {
104
- return fmt .Errorf ("failed to create temporary tar file: %v" , err )
105
- }
106
- defer os .Remove (tmpTarFile .Name ())
107
- defer tmpTarFile .Close ()
98
+ func ExtractOCIImage (img v1.Image , targetDestination string , downloadStatus func (string , string , string , float64 )) error {
99
+ var reader io.Reader
100
+ reader = mutate .Extract (img )
108
101
109
- // Download the image as tar with progress tracking
110
- err = DownloadOCIImageTar (img , imageRef , tmpTarFile .Name (), downloadStatus )
111
- if err != nil {
112
- return fmt .Errorf ("failed to download image tar: %v" , err )
102
+ if downloadStatus != nil {
103
+ var totalSize int64
104
+ layers , err := img .Layers ()
105
+ if err != nil {
106
+ return err
107
+ }
108
+ for _ , layer := range layers {
109
+ size , err := layer .Size ()
110
+ if err != nil {
111
+ return err
112
+ }
113
+ totalSize += size
114
+ }
115
+ reader = io .TeeReader (reader , & progressWriter {total : totalSize , downloadStatus : downloadStatus })
113
116
}
114
117
115
- downloadStatus ("Extracting" , "" , "" , 0 )
116
-
117
- // Extract the tar file to the target destination
118
- err = ExtractOCIImageFromTar (tmpTarFile .Name (), imageRef , targetDestination , downloadStatus )
119
- if err != nil {
120
- return fmt .Errorf ("failed to extract image tar: %v" , err )
121
- }
118
+ _ , err := archive .Apply (context .Background (),
119
+ targetDestination , reader ,
120
+ archive .WithNoSameOwner ())
122
121
123
- return nil
122
+ return err
124
123
}
125
124
126
125
func ParseImageParts (image string ) (tag , repository , dstimage string ) {
@@ -206,164 +205,3 @@ func GetOCIImageSize(targetImage, targetPlatform string, auth *registrytypes.Aut
206
205
207
206
return size , nil
208
207
}
209
-
210
- // DownloadOCIImageTar downloads the compressed layers of an image and then creates an uncompressed tar
211
- // This provides accurate size estimation and allows for later extraction
212
- func DownloadOCIImageTar (img v1.Image , imageRef string , tarFilePath string , downloadStatus func (string , string , string , float64 )) error {
213
- // Get layers to calculate total compressed size for estimation
214
- layers , err := img .Layers ()
215
- if err != nil {
216
- return fmt .Errorf ("failed to get layers: %v" , err )
217
- }
218
-
219
- // Calculate total compressed size for progress tracking
220
- var totalCompressedSize int64
221
- for _ , layer := range layers {
222
- size , err := layer .Size ()
223
- if err != nil {
224
- return fmt .Errorf ("failed to get layer size: %v" , err )
225
- }
226
- totalCompressedSize += size
227
- }
228
-
229
- // Create a temporary directory to store the compressed layers
230
- tmpDir , err := os .MkdirTemp ("" , "localai-oci-layers-*" )
231
- if err != nil {
232
- return fmt .Errorf ("failed to create temporary directory: %v" , err )
233
- }
234
- defer os .RemoveAll (tmpDir )
235
-
236
- // Download all compressed layers with progress tracking
237
- var downloadedLayers []v1.Layer
238
- var downloadedSize int64
239
-
240
- // Extract image name from the reference for display
241
- imageName := imageRef
242
- for i , layer := range layers {
243
- layerSize , err := layer .Size ()
244
- if err != nil {
245
- return fmt .Errorf ("failed to get layer size: %v" , err )
246
- }
247
-
248
- // Create a temporary file for this layer
249
- layerFile := fmt .Sprintf ("%s/layer-%d.tar.gz" , tmpDir , i )
250
- file , err := os .Create (layerFile )
251
- if err != nil {
252
- return fmt .Errorf ("failed to create layer file: %v" , err )
253
- }
254
-
255
- // Create progress writer for this layer
256
- var writer io.Writer = file
257
- if downloadStatus != nil {
258
- writer = io .MultiWriter (file , & progressWriter {
259
- total : totalCompressedSize ,
260
- fileName : fmt .Sprintf ("Downloading %d/%d %s" , i + 1 , len (layers ), imageName ),
261
- downloadStatus : downloadStatus ,
262
- })
263
- }
264
-
265
- // Download the compressed layer
266
- layerReader , err := layer .Compressed ()
267
- if err != nil {
268
- file .Close ()
269
- return fmt .Errorf ("failed to get compressed layer: %v" , err )
270
- }
271
-
272
- _ , err = io .Copy (writer , layerReader )
273
- file .Close ()
274
- if err != nil {
275
- return fmt .Errorf ("failed to download layer %d: %v" , i , err )
276
- }
277
-
278
- // Load the downloaded layer
279
- downloadedLayer , err := tarball .LayerFromFile (layerFile )
280
- if err != nil {
281
- return fmt .Errorf ("failed to load downloaded layer: %v" , err )
282
- }
283
-
284
- downloadedLayers = append (downloadedLayers , downloadedLayer )
285
- downloadedSize += layerSize
286
- }
287
-
288
- // Create a local image from the downloaded layers
289
- localImg , err := mutate .AppendLayers (img , downloadedLayers ... )
290
- if err != nil {
291
- return fmt .Errorf ("failed to create local image: %v" , err )
292
- }
293
-
294
- // Now extract the uncompressed tar from the local image
295
- tarFile , err := os .Create (tarFilePath )
296
- if err != nil {
297
- return fmt .Errorf ("failed to create tar file: %v" , err )
298
- }
299
- defer tarFile .Close ()
300
-
301
- // Extract uncompressed tar from local image
302
- extractReader := mutate .Extract (localImg )
303
- _ , err = io .Copy (tarFile , extractReader )
304
- if err != nil {
305
- return fmt .Errorf ("failed to extract uncompressed tar: %v" , err )
306
- }
307
-
308
- return nil
309
- }
310
-
311
- // ExtractOCIImageFromTar extracts an image from a previously downloaded tar file
312
- func ExtractOCIImageFromTar (tarFilePath , imageRef , targetDestination string , downloadStatus func (string , string , string , float64 )) error {
313
- // Open the tar file
314
- tarFile , err := os .Open (tarFilePath )
315
- if err != nil {
316
- return fmt .Errorf ("failed to open tar file: %v" , err )
317
- }
318
- defer tarFile .Close ()
319
-
320
- // Get file size for progress tracking
321
- fileInfo , err := tarFile .Stat ()
322
- if err != nil {
323
- return fmt .Errorf ("failed to get file info: %v" , err )
324
- }
325
-
326
- var reader io.Reader = tarFile
327
- if downloadStatus != nil {
328
- reader = io .TeeReader (tarFile , & progressWriter {
329
- total : fileInfo .Size (),
330
- fileName : fmt .Sprintf ("Extracting %s" , imageRef ),
331
- downloadStatus : downloadStatus ,
332
- })
333
- }
334
-
335
- // Extract the tar file
336
- _ , err = archive .Apply (context .Background (),
337
- targetDestination , reader ,
338
- archive .WithNoSameOwner ())
339
-
340
- return err
341
- }
342
-
343
- // GetOCIImageUncompressedSize returns the total uncompressed size of an image
344
- func GetOCIImageUncompressedSize (targetImage , targetPlatform string , auth * registrytypes.AuthConfig , t http.RoundTripper ) (int64 , error ) {
345
- var totalSize int64
346
- var img v1.Image
347
- var err error
348
-
349
- img , err = GetImage (targetImage , targetPlatform , auth , t )
350
- if err != nil {
351
- return totalSize , err
352
- }
353
-
354
- layers , err := img .Layers ()
355
- if err != nil {
356
- return totalSize , err
357
- }
358
-
359
- for _ , layer := range layers {
360
- // Use compressed size as an approximation since uncompressed size is not directly available
361
- size , err := layer .Size ()
362
- if err != nil {
363
- return totalSize , err
364
- }
365
- totalSize += size
366
- }
367
-
368
- return totalSize , nil
369
- }
0 commit comments