Skip to content

Commit ea550eb

Browse files
Merge pull request #1861 from nalind/more-fun-with-artifacts
libimage.ManifestList: add AddArtifact()
2 parents 91e0fac + cf3acdc commit ea550eb

File tree

3 files changed

+434
-8
lines changed

3 files changed

+434
-8
lines changed

libimage/manifest_list.go

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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.
298299
type 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.
375474
type 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.
393496
func (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

Comments
 (0)