detached 86ffd72

Move restart policy migration to startup

commit
86ffd722c6b18d578daeb376570f247d43fe8c8a
author
brice · · 2026-05-05 17:20 UTC
parent
edf3939
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · 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
20152015
20162016 // syncAndAutoStart syncs existing containers and auto-starts if configured.
20172017 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+
20182022 if err := svc.containerSvc.SyncContainers(ctx); err != nil {
20192023 log.Warn().Err(err).Msg("failed to sync existing containers")
20202024 }
@@ -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 {
14051405 return health
14061406 }
14071407
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+
14081425 func (s *Service) ensureManagedContainerRestartPolicies(ctx context.Context, allContainers []*domain.Container) {
14091426 log := zerowrap.FromCtx(ctx)
14101427
@@ -1437,8 +1454,6 @@ func (s *Service) SyncContainers(ctx context.Context) error {
14371454 return log.WrapErr(err, "failed to list containers")
14381455 }
14391456
1440- s.ensureManagedContainerRestartPolicies(ctx, allContainers)
1441-
14421457 managed := make(map[string]*domain.Container)
14431458 attachments := make(map[string][]string)
14441459 for _, c := range allContainers {
@@ -1405,6 +1405,23 @@ func (s *Service) HealthCheck(ctx context.Context) map[string]bool {
1405 return health1405 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) {
12391239 assert.False(t, result["unhealthy.example.com"])
12401240 }
12411241
1242-func TestService_SyncContainers_EnsuresRestartPolicyForManagedRoutesAndAttachments(t *testing.T) {
1242+func TestService_EnsureManagedContainerRestartPolicies_EnsuresRoutesAttachmentsAndStoppedContainers(t *testing.T) {
12431243 runtime := mocks.NewMockContainerRuntime(t)
12441244 envLoader := mocks.NewMockEnvLoader(t)
12451245 eventBus := mocks.NewMockEventPublisher(t)
@@ -1289,23 +1289,13 @@ func TestService_SyncContainers_EnsuresRestartPolicyForManagedRoutesAndAttachmen
12891289 runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-1", domain.RestartPolicyAlways).Return(nil).Once()
12901290 runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "attachment-1", domain.RestartPolicyAlways).Return(nil).Once()
12911291 runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "stopped-1", domain.RestartPolicyAlways).Return(nil).Once()
1292- err := svc.SyncContainers(ctx)
1292+ err := svc.EnsureManagedContainerRestartPolicies(ctx)
12931293
12941294 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")
13051295 runtime.AssertNumberOfCalls(t, "EnsureContainerRestartPolicy", 3)
13061296 }
13071297
1308-func TestService_SyncContainers_RestartPolicyMigrationErrorIsNonFatal(t *testing.T) {
1298+func TestService_EnsureManagedContainerRestartPolicies_ErrorIsNonFatal(t *testing.T) {
13091299 runtime := mocks.NewMockContainerRuntime(t)
13101300 envLoader := mocks.NewMockEnvLoader(t)
13111301 eventBus := mocks.NewMockEventPublisher(t)
@@ -1324,12 +1314,9 @@ func TestService_SyncContainers_RestartPolicyMigrationErrorIsNonFatal(t *testing
13241314
13251315 runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{routeContainer}, nil).Once()
13261316 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)
13281318
13291319 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)
13331320 }
13341321
13351322 func TestService_SyncContainers(t *testing.T) {
@@ -1477,7 +1464,6 @@ func TestService_Restart_ReconcilesStaleContainerID(t *testing.T) {
14771464 },
14781465 },
14791466 }, nil).Once()
1480- runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "fresh-container-id", domain.RestartPolicyAlways).Return(nil).Once()
14811467 runtime.EXPECT().RestartContainer(mock.Anything, "fresh-container-id").Return(nil).Once()
14821468
14831469 err := svc.Restart(ctx, "test.example.com", false)
@@ -3218,8 +3204,6 @@ func TestSyncContainersRebuildsAttachmentMap(t *testing.T) {
32183204 },
32193205 },
32203206 }, 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()
32233207
32243208 require.NoError(t, svc.SyncContainers(ctx))
32253209
@@ -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

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close