Skip to content

Commit 577dc07

Browse files
committed
manifest: merge manifest lists
Previously, merging multiple manifest lists together was not supported as a use case. However, with new versions of buildkit, manifest lists will be created by default. To support these new manifest lists, we should support merging manifest lists, just as we support this with manifests today. To do this, we refactor the manifest store to save and load groups of manifests, instead of only one. On disk, these manifests take the form of suffixed indexes, e.g. "_2", "_3", etc (note that we ignore "_1", which is permitted to allow a backwards-compatible change with the previous disk format). Then, only the commands need small updates - "create" now looks up multiple manifests and saves multiple manifests, while "annotate" now annotates all the matching manifests. Signed-off-by: Justin Chadwell <me@jedevc.com>
1 parent bde9d52 commit 577dc07

File tree

6 files changed

+109
-66
lines changed

6 files changed

+109
-66
lines changed

cli/command/manifest/annotate.go

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -58,38 +58,40 @@ func runManifestAnnotate(dockerCli command.Cli, opts annotateOptions) error {
5858
}
5959

6060
manifestStore := dockerCli.ManifestStore()
61-
imageManifest, err := manifestStore.Get(targetRef, imgRef)
61+
imageManifests, err := manifestStore.Get(targetRef, imgRef)
6262
switch {
6363
case errors.Is(err, types.ErrManifestNotFound):
6464
return fmt.Errorf("manifest for image %s does not exist in %s", opts.image, opts.target)
6565
case err != nil:
6666
return err
6767
}
6868

69-
// Update the mf
70-
if imageManifest.Descriptor.Platform == nil {
71-
imageManifest.Descriptor.Platform = new(ocispec.Platform)
72-
}
73-
if opts.os != "" {
74-
imageManifest.Descriptor.Platform.OS = opts.os
75-
}
76-
if opts.arch != "" {
77-
imageManifest.Descriptor.Platform.Architecture = opts.arch
78-
}
79-
for _, osFeature := range opts.osFeatures {
80-
imageManifest.Descriptor.Platform.OSFeatures = appendIfUnique(imageManifest.Descriptor.Platform.OSFeatures, osFeature)
81-
}
82-
if opts.variant != "" {
83-
imageManifest.Descriptor.Platform.Variant = opts.variant
84-
}
85-
if opts.osVersion != "" {
86-
imageManifest.Descriptor.Platform.OSVersion = opts.osVersion
87-
}
88-
89-
if !isValidOSArch(imageManifest.Descriptor.Platform.OS, imageManifest.Descriptor.Platform.Architecture) {
90-
return errors.Errorf("manifest entry for image has unsupported os/arch combination: %s/%s", opts.os, opts.arch)
69+
for i, imageManifest := range imageManifests {
70+
// Update the mf
71+
if imageManifest.Descriptor.Platform == nil {
72+
imageManifest.Descriptor.Platform = new(ocispec.Platform)
73+
}
74+
if opts.os != "" {
75+
imageManifest.Descriptor.Platform.OS = opts.os
76+
}
77+
if opts.arch != "" {
78+
imageManifest.Descriptor.Platform.Architecture = opts.arch
79+
}
80+
for _, osFeature := range opts.osFeatures {
81+
imageManifest.Descriptor.Platform.OSFeatures = appendIfUnique(imageManifest.Descriptor.Platform.OSFeatures, osFeature)
82+
}
83+
if opts.variant != "" {
84+
imageManifest.Descriptor.Platform.Variant = opts.variant
85+
}
86+
if opts.osVersion != "" {
87+
imageManifest.Descriptor.Platform.OSVersion = opts.osVersion
88+
}
89+
if !isValidOSArch(imageManifest.Descriptor.Platform.OS, imageManifest.Descriptor.Platform.Architecture) {
90+
return errors.Errorf("manifest entry for image has unsupported os/arch combination: %s/%s", opts.os, opts.arch)
91+
}
92+
imageManifests[i] = imageManifest
9193
}
92-
return manifestStore.Save(targetRef, imgRef, imageManifest)
94+
return manifestStore.Save(targetRef, imgRef, imageManifests...)
9395
}
9496

9597
func appendIfUnique(list []string, str string) []string {

cli/command/manifest/create_list.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,11 @@ func createManifestList(dockerCli command.Cli, args []string, opts createOpts) e
6969
return err
7070
}
7171

