Skip to content

Commit 8e26cb7

Browse files
committed
fix: clean up container on NetworkConnect failure to prevent orphans
When NetworkConnect fails for a secondary network on API < 1.44, remove the just-created container before returning the error. This prevents orphan containers that cause name conflicts on retry.
1 parent 3bb5db0 commit 8e26cb7

2 files changed

Lines changed: 67 additions & 0 deletions

File tree

pkg/compose/convergence.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -751,12 +751,16 @@ func (s *composeService) createMobyContainer(ctx context.Context, project *types
751751
}
752752
epSettings, err := createEndpointSettings(project, service, number, networkKey, cfgs.Links, opts.UseNetworkAliases)
753753
if err != nil {
754+
// Clean up the container we just created to avoid orphans
755+
_, _ = s.apiClient().ContainerRemove(ctx, response.ID, client.ContainerRemoveOptions{Force: true})
754756
return created, err
755757
}
756758
if _, err := s.apiClient().NetworkConnect(ctx, mobyNetworkName, client.NetworkConnectOptions{
757759
Container: response.ID,
758760
EndpointConfig: epSettings,
759761
}); err != nil {
762+
// Clean up the container we just created to avoid orphans
763+
_, _ = s.apiClient().ContainerRemove(ctx, response.ID, client.ContainerRemoveOptions{Force: true})
760764
return created, err
761765
}
762766
}

pkg/compose/convergence_test.go

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -593,3 +593,66 @@ func TestCreateMobyContainerLegacyAPI(t *testing.T) {
593593
assert.Equal(t, gotConnect.Container, "an-id")
594594
assert.Check(t, gotConnect.EndpointConfig != nil)
595595
}
596+
597+
func TestCreateMobyContainerLegacyAPI_NetworkConnectFailure(t *testing.T) {
598+
mockCtrl := gomock.NewController(t)
599+
defer mockCtrl.Finish()
600+
apiClient := mocks.NewMockAPIClient(mockCtrl)
601+
cli := mocks.NewMockCli(mockCtrl)
602+
tested, err := NewComposeService(cli)
603+
assert.NilError(t, err)
604+
cli.EXPECT().Client().Return(apiClient).AnyTimes()
605+
cli.EXPECT().ConfigFile().Return(&configfile.ConfigFile{}).AnyTimes()
606+
apiClient.EXPECT().DaemonHost().Return("").AnyTimes()
607+
apiClient.EXPECT().ImageInspect(anyCancellableContext(), gomock.Any()).Return(client.ImageInspectResult{}, nil).AnyTimes()
608+
609+
// force `RuntimeVersion` to return a pre-1.44 API version
610+
runtimeVersion = runtimeVersionCache{}
611+
apiClient.EXPECT().ServerVersion(gomock.Any(), gomock.Any()).Return(client.ServerVersionResult{
612+
APIVersion: "1.43",
613+
}, nil).AnyTimes()
614+
615+
service := types.ServiceConfig{
616+
Name: "test",
617+
Networks: map[string]*types.ServiceNetworkConfig{
618+
"a": {
619+
Priority: 10,
620+
},
621+
"b": {
622+
Priority: 100,
623+
},
624+
},
625+
}
626+
project := types.Project{
627+
Name: "bork",
628+
Services: types.Services{
629+
"test": service,
630+
},
631+
Networks: types.Networks{
632+
"a": types.NetworkConfig{
633+
Name: "a-moby-name",
634+
},
635+
"b": types.NetworkConfig{
636+
Name: "b-moby-name",
637+
},
638+
},
639+
}
640+
641+
apiClient.EXPECT().ContainerCreate(gomock.Any(), gomock.Any()).Return(client.ContainerCreateResult{ID: "an-id"}, nil)
642+
643+
// NetworkConnect fails
644+
connectErr := fmt.Errorf("network connect failed")
645+
apiClient.EXPECT().NetworkConnect(gomock.Any(), gomock.Eq("a-moby-name"), gomock.Any()).Return(client.NetworkConnectResult{}, connectErr)
646+
647+
// ContainerRemove should be called to clean up the orphan container
648+
apiClient.EXPECT().ContainerRemove(gomock.Any(), gomock.Eq("an-id"), gomock.Any()).DoAndReturn(func(_ context.Context, _ string, opts client.ContainerRemoveOptions) (client.ContainerRemoveResult, error) {
649+
assert.Check(t, opts.Force, "ContainerRemove should use Force")
650+
return client.ContainerRemoveResult{}, nil
651+
})
652+
653+
_, err = tested.(*composeService).createMobyContainer(t.Context(), &project, service, "test", 0, nil, createOptions{
654+
Labels: make(types.Labels),
655+
UseNetworkAliases: true,
656+
})
657+
assert.ErrorContains(t, err, "network connect failed")
658+
}

0 commit comments

Comments
 (0)