@@ -10,6 +10,7 @@ import (
1010 "strings"
1111 "time"
1212
13+ "github.com/containers/buildah/internal/metadata"
1314 "github.com/containers/buildah/pkg/blobcache"
1415 "github.com/containers/buildah/util"
1516 encconfig "github.com/containers/ocicrypt/config"
@@ -76,7 +77,8 @@ type CommitOptions struct {
7677 // github.com/containers/image/types SystemContext to hold credentials
7778 // and other authentication/authorization information.
7879 SystemContext * types.SystemContext
79- // IIDFile tells the builder to write the image ID to the specified file
80+ // IIDFile tells the builder to write the image's ID, preceded by
81+ // "sha256:", to the specified file.
8082 IIDFile string
8183 // Squash tells the builder to produce an image with a single layer
8284 // instead of with possibly more than one layer.
@@ -304,11 +306,36 @@ func (b *Builder) addManifest(ctx context.Context, manifestName string, imageSpe
304306 return imageID , err
305307}
306308
309+ // CommitResults is a structure returned when CommitResults() succeeds.
310+ type CommitResults struct {
311+ ImageID string // a local image ID, or part of the digest of the image's config blob
312+ Canonical reference.Canonical // set if destination included a DockerReference
313+ MediaType string // image manifest MIME type, always returned
314+ ImageManifest []byte // raw image manifest, always returned
315+ Digest digest.Digest // digest of the manifest, always returned
316+ Metadata map [string ]any // always returned, format is flexible
317+ }
318+
307319// Commit writes the contents of the container, along with its updated
308320// configuration, to a new image in the specified location, and if we know how,
309321// add any additional tags that were specified. Returns the ID of the new image
310- // if commit was successful and the image destination was local.
322+ // if commit was successful and the image destination was local, a canonical
323+ // reference if the destination ImageReference include a DockerReference, and
324+ // the digest of the written image's manifest.
325+ // Commit() is implemented as a wrapper around CommitResults().
311326func (b * Builder ) Commit (ctx context.Context , dest types.ImageReference , options CommitOptions ) (string , reference.Canonical , digest.Digest , error ) {
327+ results , err := b .CommitResults (ctx , dest , options )
328+ if err != nil {
329+ return "" , nil , "" , err
330+ }
331+ return results .ImageID , results .Canonical , results .Digest , nil
332+ }
333+
334+ // CommitResults writes the contents of the container, along with its updated
335+ // configuration, to a new image in the specified location, and if we know how,
336+ // add any additional tags that were specified. Returns a CommitResults
337+ // structure.
338+ func (b * Builder ) CommitResults (ctx context.Context , dest types.ImageReference , options CommitOptions ) (* CommitResults , error ) {
312339 var (
313340 imgID string
314341 src types.ImageReference
@@ -325,7 +352,7 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options
325352 // work twice.
326353 if options .OmitTimestamp {
327354 if options .HistoryTimestamp != nil {
328- return imgID , nil , "" , fmt .Errorf ("OmitTimestamp and HistoryTimestamp can not be used together" )
355+ return nil , fmt .Errorf ("OmitTimestamp and HistoryTimestamp can not be used together" )
329356 }
330357 timestamp := time .Unix (0 , 0 ).UTC ()
331358 options .HistoryTimestamp = & timestamp
@@ -339,7 +366,7 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options
339366 nameToRemove = stringid .GenerateRandomID () + "-tmp"
340367 dest2 , err := is .Transport .ParseStoreReference (b .store , nameToRemove )
341368 if err != nil {
342- return imgID , nil , "" , fmt .Errorf ("creating temporary destination reference for image: %w" , err )
369+ return nil , fmt .Errorf ("creating temporary destination reference for image: %w" , err )
343370 }
344371 dest = dest2
345372 }
@@ -348,23 +375,23 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options
348375
349376 blocked , err := isReferenceBlocked (dest , systemContext )
350377 if err != nil {
351- return "" , nil , "" , fmt .Errorf ("checking if committing to registry for %q is blocked: %w" , transports .ImageName (dest ), err )
378+ return nil , fmt .Errorf ("checking if committing to registry for %q is blocked: %w" , transports .ImageName (dest ), err )
352379 }
353380 if blocked {
354- return "" , nil , "" , fmt .Errorf ("commit access to registry for %q is blocked by configuration" , transports .ImageName (dest ))
381+ return nil , fmt .Errorf ("commit access to registry for %q is blocked by configuration" , transports .ImageName (dest ))
355382 }
356383
357384 // Load the system signing policy.
358385 commitPolicy , err := signature .DefaultPolicy (systemContext )
359386 if err != nil {
360- return "" , nil , "" , fmt .Errorf ("obtaining default signature policy: %w" , err )
387+ return nil , fmt .Errorf ("obtaining default signature policy: %w" , err )
361388 }
362389 // Override the settings for local storage to make sure that we can always read the source "image".
363390 commitPolicy .Transports [is .Transport .Name ()] = storageAllowedPolicyScopes
364391
365392 policyContext , err := signature .NewPolicyContext (commitPolicy )
366393 if err != nil {
367- return imgID , nil , "" , fmt .Errorf ("creating new signature policy context: %w" , err )
394+ return nil , fmt .Errorf ("creating new signature policy context: %w" , err )
368395 }
369396 defer func () {
370397 if err2 := policyContext .Destroy (); err2 != nil {
@@ -375,11 +402,11 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options
375402 // Check if the commit is blocked by $BUILDER_REGISTRY_SOURCES.
376403 insecure , err := checkRegistrySourcesAllows ("commit to" , dest )
377404 if err != nil {
378- return imgID , nil , "" , err
405+ return nil , err
379406 }
380407 if insecure {
381408 if systemContext .DockerInsecureSkipTLSVerify == types .OptionalBoolFalse {
382- return imgID , nil , "" , fmt .Errorf ("can't require tls verification on an insecured registry" )
409+ return nil , fmt .Errorf ("can't require tls verification on an insecured registry" )
383410 }
384411 systemContext .DockerInsecureSkipTLSVerify = types .OptionalBoolTrue
385412 systemContext .OCIInsecureSkipTLSVerify = true
@@ -393,7 +420,7 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options
393420 if len (options .SBOMScanOptions ) != 0 {
394421 var scansDirectory string
395422 if extraImageContent , extraLocalContent , scansDirectory , err = b .sbomScan (ctx , options ); err != nil {
396- return imgID , nil , "" , fmt .Errorf ("scanning rootfs to generate SBOM for container %q: %w" , b .ContainerID , err )
423+ return nil , fmt .Errorf ("scanning rootfs to generate SBOM for container %q: %w" , b .ContainerID , err )
397424 }
398425 if scansDirectory != "" {
399426 defer func () {
@@ -418,7 +445,7 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options
418445 // Build an image reference from which we can copy the finished image.
419446 src , err = b .makeContainerImageRef (options )
420447 if err != nil {
421- return imgID , nil , "" , fmt .Errorf ("computing layer digests and building metadata for container %q: %w" , b .ContainerID , err )
448+ return nil , fmt .Errorf ("computing layer digests and building metadata for container %q: %w" , b .ContainerID , err )
422449 }
423450 // In case we're using caching, decide how to handle compression for a cache.
424451 // If we're using blob caching, set it up for the source.
@@ -431,12 +458,12 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options
431458 }
432459 cache , err := blobcache .NewBlobCache (src , options .BlobDirectory , compress )
433460 if err != nil {
434- return imgID , nil , "" , fmt .Errorf ("wrapping image reference %q in blob cache at %q: %w" , transports .ImageName (src ), options .BlobDirectory , err )
461+ return nil , fmt .Errorf ("wrapping image reference %q in blob cache at %q: %w" , transports .ImageName (src ), options .BlobDirectory , err )
435462 }
436463 maybeCachedSrc = cache
437464 cache , err = blobcache .NewBlobCache (dest , options .BlobDirectory , compress )
438465 if err != nil {
439- return imgID , nil , "" , fmt .Errorf ("wrapping image reference %q in blob cache at %q: %w" , transports .ImageName (dest ), options .BlobDirectory , err )
466+ return nil , fmt .Errorf ("wrapping image reference %q in blob cache at %q: %w" , transports .ImageName (dest ), options .BlobDirectory , err )
440467 }
441468 maybeCachedDest = cache
442469 }
@@ -457,7 +484,7 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options
457484
458485 var manifestBytes []byte
459486 if manifestBytes , err = retryCopyImage (ctx , policyContext , maybeCachedDest , maybeCachedSrc , dest , getCopyOptions (b .store , options .ReportWriter , nil , systemContext , "" , false , options .SignBy , options .OciEncryptLayers , options .OciEncryptConfig , nil , destinationTimestamp ), options .MaxRetries , options .RetryDelay ); err != nil {
460- return imgID , nil , "" , fmt .Errorf ("copying layers and metadata for container %q: %w" , b .ContainerID , err )
487+ return nil , fmt .Errorf ("copying layers and metadata for container %q: %w" , b .ContainerID , err )
461488 }
462489 // If we've got more names to attach, and we know how to do that for
463490 // the transport that we're writing the new image to, add them now.
@@ -466,10 +493,10 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options
466493 case is .Transport .Name ():
467494 _ , img , err := is .ResolveReference (dest )
468495 if err != nil {
469- return imgID , nil , "" , fmt .Errorf ("locating just-written image %q: %w" , transports .ImageName (dest ), err )
496+ return nil , fmt .Errorf ("locating just-written image %q: %w" , transports .ImageName (dest ), err )
470497 }
471498 if err = util .AddImageNames (b .store , "" , systemContext , img , options .AdditionalTags ); err != nil {
472- return imgID , nil , "" , fmt .Errorf ("setting image names to %v: %w" , append (img .Names , options .AdditionalTags ... ), err )
499+ return nil , fmt .Errorf ("setting image names to %v: %w" , append (img .Names , options .AdditionalTags ... ), err )
473500 }
474501 logrus .Debugf ("assigned names %v to image %q" , img .Names , img .ID )
475502 default :
@@ -480,7 +507,7 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options
480507 if dest .Transport ().Name () == is .Transport .Name () {
481508 dest2 , img , err := is .ResolveReference (dest )
482509 if err != nil {
483- return imgID , nil , "" , fmt .Errorf ("locating image %q in local storage: %w" , transports .ImageName (dest ), err )
510+ return nil , fmt .Errorf ("locating image %q in local storage: %w" , transports .ImageName (dest ), err )
484511 }
485512 dest = dest2
486513 imgID = img .ID
@@ -492,13 +519,13 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options
492519 }
493520 if len (toPruneNames ) > 0 {
494521 if err = b .store .RemoveNames (imgID , toPruneNames ); err != nil {
495- return imgID , nil , "" , fmt .Errorf ("failed to remove temporary name from image %q: %w" , imgID , err )
522+ return nil , fmt .Errorf ("failed to remove temporary name from image %q: %w" , imgID , err )
496523 }
497524 logrus .Debugf ("removing %v from assigned names to image %q" , nameToRemove , img .ID )
498525 }
499526 if options .IIDFile != "" {
500527 if err = os .WriteFile (options .IIDFile , []byte ("sha256:" + img .ID ), 0o644 ); err != nil {
501- return imgID , nil , "" , err
528+ return nil , err
502529 }
503530 }
504531 }
@@ -521,23 +548,23 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options
521548 return nil
522549 }()
523550 if err != nil {
524- return imgID , nil , "" , err
551+ return nil , err
525552 }
526553 }
527554
528555 // Calculate the as-written digest of the image's manifest and build the digested
529556 // reference for the image.
530557 manifestDigest , err := manifest .Digest (manifestBytes )
531558 if err != nil {
532- return imgID , nil , "" , fmt .Errorf ("computing digest of manifest of new image %q: %w" , transports .ImageName (dest ), err )
559+ return nil , fmt .Errorf ("computing digest of manifest of new image %q: %w" , transports .ImageName (dest ), err )
533560 }
534- if imgID == "" {
535- parsedManifest , err := manifest . FromBlob ( manifestBytes , manifest . GuessMIMEType ( manifestBytes ))
536- if err != nil {
537- return imgID , nil , "" , fmt . Errorf ( "parsing written manifest to determine the image's ID: %w" , err )
538- }
539- configInfo := parsedManifest . ConfigInfo ()
540- if configInfo . Size > 2 && configInfo . Digest . Validate () == nil { // don't be returning a digest of "" or "{}"
561+ parsedManifest , err := manifest . FromBlob ( manifestBytes , manifest . GuessMIMEType ( manifestBytes ))
562+ if err != nil {
563+ return nil , fmt . Errorf ( "parsing written manifest to determine the image's ID: %w" , err )
564+ }
565+ configInfo := parsedManifest . ConfigInfo ()
566+ if configInfo . Size > 2 && configInfo . Digest . Validate () == nil { // don't be returning a digest of "" or "{}"
567+ if imgID == "" {
541568 imgID = configInfo .Digest .Encoded ()
542569 }
543570 }
@@ -553,9 +580,28 @@ func (b *Builder) Commit(ctx context.Context, dest types.ImageReference, options
553580 if options .Manifest != "" {
554581 manifestID , err := b .addManifest (ctx , options .Manifest , imgID )
555582 if err != nil {
556- return imgID , nil , "" , err
583+ return nil , err
557584 }
558585 logrus .Debugf ("added imgID %s to manifestID %s" , imgID , manifestID )
559586 }
560- return imgID , ref , manifestDigest , nil
587+
588+ descriptor := v1.Descriptor {
589+ MediaType : manifest .GuessMIMEType (manifestBytes ),
590+ Digest : manifestDigest ,
591+ Size : int64 (len (manifestBytes )),
592+ }
593+ metadata , err := metadata .Build (configInfo .Digest , descriptor )
594+ if err != nil {
595+ return nil , fmt .Errorf ("building metadata map for image: %w" , err )
596+ }
597+
598+ results := CommitResults {
599+ ImageID : imgID ,
600+ Canonical : ref ,
601+ MediaType : descriptor .MediaType ,
602+ ImageManifest : manifestBytes ,
603+ Digest : manifestDigest ,
604+ Metadata : metadata ,
605+ }
606+ return & results , nil
561607}
0 commit comments