72-
manifest, err := getManifest(ctx, dockerCli, targetRef, namedRef, opts.insecure)
72+
manifests, err := getManifests(ctx, dockerCli, targetRef, namedRef, opts.insecure)
7373
if err != nil {
7474
return err
7575
}
76-
if err := manifestStore.Save(targetRef, namedRef, manifest); err != nil {
76+
if err := manifestStore.Save(targetRef, namedRef, manifests...); err != nil {
7777
return err
7878
}
7979
}

cli/command/manifest/inspect.go

Lines changed: 12 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -55,40 +55,28 @@ func runInspect(dockerCli command.Cli, opts inspectOptions) error {
5555
return err
5656
}
5757

58-
// If list reference is provided, display the local manifest in a list
58+
var listRef reference.Named
5959
if opts.list != "" {
60-
listRef, err := normalizeReference(opts.list)
60+
listRef, err = normalizeReference(opts.list)
6161
if err != nil {
6262
return err
6363
}
64-
65-
imageManifest, err := dockerCli.ManifestStore().Get(listRef, namedRef)
66-
if err != nil {
67-
return err
68-
}
69-
return printManifest(dockerCli, imageManifest, opts)
70-
}
71-
72-
// Try a local manifest list first
73-
localManifestList, err := dockerCli.ManifestStore().GetList(namedRef)
74-
if err == nil {
75-
return printManifestList(dockerCli, namedRef, localManifestList, opts)
7664
}
7765

78-
// Next try a remote manifest
7966
ctx := context.Background()
80-
registryClient := dockerCli.RegistryClient(opts.insecure)
81-
imageManifest, err := registryClient.GetManifest(ctx, namedRef)
82-
if err == nil {
83-
return printManifest(dockerCli, imageManifest, opts)
84-
}
85-
86-
// Finally try a remote manifest list
87-
manifestList, err := registryClient.GetManifestList(ctx, namedRef)
67+
manifests, err := getManifests(ctx, dockerCli, listRef, namedRef, opts.insecure)
8868
if err != nil {
8969
return err
9070
}
91-
return printManifestList(dockerCli, namedRef, manifestList, opts)
71+
72+
switch len(manifests) {
73+
case 0:
74+
return nil
75+
case 1:
76+
return printManifest(dockerCli, manifests[0], opts)
77+
default:
78+
return printManifestList(dockerCli, namedRef, manifests, opts)
79+
}
9280
}
9381

