@@ -19,6 +19,7 @@ import (
1919 structcopier "github.com/jinzhu/copier"
2020 "github.com/opencontainers/go-digest"
2121 imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1"
22+ "golang.org/x/exp/maps"
2223 "golang.org/x/exp/slices"
2324)
2425
@@ -294,7 +295,7 @@ func (m *ManifestList) Inspect() (*define.ManifestListData, error) {
294295 return & inspectList , nil
295296}
296297
297- // Options for adding a manifest list.
298+ // Options for adding an image or artifact to a manifest list.
298299type ManifestListAddOptions struct {
299300 // Add all images to the list if the to-be-added image itself is a
300301 // manifest list.
@@ -371,6 +372,104 @@ func (m *ManifestList) Add(ctx context.Context, name string, options *ManifestLi
371372 return newDigest , nil
372373}
373374
375+ // Options for creating an artifact manifest for one or more files and adding
376+ // the artifact manifest to a manifest list.
377+ type ManifestListAddArtifactOptions struct {
378+ // The artifactType to set in the artifact manifest.
379+ Type * string `json:"artifact_type"`
380+ // The mediaType to set in the config.MediaType field in the artifact manifest.
381+ ConfigType string `json:"artifact_config_type"`
382+ // Content to point to from the config field in the artifact manifest.
383+ Config string `json:"artifact_config"`
384+ // The mediaType to set in the layer descriptors in the artifact manifest.
385+ LayerType string `json:"artifact_layer_type"`
386+ // Whether or not to suppress the org.opencontainers.image.title annotation in layer descriptors.
387+ ExcludeTitles bool `json:"exclude_layer_titles"`
388+ // Annotations to set in the artifact manifest.
389+ Annotations map [string ]string `json:"annotations"`
390+ // Subject to set in the artifact manifest.
391+ Subject string `json:"subject"`
392+ }
393+
394+ // Add adds one or more manifests to the manifest list and returns the digest
395+ // of the added instance.
396+ func (m * ManifestList ) AddArtifact (ctx context.Context , options * ManifestListAddArtifactOptions , files ... string ) (digest.Digest , error ) {
397+ if options == nil {
398+ options = & ManifestListAddArtifactOptions {}
399+ }
400+ opts := manifests.AddArtifactOptions {
401+ ManifestArtifactType : options .Type ,
402+ Annotations : maps .Clone (options .Annotations ),
403+ ExcludeTitles : options .ExcludeTitles ,
404+ }
405+ if options .ConfigType != "" {
406+ opts .ConfigDescriptor = & imgspecv1.Descriptor {
407+ MediaType : options .ConfigType ,
408+ Digest : imgspecv1 .DescriptorEmptyJSON .Digest ,
409+ Size : imgspecv1 .DescriptorEmptyJSON .Size ,
410+ Data : slices .Clone (imgspecv1 .DescriptorEmptyJSON .Data ),
411+ }
412+ }
413+ if options .Config != "" {
414+ if opts .ConfigDescriptor == nil {
415+ opts .ConfigDescriptor = & imgspecv1.Descriptor {
416+ MediaType : imgspecv1 .MediaTypeImageConfig ,
417+ }
418+ }
419+ opts .ConfigDescriptor .Digest = digest .FromString (options .Config )
420+ opts .ConfigDescriptor .Size = int64 (len (options .Config ))
421+ opts .ConfigDescriptor .Data = slices .Clone ([]byte (options .Config ))
422+ }
423+ if opts .ConfigDescriptor == nil {
424+ empty := imgspecv1 .DescriptorEmptyJSON
425+ opts .ConfigDescriptor = & empty
426+ }
427+ if options .LayerType != "" {
428+ opts .LayerMediaType = & options .LayerType
429+ }
430+ if options .Subject != "" {
431+ ref , err := alltransports .ParseImageName (options .Subject )
432+ if err != nil {
433+ withDocker := fmt .Sprintf ("%s://%s" , docker .Transport .Name (), options .Subject )
434+ ref , err = alltransports .ParseImageName (withDocker )
435+ if err != nil {
436+ image , _ , err := m .image .runtime .LookupImage (options .Subject , & LookupImageOptions {ManifestList : true })
437+ if err != nil {
438+ return "" , fmt .Errorf ("locating subject for artifact manifest: %w" , err )
439+ }
440+ ref = image .storageReference
441+ }
442+ }
443+ opts .SubjectReference = ref
444+ }
445+
446+ // Lock the image record where this list lives.
447+ locker , err := manifests .LockerForImage (m .image .runtime .store , m .ID ())
448+ if err != nil {
449+ return "" , err
450+ }
451+ locker .Lock ()
452+ defer locker .Unlock ()
453+
454+ systemContext := m .image .runtime .systemContextCopy ()
455+
456+ // Make sure to reload the image from the containers storage to fetch
457+ // the latest data (e.g., new or delete digests).
458+ if err := m .reload (); err != nil {
459+ return "" , err
460+ }
461+ newDigest , err := m .list .AddArtifact (ctx , systemContext , opts , files ... )
462+ if err != nil {
463+ return "" , err
464+ }
465+
466+ // Write the changes to disk.
467+ if err := m .saveAndReload (); err != nil {
468+ return "" , err
469+ }
470+ return newDigest , nil
471+ }
472+
374473// Options for annotating a manifest list.
375474type ManifestListAnnotateOptions struct {
376475 // Add the specified annotations to the added image.
@@ -387,10 +486,16 @@ type ManifestListAnnotateOptions struct {
387486 OSVersion string
388487 // Add the specified variant to the added image.
389488 Variant string
489+ // Add the specified annotations to the index itself.
490+ IndexAnnotations map [string ]string
491+ // Set the subject to which the index refers.
492+ Subject string
390493}
391494
392495// Annotate an image instance specified by `d` in the manifest list.
393496func (m * ManifestList ) AnnotateInstance (d digest.Digest , options * ManifestListAnnotateOptions ) error {
497+ ctx := context .Background ()
498+
394499 if options == nil {
395500 return nil
396501 }
@@ -430,6 +535,54 @@ func (m *ManifestList) AnnotateInstance(d digest.Digest, options *ManifestListAn
430535 return err
431536 }
432537 }
538+ if len (options .IndexAnnotations ) > 0 {
539+ if err := m .list .SetAnnotations (nil , options .IndexAnnotations ); err != nil {
540+ return err
541+ }
542+ }
543+ if options .Subject != "" {
544+ ref , err := alltransports .ParseImageName (options .Subject )
545+ if err != nil {
546+ withDocker := fmt .Sprintf ("%s://%s" , docker .Transport .Name (), options .Subject )
547+ ref , err = alltransports .ParseImageName (withDocker )
548+ if err != nil {
549+ image , _ , err := m .image .runtime .LookupImage (options .Subject , & LookupImageOptions {ManifestList : true })
550+ if err != nil {
551+ return fmt .Errorf ("locating subject for image index: %w" , err )
552+ }
553+ ref = image .storageReference
554+ }
555+ }
556+ src , err := ref .NewImageSource (ctx , & m .image .runtime .systemContext )
557+ if err != nil {
558+ return err
559+ }
560+ defer src .Close ()
561+ subjectManifestBytes , subjectManifestType , err := src .GetManifest (ctx , nil )
562+ if err != nil {
563+ return err
564+ }
565+ subjectManifestDigest , err := manifest .Digest (subjectManifestBytes )
566+ if err != nil {
567+ return err
568+ }
569+ var subjectArtifactType string
570+ if ! manifest .MIMETypeIsMultiImage (subjectManifestType ) {
571+ var subjectManifest imgspecv1.Manifest
572+ if json .Unmarshal (subjectManifestBytes , & subjectManifest ) == nil {
573+ subjectArtifactType = subjectManifest .ArtifactType
574+ }
575+ }
576+ descriptor := & imgspecv1.Descriptor {
577+ MediaType : subjectManifestType ,
578+ ArtifactType : subjectArtifactType ,
579+ Digest : subjectManifestDigest ,
580+ Size : int64 (len (subjectManifestBytes )),
581+ }
582+ if err := m .list .SetSubject (descriptor ); err != nil {
583+ return err
584+ }
585+ }
433586
434587 // Write the changes to disk.
435588 return m .saveAndReload ()
0 commit comments