@@ -219,7 +219,7 @@ func (e *imageExporterInstance) Attrs() map[string]string {
219219 return e .attrs
220220}
221221
222- func (e * imageExporterInstance ) Export (ctx context.Context , src * exporter.Source , buildInfo exporter.ExportBuildInfo ) (_ map [string ]string , descref exporter.DescriptorReference , err error ) {
222+ func (e * imageExporterInstance ) Export (ctx context.Context , src * exporter.Source , buildInfo exporter.ExportBuildInfo ) (_ map [string ]string , _ exporter. FinalizeFunc , descref exporter.DescriptorReference , err error ) {
223223 src = src .Clone ()
224224 if src .Metadata == nil {
225225 src .Metadata = make (map [string ][]byte )
@@ -229,14 +229,17 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source
229229 opts := e .opts
230230 as , _ , err := ParseAnnotations (src .Metadata )
231231 if err != nil {
232- return nil , nil , err
232+ return nil , nil , nil , err
233233 }
234234 opts .Annotations = opts .Annotations .Merge (as )
235235
236236 ctx , done , err := leaseutil .WithLease (ctx , e .opt .LeaseManager , leaseutil .MakeTemporary )
237237 if err != nil {
238- return nil , nil , err
238+ return nil , nil , nil , err
239239 }
240+ // On success, we create descref which holds the lease's done function.
241+ // The solver will release descref after recording the descriptor in build
242+ // history. On error (descref is nil), we release the lease here.
240243 defer func () {
241244 if descref == nil {
242245 done (context .WithoutCancel (ctx ))
@@ -245,13 +248,8 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source
245248
246249 desc , err := e .opt .ImageWriter .Commit (ctx , src , buildInfo .SessionID , buildInfo .InlineCache , & opts )
247250 if err != nil {
248- return nil , nil , err
251+ return nil , nil , nil , err
249252 }
250- defer func () {
251- if err == nil {
252- descref = NewDescriptorReference (* desc , done )
253- }
254- }()
255253
256254 resp := make (map [string ]string )
257255
@@ -270,6 +268,9 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source
270268 }
271269 }
272270
271+ // Collect names for finalize callback to push
272+ var namesToPush []string
273+
273274 if e .opts .ImageName != "" {
274275 targetNames := strings .SplitSeq (e .opts .ImageName , "," )
275276 for targetName := range targetNames {
@@ -299,11 +300,11 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source
299300 img .Name = targetName + sfx
300301 if _ , err := e .opt .Images .Update (imageClientCtx , img ); err != nil {
301302 if ! errors .Is (err , cerrdefs .ErrNotFound ) {
302- return nil , nil , tagDone (err )
303+ return nil , nil , nil , tagDone (err )
303304 }
304305
305306 if _ , err := e .opt .Images .Create (imageClientCtx , img ); err != nil {
306- return nil , nil , tagDone (err )
307+ return nil , nil , nil , tagDone (err )
307308 }
308309 }
309310 }
@@ -315,10 +316,10 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source
315316 // /
316317 // TODO: change e.unpackImage so that it takes Result[Remote] as parameter.
317318 // https://github.com/moby/buildkit/pull/4057#discussion_r1324106088
318- return nil , nil , errors .New ("exporter option \" rewrite-timestamp\" conflicts with \" unpack\" " )
319+ return nil , nil , nil , errors .New ("exporter option \" rewrite-timestamp\" conflicts with \" unpack\" " )
319320 }
320321 if err := e .unpackImage (ctx , img , src , session .NewGroup (buildInfo .SessionID )); err != nil {
321- return nil , nil , err
322+ return nil , nil , nil , err
322323 }
323324 }
324325
@@ -350,19 +351,13 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source
350351 })
351352 }
352353 if err := eg .Wait (); err != nil {
353- return nil , nil , err
354+ return nil , nil , nil , err
354355 }
355356 }
356357 }
358+ // Collect names for pushing in finalize
357359 if e .push {
358- err = e .pushImage (ctx , src , buildInfo .SessionID , targetName , desc .Digest )
359- if err != nil {
360- var statusErr remoteserrors.ErrUnexpectedStatus
361- if errors .As (err , & statusErr ) {
362- err = errutil .WithDetails (err )
363- }
364- return nil , nil , errors .Wrapf (err , "failed to push %v" , targetName )
365- }
360+ namesToPush = append (namesToPush , targetName )
366361 }
367362 }
368363 resp [exptypes .ExporterImageNameKey ] = e .opts .ImageName
@@ -376,11 +371,34 @@ func (e *imageExporterInstance) Export(ctx context.Context, src *exporter.Source
376371
377372 dtdesc , err := json .Marshal (desc )
378373 if err != nil {
379- return nil , nil , err
374+ return nil , nil , nil , err
380375 }
381376 resp [exptypes .ExporterImageDescriptorKey ] = base64 .StdEncoding .EncodeToString (dtdesc )
382377
383- return resp , nil , nil
378+ // Create descref so descriptor is recorded in build history.
379+ // Transfer lease ownership to descref - caller releases after finalize.
380+ descref = NewDescriptorReference (* desc , done )
381+
382+ if len (namesToPush ) == 0 {
383+ return resp , nil , descref , nil
384+ }
385+
386+ // Create finalize callback for pushing
387+ finalize := func (ctx context.Context ) error {
388+ for _ , targetName := range namesToPush {
389+ err := e .pushImage (ctx , src , buildInfo .SessionID , targetName , desc .Digest )
390+ if err != nil {
391+ var statusErr remoteserrors.ErrUnexpectedStatus
392+ if errors .As (err , & statusErr ) {
393+ err = errutil .WithDetails (err )
394+ }
395+ return errors .Wrapf (err , "failed to push %v" , targetName )
396+ }
397+ }
398+ return nil
399+ }
400+
401+ return resp , finalize , descref , nil
384402}
385403
386404func (e * imageExporterInstance ) pushImage (ctx context.Context , src * exporter.Source , sessionID string , targetName string , dgst digest.Digest ) error {
0 commit comments