@@ -44,20 +44,23 @@ import (
44
44
"github.com/containerd/log"
45
45
46
46
"github.com/containerd/nerdctl/v2/pkg/api/types"
47
+ "github.com/containerd/nerdctl/v2/pkg/idutil/imagewalker"
47
48
"github.com/containerd/nerdctl/v2/pkg/imgutil"
48
49
)
49
50
50
51
const (
51
52
emptyDigest = digest .Digest ("" )
52
53
)
53
54
55
+ // squashImage is the image for squash operation
54
56
type squashImage struct {
55
- ClientImage containerd.Image
56
- Config ocispec.Image
57
- Image images.Image
58
- Manifest * ocispec.Manifest
57
+ clientImage containerd.Image
58
+ config ocispec.Image
59
+ image images.Image
60
+ manifest * ocispec.Manifest
59
61
}
60
62
63
+ // squashRuntime is the runtime for squash operation
61
64
type squashRuntime struct {
62
65
opt types.ImageSquashOptions
63
66
@@ -70,6 +73,7 @@ type squashRuntime struct {
70
73
snapshotter snapshots.Snapshotter
71
74
}
72
75
76
+ // initImage initializes the squashImage based on the source image reference
73
77
func (sr * squashRuntime ) initImage (ctx context.Context ) (* squashImage , error ) {
74
78
containerImage , err := sr .imageStore .Get (ctx , sr .opt .SourceImageRef )
75
79
if err != nil {
@@ -86,20 +90,21 @@ func (sr *squashRuntime) initImage(ctx context.Context) (*squashImage, error) {
86
90
return & squashImage {}, err
87
91
}
88
92
resImage := & squashImage {
89
- ClientImage : clientImage ,
90
- Config : config ,
91
- Image : containerImage ,
92
- Manifest : manifest ,
93
+ clientImage : clientImage ,
94
+ config : config ,
95
+ image : containerImage ,
96
+ manifest : manifest ,
93
97
}
94
98
return resImage , err
95
99
}
96
100
101
+ // generateSquashLayer generates the squash layer based on the given options
97
102
func (sr * squashRuntime ) generateSquashLayer (image * squashImage ) ([]ocispec.Descriptor , error ) {
98
103
// get the layer descriptors by the layer digest
99
104
if sr .opt .SquashLayerDigest != "" {
100
105
find := false
101
106
var res []ocispec.Descriptor
102
- for _ , layer := range image .Manifest .Layers {
107
+ for _ , layer := range image .manifest .Layers {
103
108
if layer .Digest .String () == sr .opt .SquashLayerDigest {
104
109
find = true
105
110
}
@@ -114,13 +119,14 @@ func (sr *squashRuntime) generateSquashLayer(image *squashImage) ([]ocispec.Desc
114
119
}
115
120
116
121
// get the layer descriptors by the layer count
117
- if sr .opt .SquashLayerCount > 1 && sr .opt .SquashLayerCount <= len (image .Manifest .Layers ) {
118
- return image .Manifest .Layers [len (image .Manifest .Layers )- sr .opt .SquashLayerCount :], nil
122
+ if sr .opt .SquashLayerCount > 1 && sr .opt .SquashLayerCount <= len (image .manifest .Layers ) {
123
+ return image .manifest .Layers [len (image .manifest .Layers )- sr .opt .SquashLayerCount :], nil
119
124
}
120
125
121
126
return nil , fmt .Errorf ("invalid squash option: %w" , errdefs .ErrInvalidArgument )
122
127
}
123
128
129
+ // applyLayersToSnapshot applies the layers to the snapshot
124
130
func (sr * squashRuntime ) applyLayersToSnapshot (ctx context.Context , mount []mount.Mount , layers []ocispec.Descriptor ) error {
125
131
for _ , layer := range layers {
126
132
if _ , err := sr .differ .Apply (ctx , layer , mount ); err != nil {
@@ -157,7 +163,7 @@ func (sr *squashRuntime) createDiff(ctx context.Context, snapshotName string) (o
157
163
158
164
func (sr * squashRuntime ) generateBaseImageConfig (ctx context.Context , image * squashImage , remainingLayerCount int ) (ocispec.Image , error ) {
159
165
// generate squash squashImage config
160
- orginalConfig , _ , err := imgutil .ReadImageConfig (ctx , image .ClientImage ) // aware of img.platform
166
+ orginalConfig , _ , err := imgutil .ReadImageConfig (ctx , image .clientImage ) // aware of img.platform
161
167
if err != nil {
162
168
return ocispec.Image {}, err
163
169
}
@@ -257,9 +263,9 @@ func (sr *squashRuntime) writeContentsForImage(ctx context.Context, snName strin
257
263
return newMfstDesc , configDesc .Digest , nil
258
264
}
259
265
266
+ // createSquashImage creates a new squashImage in the image store.
260
267
func (sr * squashRuntime ) createSquashImage (ctx context.Context , img images.Image ) (images.Image , error ) {
261
268
newImg , err := sr .imageStore .Update (ctx , img )
262
- log .G (ctx ).Infof ("updated new squashImage %s" , img .Name )
263
269
if err != nil {
264
270
// if err is `not found` in the message then create the squashImage, otherwise return the error
265
271
if ! errdefs .IsNotFound (err ) {
@@ -268,13 +274,12 @@ func (sr *squashRuntime) createSquashImage(ctx context.Context, img images.Image
268
274
if _ , err := sr .imageStore .Create (ctx , img ); err != nil {
269
275
return newImg , fmt .Errorf ("failed to create new squashImage %s: %w" , img .Name , err )
270
276
}
271
- log .G (ctx ).Infof ("created new squashImage %s" , img .Name )
272
277
}
273
278
return newImg , nil
274
279
}
275
280
276
281
// generateCommitImageConfig returns commit oci image config based on the container's image.
277
- func (sr * squashRuntime ) generateCommitImageConfig (ctx context.Context , baseConfig ocispec.Image , diffID digest.Digest ) (ocispec.Image , error ) {
282
+ func (sr * squashRuntime ) generateCommitImageConfig (ctx context.Context , baseImg images. Image , baseConfig ocispec.Image , diffID digest.Digest ) (ocispec.Image , error ) {
278
283
createdTime := time .Now ()
279
284
arch := baseConfig .Architecture
280
285
if arch == "" {
@@ -292,6 +297,7 @@ func (sr *squashRuntime) generateCommitImageConfig(ctx context.Context, baseConf
292
297
}
293
298
comment := strings .TrimSpace (sr .opt .Message )
294
299
300
+ baseImageDigest := strings .Split (baseImg .Target .Digest .String (), ":" )[1 ][:12 ]
295
301
return ocispec.Image {
296
302
Platform : ocispec.Platform {
297
303
Architecture : arch ,
@@ -307,7 +313,7 @@ func (sr *squashRuntime) generateCommitImageConfig(ctx context.Context, baseConf
307
313
},
308
314
History : append (baseConfig .History , ocispec.History {
309
315
Created : & createdTime ,
310
- CreatedBy : "" ,
316
+ CreatedBy : fmt . Sprintf ( "squash from %s" , baseImageDigest ) ,
311
317
Author : author ,
312
318
Comment : comment ,
313
319
EmptyLayer : false ,
@@ -317,19 +323,38 @@ func (sr *squashRuntime) generateCommitImageConfig(ctx context.Context, baseConf
317
323
318
324
// Squash will squash the image with the given options.
319
325
func Squash (ctx context.Context , client * containerd.Client , option types.ImageSquashOptions ) error {
326
+ var srcName string
327
+ walker := & imagewalker.ImageWalker {
328
+ Client : client ,
329
+ OnFound : func (ctx context.Context , found imagewalker.Found ) error {
330
+ if srcName == "" {
331
+ srcName = found .Image .Name
332
+ }
333
+ return nil
334
+ },
335
+ }
336
+ matchCount , err := walker .Walk (ctx , option .SourceImageRef )
337
+ if err != nil {
338
+ return err
339
+ }
340
+ if matchCount < 1 {
341
+ return fmt .Errorf ("%s: not found" , option .SourceImageRef )
342
+ }
343
+
344
+ option .SourceImageRef = srcName
320
345
sr := newSquashRuntime (client , option )
321
346
ctx = namespaces .WithNamespace (ctx , sr .namespace )
322
347
// init squashImage
323
- image , err := sr .initImage (ctx )
348
+ img , err := sr .initImage (ctx )
324
349
if err != nil {
325
350
return err
326
351
}
327
352
// generate squash layers
328
- sLayers , err := sr .generateSquashLayer (image )
353
+ sLayers , err := sr .generateSquashLayer (img )
329
354
if err != nil {
330
355
return err
331
356
}
332
- remainingLayerCount := len (image . Manifest .Layers ) - len (sLayers )
357
+ remainingLayerCount := len (img . manifest .Layers ) - len (sLayers )
333
358
// Don't gc me and clean the dirty data after 1 hour!
334
359
ctx , done , err := sr .client .WithLease (ctx , leases .WithRandomID (), leases .WithExpiration (1 * time .Hour ))
335
360
if err != nil {
@@ -338,7 +363,7 @@ func Squash(ctx context.Context, client *containerd.Client, option types.ImageSq
338
363
defer done (ctx )
339
364
340
365
// generate remaining base squashImage config
341
- baseImage , err := sr .generateBaseImageConfig (ctx , image , remainingLayerCount )
366
+ baseImage , err := sr .generateBaseImageConfig (ctx , img , remainingLayerCount )
342
367
if err != nil {
343
368
return err
344
369
}
@@ -348,27 +373,27 @@ func Squash(ctx context.Context, client *containerd.Client, option types.ImageSq
348
373
return err
349
374
}
350
375
// generate commit image config
351
- imageConfig , err := sr .generateCommitImageConfig (ctx , baseImage , diffID )
376
+ imageConfig , err := sr .generateCommitImageConfig (ctx , img . image , baseImage , diffID )
352
377
if err != nil {
353
378
log .G (ctx ).WithError (err ).Error ("failed to generate commit image config" )
354
379
return fmt .Errorf ("failed to generate commit image config: %w" , err )
355
380
}
356
- commitManifestDesc , _ , err := sr .writeContentsForImage (ctx , sr .opt .GOptions .Snapshotter , imageConfig , image . Manifest .Layers [:remainingLayerCount ], diffLayerDesc )
381
+ commitManifestDesc , _ , err := sr .writeContentsForImage (ctx , sr .opt .GOptions .Snapshotter , imageConfig , img . manifest .Layers [:remainingLayerCount ], diffLayerDesc )
357
382
if err != nil {
358
383
log .G (ctx ).WithError (err ).Error ("failed to write contents for image" )
359
384
return err
360
385
}
361
- nimg := images.Image {
386
+ nImg := images.Image {
362
387
Name : sr .opt .TargetImageName ,
363
388
Target : commitManifestDesc ,
364
389
UpdatedAt : time .Now (),
365
390
}
366
- _ , err = sr .createSquashImage (ctx , nimg )
391
+ _ , err = sr .createSquashImage (ctx , nImg )
367
392
if err != nil {
368
393
log .G (ctx ).WithError (err ).Error ("failed to create squash image" )
369
394
return err
370
395
}
371
- cimg := containerd .NewImage (sr .client , nimg )
396
+ cimg := containerd .NewImage (sr .client , nImg )
372
397
if err := cimg .Unpack (ctx , sr .opt .GOptions .Snapshotter , containerd .WithSnapshotterPlatformCheck ()); err != nil {
373
398
log .G (ctx ).WithError (err ).Error ("failed to unpack squash image" )
374
399
return err
@@ -434,6 +459,7 @@ func newSquashRuntime(client *containerd.Client, option types.ImageSquashOptions
434
459
}
435
460
436
461
// copied from github.com/containerd/containerd/rootfs/apply.go
462
+ // which commit hash is 597d0d76ae03e945996ae6e003dae0c668fa158e by McGowan
437
463
func uniquePart () string {
438
464
t := time .Now ()
439
465
var b [3 ]byte
0 commit comments