From 9c6c0eab43ebb91f46a7ca692ddbac78c351805c Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Fri, 14 May 2021 12:21:05 +0200 Subject: [PATCH] libimage: add save tests Add save tests. Also fix a bug when saving a single image but with multiple tags. Signed-off-by: Valentin Rothberg --- common/libimage/save.go | 17 ++++++ common/libimage/save_test.go | 109 +++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 common/libimage/save_test.go diff --git a/common/libimage/save.go b/common/libimage/save.go index c00c0107e4..7afde6e170 100644 --- a/common/libimage/save.go +++ b/common/libimage/save.go @@ -57,9 +57,13 @@ func (r *Runtime) Save(ctx context.Context, names []string, format, path string, // Dispatch the save operations. switch format { case "oci-archive", "oci-dir", "docker-dir": + if len(names) > 1 { + return errors.Errorf("%q does not support saving multiple images (%v)", format, names) + } return r.saveSingleImage(ctx, names[0], format, path, options) case "docker-archive": + options.ManifestMIMEType = manifest.DockerV2Schema2MediaType return r.saveDockerArchive(ctx, names, path, options) } @@ -134,6 +138,18 @@ func (r *Runtime) saveDockerArchive(ctx context.Context, names []string, path st tags []reference.NamedTagged } + additionalTags := []reference.NamedTagged{} + for _, tag := range options.AdditionalTags { + named, err := NormalizeName(tag) + if err == nil { + tagged, withTag := named.(reference.NamedTagged) + if !withTag { + return errors.Errorf("invalid additional tag %q: normalized to untagged %q", tag, named.String()) + } + additionalTags = append(additionalTags, tagged) + } + } + orderedIDs := []string{} // to preserve the relative order localImages := make(map[string]*localImage) // to assemble tags visitedNames := make(map[string]bool) // filters duplicate names @@ -153,6 +169,7 @@ func (r *Runtime) saveDockerArchive(ctx context.Context, names []string, path st local, exists := localImages[image.ID()] if !exists { local = &localImage{image: image} + local.tags = additionalTags orderedIDs = append(orderedIDs, image.ID()) } // Add the tag if the locally resolved name is properly tagged diff --git a/common/libimage/save_test.go b/common/libimage/save_test.go new file mode 100644 index 0000000000..8d9d76015c --- /dev/null +++ b/common/libimage/save_test.go @@ -0,0 +1,109 @@ +package libimage + +import ( + "context" + "io/ioutil" + "os" + "strings" + "testing" + + "github.com/containers/common/pkg/config" + "github.com/stretchr/testify/require" +) + +func TestSave(t *testing.T) { + runtime, cleanup := testNewRuntime(t) + defer cleanup() + ctx := context.Background() + + // Prefetch alpine, busybox. + pullOptions := &PullOptions{} + pullOptions.Writer = os.Stdout + _, err := runtime.Pull(ctx, "docker.io/library/alpine:latest", config.PullPolicyAlways, pullOptions) + require.NoError(t, err) + _, err = runtime.Pull(ctx, "docker.io/library/busybox:latest", config.PullPolicyAlways, pullOptions) + require.NoError(t, err) + + // Save the two images into a multi-image archive. This way, we can + // reload the images for each test. + saveOptions := &SaveOptions{} + saveOptions.Writer = os.Stdout + imageCache, err := ioutil.TempFile("", "saveimagecache") + require.NoError(t, err) + imageCache.Close() + defer os.Remove(imageCache.Name()) + err = runtime.Save(ctx, []string{"alpine", "busybox"}, "docker-archive", imageCache.Name(), saveOptions) + require.NoError(t, err) + + loadOptions := &LoadOptions{} + loadOptions.Writer = os.Stdout + + // The table tests are smoke tests to exercise the different code + // paths. More detailed tests follow below. + for _, test := range []struct { + names []string + tags []string + format string + isDir bool + expectError bool + }{ + // No `names` + {nil, nil, "", false, true}, + {[]string{}, nil, "", false, true}, + // Invalid/unsupported format + {[]string{"something"}, nil, "", false, true}, + {[]string{"something"}, nil, "else", false, true}, + // oci + {[]string{"busybox"}, nil, "oci-dir", true, false}, + {[]string{"busybox"}, nil, "oci-archive", false, false}, + // oci-archive doesn't support multi-image archives + {[]string{"busybox", "alpine"}, nil, "oci-archive", false, true}, + // docker + {[]string{"busybox"}, nil, "docker-archive", false, false}, + {[]string{"busybox"}, []string{"localhost/tag:1", "quay.io/repo/image:tag"}, "docker-archive", false, false}, + {[]string{"busybox"}, nil, "docker-dir", true, false}, + {[]string{"busybox", "alpine"}, nil, "docker-archive", false, false}, + // additional tags and multi-images conflict + {[]string{"busybox", "alpine"}, []string{"tag"}, "docker-archive", false, true}, + } { + // First clean up all images and load the cache. + _, rmErrors := runtime.RemoveImages(ctx, nil, nil) + require.Nil(t, rmErrors) + _, err = runtime.Load(ctx, imageCache.Name(), loadOptions) + require.NoError(t, err) + + tmp, err := ioutil.TempDir("", "libimagesavetest") + require.NoError(t, err) + defer os.RemoveAll(tmp) + if !test.isDir { + tmp += "/archive.tar" + } + + saveOptions.AdditionalTags = test.tags + err = runtime.Save(ctx, test.names, test.format, tmp, saveOptions) + if test.expectError { + require.Error(t, err, "%v", test) + continue + } + require.NoError(t, err, "%v", test) + + // Now remove all images again and attempt to load the + // previously saved ones. + _, rmErrors = runtime.RemoveImages(ctx, nil, nil) + require.Nil(t, rmErrors) + + namesAndTags := append(test.names, test.tags...) + loadedImages, err := runtime.Load(ctx, tmp, loadOptions) + require.NoError(t, err) + require.Len(t, loadedImages, len(namesAndTags)) + + // Now make sure that all specified names (and tags) resolve to + // an image the local containers storage. Note that names are + // only preserved in archives. + if strings.HasSuffix(test.format, "-dir") { + continue + } + _, err = runtime.ListImages(ctx, namesAndTags, nil) + require.NoError(t, err, "%v", test) + } +}