Skip to content

Commit e0e108c

Browse files
Merge pull request #6442 from nalind/metadata-file
Introduce CommitResults(), add --metadata-file
2 parents 5aa3362 + 1e6bb46 commit e0e108c

File tree

14 files changed

+371
-160
lines changed

14 files changed

+371
-160
lines changed

cmd/buildah/commit.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ type commitInputOptions struct {
6969
unsetAnnotation []string
7070
annotation []string
7171
createdAnnotation bool
72+
metadataFile string
7273
}
7374

7475
func init() {
@@ -123,6 +124,9 @@ func commitListFlagSet(cmd *cobra.Command, opts *commitInputOptions) {
123124
_ = cmd.RegisterFlagCompletionFunc("manifest", completion.AutocompleteNone)
124125
flags.StringVar(&opts.iidfile, "iidfile", "", "write the image ID to the file")
125126
_ = cmd.RegisterFlagCompletionFunc("iidfile", completion.AutocompleteDefault)
127+
flags.StringVar(&opts.metadataFile, "metadata-file", "", "`file` to write metadata about the image to")
128+
_ = cmd.RegisterFlagCompletionFunc("metadata-file", completion.AutocompleteDefault)
129+
126130
flags.BoolVar(&opts.omitTimestamp, "omit-timestamp", false, "set created timestamp to epoch 0 to allow for deterministic builds")
127131
sourceDateEpochUsageDefault := "current time"
128132
if v := os.Getenv(internal.SourceDateEpochName); v != "" {
@@ -401,10 +405,12 @@ func commitCmd(c *cobra.Command, args []string, iopts commitInputOptions) error
401405
if !iopts.quiet {
402406
options.ReportWriter = os.Stderr
403407
}
404-
id, ref, _, err := builder.Commit(ctx, dest, options)
408+
results, err := builder.CommitResults(ctx, dest, options)
405409
if err != nil {
406410
return util.GetFailureCause(err, fmt.Errorf("committing container %q to %q: %w", builder.Container, image, err))
407411
}
412+
ref := results.Canonical
413+
id := results.ImageID
408414
if ref != nil && id != "" {
409415
logrus.Debugf("wrote image %s with ID %s", ref, id)
410416
} else if ref != nil {
@@ -417,6 +423,15 @@ func commitCmd(c *cobra.Command, args []string, iopts commitInputOptions) error
417423
if options.IIDFile == "" && id != "" {
418424
fmt.Printf("%s\n", id)
419425
}
426+
if iopts.metadataFile != "" {
427+
metadataBytes, err := json.Marshal(results.Metadata)
428+
if err != nil {
429+
return fmt.Errorf("encoding contents for %q: %w", iopts.metadataFile, err)
430+
}
431+
if err := os.WriteFile(iopts.metadataFile, metadataBytes, 0o644); err != nil {
432+
return err
433+
}
434+
}
420435

421436
if iopts.rm {
422437
return builder.Delete()

commit.go

Lines changed: 77 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -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().
311326
func (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
}

define/build.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,4 +418,7 @@ type BuildOptions struct {
418418
// CreatedAnnotation controls whether or not an "org.opencontainers.image.created"
419419
// annotation is present in the output image.
420420
CreatedAnnotation types.OptionalBool
421+
// MetadataFile is the name of a file to which the builder should write a JSON map
422+
// containing metadata about the built image.
423+
MetadataFile string
421424
}

docker/types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,3 +257,10 @@ type V2S2Manifest struct {
257257
// configuration.
258258
Layers []V2S2Descriptor `json:"layers"`
259259
}
260+
261+
const (
262+
// github.com/moby/buildkit/exporter/containerimage/exptypes/types.go
263+
ExporterImageDigestKey = "containerimage.digest"
264+
ExporterImageConfigDigestKey = "containerimage.config.digest"
265+
ExporterImageDescriptorKey = "containerimage.descriptor"
266+
)

docs/buildah-build.1.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -625,6 +625,12 @@ The format of `LIMIT` is `<number>[<unit>]`. Unit can be `b` (bytes),
625625
`k` (kilobytes), `m` (megabytes), or `g` (gigabytes). If you don't specify a
626626
unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap.
627627

628+
**--metadata-file** *MetadataFile*
629+
630+
Write information about the built image to the named file. When `--platform`
631+
is specified more than once, attempting to use this option will trigger an
632+
error.
633+
628634
**--network**, **--net**=*mode*
629635

630636
Sets the configuration for network namespaces when handling `RUN` instructions.

docs/buildah-commit.1.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,10 @@ Write the image ID to the file.
176176
Name of the manifest list to which the built image will be added. Creates the manifest list
177177
if it does not exist. This option is useful for building multi architecture images.
178178

179+
**--metadata-file** *MetadataFile*
180+
181+
Write information about the committed image to the named file.
182+
179183
**--omit-history** *bool-value*
180184

181185
Omit build history information in the built image. (default false).

imagebuildah/build.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ func BuildDockerfiles(ctx context.Context, store storage.Store, options define.B
8686
if len(options.Platforms) > 1 && options.IIDFile != "" {
8787
return "", nil, fmt.Errorf("building multiple images, but iidfile %q can only be used to store one image ID", options.IIDFile)
8888
}
89+
if len(options.Platforms) > 1 && options.MetadataFile != "" {
90+
return "", nil, fmt.Errorf("building multiple images, but metadata file %q can only be used to store information about one image", options.MetadataFile)
91+
}
8992

9093
logger := logrus.New()
9194
if options.Err != nil {

0 commit comments

Comments
 (0)