Files
moby/integration/image/history_test.go
Paweł Gronowski 2d69edd28a client/image_(inspect,history,load,save): Wrap return values
Signed-off-by: Paweł Gronowski <pawel.gronowski@docker.com>
Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2025-10-21 14:23:38 +02:00

142 lines
4.0 KiB
Go

package image
import (
"context"
"io"
"testing"
"github.com/containerd/platforms"
buildtypes "github.com/moby/moby/api/types/build"
"github.com/moby/moby/client"
build "github.com/moby/moby/v2/integration/internal/build"
"github.com/moby/moby/v2/internal/testutil"
"github.com/moby/moby/v2/internal/testutil/fakecontext"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/skip"
)
func TestAPIImagesHistory(t *testing.T) {
ctx := setupTest(t)
client := testEnv.APIClient()
dockerfile := "FROM busybox\nENV FOO bar"
imgID := build.Do(ctx, t, client, fakecontext.New(t, t.TempDir(), fakecontext.WithDockerfile(dockerfile)))
res, err := client.ImageHistory(ctx, imgID)
assert.NilError(t, err)
assert.Assert(t, len(res.Items) != 0)
var found bool
for _, imageLayer := range res.Items {
if imageLayer.ID == imgID {
found = true
break
}
}
assert.Assert(t, found)
}
// TestAPIImageHistoryCrossPlatform tests the image history functionality
// when dealing with cross-platform image builds.
// This is a regression test for https://github.com/moby/moby/issues/50851
// where `docker history` fails with "snapshot does not exist" error for
// images built for non-native platforms.
func TestAPIImageHistoryCrossPlatform(t *testing.T) {
skip.If(t, testEnv.DaemonInfo.OSType == "windows")
ctx := setupTest(t)
apiClient := testEnv.APIClient()
// Determine the non-native platform to use for testing
nonNativePlatform := ocispec.Platform{OS: testEnv.DaemonInfo.OSType, Architecture: "amd64"}
if testEnv.DaemonInfo.Architecture == "amd64" {
nonNativePlatform = ocispec.Platform{OS: testEnv.DaemonInfo.OSType, Architecture: "arm64"}
}
// We need to pull the image for the non-native platform
// TODO: Make sure we have a multi-platform frozen image we could use
pullImageForPlatform(t, ctx, apiClient, "alpine", nonNativePlatform)
dockerfile := "FROM alpine\nRUN true"
buildCtx := fakecontext.New(t, t.TempDir(), fakecontext.WithDockerfile(dockerfile))
defer buildCtx.Close()
// Build the image for a non-native platform
resp, err := apiClient.ImageBuild(ctx, buildCtx.AsTarReader(t), client.ImageBuildOptions{
Version: buildtypes.BuilderBuildKit,
Tags: []string{"cross-platform-test"},
Platform: platforms.FormatAll(nonNativePlatform),
})
assert.NilError(t, err)
defer resp.Body.Close()
imgID := build.GetImageIDFromBody(t, resp.Body)
t.Cleanup(func() {
apiClient.ImageRemove(ctx, imgID, client.ImageRemoveOptions{Force: true})
})
testCases := []struct {
name string
imageRef string
options []client.ImageHistoryOption
}{
{
name: "without explicit platform",
imageRef: imgID,
options: nil,
},
{
name: "with explicit platform",
imageRef: imgID,
options: []client.ImageHistoryOption{client.ImageHistoryWithPlatform(nonNativePlatform)},
},
{
name: "using image reference",
imageRef: "cross-platform-test",
options: nil,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := testutil.StartSpan(ctx, t)
res, err := apiClient.ImageHistory(ctx, tc.imageRef, tc.options...)
assert.NilError(t, err)
found := false
for _, layer := range res.Items {
if layer.ID == imgID {
found = true
break
}
}
assert.Assert(t, found, "History should contain the built image ID")
assert.Assert(t, is.Len(res.Items, 3))
for i, layer := range res.Items {
assert.Assert(t, layer.Size >= 0, "Layer %d should not have negative size", i)
}
})
}
}
func pullImageForPlatform(t *testing.T, ctx context.Context, apiClient client.APIClient, ref string, platform ocispec.Platform) {
pullResp, err := apiClient.ImagePull(ctx, ref, client.ImagePullOptions{Platform: platforms.FormatAll(platform)})
assert.NilError(t, err)
_, _ = io.Copy(io.Discard, pullResp)
_, err = apiClient.ImageInspect(ctx, ref)
assert.NilError(t, err)
t.Cleanup(func() {
_, _ = apiClient.ImageRemove(ctx, ref, client.ImageRemoveOptions{Force: true})
})
}