Skip to content

Fix image prune events for containerd backend#50725

Merged
thaJeztah merged 1 commit intomoby:masterfrom
dmcgowan:fix-containerd-prune-events
Aug 28, 2025
Merged

Fix image prune events for containerd backend#50725
thaJeztah merged 1 commit intomoby:masterfrom
dmcgowan:fix-containerd-prune-events

Conversation

@dmcgowan
Copy link
Member

@dmcgowan dmcgowan commented Aug 13, 2025

Ensure events for containerd backend are properly sent when deleted via image prune. Fix prune output to only show deleted images rather than the deletion of each blob.

We should backport this to 28 as well.

containerd image store: Fix `docker image prune` to emit correct `untag` and `delete` events and list only the deleted images root digests instead of every blob.

@thaJeztah thaJeztah added this to the 29.0.0 milestone Aug 14, 2025
@thaJeztah thaJeztah added the kind/bugfix PR's that fix bugs label Aug 14, 2025
Deleted: blob.Digest.String(),
},
)
if c8dimages.IsManifestType(blob.MediaType) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also handle indexes?

Suggested change
if c8dimages.IsManifestType(blob.MediaType) {
if c8dimages.IsManifestType(blob.MediaType) || c8dimages.IsIndexType(blob.MediaType){

Copy link
Contributor

@vvoland vvoland left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/moby/moby/actions/runs/16950893722/job/48063423574?pr=50725#step:7:79

______________________ PruneImagesTest.test_prune_images _______________________
tests/integration/api_image_test.py:326: in test_prune_images
    assert img_id in [
E   AssertionError: assert 'sha256:4a96d7fec90f38afb825c65f9c5b08c726879ddecca2cd8580b615c7557ea28e' in [None, 'sha256:ed5c69b7b7ccca09e74045e1fe59ca7f56f121f5d34e991758fb4e0461bc1fe3', None, 'sha256:cbad98579cb46fa4b855c6...fa0a096a22351020f9fcda6a846a041', None, 'sha256:7c0ffe5751238c8479f952f3fbc3b719d47bccac0e9bf0a21c77a27cba9ef12d', ...]
------- generated xml file: /src/bundles/test-docker-py/junit-report.xml -------

@thaJeztah
Copy link
Member

Interesting that docker-py caught things, and our CI didn't; looks like we may be missing coverage? 🤔

Ensure events for containerd backend are properly sent when deleted via
image prune. Fix prune output to only show deleted images rather than
the deletion of each blob.

Signed-off-by: Derek McGowan <derek@mcg.dev>
@dmcgowan dmcgowan force-pushed the fix-containerd-prune-events branch from 70148d6 to da2b1a2 Compare August 14, 2025 19:05
@thaJeztah
Copy link
Member

@thaJeztah thaJeztah moved this from To do to In progress in containerd integration Aug 14, 2025
@thaJeztah thaJeztah moved this to Required for default containerd in Containerd-as-default Aug 14, 2025
@thaJeztah
Copy link
Member

w.r.t. the failing docker-py test; do we need an integration test somewhere that would catch those in our CI (not depending on the docker-py tests)?

@dmcgowan
Copy link
Member Author

w.r.t. the failing docker-py test; do we need an integration test somewhere that would catch those in our CI (not depending on the docker-py tests)?

First the "correct" output would need to be defined, the output is mostly informational and events should be used for triggering actions. I would say this is another case where unless we want to clearly define the output, we shouldn't just write a test to capture what it currently does.

@thaJeztah
Copy link
Member

First the "correct" output would need to be defined, the output is mostly informational and events should be used for triggering actions.

So there's 2 things that stood out to me; first of all, but that may be related to how the docker-py code collects the events, the test-output showed various None in the result;

E   AssertionError: assert 'sha256:4a96d7fec90f38afb825c65f9c5b08c726879ddecca2cd8580b615c7557ea28e' in

[None, 
'sha256:ed5c69b7b7ccca09e74045e1fe59ca7f56f121f5d34e991758fb4e0461bc1fe3', 
None, 'sha256:cbad98579cb46fa4b855c6...fa0a096a22351020f9fcda6a846a041', 
None, 'sha256:7c0ffe5751238c8479f952f3fbc3b719d47bccac0e9bf0a21c77a27cba9ef12d', 
...]

Which made me wonder if we were producing events without an Actor. If that's the case, that would be a bug, and that's something we should catch in our tests.

I would say this is another case where unless we want to clearly define the output, we shouldn't just write a test to capture what it currently does.

That's a fair point; that said: we already have an "implicit" contract here. I think it's fine to have a test even if that tests the current "status quo" behavior. As long as the test clearly outlines that that's what it does.

We've honestly had too many cases where we did not have coverage, and had to depend on docker-py to tell us that we changed behavior. Was the docker-py failure always correct? No, but, specifically events are widely used (e.g. in compose, but also desktop), so getting notified of changes in behavior through a test that starts failing is good. At least in that case we can make a conscious decision if the change in behavior was intentional or unintentional. I'd rather have a test failing that needs updating, than having to discover later that we broke compose (or desktop).

As to "what do we want it to do?" The docker-py test doesn't look unreasonable; it checks that an unused image appears as event when pruning;

@requires_api_version('1.25')
class PruneImagesTest(BaseAPIIntegrationTest):
    def test_prune_images(self):
        try:
            self.client.remove_image('hello-world')
        except docker.errors.APIError:
            pass

        # Ensure busybox does not get pruned
        ctnr = self.client.create_container(TEST_IMG, ['sleep', '9999'])
        self.tmp_containers.append(ctnr)

        self.client.pull('hello-world', tag='latest')
        self.tmp_imgs.append('hello-world')
        img_id = self.client.inspect_image('hello-world')['Id']
        result = self.client.prune_images()
        assert img_id not in [
            img.get('Deleted') for img in result.get('ImagesDeleted') or []
        ]
        result = self.client.prune_images({'dangling': False})
        assert result['SpaceReclaimed'] > 0
        assert 'hello-world:latest' in [
            img.get('Untagged') for img in result['ImagesDeleted']
        ]
        assert img_id in [
            img.get('Deleted') for img in result['ImagesDeleted']
        ]

@thaJeztah
Copy link
Member

OK; let's bring this one in, but we should create a tracking ticket to have a test.

Copy link
Member

@thaJeztah thaJeztah left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"LGTM"

@thaJeztah thaJeztah merged commit e6c1660 into moby:master Aug 28, 2025
278 of 281 checks passed
@github-project-automation github-project-automation bot moved this from In progress to Done in containerd integration Aug 28, 2025
Comment on lines +15 to +22
"github.com/moby/moby/api/types/events"
"github.com/moby/moby/api/types/filters"
"github.com/moby/moby/api/types/image"
"github.com/moby/moby/v2/daemon/container"
"github.com/moby/moby/v2/errdefs"
"github.com/opencontainers/go-digest"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"

"github.com/moby/moby/v2/daemon/container"
"github.com/moby/moby/v2/errdefs"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh; forgot to comment.

We should try to avoid these changes; we can decide to switch the format / grouping we use, but better to do it in a single / large PR across the board. These things can be tricky when back porting.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: Required for default containerd

Development

Successfully merging this pull request may close these issues.

4 participants