Files
moby/integration/image/pull_test.go
Sebastiaan van Stijn d0aa3eaccf Migrate to github.com/containerd/platforms module
Switch to use github.com/containerd/platforms module, because containerd's
platforms package has moved to a separate module. This allows updating the
platforms parsing independent of the containerd module itself.

The package in containerd is deprecated, but kept as an alias to provide
compatibility between codebases.

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2024-07-02 21:20:29 +02:00

198 lines
6.0 KiB
Go

package image
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"path"
"strings"
"testing"
"github.com/containerd/containerd"
"github.com/containerd/containerd/content"
"github.com/containerd/containerd/content/local"
"github.com/containerd/containerd/images"
"github.com/containerd/platforms"
"github.com/docker/docker/api/types/image"
"github.com/docker/docker/errdefs"
"github.com/docker/docker/testutil/registry"
"github.com/opencontainers/go-digest"
"github.com/opencontainers/image-spec/specs-go"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/skip"
)
func TestImagePullPlatformInvalid(t *testing.T) {
ctx := setupTest(t)
client := testEnv.APIClient()
_, err := client.ImagePull(ctx, "docker.io/library/hello-world:latest", image.PullOptions{Platform: "foobar"})
assert.Assert(t, err != nil)
assert.Check(t, is.ErrorContains(err, "unknown operating system or architecture"))
assert.Check(t, is.ErrorType(err, errdefs.IsInvalidParameter))
}
func createTestImage(ctx context.Context, t testing.TB, store content.Store) ocispec.Descriptor {
w, err := store.Writer(ctx, content.WithRef("layer"))
assert.NilError(t, err)
defer w.Close()
// Empty layer with just a root dir
const layer = `./0000775000000000000000000000000014201045023007702 5ustar rootroot`
_, err = w.Write([]byte(layer))
assert.NilError(t, err)
err = w.Commit(ctx, int64(len(layer)), digest.FromBytes([]byte(layer)))
assert.NilError(t, err)
layerDigest := w.Digest()
assert.Check(t, w.Close())
img := ocispec.Image{
Platform: platforms.DefaultSpec(),
RootFS: ocispec.RootFS{Type: "layers", DiffIDs: []digest.Digest{layerDigest}},
Config: ocispec.ImageConfig{WorkingDir: "/"},
}
imgJSON, err := json.Marshal(img)
assert.NilError(t, err)
w, err = store.Writer(ctx, content.WithRef("config"))
assert.NilError(t, err)
defer w.Close()
_, err = w.Write(imgJSON)
assert.NilError(t, err)
assert.NilError(t, w.Commit(ctx, int64(len(imgJSON)), digest.FromBytes(imgJSON)))
configDigest := w.Digest()
assert.Check(t, w.Close())
info, err := store.Info(ctx, layerDigest)
assert.NilError(t, err)
manifest := ocispec.Manifest{
Versioned: specs.Versioned{
SchemaVersion: 2,
},
MediaType: images.MediaTypeDockerSchema2Manifest,
Config: ocispec.Descriptor{
MediaType: images.MediaTypeDockerSchema2Config,
Digest: configDigest,
Size: int64(len(imgJSON)),
},
Layers: []ocispec.Descriptor{{
MediaType: images.MediaTypeDockerSchema2Layer,
Digest: layerDigest,
Size: info.Size,
}},
}
manifestJSON, err := json.Marshal(manifest)
assert.NilError(t, err)
w, err = store.Writer(ctx, content.WithRef("manifest"))
assert.NilError(t, err)
defer w.Close()
_, err = w.Write(manifestJSON)
assert.NilError(t, err)
assert.NilError(t, w.Commit(ctx, int64(len(manifestJSON)), digest.FromBytes(manifestJSON)))
manifestDigest := w.Digest()
assert.Check(t, w.Close())
return ocispec.Descriptor{
MediaType: images.MediaTypeDockerSchema2Manifest,
Digest: manifestDigest,
Size: int64(len(manifestJSON)),
}
}
// Make sure that pulling by an already cached digest but for a different ref (that should not have that digest)
// verifies with the remote that the digest exists in that repo.
func TestImagePullStoredDigestForOtherRepo(t *testing.T) {
skip.If(t, testEnv.IsRemoteDaemon, "cannot run daemon when remote daemon")
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "We don't run a test registry on Windows")
skip.If(t, testEnv.IsRootless, "Rootless has a different view of localhost (needed for test registry access)")
ctx := setupTest(t)
reg := registry.NewV2(t, registry.WithStdout(os.Stdout), registry.WithStderr(os.Stderr))
defer reg.Close()
reg.WaitReady(t)
// First create an image and upload it to our local registry
// Then we'll download it so that we can make sure the content is available in dockerd's manifest cache.
// Then we'll try to pull the same digest but with a different repo name.
dir := t.TempDir()
store, err := local.NewStore(dir)
assert.NilError(t, err)
desc := createTestImage(ctx, t, store)
remote := path.Join(registry.DefaultURL, "test:latest")
c8dClient, err := containerd.New("", containerd.WithServices(containerd.WithContentStore(store)))
assert.NilError(t, err)
c8dClient.Push(ctx, remote, desc)
assert.NilError(t, err)
client := testEnv.APIClient()
rdr, err := client.ImagePull(ctx, remote, image.PullOptions{})
assert.NilError(t, err)
defer rdr.Close()
_, err = io.Copy(io.Discard, rdr)
assert.Check(t, err)
// Now, pull a totally different repo with a the same digest
rdr, err = client.ImagePull(ctx, path.Join(registry.DefaultURL, "other:image@"+desc.Digest.String()), image.PullOptions{})
if rdr != nil {
assert.Check(t, rdr.Close())
}
assert.Assert(t, err != nil, "Expected error, got none: %v", err)
assert.Assert(t, errdefs.IsNotFound(err), err)
assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
}
// TestImagePullNonExisting pulls non-existing images from the central registry, with different
// combinations of implicit tag and library prefix.
func TestImagePullNonExisting(t *testing.T) {
ctx := setupTest(t)
for _, ref := range []string{
"asdfasdf:foobar",
"library/asdfasdf:foobar",
"asdfasdf",
"asdfasdf:latest",
"library/asdfasdf",
"library/asdfasdf:latest",
} {
ref := ref
all := strings.Contains(ref, ":")
t.Run(ref, func(t *testing.T) {
t.Parallel()
client := testEnv.APIClient()
rdr, err := client.ImagePull(ctx, ref, image.PullOptions{
All: all,
})
if err == nil {
rdr.Close()
}
expectedMsg := fmt.Sprintf("pull access denied for %s, repository does not exist or may require 'docker login'", "asdfasdf")
assert.Check(t, is.ErrorContains(err, expectedMsg))
assert.Check(t, is.ErrorType(err, errdefs.IsNotFound))
if all {
// pull -a on a nonexistent registry should fall back as well
assert.Check(t, !strings.Contains(err.Error(), "unauthorized"), `message should not contain "unauthorized"`)
}
})
}
}