diff options
-rw-r--r-- | libpod/image/pull.go | 164 | ||||
-rw-r--r-- | libpod/image/pull_test.go | 152 |
2 files changed, 169 insertions, 147 deletions
diff --git a/libpod/image/pull.go b/libpod/image/pull.go index f3a10d94d..ff978d563 100644 --- a/libpod/image/pull.go +++ b/libpod/image/pull.go @@ -63,31 +63,17 @@ type pullGoal struct { searchedRegistries []string // The list of search registries used; set only if usedSearchRegistries } -// pullRefName records a prepared source reference and a destination name to pull. -type pullRefName struct { - image string - srcRef types.ImageReference - dstName string -} - -// pullGoalNames is an intermediate variant of pullGoal which uses pullRefName instead of pullRefPair. -type pullGoalNames struct { - refNames []pullRefName - pullAllPairs bool // Pull all refNames instead of stopping on first success. - usedSearchRegistries bool // refPairs construction has depended on registries.GetRegistries() - searchedRegistries []string // The list of search registries used; set only if usedSearchRegistries -} - -func singlePullRefNameGoal(rn pullRefName) *pullGoalNames { - return &pullGoalNames{ - refNames: []pullRefName{rn}, +// singlePullRefPairGoal returns a no-frills pull goal for the specified reference pair. +func singlePullRefPairGoal(rp pullRefPair) *pullGoal { + return &pullGoal{ + refPairs: []pullRefPair{rp}, pullAllPairs: false, // Does not really make a difference. usedSearchRegistries: false, searchedRegistries: nil, } } -func getPullRefName(srcRef types.ImageReference, destName string) pullRefName { +func (ir *Runtime) getPullRefPair(srcRef types.ImageReference, destName string) (pullRefPair, error) { imgPart, err := decompose(destName) if err == nil && !imgPart.hasRegistry { // If the image doesn't have a registry, set it as the default repo @@ -100,15 +86,28 @@ func getPullRefName(srcRef types.ImageReference, destName string) pullRefName { if srcRef.DockerReference() != nil { reference = srcRef.DockerReference().String() } - return pullRefName{ - image: destName, - srcRef: srcRef, - dstName: reference, + destRef, err := is.Transport.ParseStoreReference(ir.store, reference) + if err != nil { + return pullRefPair{}, errors.Wrapf(err, "error parsing dest reference name %#v", destName) + } + return pullRefPair{ + image: destName, + srcRef: srcRef, + dstRef: destRef, + }, nil +} + +// getSinglePullRefPairGoal calls getPullRefPair with the specified parameters, and returns a single-pair goal for the return value. +func (ir *Runtime) getSinglePullRefPairGoal(srcRef types.ImageReference, destName string) (*pullGoal, error) { + rp, err := ir.getPullRefPair(srcRef, destName) + if err != nil { + return nil, err } + return singlePullRefPairGoal(rp), nil } -// pullGoalNamesFromImageReference returns a pullGoalNames for a single ImageReference, depending on the used transport. -func pullGoalNamesFromImageReference(ctx context.Context, srcRef types.ImageReference, imgName string, sc *types.SystemContext) (*pullGoalNames, error) { +// pullGoalFromImageReference returns a pull goal for a single ImageReference, depending on the used transport. +func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types.ImageReference, imgName string, sc *types.SystemContext) (*pullGoal, error) { // supports pulling from docker-archive, oci, and registries switch srcRef.Transport().Name() { case DockerArchive: @@ -129,7 +128,7 @@ func pullGoalNamesFromImageReference(ctx context.Context, srcRef types.ImageRefe if err != nil { return nil, err } - return singlePullRefNameGoal(getPullRefName(srcRef, reference)), nil + return ir.getSinglePullRefPairGoal(srcRef, reference) } if len(manifest[0].RepoTags) == 0 { @@ -138,17 +137,20 @@ func pullGoalNamesFromImageReference(ctx context.Context, srcRef types.ImageRefe if err != nil { return nil, err } - return singlePullRefNameGoal(getPullRefName(srcRef, digest)), nil + return ir.getSinglePullRefPairGoal(srcRef, digest) } // Need to load in all the repo tags from the manifest - res := []pullRefName{} + res := []pullRefPair{} for _, dst := range manifest[0].RepoTags { - pullInfo := getPullRefName(srcRef, dst) + pullInfo, err := ir.getPullRefPair(srcRef, dst) + if err != nil { + return nil, err + } res = append(res, pullInfo) } - return &pullGoalNames{ - refNames: res, + return &pullGoal{ + refPairs: res, pullAllPairs: true, usedSearchRegistries: false, searchedRegistries: nil, @@ -172,7 +174,7 @@ func pullGoalNamesFromImageReference(ctx context.Context, srcRef types.ImageRefe } else { dest = manifest.Annotations["org.opencontainers.image.ref.name"] } - return singlePullRefNameGoal(getPullRefName(srcRef, dest)), nil + return ir.getSinglePullRefPairGoal(srcRef, dest) case DirTransport: path := srcRef.StringWithinTransport() @@ -183,27 +185,17 @@ func pullGoalNamesFromImageReference(ctx context.Context, srcRef types.ImageRefe // so docker.io isn't prepended, and the path becomes the repository image = DefaultLocalRepo + image } - return singlePullRefNameGoal(getPullRefName(srcRef, image)), nil + return ir.getSinglePullRefPairGoal(srcRef, image) default: - return singlePullRefNameGoal(getPullRefName(srcRef, imgName)), nil + return ir.getSinglePullRefPairGoal(srcRef, imgName) } } -// pullGoalFromImageReference returns a pull goal for a single ImageReference, depending on the used transport. -func (ir *Runtime) pullGoalFromImageReference(ctx context.Context, srcRef types.ImageReference, imgName string, sc *types.SystemContext) (pullGoal, error) { - goalNames, err := pullGoalNamesFromImageReference(ctx, srcRef, imgName, sc) - if err != nil { - return pullGoal{}, err - } - - return ir.pullGoalFromGoalNames(goalNames) -} - // pullImageFromHeuristicSource pulls an image based on inputName, which is heuristically parsed and may involve configured registries. // Use pullImageFromReference if the source is known precisely. func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName string, writer io.Writer, authfile, signaturePolicyPath string, signingOptions SigningOptions, dockerOptions *DockerRegistryOptions, forceSecure bool) ([]string, error) { - var goal pullGoal + var goal *pullGoal sc := GetSystemContext(signaturePolicyPath, authfile, false) srcRef, err := alltransports.ParseImageName(inputName) if err != nil { @@ -218,7 +210,7 @@ func (ir *Runtime) pullImageFromHeuristicSource(ctx context.Context, inputName s return nil, errors.Wrapf(err, "error determining pull goal for image %q", inputName) } } - return ir.doPullImage(ctx, sc, goal, writer, signingOptions, dockerOptions, forceSecure) + return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, forceSecure) } // pullImageFromReference pulls an image from a types.imageReference. @@ -228,7 +220,7 @@ func (ir *Runtime) pullImageFromReference(ctx context.Context, srcRef types.Imag if err != nil { return nil, errors.Wrapf(err, "error determining pull goal for image %q", transports.ImageName(srcRef)) } - return ir.doPullImage(ctx, sc, goal, writer, signingOptions, dockerOptions, forceSecure) + return ir.doPullImage(ctx, sc, *goal, writer, signingOptions, dockerOptions, forceSecure) } // doPullImage is an internal helper interpreting pullGoal. Almost everyone should call one of the callers of doPullImage instead. @@ -291,15 +283,15 @@ func hasShaInInputName(inputName string) bool { return strings.Contains(inputName, "@sha256:") } -// pullGoalNamesFromPossiblyUnqualifiedName looks at a decomposed image and determines the possible -// image names to try pulling in combination with the registries.conf file as well -func pullGoalNamesFromPossiblyUnqualifiedName(inputName string) (*pullGoalNames, error) { +// pullGoalFromPossiblyUnqualifiedName looks at inputName and determines the possible +// image references to try pulling in combination with the registries.conf file as well +func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(inputName string) (*pullGoal, error) { decomposedImage, err := decompose(inputName) if err != nil { return nil, err } if decomposedImage.hasRegistry { - var imageName string + var imageName, destName string if hasShaInInputName(inputName) { imageName = fmt.Sprintf("%s%s", decomposedImage.transport, inputName) } else { @@ -309,23 +301,28 @@ func pullGoalNamesFromPossiblyUnqualifiedName(inputName string) (*pullGoalNames, if err != nil { return nil, errors.Wrapf(err, "unable to parse '%s'", inputName) } - ps := pullRefName{ - image: inputName, - srcRef: srcRef, - } if hasShaInInputName(inputName) { - ps.dstName = decomposedImage.assemble() + destName = decomposedImage.assemble() } else { - ps.dstName = ps.image + destName = inputName + } + destRef, err := is.Transport.ParseStoreReference(ir.store, destName) + if err != nil { + return nil, errors.Wrapf(err, "error parsing dest reference name %#v", destName) } - return singlePullRefNameGoal(ps), nil + ps := pullRefPair{ + image: inputName, + srcRef: srcRef, + dstRef: destRef, + } + return singlePullRefPairGoal(ps), nil } searchRegistries, err := registries.GetRegistries() if err != nil { return nil, err } - var pullNames []pullRefName + var refPairs []pullRefPair for _, registry := range searchRegistries { decomposedImage.registry = registry imageName := decomposedImage.assembleWithTransport() @@ -336,53 +333,20 @@ func pullGoalNamesFromPossiblyUnqualifiedName(inputName string) (*pullGoalNames, if err != nil { return nil, errors.Wrapf(err, "unable to parse '%s'", inputName) } - ps := pullRefName{ + ps := pullRefPair{ image: decomposedImage.assemble(), srcRef: srcRef, } - ps.dstName = ps.image - pullNames = append(pullNames, ps) + ps.dstRef, err = is.Transport.ParseStoreReference(ir.store, ps.image) + if err != nil { + return nil, errors.Wrapf(err, "error parsing dest reference name %#v", ps.image) + } + refPairs = append(refPairs, ps) } - return &pullGoalNames{ - refNames: pullNames, + return &pullGoal{ + refPairs: refPairs, pullAllPairs: false, usedSearchRegistries: true, searchedRegistries: searchRegistries, }, nil } - -// pullGoalFromPossiblyUnqualifiedName looks at inputName and determines the possible -// image references to try pulling in combination with the registries.conf file as well -func (ir *Runtime) pullGoalFromPossiblyUnqualifiedName(inputName string) (pullGoal, error) { - goalNames, err := pullGoalNamesFromPossiblyUnqualifiedName(inputName) - if err != nil { - return pullGoal{}, err - } - return ir.pullGoalFromGoalNames(goalNames) -} - -// pullGoalFromGoalNames converts a pullGoalNames to a pullGoal -func (ir *Runtime) pullGoalFromGoalNames(goalNames *pullGoalNames) (pullGoal, error) { - if goalNames == nil { // The value is a pointer only to make (return nil, err) possible in callers; they should never return nil on success - return pullGoal{}, errors.New("internal error: pullGoalFromGoalNames(nil)") - } - // Here we construct the destination references - res := make([]pullRefPair, len(goalNames.refNames)) - for i, rn := range goalNames.refNames { - destRef, err := is.Transport.ParseStoreReference(ir.store, rn.dstName) - if err != nil { - return pullGoal{}, errors.Wrapf(err, "error parsing dest reference name %#v", rn.dstName) - } - res[i] = pullRefPair{ - image: rn.image, - srcRef: rn.srcRef, - dstRef: destRef, - } - } - return pullGoal{ - refPairs: res, - pullAllPairs: goalNames.pullAllPairs, - usedSearchRegistries: goalNames.usedSearchRegistries, - searchedRegistries: goalNames.searchedRegistries, - }, nil -} diff --git a/libpod/image/pull_test.go b/libpod/image/pull_test.go index 13ba759c8..5ef8c47a5 100644 --- a/libpod/image/pull_test.go +++ b/libpod/image/pull_test.go @@ -5,24 +5,74 @@ import ( "fmt" "io/ioutil" "os" + "path/filepath" + "strings" "testing" "github.com/containers/image/transports" "github.com/containers/image/transports/alltransports" + "github.com/containers/image/types" + "github.com/containers/storage" + "github.com/containers/storage/pkg/idtools" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestGetPullRefName(t *testing.T) { +// newTestRuntime returns a *Runtime implementation and a cleanup function which the caller is expected to call. +func newTestRuntime(t *testing.T) (*Runtime, func()) { + wd, err := ioutil.TempDir("", "testStorageRuntime") + require.NoError(t, err) + err = os.MkdirAll(wd, 0700) + require.NoError(t, err) + + store, err := storage.GetStore(storage.StoreOptions{ + RunRoot: filepath.Join(wd, "run"), + GraphRoot: filepath.Join(wd, "root"), + GraphDriverName: "vfs", + GraphDriverOptions: []string{}, + UIDMap: []idtools.IDMap{{ + ContainerID: 0, + HostID: os.Getuid(), + Size: 1, + }}, + GIDMap: []idtools.IDMap{{ + ContainerID: 0, + HostID: os.Getgid(), + Size: 1, + }}, + }) + require.NoError(t, err) + + ir := NewImageRuntimeFromStore(store) + cleanup := func() { _ = os.RemoveAll(wd) } + return ir, cleanup +} + +// storageReferenceWithoutLocation returns ref.StringWithinTransport(), +// stripping the [store-specification] prefix from containers/image/storage reference format. +func storageReferenceWithoutLocation(ref types.ImageReference) string { + res := ref.StringWithinTransport() + if res[0] == '[' { + closeIndex := strings.IndexRune(res, ']') + if closeIndex > 0 { + res = res[closeIndex+1:] + } + } + return res +} + +func TestGetPullRefPair(t *testing.T) { const imageID = "@0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" + ir, cleanup := newTestRuntime(t) + defer cleanup() + for _, c := range []struct{ srcName, destName, expectedImage, expectedDstName string }{ // == Source does not have a Docker reference (as is the case for docker-archive:, oci-archive, dir:); destination formats: { // registry/name, no tag: "dir:/dev/this-does-not-exist", "example.com/from-directory", - // The destName value will be interpreted as "example.com/from-directory:latest" by storageTransport. - "example.com/from-directory", "example.com/from-directory", + "example.com/from-directory", "example.com/from-directory:latest", }, { // name, no registry, no tag: "dir:/dev/this-does-not-exist", "from-directory", @@ -50,7 +100,7 @@ func TestGetPullRefName(t *testing.T) { { // ns/name:tag, no registry: // FIXME: This is interpreted as "registry == ns" "dir:/dev/this-does-not-exist", "ns/from-directory:notlatest", - "ns/from-directory:notlatest", "ns/from-directory:notlatest", + "ns/from-directory:notlatest", "docker.io/ns/from-directory:notlatest", }, { // containers-storage image ID "dir:/dev/this-does-not-exist", imageID, @@ -68,17 +118,29 @@ func TestGetPullRefName(t *testing.T) { "docker://busybox", "docker://busybox:destination", "docker://busybox:destination", "docker.io/library/busybox:latest", }, + // == Invalid destination format. + {"tarball:/dev/null", "tarball:/dev/null", "", ""}, } { + testDescription := fmt.Sprintf("%#v %#v", c.srcName, c.destName) srcRef, err := alltransports.ParseImageName(c.srcName) - require.NoError(t, err, c.srcName) + require.NoError(t, err, testDescription) - res := getPullRefName(srcRef, c.destName) - assert.Equal(t, pullRefName{image: c.expectedImage, srcRef: srcRef, dstName: c.expectedDstName}, res, - fmt.Sprintf("%#v %#v", c.srcName, c.destName)) + res, err := ir.getPullRefPair(srcRef, c.destName) + if c.expectedDstName == "" { + assert.Error(t, err, testDescription) + } else { + require.NoError(t, err, testDescription) + assert.Equal(t, c.expectedImage, res.image, testDescription) + assert.Equal(t, srcRef, res.srcRef, testDescription) + assert.Equal(t, c.expectedDstName, storageReferenceWithoutLocation(res.dstRef), testDescription) + } } } -func TestPullGoalNamesFromImageReference(t *testing.T) { +func TestPullGoalFromImageReference(t *testing.T) { + ir, cleanup := newTestRuntime(t) + defer cleanup() + type expected struct{ image, dstName string } for _, c := range []struct { srcName string @@ -139,29 +201,26 @@ func TestPullGoalNamesFromImageReference(t *testing.T) { // []expected{{"example.com/empty:latest", "example.com/empty:latest"}}, // false, // }, - // { // Name exists, but is an invalid Docker reference; such names are passed through, and will fail when intepreting dstName. - // "oci-archive:testdata/oci-non-docker-name.tar.gz", - // []expected{{"UPPERCASE-IS-INVALID", "UPPERCASE-IS-INVALID"}}, - // false, - // }, + // // Name exists, but is an invalid Docker reference; such names will fail when creating dstReference. + // {"oci-archive:testdata/oci-non-docker-name.tar.gz", nil, false}, // Maybe test support of two images in a single archive? It should be transparently handled by adding a reference to srcRef. // == dir: { // Absolute path "dir:/dev/this-does-not-exist", - []expected{{"localhost/dev/this-does-not-exist", "localhost/dev/this-does-not-exist"}}, + []expected{{"localhost/dev/this-does-not-exist", "localhost/dev/this-does-not-exist:latest"}}, false, }, { // Relative path, single element. - // FIXME? Note the :latest difference in .image. (In .dstName as well, but it has the same semantics in there.) + // FIXME? Note the :latest difference in .image. "dir:this-does-not-exist", []expected{{"localhost/this-does-not-exist:latest", "localhost/this-does-not-exist:latest"}}, false, }, { // Relative path, multiple elements. - // FIXME: This does not add localhost/, and dstName is parsed as docker.io/testdata. + // FIXME: This does not add localhost/, so dstName is normalized to docker.io/testdata. "dir:testdata/this-does-not-exist", - []expected{{"testdata/this-does-not-exist", "testdata/this-does-not-exist"}}, + []expected{{"testdata/this-does-not-exist", "docker.io/testdata/this-does-not-exist:latest"}}, false, }, @@ -179,24 +238,24 @@ func TestPullGoalNamesFromImageReference(t *testing.T) { }, // === tarball: (as an example of what happens when ImageReference.DockerReference is nil). - { // FIXME? The dstName value is invalid, and will fail. - // (This is NOT an API promise that the results will continue to be this way.) - "tarball:/dev/null", - []expected{{"tarball:/dev/null", "tarball:/dev/null"}}, - false, - }, + // FIXME? This tries to parse "tarball:/dev/null" as a storageReference, and fails. + // (This is NOT an API promise that the results will continue to be this way.) + {"tarball:/dev/null", nil, false}, } { srcRef, err := alltransports.ParseImageName(c.srcName) require.NoError(t, err, c.srcName) - res, err := pullGoalNamesFromImageReference(context.Background(), srcRef, c.srcName, nil) + res, err := ir.pullGoalFromImageReference(context.Background(), srcRef, c.srcName, nil) if len(c.expected) == 0 { assert.Error(t, err, c.srcName) } else { require.NoError(t, err, c.srcName) - require.Len(t, res.refNames, len(c.expected), c.srcName) + require.Len(t, res.refPairs, len(c.expected), c.srcName) for i, e := range c.expected { - assert.Equal(t, pullRefName{image: e.image, srcRef: srcRef, dstName: e.dstName}, res.refNames[i], fmt.Sprintf("%s #%d", c.srcName, i)) + testDescription := fmt.Sprintf("%s #%d", c.srcName, i) + assert.Equal(t, e.image, res.refPairs[i].image, testDescription) + assert.Equal(t, srcRef, res.refPairs[i].srcRef, testDescription) + assert.Equal(t, e.dstName, storageReferenceWithoutLocation(res.refPairs[i].dstRef), testDescription) } assert.Equal(t, c.expectedPullAllPairs, res.pullAllPairs, c.srcName) assert.False(t, res.usedSearchRegistries, c.srcName) @@ -209,11 +268,11 @@ const registriesConfWithSearch = `[registries.search] registries = ['example.com', 'docker.io'] ` -func TestPullGoalNamesFromPossiblyUnqualifiedName(t *testing.T) { +func TestPullGoalFromPossiblyUnqualifiedName(t *testing.T) { const digestSuffix = "@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - type pullRefStrings struct{ image, srcRef, dstName string } // pullRefName with string data only + type pullRefStrings struct{ image, srcRef, dstName string } // pullRefPair with string data only - registriesConf, err := ioutil.TempFile("", "TestPullGoalNamesFromPossiblyUnqualifiedName") + registriesConf, err := ioutil.TempFile("", "TestPullGoalFromPossiblyUnqualifiedName") require.NoError(t, err) defer registriesConf.Close() defer os.Remove(registriesConf.Name()) @@ -221,6 +280,9 @@ func TestPullGoalNamesFromPossiblyUnqualifiedName(t *testing.T) { err = ioutil.WriteFile(registriesConf.Name(), []byte(registriesConfWithSearch), 0600) require.NoError(t, err) + ir, cleanup := newTestRuntime(t) + defer cleanup() + // Environment is per-process, so this looks very unsafe; actually it seems fine because tests are not // run in parallel unless they opt in by calling t.Parallel(). So don’t do that. oldRCP, hasRCP := os.LookupEnv("REGISTRIES_CONFIG_PATH") @@ -242,19 +304,18 @@ func TestPullGoalNamesFromPossiblyUnqualifiedName(t *testing.T) { { // Fully-explicit docker.io, name-only. "docker.io/library/busybox", // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - []pullRefStrings{{"docker.io/library/busybox", "docker://busybox:latest", "docker.io/library/busybox"}}, + []pullRefStrings{{"docker.io/library/busybox", "docker://busybox:latest", "docker.io/library/busybox:latest"}}, false, }, { // docker.io with implied /library/, name-only. "docker.io/busybox", // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - // The .dstName fields differ for the explicit/implicit /library/ cases, but StorageTransport.ParseStoreReference normalizes that. - []pullRefStrings{{"docker.io/busybox", "docker://busybox:latest", "docker.io/busybox"}}, + []pullRefStrings{{"docker.io/busybox", "docker://busybox:latest", "docker.io/library/busybox:latest"}}, false, }, { // Qualified example.com, name-only. "example.com/ns/busybox", - []pullRefStrings{{"example.com/ns/busybox", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox"}}, + []pullRefStrings{{"example.com/ns/busybox", "docker://example.com/ns/busybox:latest", "example.com/ns/busybox:latest"}}, false, }, { // Qualified example.com, name:tag. @@ -276,7 +337,7 @@ func TestPullGoalNamesFromPossiblyUnqualifiedName(t *testing.T) { []pullRefStrings{ {"example.com/busybox:latest", "docker://example.com/busybox:latest", "example.com/busybox:latest"}, // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - {"docker.io/busybox:latest", "docker://busybox:latest", "docker.io/busybox:latest"}, + {"docker.io/busybox:latest", "docker://busybox:latest", "docker.io/library/busybox:latest"}, }, true, }, @@ -285,7 +346,7 @@ func TestPullGoalNamesFromPossiblyUnqualifiedName(t *testing.T) { // FIXME: This is interpreted as "registry == ns", and actual pull happens from docker.io/ns/busybox:latest; // example.com should be first in the list but isn't used at all. []pullRefStrings{ - {"ns/busybox", "docker://ns/busybox:latest", "ns/busybox"}, + {"ns/busybox", "docker://ns/busybox:latest", "docker.io/ns/busybox:latest"}, }, false, }, @@ -294,7 +355,7 @@ func TestPullGoalNamesFromPossiblyUnqualifiedName(t *testing.T) { []pullRefStrings{ {"example.com/busybox:notlatest", "docker://example.com/busybox:notlatest", "example.com/busybox:notlatest"}, // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - {"docker.io/busybox:notlatest", "docker://busybox:notlatest", "docker.io/busybox:notlatest"}, + {"docker.io/busybox:notlatest", "docker://busybox:notlatest", "docker.io/library/busybox:notlatest"}, }, true, }, @@ -304,27 +365,24 @@ func TestPullGoalNamesFromPossiblyUnqualifiedName(t *testing.T) { // FIXME?! Why is .input and .dstName dropping the digest, and adding :none?! {"example.com/busybox:none", "docker://example.com/busybox" + digestSuffix, "example.com/busybox:none"}, // (The docker:// representation is shortened by c/image/docker.Reference but it refers to "docker.io/library".) - {"docker.io/busybox:none", "docker://busybox" + digestSuffix, "docker.io/busybox:none"}, + {"docker.io/busybox:none", "docker://busybox" + digestSuffix, "docker.io/library/busybox:none"}, }, true, }, // Unqualified, name:tag@digest. This code is happy to try, but .srcRef parsing currently rejects such input. {"busybox:notlatest" + digestSuffix, nil, false}, } { - res, err := pullGoalNamesFromPossiblyUnqualifiedName(c.input) + res, err := ir.pullGoalFromPossiblyUnqualifiedName(c.input) if len(c.expected) == 0 { assert.Error(t, err, c.input) } else { assert.NoError(t, err, c.input) - strings := make([]pullRefStrings, len(res.refNames)) - for i, rn := range res.refNames { - strings[i] = pullRefStrings{ - image: rn.image, - srcRef: transports.ImageName(rn.srcRef), - dstName: rn.dstName, - } + for i, e := range c.expected { + testDescription := fmt.Sprintf("%s #%d", c.input, i) + assert.Equal(t, e.image, res.refPairs[i].image, testDescription) + assert.Equal(t, e.srcRef, transports.ImageName(res.refPairs[i].srcRef), testDescription) + assert.Equal(t, e.dstName, storageReferenceWithoutLocation(res.refPairs[i].dstRef), testDescription) } - assert.Equal(t, c.expected, strings, c.input) assert.False(t, res.pullAllPairs, c.input) assert.Equal(t, c.expectedUsedSearchRegistries, res.usedSearchRegistries, c.input) if !c.expectedUsedSearchRegistries { |