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/testutils/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, "busybox", 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) } 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)) }) } }) } }