From 4372887095f9ed0a37f6e799d8206a26a6c7fc11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20L=C3=A1zni=C4=8Dka?= Date: Thu, 12 Mar 2026 13:29:57 +0100 Subject: [PATCH 1/2] PullImage: return image ID in PullImageResponse.image_ref MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is required to stay compatible with the latest CRI API requirements. Signed-off-by: Stanislav Láznička --- core/image.go | 13 ++++--------- core/image_test.go | 40 +++++++++++++++++++++++++++++++++++----- libdocker/fake_client.go | 35 +++++++++++++++++++++++++++++------ 3 files changed, 68 insertions(+), 20 deletions(-) diff --git a/core/image.go b/core/image.go index 82b9b0ba1..a5c2f8276 100644 --- a/core/image.go +++ b/core/image.go @@ -155,12 +155,12 @@ func (ds *dockerService) PullImage( return nil, filterHTTPError(err, image.Image) } - imageRef, err := getImageRef(ds.client, image.Image) + imageID, err := getImageID(ds.client, image.Image) if err != nil { return nil, err } - return &runtimeapi.PullImageResponse{ImageRef: imageRef}, nil + return &runtimeapi.PullImageResponse{ImageRef: imageID}, nil } // RemoveImage removes the image. @@ -202,8 +202,8 @@ func (ds *dockerService) RemoveImage( return &runtimeapi.RemoveImageResponse{}, nil } -// getImageRef returns the image digest if exists, or else returns the image ID. -func getImageRef(client libdocker.DockerClientInterface, image string) (string, error) { +// getImageID returns the image ID. +func getImageID(client libdocker.DockerClientInterface, image string) (string, error) { img, err := client.InspectImageByRef(image) if err != nil { return "", err @@ -212,11 +212,6 @@ func getImageRef(client libdocker.DockerClientInterface, image string) (string, return "", fmt.Errorf("unable to inspect image %s", image) } - // Returns the digest if it exist. - if len(img.RepoDigests) > 0 { - return img.RepoDigests[0], nil - } - return img.ID, nil } diff --git a/core/image_test.go b/core/image_test.go index 1685397fb..6759c94b6 100644 --- a/core/image_test.go +++ b/core/image_test.go @@ -18,6 +18,7 @@ package core import ( "fmt" + "reflect" "testing" dockertypes "github.com/docker/docker/api/types" @@ -138,17 +139,19 @@ func TestRemoveImage(t *testing.T) { } } -func TestPullWithJSONError(t *testing.T) { +func TestPullImage(t *testing.T) { ds, fakeDocker, _ := newTestDockerService() tests := map[string]struct { image *runtimeapi.ImageSpec err error expectedError string + expectImage bool }{ "Json error": { &runtimeapi.ImageSpec{Image: "ubuntu"}, &jsonmessage.JSONError{Code: 50, Message: "Json error"}, "Json error", + false, }, "Bad gateway": { &runtimeapi.ImageSpec{Image: "ubuntu"}, @@ -157,15 +160,42 @@ func TestPullWithJSONError(t *testing.T) { Message: "\n\n \n \n \n

Oops, there was an error!

\n

We have been contacted of this error, feel free to check out status.docker.com\n to see if there is a bigger issue.

