Move restart policy migration to startup
- commit
- 86ffd722c6b18d578daeb376570f247d43fe8c8a
- author
- brice · · 2026-05-05 17:20 UTC
- parent
- edf3939
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
3 files · +25 -22
M internal/app/run.go +4 -0
| @@ -2015,6 +2015,10 @@ func setupConfigHotReload(ctx context.Context, configSvc configWatcher, coordina | |||
| 2015 | 2015 | ||
| 2016 | 2016 | // syncAndAutoStart syncs existing containers and auto-starts if configured. | |
| 2017 | 2017 | func syncAndAutoStart(ctx context.Context, svc *services, log zerowrap.Logger) { | |
| 2018 | + | if err := svc.containerSvc.EnsureManagedContainerRestartPolicies(ctx); err != nil { | |
| 2019 | + | log.Warn().Err(err).Msg("failed to migrate managed container restart policies") | |
| 2020 | + | } | |
| 2021 | + | ||
| 2018 | 2022 | if err := svc.containerSvc.SyncContainers(ctx); err != nil { | |
| 2019 | 2023 | log.Warn().Err(err).Msg("failed to sync existing containers") | |
| 2020 | 2024 | } | |
| @@ -2015,6 +2015,10 @@ func setupConfigHotReload(ctx context.Context, configSvc configWatcher, coordina | |||||
| 2015 | 2015 | ||||
| 2016 | // syncAndAutoStart syncs existing containers and auto-starts if configured. | 2016 | // syncAndAutoStart syncs existing containers and auto-starts if configured. | ||
| 2017 | func syncAndAutoStart(ctx context.Context, svc *services, log zerowrap.Logger) { | 2017 | func syncAndAutoStart(ctx context.Context, svc *services, log zerowrap.Logger) { | ||
| 2018 | + | if err := svc.containerSvc.EnsureManagedContainerRestartPolicies(ctx); err != nil { | |||
| 2019 | + | log.Warn().Err(err).Msg("failed to migrate managed container restart policies") | |||
| 2020 | + | } | |||
| 2021 | + | ||||
| 2018 | if err := svc.containerSvc.SyncContainers(ctx); err != nil { | 2022 | if err := svc.containerSvc.SyncContainers(ctx); err != nil { | ||
| 2019 | log.Warn().Err(err).Msg("failed to sync existing containers") | 2023 | log.Warn().Err(err).Msg("failed to sync existing containers") | ||
| 2020 | } | 2024 | } | ||
M internal/usecase/container/service.go +17 -2
| @@ -1405,6 +1405,23 @@ func (s *Service) HealthCheck(ctx context.Context) map[string]bool { | |||
| 1405 | 1405 | return health | |
| 1406 | 1406 | } | |
| 1407 | 1407 | ||
| 1408 | + | // EnsureManagedContainerRestartPolicies updates restart policy for all managed containers. | |
| 1409 | + | func (s *Service) EnsureManagedContainerRestartPolicies(ctx context.Context) error { | |
| 1410 | + | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | |
| 1411 | + | zerowrap.FieldLayer: "usecase", | |
| 1412 | + | zerowrap.FieldUseCase: "EnsureManagedContainerRestartPolicies", | |
| 1413 | + | }) | |
| 1414 | + | log := zerowrap.FromCtx(ctx) | |
| 1415 | + | ||
| 1416 | + | allContainers, err := s.runtime.ListContainers(ctx, true) | |
| 1417 | + | if err != nil { | |
| 1418 | + | return log.WrapErr(err, "failed to list containers for restart policy migration") | |
| 1419 | + | } | |
| 1420 | + | ||
| 1421 | + | s.ensureManagedContainerRestartPolicies(ctx, allContainers) | |
| 1422 | + | return nil | |
| 1423 | + | } | |
| 1424 | + | ||
| 1408 | 1425 | func (s *Service) ensureManagedContainerRestartPolicies(ctx context.Context, allContainers []*domain.Container) { | |
| 1409 | 1426 | log := zerowrap.FromCtx(ctx) | |
| 1410 | 1427 | ||
| @@ -1437,8 +1454,6 @@ func (s *Service) SyncContainers(ctx context.Context) error { | |||
| 1437 | 1454 | return log.WrapErr(err, "failed to list containers") | |
| 1438 | 1455 | } | |
| 1439 | 1456 | ||
| 1440 | - | s.ensureManagedContainerRestartPolicies(ctx, allContainers) | |
| 1441 | - | ||
| 1442 | 1457 | managed := make(map[string]*domain.Container) | |
| 1443 | 1458 | attachments := make(map[string][]string) | |
| 1444 | 1459 | for _, c := range allContainers { | |
| @@ -1405,6 +1405,23 @@ func (s *Service) HealthCheck(ctx context.Context) map[string]bool { | |||||
| 1405 | return health | 1405 | return health | ||
| 1406 | } | 1406 | } | ||
| 1407 | 1407 | ||||
| 1408 | + | // EnsureManagedContainerRestartPolicies updates restart policy for all managed containers. | |||
| 1409 | + | func (s *Service) EnsureManagedContainerRestartPolicies(ctx context.Context) error { | |||
| 1410 | + | ctx = zerowrap.CtxWithFields(ctx, map[string]any{ | |||
| 1411 | + | zerowrap.FieldLayer: "usecase", | |||
| 1412 | + | zerowrap.FieldUseCase: "EnsureManagedContainerRestartPolicies", | |||
| 1413 | + | }) | |||
| 1414 | + | log := zerowrap.FromCtx(ctx) | |||
| 1415 | + | ||||
| 1416 | + | allContainers, err := s.runtime.ListContainers(ctx, true) | |||
| 1417 | + | if err != nil { | |||
| 1418 | + | return log.WrapErr(err, "failed to list containers for restart policy migration") | |||
| 1419 | + | } | |||
| 1420 | + | ||||
| 1421 | + | s.ensureManagedContainerRestartPolicies(ctx, allContainers) | |||
| 1422 | + | return nil | |||
| 1423 | + | } | |||
| 1424 | + | ||||
| 1408 | func (s *Service) ensureManagedContainerRestartPolicies(ctx context.Context, allContainers []*domain.Container) { | 1425 | func (s *Service) ensureManagedContainerRestartPolicies(ctx context.Context, allContainers []*domain.Container) { | ||
| 1409 | log := zerowrap.FromCtx(ctx) | 1426 | log := zerowrap.FromCtx(ctx) | ||
| 1410 | 1427 | ||||
| @@ -1437,8 +1454,6 @@ func (s *Service) SyncContainers(ctx context.Context) error { | |||||
| 1437 | return log.WrapErr(err, "failed to list containers") | 1454 | return log.WrapErr(err, "failed to list containers") | ||
| 1438 | } | 1455 | } | ||
| 1439 | 1456 | ||||
| 1440 | - | s.ensureManagedContainerRestartPolicies(ctx, allContainers) | |||
| 1441 | - | ||||
| 1442 | managed := make(map[string]*domain.Container) | 1457 | managed := make(map[string]*domain.Container) | ||
| 1443 | attachments := make(map[string][]string) | 1458 | attachments := make(map[string][]string) | ||
| 1444 | for _, c := range allContainers { | 1459 | for _, c := range allContainers { | ||
M internal/usecase/container/service_test.go +4 -20
| @@ -1239,7 +1239,7 @@ func TestService_HealthCheck(t *testing.T) { | |||
| 1239 | 1239 | assert.False(t, result["unhealthy.example.com"]) | |
| 1240 | 1240 | } | |
| 1241 | 1241 | ||
| 1242 | - | func TestService_SyncContainers_EnsuresRestartPolicyForManagedRoutesAndAttachments(t *testing.T) { | |
| 1242 | + | func TestService_EnsureManagedContainerRestartPolicies_EnsuresRoutesAttachmentsAndStoppedContainers(t *testing.T) { | |
| 1243 | 1243 | runtime := mocks.NewMockContainerRuntime(t) | |
| 1244 | 1244 | envLoader := mocks.NewMockEnvLoader(t) | |
| 1245 | 1245 | eventBus := mocks.NewMockEventPublisher(t) | |
| @@ -1289,23 +1289,13 @@ func TestService_SyncContainers_EnsuresRestartPolicyForManagedRoutesAndAttachmen | |||
| 1289 | 1289 | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-1", domain.RestartPolicyAlways).Return(nil).Once() | |
| 1290 | 1290 | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "attachment-1", domain.RestartPolicyAlways).Return(nil).Once() | |
| 1291 | 1291 | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "stopped-1", domain.RestartPolicyAlways).Return(nil).Once() | |
| 1292 | - | err := svc.SyncContainers(ctx) | |
| 1292 | + | err := svc.EnsureManagedContainerRestartPolicies(ctx) | |
| 1293 | 1293 | ||
| 1294 | 1294 | require.NoError(t, err) | |
| 1295 | - | tracked, exists := svc.Get(ctx, "app.example.com") | |
| 1296 | - | require.True(t, exists) | |
| 1297 | - | assert.Equal(t, "route-1", tracked.ID) | |
| 1298 | - | ||
| 1299 | - | svc.mu.RLock() | |
| 1300 | - | attachmentIDs := append([]string{}, svc.attachments["app.example.com"]...) | |
| 1301 | - | svc.mu.RUnlock() | |
| 1302 | - | assert.Equal(t, []string{"attachment-1"}, attachmentIDs) | |
| 1303 | - | assert.NotContains(t, svc.containers, "other-1") | |
| 1304 | - | assert.NotContains(t, svc.containers, "stopped.example.com") | |
| 1305 | 1295 | runtime.AssertNumberOfCalls(t, "EnsureContainerRestartPolicy", 3) | |
| 1306 | 1296 | } | |
| 1307 | 1297 | ||
| 1308 | - | func TestService_SyncContainers_RestartPolicyMigrationErrorIsNonFatal(t *testing.T) { | |
| 1298 | + | func TestService_EnsureManagedContainerRestartPolicies_ErrorIsNonFatal(t *testing.T) { | |
| 1309 | 1299 | runtime := mocks.NewMockContainerRuntime(t) | |
| 1310 | 1300 | envLoader := mocks.NewMockEnvLoader(t) | |
| 1311 | 1301 | eventBus := mocks.NewMockEventPublisher(t) | |
| @@ -1324,12 +1314,9 @@ func TestService_SyncContainers_RestartPolicyMigrationErrorIsNonFatal(t *testing | |||
| 1324 | 1314 | ||
| 1325 | 1315 | runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{routeContainer}, nil).Once() | |
| 1326 | 1316 | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-1", domain.RestartPolicyAlways).Return(errors.New("update failed")).Once() | |
| 1327 | - | err := svc.SyncContainers(ctx) | |
| 1317 | + | err := svc.EnsureManagedContainerRestartPolicies(ctx) | |
| 1328 | 1318 | ||
| 1329 | 1319 | require.NoError(t, err) | |
| 1330 | - | tracked, exists := svc.Get(ctx, "app.example.com") | |
| 1331 | - | require.True(t, exists) | |
| 1332 | - | assert.Equal(t, "route-1", tracked.ID) | |
| 1333 | 1320 | } | |
| 1334 | 1321 | ||
| 1335 | 1322 | func TestService_SyncContainers(t *testing.T) { | |
| @@ -1477,7 +1464,6 @@ func TestService_Restart_ReconcilesStaleContainerID(t *testing.T) { | |||
| 1477 | 1464 | }, | |
| 1478 | 1465 | }, | |
| 1479 | 1466 | }, nil).Once() | |
| 1480 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "fresh-container-id", domain.RestartPolicyAlways).Return(nil).Once() | |
| 1481 | 1467 | runtime.EXPECT().RestartContainer(mock.Anything, "fresh-container-id").Return(nil).Once() | |
| 1482 | 1468 | ||
| 1483 | 1469 | err := svc.Restart(ctx, "test.example.com", false) | |
| @@ -3218,8 +3204,6 @@ func TestSyncContainersRebuildsAttachmentMap(t *testing.T) { | |||
| 3218 | 3204 | }, | |
| 3219 | 3205 | }, | |
| 3220 | 3206 | }, nil).Once() | |
| 3221 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "main-container-1", domain.RestartPolicyAlways).Return(nil).Once() | |
| 3222 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "attachment-container-1", domain.RestartPolicyAlways).Return(nil).Once() | |
| 3223 | 3207 | ||
| 3224 | 3208 | require.NoError(t, svc.SyncContainers(ctx)) | |
| 3225 | 3209 | ||
| @@ -1239,7 +1239,7 @@ func TestService_HealthCheck(t *testing.T) { | |||||
| 1239 | assert.False(t, result["unhealthy.example.com"]) | 1239 | assert.False(t, result["unhealthy.example.com"]) | ||
| 1240 | } | 1240 | } | ||
| 1241 | 1241 | ||||
| 1242 | - | func TestService_SyncContainers_EnsuresRestartPolicyForManagedRoutesAndAttachments(t *testing.T) { | 1242 | + | func TestService_EnsureManagedContainerRestartPolicies_EnsuresRoutesAttachmentsAndStoppedContainers(t *testing.T) { |
| 1243 | runtime := mocks.NewMockContainerRuntime(t) | 1243 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 1244 | envLoader := mocks.NewMockEnvLoader(t) | 1244 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 1245 | eventBus := mocks.NewMockEventPublisher(t) | 1245 | eventBus := mocks.NewMockEventPublisher(t) | ||
| @@ -1289,23 +1289,13 @@ func TestService_SyncContainers_EnsuresRestartPolicyForManagedRoutesAndAttachmen | |||||
| 1289 | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-1", domain.RestartPolicyAlways).Return(nil).Once() | 1289 | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-1", domain.RestartPolicyAlways).Return(nil).Once() | ||
| 1290 | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "attachment-1", domain.RestartPolicyAlways).Return(nil).Once() | 1290 | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "attachment-1", domain.RestartPolicyAlways).Return(nil).Once() | ||
| 1291 | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "stopped-1", domain.RestartPolicyAlways).Return(nil).Once() | 1291 | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "stopped-1", domain.RestartPolicyAlways).Return(nil).Once() | ||
| 1292 | - | err := svc.SyncContainers(ctx) | 1292 | + | err := svc.EnsureManagedContainerRestartPolicies(ctx) |
| 1293 | 1293 | ||||
| 1294 | require.NoError(t, err) | 1294 | require.NoError(t, err) | ||
| 1295 | - | tracked, exists := svc.Get(ctx, "app.example.com") | |||
| 1296 | - | require.True(t, exists) | |||
| 1297 | - | assert.Equal(t, "route-1", tracked.ID) | |||
| 1298 | - | ||||
| 1299 | - | svc.mu.RLock() | |||
| 1300 | - | attachmentIDs := append([]string{}, svc.attachments["app.example.com"]...) | |||
| 1301 | - | svc.mu.RUnlock() | |||
| 1302 | - | assert.Equal(t, []string{"attachment-1"}, attachmentIDs) | |||
| 1303 | - | assert.NotContains(t, svc.containers, "other-1") | |||
| 1304 | - | assert.NotContains(t, svc.containers, "stopped.example.com") | |||
| 1305 | runtime.AssertNumberOfCalls(t, "EnsureContainerRestartPolicy", 3) | 1295 | runtime.AssertNumberOfCalls(t, "EnsureContainerRestartPolicy", 3) | ||
| 1306 | } | 1296 | } | ||
| 1307 | 1297 | ||||
| 1308 | - | func TestService_SyncContainers_RestartPolicyMigrationErrorIsNonFatal(t *testing.T) { | 1298 | + | func TestService_EnsureManagedContainerRestartPolicies_ErrorIsNonFatal(t *testing.T) { |
| 1309 | runtime := mocks.NewMockContainerRuntime(t) | 1299 | runtime := mocks.NewMockContainerRuntime(t) | ||
| 1310 | envLoader := mocks.NewMockEnvLoader(t) | 1300 | envLoader := mocks.NewMockEnvLoader(t) | ||
| 1311 | eventBus := mocks.NewMockEventPublisher(t) | 1301 | eventBus := mocks.NewMockEventPublisher(t) | ||
| @@ -1324,12 +1314,9 @@ func TestService_SyncContainers_RestartPolicyMigrationErrorIsNonFatal(t *testing | |||||
| 1324 | 1314 | ||||
| 1325 | runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{routeContainer}, nil).Once() | 1315 | runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{routeContainer}, nil).Once() | ||
| 1326 | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-1", domain.RestartPolicyAlways).Return(errors.New("update failed")).Once() | 1316 | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-1", domain.RestartPolicyAlways).Return(errors.New("update failed")).Once() | ||
| 1327 | - | err := svc.SyncContainers(ctx) | 1317 | + | err := svc.EnsureManagedContainerRestartPolicies(ctx) |
| 1328 | 1318 | ||||
| 1329 | require.NoError(t, err) | 1319 | require.NoError(t, err) | ||
| 1330 | - | tracked, exists := svc.Get(ctx, "app.example.com") | |||
| 1331 | - | require.True(t, exists) | |||
| 1332 | - | assert.Equal(t, "route-1", tracked.ID) | |||
| 1333 | } | 1320 | } | ||
| 1334 | 1321 | ||||
| 1335 | func TestService_SyncContainers(t *testing.T) { | 1322 | func TestService_SyncContainers(t *testing.T) { | ||
| @@ -1477,7 +1464,6 @@ func TestService_Restart_ReconcilesStaleContainerID(t *testing.T) { | |||||
| 1477 | }, | 1464 | }, | ||
| 1478 | }, | 1465 | }, | ||
| 1479 | }, nil).Once() | 1466 | }, nil).Once() | ||
| 1480 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "fresh-container-id", domain.RestartPolicyAlways).Return(nil).Once() | |||
| 1481 | runtime.EXPECT().RestartContainer(mock.Anything, "fresh-container-id").Return(nil).Once() | 1467 | runtime.EXPECT().RestartContainer(mock.Anything, "fresh-container-id").Return(nil).Once() | ||
| 1482 | 1468 | ||||
| 1483 | err := svc.Restart(ctx, "test.example.com", false) | 1469 | err := svc.Restart(ctx, "test.example.com", false) | ||
| @@ -3218,8 +3204,6 @@ func TestSyncContainersRebuildsAttachmentMap(t *testing.T) { | |||||
| 3218 | }, | 3204 | }, | ||
| 3219 | }, | 3205 | }, | ||
| 3220 | }, nil).Once() | 3206 | }, nil).Once() | ||
| 3221 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "main-container-1", domain.RestartPolicyAlways).Return(nil).Once() | |||
| 3222 | - | runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "attachment-container-1", domain.RestartPolicyAlways).Return(nil).Once() | |||
| 3223 | 3207 | ||||
| 3224 | require.NoError(t, svc.SyncContainers(ctx)) | 3208 | require.NoError(t, svc.SyncContainers(ctx)) | ||
| 3225 | 3209 | ||||