9482
func printManifest(dockerCli command.Cli, manifest types.ImageManifest, opts inspectOptions) error {

cli/command/manifest/util.go

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -67,29 +67,42 @@ func normalizeReference(ref string) (reference.Named, error) {
6767
return namedRef, nil
6868
}
6969

70-
// getManifest from the local store, and fallback to the remote registry if it
70+
// getManifests from the local store, and fallback to the remote registry if it
7171
// doesn't exist locally
72-
func getManifest(ctx context.Context, dockerCli command.Cli, listRef, namedRef reference.Named, insecure bool) (types.ImageManifest, error) {
72+
func getManifests(ctx context.Context, dockerCli command.Cli, listRef, namedRef reference.Named, insecure bool) ([]types.ImageManifest, error) {
7373
// load from the local store
7474
if listRef != nil {
7575
data, err := dockerCli.ManifestStore().Get(listRef, namedRef)
7676
if err == nil {
7777
return data, nil
7878
} else if !errors.Is(err, types.ErrManifestNotFound) {
79-
return types.ImageManifest{}, err
79+
return nil, err
8080
}
8181
}
82+
datas, err := dockerCli.ManifestStore().GetList(namedRef)
83+
if err == nil {
84+
return datas, nil
85+
} else if !errors.Is(err, types.ErrManifestNotFound) {
86+
return nil, err
87+
}
8288

8389
// load from the remote registry
8490
client := dockerCli.RegistryClient(insecure)
8591
if client != nil {
8692
data, err := client.GetManifest(ctx, namedRef)
8793
if err == nil {
88-
return data, nil
94+
return []types.ImageManifest{data}, nil
95+
} else if !errors.Is(err, types.ErrManifestNotFound) {
96+
return nil, err
97+
}
98+
99+
datas, err = client.GetManifestList(ctx, namedRef)
100+
if err == nil {
101+
return datas, nil
89102
} else if !errors.Is(err, types.ErrManifestNotFound) {
90-
return types.ImageManifest{}, err
103+
return nil, err
91104
}
92105
}
93106

94-
return types.ImageManifest{}, errors.Wrapf(types.ErrManifestNotFound, "%q does not exist", namedRef)
107+
return nil, errors.Wrapf(types.ErrManifestNotFound, "%q does not exist", namedRef)
95108
}

cli/manifest/store/store.go

Lines changed: 47 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package store
22

33
import (
44
"encoding/json"
5+
"fmt"
56
"os"
67
"path/filepath"
78
"strings"
@@ -17,9 +18,9 @@ import (
1718
// Store manages local storage of image distribution manifests
1819
type Store interface {
1920
Remove(listRef reference.Reference) error
20-
Get(listRef reference.Reference, manifest reference.Reference) (types.ImageManifest, error)
21+
Get(listRef reference.Reference, manifest reference.Reference) ([]types.ImageManifest, error)
2122
GetList(listRef reference.Reference) ([]types.ImageManifest, error)
22-
Save(listRef reference.Reference, manifest reference.Reference, image types.ImageManifest) error
23+
Save(listRef reference.Reference, manifest reference.Reference, image ...types.ImageManifest) error
2324
}
2425

2526
// fsStore manages manifest files stored on the local filesystem
@@ -39,9 +40,30 @@ func (s *fsStore) Remove(listRef reference.Reference) error {
3940
}
4041

4142
// Get returns the local manifest
42-
func (s *fsStore) Get(listRef reference.Reference, manifest reference.Reference) (types.ImageManifest, error) {
43+
func (s *fsStore) Get(listRef reference.Reference, manifest reference.Reference) ([]types.ImageManifest, error) {
44+
var imgs []types.ImageManifest
4345
filename := manifestToFilename(s.root, listRef.String(), manifest.String())
44-
return s.getFromFilename(manifest, filename)
46+
47+
img, err := s.getFromFilename(manifest, filename)
48+
if err != nil {
49+
return nil, err
50+
}
51+
imgs = append(imgs, img)
52+
53+
i := 2
54+
for {
55+
img, err := s.getFromFilename(manifest, fmt.Sprintf("%s_%d", filename, i))
56+
if errors.Is(err, types.ErrManifestNotFound) {
57+
break
58+
} else if err != nil {
59+
return nil, err
60+
}
61+
imgs = append(imgs, img)
62+
63+
i++
64+
}
65+
66+
return imgs, nil
4567
}
4668

4769
func (s *fsStore) getFromFilename(ref reference.Reference, filename string) (types.ImageManifest, error) {
@@ -126,16 +148,34 @@ func (s *fsStore) listManifests(transaction string) ([]string, error) {
126148
}
127149

128150
// Save a manifest as part of a local manifest list
129-
func (s *fsStore) Save(listRef reference.Reference, manifest reference.Reference, image types.ImageManifest) error {
151+
func (s *fsStore) Save(listRef reference.Reference, manifest reference.Reference, images ...types.ImageManifest) error {
130152
if err := s.createManifestListDirectory(listRef.String()); err != nil {
131153
return err
132154
}
155+
if len(images) == 0 {
156+
return nil
157+
}
158+
133159
filename := manifestToFilename(s.root, listRef.String(), manifest.String())
134-
bytes, err := json.Marshal(image)
160+
bytes, err := json.Marshal(images[0])
135161
if err != nil {
136162
return err
137163
}
138-
return os.WriteFile(filename, bytes, 0o644)
164+
if err := os.WriteFile(filename, bytes, 0o644); err != nil {
165+
return err
166+
}
167+
168+
for i, image := range images[1:] {
169+
bytes, err := json.Marshal(image)
170+
if err != nil {
171+
return err
172+
}
173+
if err := os.WriteFile(fmt.Sprintf("%s_%d", filename, i+2), bytes, 0o644); err != nil {
174+
return err
175+
}
176+
}
177+
178+
return nil
139179
}
140180

141181
func (s *fsStore) createManifestListDirectory(transaction string) error {

cli/manifest/store/store_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,14 @@ func TestStoreRemove(t *testing.T) {
5555
func TestStoreSaveAndGet(t *testing.T) {
5656
store := NewStore(t.TempDir())
5757
listRef := ref("list")
58-
data := types.ImageManifest{Ref: sref(t, "abcdef")}
59-
err := store.Save(listRef, ref("exists"), data)
58+
data := []types.ImageManifest{{Ref: sref(t, "abcdef")}}
59+
err := store.Save(listRef, ref("exists"), data...)
6060
assert.NilError(t, err)
6161

6262
testcases := []struct {
6363
listRef reference.Reference
6464
manifestRef reference.Reference
65-
expected types.ImageManifest
65+
expected []types.ImageManifest
6666
expectedErr error
6767
}{
6868
{

0 commit comments

Comments
 (0)