\n\n \n", }, "RegistryUnavailable", + false, + }, + "Successful Pull": { + &runtimeapi.ImageSpec{Image: "ubuntu"}, + nil, + "", + true, }, } for key, test := range tests { - fakeDocker.InjectError("pull", test.err) - _, err := ds.PullImage( + if test.err != nil { + fakeDocker.InjectError("pull", test.err) + } + + gotResp, gotErr := ds.PullImage( getTestCTX(), &runtimeapi.PullImageRequest{Image: test.image, Auth: &runtimeapi.AuthConfig{}}, ) - require.Error(t, err, fmt.Sprintf("TestCase [%s]", key)) - assert.Contains(t, err.Error(), test.expectedError) + if (len(test.expectedError) > 0) != (gotErr != nil) { + t.Fatalf("expected err %q but got %v", test.expectedError, gotErr) + } + + if len(test.expectedError) > 0 { + require.Error(t, gotErr, fmt.Sprintf("TestCase [%s]", key)) + assert.Contains(t, gotErr.Error(), test.expectedError) + } + + if test.expectImage { + expectedResp := &runtimeapi.PullImageResponse{ + ImageRef: libdocker.FakePullImageIDMapping(test.image.Image), + } + + if !reflect.DeepEqual(gotResp, expectedResp) { + t.Errorf("expected pull response %v, got %v", expectedResp, gotResp) + } + } + } } diff --git a/libdocker/fake_client.go b/libdocker/fake_client.go index 828422df5..7cf96d710 100644 --- a/libdocker/fake_client.go +++ b/libdocker/fake_client.go @@ -17,6 +17,8 @@ limitations under the License. package libdocker import ( + "crypto/sha256" + "crypto/sha512" "encoding/json" "fmt" "hash/fnv" @@ -696,6 +698,10 @@ func (f *FakeDockerClient) PullImage( authJson, _ := json.Marshal(auth) inspect := createImageInspectFromRef(image) f.ImageInspects[image] = inspect + f.ImageInspects[inspect.ID] = inspect + for _, repoDigest := range inspect.RepoDigests { + f.ImageInspects[repoDigest] = inspect + } f.appendPulled(fmt.Sprintf("%s using %s", image, string(authJson))) f.Images = append(f.Images, *createImageFromImageInspect(*inspect)) f.ImagesPulled = append(f.ImagesPulled, image) @@ -842,8 +848,9 @@ func (f *FakeDockerClient) ResizeContainerTTY(id string, height, width uint) err func createImageInspectFromRef(ref string) *dockertypes.ImageInspect { return &dockertypes.ImageInspect{ - ID: ref, - RepoTags: []string{ref}, + ID: FakePullImageIDMapping(ref), + RepoTags: []string{ref}, + RepoDigests: []string{fakePullImageDigestMapping(ref)}, // Image size is required to be non-zero for CRI integration. VirtualSize: fakeImageSize, Size: fakeImageSize, @@ -853,8 +860,9 @@ func createImageInspectFromRef(ref string) *dockertypes.ImageInspect { func createImageInspectFromImage(image dockerimagetypes.Summary) *dockertypes.ImageInspect { return &dockertypes.ImageInspect{ - ID: image.ID, - RepoTags: image.RepoTags, + ID: image.ID, + RepoTags: image.RepoTags, + RepoDigests: image.RepoDigests, // Image size is required to be non-zero for CRI integration. VirtualSize: fakeImageSize, Size: fakeImageSize, @@ -864,8 +872,9 @@ func createImageInspectFromImage(image dockerimagetypes.Summary) *dockertypes.Im func createImageFromImageInspect(inspect dockertypes.ImageInspect) *dockerimagetypes.Summary { return &dockerimagetypes.Summary{ - ID: inspect.ID, - RepoTags: inspect.RepoTags, + ID: inspect.ID, + RepoTags: inspect.RepoTags, + RepoDigests: inspect.RepoDigests, // Image size is required to be non-zero for CRI integration. VirtualSize: fakeImageSize, Size: fakeImageSize, @@ -927,3 +936,17 @@ func (f *FakeDockerClient) GetContainerStats(id string) (*dockercontainer.StatsR } return stats, nil } + +// FakePullImageIDMapping is used by the fake docker client to map an image ref +// to image ID during PullImage operation +func FakePullImageIDMapping(imageRef string) string { + return fmt.Sprintf("sha256:%x", sha256.Sum256([]byte(imageRef))) +} + +func fakePullImageDigestMapping(imageName string) string { + noDigestImageName, _, found := strings.Cut(imageName, "@") + if found { + return imageName + } + return fmt.Sprintf("%s@sha512:%x", noDigestImageName, sha512.Sum512([]byte(imageName))) +} From b599f3945c9ce11bc52e2bdcd2c96d9ede3dbdce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stanislav=20L=C3=A1zni=C4=8Dka?= Date: Thu, 12 Mar 2026 13:34:26 +0100 Subject: [PATCH 2/2] core/stats_windows: fix logging format strings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stanislav Láznička --- core/stats_windows.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/stats_windows.go b/core/stats_windows.go index b41742154..a3293183e 100644 --- a/core/stats_windows.go +++ b/core/stats_windows.go @@ -36,14 +36,14 @@ func (ds *dockerService) getContainerStats(container *runtimeapi.Container) (*ru // That will typically happen with init-containers in Exited state. Docker still knows about them but the HCS does not. // As we don't want to block stats retrieval for other containers, we only log errors. if !hcsshim.IsNotExist(err) && !hcsshim.IsAlreadyStopped(err) { - logrus.Info("Error opening container for ID: %d (stats will be missing): %v", containerID, err) + logrus.Infof("Error opening container for ID: %s (stats will be missing): %v", containerID, err) } return nil, nil } defer func() { closeErr := hcsshimContainer.Close() if closeErr != nil { - logrus.Errorf("Error closing container %d: %v", containerID, err) + logrus.Errorf("Error closing container %s: %v", containerID, closeErr) } }() @@ -56,8 +56,8 @@ func (ds *dockerService) getContainerStats(container *runtimeapi.Container) (*ru // These hcs errors do not have helpers exposed in public package so need to query for the known codes // https://github.com/microsoft/hcsshim/blob/master/internal/hcs/errors.go // PR to expose helpers in hcsshim: https://github.com/microsoft/hcsshim/pull/933 - logrus.Info( - "Container %dis not in a state that stats can be accessed. This occurs when the container is created but not started: %v", + logrus.Infof( + "Container %s is not in a state that stats can be accessed. This occurs when the container is created but not started: %v", containerID, err, )