blob: 0b6547ccd5cd789a2bb031bbbf58fb833f142443 [file] [edit]
package image
import (
"bytes"
"encoding/json"
"testing"
"github.com/moby/moby/client"
iimage "github.com/moby/moby/v2/integration/internal/image"
"github.com/moby/moby/v2/internal/testutil/specialimage"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/skip"
)
// Regression test for: https://github.com/moby/moby/issues/45556
func TestImageInspectEmptyTagsAndDigests(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "build-empty-images is not called on Windows")
ctx := setupTest(t)
apiClient := testEnv.APIClient()
danglingID := iimage.Load(ctx, t, apiClient, specialimage.Dangling)
var raw bytes.Buffer
inspect, err := apiClient.ImageInspect(ctx, danglingID, client.ImageInspectWithRawResponse(&raw))
assert.NilError(t, err)
// Must be a zero length array, not null.
assert.Check(t, is.Len(inspect.RepoTags, 0))
assert.Check(t, is.Len(inspect.RepoDigests, 0))
var rawJson map[string]any
err = json.Unmarshal(raw.Bytes(), &rawJson)
assert.NilError(t, err)
// Check if the raw json is also an array, not null.
assert.Check(t, is.Len(rawJson["RepoTags"], 0))
assert.Check(t, is.Len(rawJson["RepoDigests"], 0))
}
// Regression test for: https://github.com/moby/moby/issues/48747
func TestImageInspectUniqueRepoDigests(t *testing.T) {
ctx := setupTest(t)
apiClient := testEnv.APIClient()
before, err := apiClient.ImageInspect(ctx, "busybox")
assert.NilError(t, err)
for _, tag := range []string{"master", "newest"} {
imgName := "busybox:" + tag
_, err := apiClient.ImageTag(ctx, client.ImageTagOptions{Source: "busybox", Target: imgName})
assert.NilError(t, err)
defer func() {
_, _ = apiClient.ImageRemove(ctx, imgName, client.ImageRemoveOptions{Force: true})
}()
}
after, err := apiClient.ImageInspect(ctx, "busybox")
assert.NilError(t, err)
assert.Check(t, is.Len(after.RepoDigests, len(before.RepoDigests)))
}
func TestImageInspectDescriptor(t *testing.T) {
ctx := setupTest(t)
apiClient := testEnv.APIClient()
inspect, err := apiClient.ImageInspect(ctx, "busybox")
assert.NilError(t, err)
if !testEnv.UsingSnapshotter() {
assert.Check(t, is.Nil(inspect.Descriptor))
return
}
assert.Assert(t, inspect.Descriptor != nil)
assert.Check(t, inspect.Descriptor.Digest.String() == inspect.ID)
assert.Check(t, inspect.Descriptor.Size > 0)
}
// Regression test for: https://github.com/moby/moby/issues/51566
//
// This can be reproduced with two image that share the same uncompressed layer
// but have a different compressed blob is pulled.
//
// Example:
// ```
// docker pull nginx@sha256:3b7732505933ca591ce4a6d860cb713ad96a3176b82f7979a8dfa9973486a0d6
// docker pull gotenberg/gotenberg@sha256:b116a40a1c24917e2bf3e153692da5acd2e78e7cd67e1b2d243b47c178f31c90
// ```
//
// In this case, it's the base debian trixie image that's used as a base.
// They're effectively the same layer (unpacked diff ID
// `sha256:1d46119d249f7719e1820e24a311aa7c453f166f714969cffe89504678eaa447`),
// but different compressed blobs:
//
// # nginx
// {
// "mediaType": "application/vnd.oci.image.layer.v1.tar+gzip",
// "size": 29777766,
// "digest": "sha256:8c7716127147648c1751940b9709b6325f2256290d3201662eca2701cadb2cdf"
// }
//
// # gotenberg
// {
// "mediaType": "application/vnd.docker.image.rootfs.diff.tar.gzip",
// "size": 30781333,
// "digest": "sha256:b96413fb491a5ed179bb2746ff3be6cbddd72e14c6503bea80d58e579a3b92bc"
// },
func TestImageInspectWithoutSomeBlobs(t *testing.T) {
t.Skip("TODO(vvoland): Come up with minimal images for this test")
skip.If(t, testEnv.DaemonInfo.OSType != "linux", "The test images are Linux-only")
ctx := setupTest(t)
apiClient := testEnv.APIClient()
const baseImage = "nginx@sha256:3b7732505933ca591ce4a6d860cb713ad96a3176b82f7979a8dfa9973486a0d6"
const childImage = "gotenberg/gotenberg:8.24@sha256:b116a40a1c24917e2bf3e153692da5acd2e78e7cd67e1b2d243b47c178f31c90"
// Pull the base image first and then the child image
for _, image := range []string{baseImage, childImage} {
rdr, err := apiClient.ImagePull(ctx, image, client.ImagePullOptions{})
assert.NilError(t, err)
assert.NilError(t, rdr.Wait(ctx))
t.Cleanup(func() {
_, _ = apiClient.ImageRemove(ctx, image, client.ImageRemoveOptions{})
})
}
var raw bytes.Buffer
inspect, err := apiClient.ImageInspect(ctx, childImage, client.ImageInspectWithRawResponse(&raw))
assert.NilError(t, err)
var rawJson map[string]any
err = json.Unmarshal(raw.Bytes(), &rawJson)
assert.NilError(t, err)
configVal, hasConfig := rawJson["Config"]
assert.Check(t, hasConfig, "Config field should exist in JSON response")
if assert.Check(t, configVal != nil, "Config should not be null in JSON response") {
assert.Check(t, is.DeepEqual(inspect.Config.Cmd, []string{"gotenberg"}))
assert.Check(t, inspect.Os != "")
assert.Check(t, inspect.Architecture != "")
}
}
func TestImageInspectWithPlatform(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows", "The test image is a Linux image")
ctx := setupTest(t)
apiClient := testEnv.APIClient()
nativePlatform := ocispec.Platform{
OS: testEnv.DaemonInfo.OSType,
Architecture: testEnv.DaemonInfo.Architecture,
}
// Create a platform that does not match the host platform
differentOS := "linux"
if nativePlatform.OS == "linux" {
differentOS = "windows"
}
differentPlatform := ocispec.Platform{
OS: differentOS,
Architecture: "amd64",
}
imageID := iimage.Load(ctx, t, apiClient, func(dir string) (*ocispec.Index, error) {
i, descs, err := specialimage.MultiPlatform(dir, "multiplatform:latest", []ocispec.Platform{nativePlatform, differentPlatform})
assert.NilError(t, err)
err = specialimage.LegacyManifest(dir, "multiplatform:latest", descs[0])
assert.NilError(t, err)
return i, err
})
for _, tc := range []struct {
name string
requestedPlatform *ocispec.Platform
expectedPlatform *ocispec.Platform
expectedError string
withManifests bool
snapshotterOnly bool
graphdriverOnly bool
}{
{
name: "default",
requestedPlatform: nil,
expectedPlatform: &nativePlatform,
},
{
name: "snapshotter/with-manifests",
requestedPlatform: nil,
expectedPlatform: &nativePlatform,
snapshotterOnly: true,
withManifests: true,
},
{
name: "native",
requestedPlatform: &nativePlatform,
expectedPlatform: &nativePlatform,
},
{
name: "different",
requestedPlatform: &differentPlatform,
expectedPlatform: &differentPlatform,
snapshotterOnly: true,
},
{
name: "different not supported on graphdriver",
requestedPlatform: &differentPlatform,
graphdriverOnly: true,
// image with reference multiplatform:latest was found but its platform (linux/aarch64) does not match the specified platform (windows/amd64)
expectedError: "image with reference multiplatform:latest was found but its platform",
},
} {
if tc.snapshotterOnly && !testEnv.UsingSnapshotter() {
continue
}
if tc.graphdriverOnly && testEnv.UsingSnapshotter() {
continue
}
t.Run(tc.name, func(t *testing.T) {
var opts []client.ImageInspectOption
if tc.requestedPlatform != nil {
opts = append(opts, client.ImageInspectWithPlatform(tc.requestedPlatform))
}
if tc.withManifests {
opts = append(opts, client.ImageInspectWithManifests(true))
}
inspect, err := apiClient.ImageInspect(ctx, imageID, opts...)
if tc.expectedError != "" {
assert.Assert(t, is.ErrorContains(err, tc.expectedError))
return
}
assert.NilError(t, err)
assert.Check(t, is.Equal(inspect.Architecture, tc.expectedPlatform.Architecture))
assert.Check(t, is.Equal(inspect.Os, tc.expectedPlatform.OS))
if testEnv.UsingSnapshotter() {
assert.Assert(t, inspect.Descriptor != nil)
if tc.requestedPlatform != nil {
if assert.Check(t, inspect.Descriptor.Platform != nil) {
assert.Check(t, is.DeepEqual(*inspect.Descriptor.Platform, *tc.expectedPlatform))
}
}
} else {
assert.Check(t, inspect.Descriptor == nil)
}
if tc.withManifests {
t.Run("has manifests", func(t *testing.T) {
assert.Check(t, is.Len(inspect.Manifests, 2))
})
} else {
t.Run("has no manifests", func(t *testing.T) {
assert.Check(t, is.Nil(inspect.Manifests))
})
}
})
}
}