@@ -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,41 +90,25 @@ 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
- // get the layer descriptors by the layer digest
99
- if sr .opt .SquashLayerDigest != "" {
100
- find := false
101
- var res []ocispec.Descriptor
102
- for _ , layer := range image .Manifest .Layers {
103
- if layer .Digest .String () == sr .opt .SquashLayerDigest {
104
- find = true
105
- }
106
- if find {
107
- res = append (res , layer )
108
- }
109
- }
110
- if ! find {
111
- return nil , fmt .Errorf ("layer digest %s not found in the image: %w" , sr .opt .SquashLayerDigest , errdefs .ErrNotFound )
112
- }
113
- return res , nil
114
- }
115
-
116
103
// 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
104
+ if sr .opt .SquashLayerLastN > 1 && sr .opt .SquashLayerLastN <= len (image .manifest .Layers ) {
105
+ return image .manifest .Layers [len (image .manifest .Layers )- sr .opt .SquashLayerLastN :], nil
119
106
}
120
107
121
108
return nil , fmt .Errorf ("invalid squash option: %w" , errdefs .ErrInvalidArgument )
122
109
}
123
110
111
+ // applyLayersToSnapshot applies the layers to the snapshot
124
112
func (sr * squashRuntime ) applyLayersToSnapshot (ctx context.Context , mount []mount.Mount , layers []ocispec.Descriptor ) error {
125
113
for _ , layer := range layers {
126
114
if _ , err := sr .differ .Apply (ctx , layer , mount ); err != nil {
@@ -157,7 +145,7 @@ func (sr *squashRuntime) createDiff(ctx context.Context, snapshotName string) (o
157
145
158
146
func (sr * squashRuntime ) generateBaseImageConfig (ctx context.Context , image * squashImage , remainingLayerCount int ) (ocispec.Image , error ) {
159
147
// generate squash squashImage config
160
- orginalConfig , _ , err := imgutil .ReadImageConfig (ctx , image .ClientImage ) // aware of img.platform
148
+ orginalConfig , _ , err := imgutil .ReadImageConfig (ctx , image .clientImage ) // aware of img.platform
161
149
if err != nil {
162
150
return ocispec.Image {}, err
163
151
}
@@ -257,9 +245,9 @@ func (sr *squashRuntime) writeContentsForImage(ctx context.Context, snName strin
257
245
return newMfstDesc , configDesc .Digest , nil
258
246
}
259
247
248
+ // createSquashImage creates a new squashImage in the image store.
260
249
func (sr * squashRuntime ) createSquashImage (ctx context.Context , img images.Image ) (images.Image , error ) {
261
250
newImg , err := sr .imageStore .Update (ctx , img )
262
- log .G (ctx ).Infof ("updated new squashImage %s" , img .Name )
263
251
if err != nil {
264
252
// if err is `not found` in the message then create the squashImage, otherwise return the error
265
253
if ! errdefs .IsNotFound (err ) {
@@ -268,13 +256,12 @@ func (sr *squashRuntime) createSquashImage(ctx context.Context, img images.Image
268
256
if _ , err := sr .imageStore .Create (ctx , img ); err != nil {
269
257
return newImg , fmt .Errorf ("failed to create new squashImage %s: %w" , img .Name , err )
270
258
}
271
- log .G (ctx ).Infof ("created new squashImage %s" , img .Name )
272
259
}
273
260
return newImg , nil
274
261
}
275
262
276
263
// 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 ) {
264
+ func (sr * squashRuntime ) generateCommitImageConfig (ctx context.Context , baseImg images. Image , baseConfig ocispec.Image , diffID digest.Digest ) (ocispec.Image , error ) {
278
265
createdTime := time .Now ()
279
266
arch := baseConfig .Architecture
280
267
if arch == "" {
@@ -292,6 +279,7 @@ func (sr *squashRuntime) generateCommitImageConfig(ctx context.Context, baseConf
292
279
}
293
280
comment := strings .TrimSpace (sr .opt .Message )
294
281
282
+ baseImageDigest := strings .Split (baseImg .Target .Digest .String (), ":" )[1 ][:12 ]
295
283
return ocispec.Image {
296
284
Platform : ocispec.Platform {
297
285
Architecture : arch ,
@@ -307,7 +295,7 @@ func (sr *squashRuntime) generateCommitImageConfig(ctx context.Context, baseConf
307
295
},
308
296
History : append (baseConfig .History , ocispec.History {
309
297
Created : & createdTime ,
310
- CreatedBy : "" ,
298
+ CreatedBy : fmt . Sprintf ( "squash from %s" , baseImageDigest ) ,
311
299
Author : author ,
312
300
Comment : comment ,
313
301
EmptyLayer : false ,
@@ -317,19 +305,38 @@ func (sr *squashRuntime) generateCommitImageConfig(ctx context.Context, baseConf
317
305
318
306
// Squash will squash the image with the given options.
319
307
func Squash (ctx context.Context , client * containerd.Client , option types.ImageSquashOptions ) error {
308
+ var srcName string
309
+ walker := & imagewalker.ImageWalker {
310
+ Client : client ,
311
+ OnFound : func (ctx context.Context , found imagewalker.Found ) error {
312
+ if srcName == "" {
313
+ srcName = found .Image .Name
314
+ }
315
+ return nil
316
+ },
317
+ }
318
+ matchCount , err := walker .Walk (ctx , option .SourceImageRef )
319
+ if err != nil {
320
+ return err
321
+ }
322
+ if matchCount < 1 {
323
+ return fmt .Errorf ("%s: not found" , option .SourceImageRef )
324
+ }
325
+
326
+ option .SourceImageRef = srcName
320
327
sr := newSquashRuntime (client , option )
321
328
ctx = namespaces .WithNamespace (ctx , sr .namespace )
322
329
// init squashImage
323
- image , err := sr .initImage (ctx )
330
+ img , err := sr .initImage (ctx )
324
331
if err != nil {
325
332
return err
326
333
}
327
334
// generate squash layers
328
- sLayers , err := sr .generateSquashLayer (image )
335
+ sLayers , err := sr .generateSquashLayer (img )
329
336
if err != nil {
330
337
return err
331
338
}
332
- remainingLayerCount := len (image . Manifest .Layers ) - len (sLayers )
339
+ remainingLayerCount := len (img . manifest .Layers ) - len (sLayers )
333
340
// Don't gc me and clean the dirty data after 1 hour!
334
341
ctx , done , err := sr .client .WithLease (ctx , leases .WithRandomID (), leases .WithExpiration (1 * time .Hour ))
335
342
if err != nil {
@@ -338,7 +345,7 @@ func Squash(ctx context.Context, client *containerd.Client, option types.ImageSq
338
345
defer done (ctx )
339
346
340
347
// generate remaining base squashImage config
341
- baseImage , err := sr .generateBaseImageConfig (ctx , image , remainingLayerCount )
348
+ baseImage , err := sr .generateBaseImageConfig (ctx , img , remainingLayerCount )
342
349
if err != nil {
343
350
return err
344
351
}
@@ -348,27 +355,27 @@ func Squash(ctx context.Context, client *containerd.Client, option types.ImageSq
348
355
return err
349
356
}
350
357
// generate commit image config
351
- imageConfig , err := sr .generateCommitImageConfig (ctx , baseImage , diffID )
358
+ imageConfig , err := sr .generateCommitImageConfig (ctx , img . image , baseImage , diffID )
352
359
if err != nil {
353
360
log .G (ctx ).WithError (err ).Error ("failed to generate commit image config" )
354
361
return fmt .Errorf ("failed to generate commit image config: %w" , err )
355
362
}
356
- commitManifestDesc , _ , err := sr .writeContentsForImage (ctx , sr .opt .GOptions .Snapshotter , imageConfig , image . Manifest .Layers [:remainingLayerCount ], diffLayerDesc )
363
+ commitManifestDesc , _ , err := sr .writeContentsForImage (ctx , sr .opt .GOptions .Snapshotter , imageConfig , img . manifest .Layers [:remainingLayerCount ], diffLayerDesc )
357
364
if err != nil {
358
365
log .G (ctx ).WithError (err ).Error ("failed to write contents for image" )
359
366
return err
360
367
}
361
- nimg := images.Image {
368
+ nImg := images.Image {
362
369
Name : sr .opt .TargetImageName ,
363
370
Target : commitManifestDesc ,
364
371
UpdatedAt : time .Now (),
365
372
}
366
- _ , err = sr .createSquashImage (ctx , nimg )
373
+ _ , err = sr .createSquashImage (ctx , nImg )
367
374
if err != nil {
368
375
log .G (ctx ).WithError (err ).Error ("failed to create squash image" )
369
376
return err
370
377
}
371
- cimg := containerd .NewImage (sr .client , nimg )
378
+ cimg := containerd .NewImage (sr .client , nImg )
372
379
if err := cimg .Unpack (ctx , sr .opt .GOptions .Snapshotter , containerd .WithSnapshotterPlatformCheck ()); err != nil {
373
380
log .G (ctx ).WithError (err ).Error ("failed to unpack squash image" )
374
381
return err
@@ -433,7 +440,7 @@ func newSquashRuntime(client *containerd.Client, option types.ImageSquashOptions
433
440
}
434
441
}
435
442
436
- // copied from github.com/containerd/containerd/rootfs/apply.go
443
+ // copied from https:// github.com/containerd/containerd/blob/89623f28b87a6004d4b785663257362d1658a729/ rootfs/apply.go#L106
437
444
func uniquePart () string {
438
445
t := time .Now ()
439
446
var b [3 ]byte
0 commit comments