Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion pkg/compose/watch.go
Original file line number Diff line number Diff line change
Expand Up @@ -726,8 +726,14 @@ func (s *composeService) pruneDanglingImagesOnRebuild(ctx context.Context, proje
return
}

// imageNameToIdMap is keyed by image name; the freshly built images to
// spare are its VALUES (image IDs), matched against the dangling IDs
builtIDs := make(map[string]struct{}, len(imageNameToIdMap))
for _, id := range imageNameToIdMap {
builtIDs[id] = struct{}{}
}
for _, img := range images.Items {
if _, ok := imageNameToIdMap[img.ID]; !ok {
if _, ok := builtIDs[img.ID]; !ok {
_, err := s.apiClient().ImageRemove(ctx, img.ID, client.ImageRemoveOptions{})
if err != nil {
logrus.Debugf("Failed to remove image %s: %v", img.ID, err)
Expand Down
26 changes: 26 additions & 0 deletions pkg/compose/watch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,29 @@ func (f *fakeSyncer) Sync(ctx context.Context, service string, paths []*sync.Pat
f.synced <- paths
return nil
}

// TestPruneDanglingImagesOnRebuild verifies the post-rebuild prune only
// removes superseded dangling images: a dangling image whose ID matches one
// of the freshly built images must be spared. The lookup used to probe the
// name-keyed map with an image ID, matching nothing — every dangling image
// of the project was removed on each rebuild.
func TestPruneDanglingImagesOnRebuild(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
apiMock, cli := prepareMocks(mockCtrl)
tested, err := NewComposeService(cli)
assert.NilError(t, err)

apiMock.EXPECT().ImageList(gomock.Any(), gomock.Any()).
Return(client.ImageListResult{Items: []image.Summary{
{ID: "sha256:justbuilt"},
{ID: "sha256:superseded"},
}}, nil)
// only the superseded image may be removed; removing the just-built one
// would be an unexpected call and fail the test
apiMock.EXPECT().ImageRemove(gomock.Any(), "sha256:superseded", gomock.Any()).
Return(client.ImageRemoveResult{}, nil)

tested.(*composeService).pruneDanglingImagesOnRebuild(t.Context(), "proj",
map[string]string{"app-image:latest": "sha256:justbuilt"})
}
Loading