@@ -44,20 +44,23 @@ import (
4444 "github.com/containerd/log"
4545
4646 "github.com/containerd/nerdctl/v2/pkg/api/types"
47+ "github.com/containerd/nerdctl/v2/pkg/idutil/imagewalker"
4748 "github.com/containerd/nerdctl/v2/pkg/imgutil"
4849)
4950
5051const (
5152 emptyDigest = digest .Digest ("" )
5253)
5354
55+ // squashImage is the image for squash operation
5456type 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
5961}
6062
63+ // squashRuntime is the runtime for squash operation
6164type squashRuntime struct {
6265 opt types.ImageSquashOptions
6366
@@ -70,6 +73,7 @@ type squashRuntime struct {
7073 snapshotter snapshots.Snapshotter
7174}
7275
76+ // initImage initializes the squashImage based on the source image reference
7377func (sr * squashRuntime ) initImage (ctx context.Context ) (* squashImage , error ) {
7478 containerImage , err := sr .imageStore .Get (ctx , sr .opt .SourceImageRef )
7579 if err != nil {
@@ -86,20 +90,21 @@ func (sr *squashRuntime) initImage(ctx context.Context) (*squashImage, error) {
8690 return & squashImage {}, err
8791 }
8892 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 ,
9397 }
9498 return resImage , err
9599}
96100
101+ // generateSquashLayer generates the squash layer based on the given options
97102func (sr * squashRuntime ) generateSquashLayer (image * squashImage ) ([]ocispec.Descriptor , error ) {
98103 // get the layer descriptors by the layer digest
99104 if sr .opt .SquashLayerDigest != "" {
100105 find := false
101106 var res []ocispec.Descriptor
102- for _ , layer := range image .Manifest .Layers {
107+ for _ , layer := range image .manifest .Layers {
103108 if layer .Digest .String () == sr .opt .SquashLayerDigest {
104109 find = true
105110 }
@@ -114,13 +119,14 @@ func (sr *squashRuntime) generateSquashLayer(image *squashImage) ([]ocispec.Desc
114119 }
115120
116121 // 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
119124 }
120125
121126 return nil , fmt .Errorf ("invalid squash option: %w" , errdefs .ErrInvalidArgument )
122127}
123128
129+ // applyLayersToSnapshot applies the layers to the snapshot
124130func (sr * squashRuntime ) applyLayersToSnapshot (ctx context.Context , mount []mount.Mount , layers []ocispec.Descriptor ) error {
125131 for _ , layer := range layers {
126132 if _ , err := sr .differ .Apply (ctx , layer , mount ); err != nil {
@@ -157,7 +163,7 @@ func (sr *squashRuntime) createDiff(ctx context.Context, snapshotName string) (o
157163
158164func (sr * squashRuntime ) generateBaseImageConfig (ctx context.Context , image * squashImage , remainingLayerCount int ) (ocispec.Image , error ) {
159165 // 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
161167 if err != nil {
162168 return ocispec.Image {}, err
163169 }
@@ -257,9 +263,9 @@ func (sr *squashRuntime) writeContentsForImage(ctx context.Context, snName strin
257263 return newMfstDesc , configDesc .Digest , nil
258264}
259265
266+ // createSquashImage creates a new squashImage in the image store.
260267func (sr * squashRuntime ) createSquashImage (ctx context.Context , img images.Image ) (images.Image , error ) {
261268 newImg , err := sr .imageStore .Update (ctx , img )
262- log .G (ctx ).Infof ("updated new squashImage %s" , img .Name )
263269 if err != nil {
264270 // if err is `not found` in the message then create the squashImage, otherwise return the error
265271 if ! errdefs .IsNotFound (err ) {
@@ -268,13 +274,12 @@ func (sr *squashRuntime) createSquashImage(ctx context.Context, img images.Image
268274 if _ , err := sr .imageStore .Create (ctx , img ); err != nil {
269275 return newImg , fmt .Errorf ("failed to create new squashImage %s: %w" , img .Name , err )
270276 }
271- log .G (ctx ).Infof ("created new squashImage %s" , img .Name )
272277 }
273278 return newImg , nil
274279}
275280
276281// 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 ) {
278283 createdTime := time .Now ()
279284 arch := baseConfig .Architecture
280285 if arch == "" {
@@ -292,6 +297,7 @@ func (sr *squashRuntime) generateCommitImageConfig(ctx context.Context, baseConf
292297 }
293298 comment := strings .TrimSpace (sr .opt .Message )
294299
300+ baseImageDigest := strings .Split (baseImg .Target .Digest .String (), ":" )[1 ][:12 ]
295301 return ocispec.Image {
296302 Platform : ocispec.Platform {
297303 Architecture : arch ,
@@ -307,7 +313,7 @@ func (sr *squashRuntime) generateCommitImageConfig(ctx context.Context, baseConf
307313 },
308314 History : append (baseConfig .History , ocispec.History {
309315 Created : & createdTime ,
310- CreatedBy : "" ,
316+ CreatedBy : fmt . Sprintf ( "squash from %s" , baseImageDigest ) ,
311317 Author : author ,
312318 Comment : comment ,
313319 EmptyLayer : false ,
@@ -317,19 +323,38 @@ func (sr *squashRuntime) generateCommitImageConfig(ctx context.Context, baseConf
317323
318324// Squash will squash the image with the given options.
319325func 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
320345 sr := newSquashRuntime (client , option )
321346 ctx = namespaces .WithNamespace (ctx , sr .namespace )
322347 // init squashImage
323- image , err := sr .initImage (ctx )
348+ img , err := sr .initImage (ctx )
324349 if err != nil {
325350 return err
326351 }
327352 // generate squash layers
328- sLayers , err := sr .generateSquashLayer (image )
353+ sLayers , err := sr .generateSquashLayer (img )
329354 if err != nil {
330355 return err
331356 }
332- remainingLayerCount := len (image . Manifest .Layers ) - len (sLayers )
357+ remainingLayerCount := len (img . manifest .Layers ) - len (sLayers )
333358 // Don't gc me and clean the dirty data after 1 hour!
334359 ctx , done , err := sr .client .WithLease (ctx , leases .WithRandomID (), leases .WithExpiration (1 * time .Hour ))
335360 if err != nil {
@@ -338,7 +363,7 @@ func Squash(ctx context.Context, client *containerd.Client, option types.ImageSq
338363 defer done (ctx )
339364
340365 // generate remaining base squashImage config
341- baseImage , err := sr .generateBaseImageConfig (ctx , image , remainingLayerCount )
366+ baseImage , err := sr .generateBaseImageConfig (ctx , img , remainingLayerCount )
342367 if err != nil {
343368 return err
344369 }
@@ -348,27 +373,27 @@ func Squash(ctx context.Context, client *containerd.Client, option types.ImageSq
348373 return err
349374 }
350375 // generate commit image config
351- imageConfig , err := sr .generateCommitImageConfig (ctx , baseImage , diffID )
376+ imageConfig , err := sr .generateCommitImageConfig (ctx , img . image , baseImage , diffID )
352377 if err != nil {
353378 log .G (ctx ).WithError (err ).Error ("failed to generate commit image config" )
354379 return fmt .Errorf ("failed to generate commit image config: %w" , err )
355380 }
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 )
357382 if err != nil {
358383 log .G (ctx ).WithError (err ).Error ("failed to write contents for image" )
359384 return err
360385 }
361- nimg := images.Image {
386+ nImg := images.Image {
362387 Name : sr .opt .TargetImageName ,
363388 Target : commitManifestDesc ,
364389 UpdatedAt : time .Now (),
365390 }
366- _ , err = sr .createSquashImage (ctx , nimg )
391+ _ , err = sr .createSquashImage (ctx , nImg )
367392 if err != nil {
368393 log .G (ctx ).WithError (err ).Error ("failed to create squash image" )
369394 return err
370395 }
371- cimg := containerd .NewImage (sr .client , nimg )
396+ cimg := containerd .NewImage (sr .client , nImg )
372397 if err := cimg .Unpack (ctx , sr .opt .GOptions .Snapshotter , containerd .WithSnapshotterPlatformCheck ()); err != nil {
373398 log .G (ctx ).WithError (err ).Error ("failed to unpack squash image" )
374399 return err
@@ -434,6 +459,7 @@ func newSquashRuntime(client *containerd.Client, option types.ImageSquashOptions
434459}
435460
436461// copied from github.com/containerd/containerd/rootfs/apply.go
462+ // which commit hash is 597d0d76ae03e945996ae6e003dae0c668fa158e by McGowan
437463func uniquePart () string {
438464 t := time .Now ()
439465 var b [3 ]byte
0 commit comments