detached 95b3044

Remove dead restart policy migration path

commit
95b3044a727b00c25d6d6d323f93364050b03cfb
author
brice · · 2026-05-10 16:59 UTC
parent
53f9355
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
6 files · +3 -358
M internal/adapters/out/docker/runtime.go +0 -36
@@ -363,42 +363,6 @@ func (r *Runtime) RenameContainer(ctx context.Context, containerID, newName stri
363363 return nil
364364 }
365365
366-// EnsureContainerRestartPolicy updates a container restart policy when it does not match.
367-func (r *Runtime) EnsureContainerRestartPolicy(ctx context.Context, containerID, policy string) error {
368- ctx = zerowrap.CtxWithFields(ctx, map[string]any{
369- zerowrap.FieldLayer: "adapter",
370- zerowrap.FieldAdapter: "docker",
371- zerowrap.FieldAction: "EnsureContainerRestartPolicy",
372- zerowrap.FieldEntityID: containerID,
373- "restart_policy": policy,
374- })
375- log := zerowrap.FromCtx(ctx)
376-
377- inspect, err := r.client.ContainerInspect(ctx, containerID)
378- if err != nil {
379- return log.WrapErr(err, "failed to inspect container")
380- }
381- if inspect.HostConfig == nil {
382- return fmt.Errorf("container inspect response missing host config: %s", containerID)
383- }
384- if string(inspect.HostConfig.RestartPolicy.Name) == policy {
385- return nil
386- }
387-
388- updateConfig := container.UpdateConfig{
389- Resources: inspect.HostConfig.Resources,
390- RestartPolicy: container.RestartPolicy{Name: container.RestartPolicyMode(policy)},
391- }
392- resp, err := r.client.ContainerUpdate(ctx, containerID, updateConfig)
393- if err != nil {
394- return log.WrapErr(err, "failed to update container restart policy")
395- }
396- for _, warning := range resp.Warnings {
397- log.Warn().Str("warning", warning).Msg("container restart policy update warning")
398- }
399- return nil
400-}
401-
402366 // ListContainers lists containers.
403367 func (r *Runtime) ListContainers(ctx context.Context, all bool) ([]*domain.Container, error) {
404368 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
@@ -363,42 +363,6 @@ func (r *Runtime) RenameContainer(ctx context.Context, containerID, newName stri
363 return nil363 return nil
364 }364 }
365 365
366-// EnsureContainerRestartPolicy updates a container restart policy when it does not match.
367-func (r *Runtime) EnsureContainerRestartPolicy(ctx context.Context, containerID, policy string) error {
368- ctx = zerowrap.CtxWithFields(ctx, map[string]any{
369- zerowrap.FieldLayer: "adapter",
370- zerowrap.FieldAdapter: "docker",
371- zerowrap.FieldAction: "EnsureContainerRestartPolicy",
372- zerowrap.FieldEntityID: containerID,
373- "restart_policy": policy,
374- })
375- log := zerowrap.FromCtx(ctx)
376-
377- inspect, err := r.client.ContainerInspect(ctx, containerID)
378- if err != nil {
379- return log.WrapErr(err, "failed to inspect container")
380- }
381- if inspect.HostConfig == nil {
382- return fmt.Errorf("container inspect response missing host config: %s", containerID)
383- }
384- if string(inspect.HostConfig.RestartPolicy.Name) == policy {
385- return nil
386- }
387-
388- updateConfig := container.UpdateConfig{
389- Resources: inspect.HostConfig.Resources,
390- RestartPolicy: container.RestartPolicy{Name: container.RestartPolicyMode(policy)},
391- }
392- resp, err := r.client.ContainerUpdate(ctx, containerID, updateConfig)
393- if err != nil {
394- return log.WrapErr(err, "failed to update container restart policy")
395- }
396- for _, warning := range resp.Warnings {
397- log.Warn().Str("warning", warning).Msg("container restart policy update warning")
398- }
399- return nil
400-}
401-
402 // ListContainers lists containers.366 // ListContainers lists containers.
403 func (r *Runtime) ListContainers(ctx context.Context, all bool) ([]*domain.Container, error) {367 func (r *Runtime) ListContainers(ctx context.Context, all bool) ([]*domain.Container, error) {
404 ctx = zerowrap.CtxWithFields(ctx, map[string]any{368 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
D internal/adapters/out/docker/runtime_restart_policy_test.go +0 -129
@@ -1,129 +0,0 @@
1-package docker
2-
3-import (
4- "context"
5- "encoding/json"
6- "net/http"
7- "net/http/httptest"
8- "strings"
9- "testing"
10-
11- "github.com/docker/docker/client"
12- "github.com/stretchr/testify/assert"
13- "github.com/stretchr/testify/require"
14-
15- "github.com/bnema/gordon/internal/domain"
16-)
17-
18-func TestRuntime_EnsureContainerRestartPolicy_SkipsWhenAlreadySet(t *testing.T) {
19- var updateCalled bool
20- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
21- switch {
22- case r.Method == http.MethodGet && r.URL.Path == "/v1.41/containers/abc123/json":
23- w.Header().Set("Content-Type", "application/json")
24- _, _ = w.Write([]byte(`{
25- "Id": "abc123",
26- "HostConfig": {
27- "RestartPolicy": {
28- "Name": "always"
29- }
30- }
31- }`))
32- case r.Method == http.MethodPost && r.URL.Path == "/v1.41/containers/abc123/update":
33- updateCalled = true
34- w.WriteHeader(http.StatusInternalServerError)
35- default:
36- t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
37- }
38- }))
39- defer server.Close()
40-
41- runtime := newRuntimeForRestartPolicyHTTPServer(t, server)
42- err := runtime.EnsureContainerRestartPolicy(context.Background(), "abc123", domain.RestartPolicyAlways)
43- require.NoError(t, err)
44- assert.False(t, updateCalled, "update should not be called when restart policy is already set")
45-}
46-
47-func TestRuntime_EnsureContainerRestartPolicy_UpdatesAndPreservesResources(t *testing.T) {
48- var updateBody map[string]any
49- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
50- switch {
51- case r.Method == http.MethodGet && r.URL.Path == "/v1.41/containers/abc123/json":
52- w.Header().Set("Content-Type", "application/json")
53- _, _ = w.Write([]byte(`{
54- "Id": "abc123",
55- "HostConfig": {
56- "RestartPolicy": {
57- "Name": ""
58- },
59- "Memory": 268435456,
60- "NanoCpus": 500000000,
61- "PidsLimit": 128,
62- "Ulimits": [
63- {
64- "Name": "nofile",
65- "Soft": 65536,
66- "Hard": 65536
67- }
68- ]
69- }
70- }`))
71- case r.Method == http.MethodPost && r.URL.Path == "/v1.41/containers/abc123/update":
72- require.NoError(t, json.NewDecoder(r.Body).Decode(&updateBody))
73- w.Header().Set("Content-Type", "application/json")
74- _, _ = w.Write([]byte(`{"Warnings":null}`))
75- default:
76- t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
77- }
78- }))
79- defer server.Close()
80-
81- runtime := newRuntimeForRestartPolicyHTTPServer(t, server)
82- err := runtime.EnsureContainerRestartPolicy(context.Background(), "abc123", domain.RestartPolicyAlways)
83- require.NoError(t, err)
84- require.NotNil(t, updateBody)
85- assert.Equal(t, domain.RestartPolicyAlways, updateBody["RestartPolicy"].(map[string]any)["Name"])
86- assert.Equal(t, float64(268435456), updateBody["Memory"])
87- assert.Equal(t, float64(500000000), updateBody["NanoCpus"])
88- assert.Equal(t, float64(128), updateBody["PidsLimit"])
89- ulimits, ok := updateBody["Ulimits"].([]any)
90- require.True(t, ok, "Ulimits should be an array")
91- require.Greater(t, len(ulimits), 0)
92- firstUlimit, ok := ulimits[0].(map[string]any)
93- require.True(t, ok, "first Ulimit should be a map")
94- assert.Equal(t, "nofile", firstUlimit["Name"])
95- assert.Equal(t, float64(65536), firstUlimit["Soft"])
96- assert.Equal(t, float64(65536), firstUlimit["Hard"])
97-}
98-
99-func TestRuntime_EnsureContainerRestartPolicy_ReturnsErrorWhenHostConfigMissing(t *testing.T) {
100- var updateCalled bool
101- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
102- switch {
103- case r.Method == http.MethodGet && r.URL.Path == "/v1.41/containers/abc123/json":
104- w.Header().Set("Content-Type", "application/json")
105- _, _ = w.Write([]byte(`{
106- "Id": "abc123"
107- }`))
108- case r.Method == http.MethodPost && r.URL.Path == "/v1.41/containers/abc123/update":
109- updateCalled = true
110- w.WriteHeader(http.StatusInternalServerError)
111- default:
112- t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
113- }
114- }))
115- defer server.Close()
116-
117- runtime := newRuntimeForRestartPolicyHTTPServer(t, server)
118- err := runtime.EnsureContainerRestartPolicy(context.Background(), "abc123", domain.RestartPolicyAlways)
119- require.Error(t, err)
120- assert.Contains(t, err.Error(), "missing host config")
121- assert.False(t, updateCalled, "update should not be called when host config is missing")
122-}
123-
124-func newRuntimeForRestartPolicyHTTPServer(t *testing.T, server *httptest.Server) *Runtime {
125- host := strings.TrimPrefix(server.URL, "http://")
126- cli, err := client.NewClientWithOpts(client.WithHost("tcp://"+host), client.WithVersion("1.41"), client.WithHTTPClient(server.Client()))
127- require.NoError(t, err)
128- return NewRuntimeWithClient(cli)
129-}
@@ -1,129 +0,0 @@
1-package docker
2-
3-import (
4- "context"
5- "encoding/json"
6- "net/http"
7- "net/http/httptest"
8- "strings"
9- "testing"
10-
11- "github.com/docker/docker/client"
12- "github.com/stretchr/testify/assert"
13- "github.com/stretchr/testify/require"
14-
15- "github.com/bnema/gordon/internal/domain"
16-)
17-
18-func TestRuntime_EnsureContainerRestartPolicy_SkipsWhenAlreadySet(t *testing.T) {
19- var updateCalled bool
20- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
21- switch {
22- case r.Method == http.MethodGet && r.URL.Path == "/v1.41/containers/abc123/json":
23- w.Header().Set("Content-Type", "application/json")
24- _, _ = w.Write([]byte(`{
25- "Id": "abc123",
26- "HostConfig": {
27- "RestartPolicy": {
28- "Name": "always"
29- }
30- }
31- }`))
32- case r.Method == http.MethodPost && r.URL.Path == "/v1.41/containers/abc123/update":
33- updateCalled = true
34- w.WriteHeader(http.StatusInternalServerError)
35- default:
36- t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
37- }
38- }))
39- defer server.Close()
40-
41- runtime := newRuntimeForRestartPolicyHTTPServer(t, server)
42- err := runtime.EnsureContainerRestartPolicy(context.Background(), "abc123", domain.RestartPolicyAlways)
43- require.NoError(t, err)
44- assert.False(t, updateCalled, "update should not be called when restart policy is already set")
45-}
46-
47-func TestRuntime_EnsureContainerRestartPolicy_UpdatesAndPreservesResources(t *testing.T) {
48- var updateBody map[string]any
49- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
50- switch {
51- case r.Method == http.MethodGet && r.URL.Path == "/v1.41/containers/abc123/json":
52- w.Header().Set("Content-Type", "application/json")
53- _, _ = w.Write([]byte(`{
54- "Id": "abc123",
55- "HostConfig": {
56- "RestartPolicy": {
57- "Name": ""
58- },
59- "Memory": 268435456,
60- "NanoCpus": 500000000,
61- "PidsLimit": 128,
62- "Ulimits": [
63- {
64- "Name": "nofile",
65- "Soft": 65536,
66- "Hard": 65536
67- }
68- ]
69- }
70- }`))
71- case r.Method == http.MethodPost && r.URL.Path == "/v1.41/containers/abc123/update":
72- require.NoError(t, json.NewDecoder(r.Body).Decode(&updateBody))
73- w.Header().Set("Content-Type", "application/json")
74- _, _ = w.Write([]byte(`{"Warnings":null}`))
75- default:
76- t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
77- }
78- }))
79- defer server.Close()
80-
81- runtime := newRuntimeForRestartPolicyHTTPServer(t, server)
82- err := runtime.EnsureContainerRestartPolicy(context.Background(), "abc123", domain.RestartPolicyAlways)
83- require.NoError(t, err)
84- require.NotNil(t, updateBody)
85- assert.Equal(t, domain.RestartPolicyAlways, updateBody["RestartPolicy"].(map[string]any)["Name"])
86- assert.Equal(t, float64(268435456), updateBody["Memory"])
87- assert.Equal(t, float64(500000000), updateBody["NanoCpus"])
88- assert.Equal(t, float64(128), updateBody["PidsLimit"])
89- ulimits, ok := updateBody["Ulimits"].([]any)
90- require.True(t, ok, "Ulimits should be an array")
91- require.Greater(t, len(ulimits), 0)
92- firstUlimit, ok := ulimits[0].(map[string]any)
93- require.True(t, ok, "first Ulimit should be a map")
94- assert.Equal(t, "nofile", firstUlimit["Name"])
95- assert.Equal(t, float64(65536), firstUlimit["Soft"])
96- assert.Equal(t, float64(65536), firstUlimit["Hard"])
97-}
98-
99-func TestRuntime_EnsureContainerRestartPolicy_ReturnsErrorWhenHostConfigMissing(t *testing.T) {
100- var updateCalled bool
101- server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
102- switch {
103- case r.Method == http.MethodGet && r.URL.Path == "/v1.41/containers/abc123/json":
104- w.Header().Set("Content-Type", "application/json")
105- _, _ = w.Write([]byte(`{
106- "Id": "abc123"
107- }`))
108- case r.Method == http.MethodPost && r.URL.Path == "/v1.41/containers/abc123/update":
109- updateCalled = true
110- w.WriteHeader(http.StatusInternalServerError)
111- default:
112- t.Fatalf("unexpected request: %s %s", r.Method, r.URL.Path)
113- }
114- }))
115- defer server.Close()
116-
117- runtime := newRuntimeForRestartPolicyHTTPServer(t, server)
118- err := runtime.EnsureContainerRestartPolicy(context.Background(), "abc123", domain.RestartPolicyAlways)
119- require.Error(t, err)
120- assert.Contains(t, err.Error(), "missing host config")
121- assert.False(t, updateCalled, "update should not be called when host config is missing")
122-}
123-
124-func newRuntimeForRestartPolicyHTTPServer(t *testing.T, server *httptest.Server) *Runtime {
125- host := strings.TrimPrefix(server.URL, "http://")
126- cli, err := client.NewClientWithOpts(client.WithHost("tcp://"+host), client.WithVersion("1.41"), client.WithHTTPClient(server.Client()))
127- require.NoError(t, err)
128- return NewRuntimeWithClient(cli)
129-}
M internal/boundaries/out/mocks/mock_container_runtime.go +0 -63
@@ -428,69 +428,6 @@ func (_c *MockContainerRuntime_DisconnectContainerFromNetwork_Call) RunAndReturn
428428 return _c
429429 }
430430
431-// EnsureContainerRestartPolicy provides a mock function for the type MockContainerRuntime
432-func (_mock *MockContainerRuntime) EnsureContainerRestartPolicy(ctx context.Context, containerID string, policy string) error {
433- ret := _mock.Called(ctx, containerID, policy)
434-
435- if len(ret) == 0 {
436- panic("no return value specified for EnsureContainerRestartPolicy")
437- }
438-
439- var r0 error
440- if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
441- r0 = returnFunc(ctx, containerID, policy)
442- } else {
443- r0 = ret.Error(0)
444- }
445- return r0
446-}
447-
448-// MockContainerRuntime_EnsureContainerRestartPolicy_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EnsureContainerRestartPolicy'
449-type MockContainerRuntime_EnsureContainerRestartPolicy_Call struct {
450- *mock.Call
451-}
452-
453-// EnsureContainerRestartPolicy is a helper method to define mock.On call
454-// - ctx context.Context
455-// - containerID string
456-// - policy string
457-func (_e *MockContainerRuntime_Expecter) EnsureContainerRestartPolicy(ctx interface{}, containerID interface{}, policy interface{}) *MockContainerRuntime_EnsureContainerRestartPolicy_Call {
458- return &MockContainerRuntime_EnsureContainerRestartPolicy_Call{Call: _e.mock.On("EnsureContainerRestartPolicy", ctx, containerID, policy)}
459-}
460-
461-func (_c *MockContainerRuntime_EnsureContainerRestartPolicy_Call) Run(run func(ctx context.Context, containerID string, policy string)) *MockContainerRuntime_EnsureContainerRestartPolicy_Call {
462- _c.Call.Run(func(args mock.Arguments) {
463- var arg0 context.Context
464- if args[0] != nil {
465- arg0 = args[0].(context.Context)
466- }
467- var arg1 string
468- if args[1] != nil {
469- arg1 = args[1].(string)
470- }
471- var arg2 string
472- if args[2] != nil {
473- arg2 = args[2].(string)
474- }
475- run(
476- arg0,
477- arg1,
478- arg2,
479- )
480- })
481- return _c
482-}
483-
484-func (_c *MockContainerRuntime_EnsureContainerRestartPolicy_Call) Return(err error) *MockContainerRuntime_EnsureContainerRestartPolicy_Call {
485- _c.Call.Return(err)
486- return _c
487-}
488-
489-func (_c *MockContainerRuntime_EnsureContainerRestartPolicy_Call) RunAndReturn(run func(ctx context.Context, containerID string, policy string) error) *MockContainerRuntime_EnsureContainerRestartPolicy_Call {
490- _c.Call.Return(run)
491- return _c
492-}
493-
494431 // ExecInContainer provides a mock function for the type MockContainerRuntime
495432 func (_mock *MockContainerRuntime) ExecInContainer(ctx context.Context, containerID string, cmd []string) (*out.ExecResult, error) {
496433 ret := _mock.Called(ctx, containerID, cmd)
@@ -428,69 +428,6 @@ func (_c *MockContainerRuntime_DisconnectContainerFromNetwork_Call) RunAndReturn
428 return _c428 return _c
429 }429 }
430 430
431-// EnsureContainerRestartPolicy provides a mock function for the type MockContainerRuntime
432-func (_mock *MockContainerRuntime) EnsureContainerRestartPolicy(ctx context.Context, containerID string, policy string) error {
433- ret := _mock.Called(ctx, containerID, policy)
434-
435- if len(ret) == 0 {
436- panic("no return value specified for EnsureContainerRestartPolicy")
437- }
438-
439- var r0 error
440- if returnFunc, ok := ret.Get(0).(func(context.Context, string, string) error); ok {
441- r0 = returnFunc(ctx, containerID, policy)
442- } else {
443- r0 = ret.Error(0)
444- }
445- return r0
446-}
447-
448-// MockContainerRuntime_EnsureContainerRestartPolicy_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'EnsureContainerRestartPolicy'
449-type MockContainerRuntime_EnsureContainerRestartPolicy_Call struct {
450- *mock.Call
451-}
452-
453-// EnsureContainerRestartPolicy is a helper method to define mock.On call
454-// - ctx context.Context
455-// - containerID string
456-// - policy string
457-func (_e *MockContainerRuntime_Expecter) EnsureContainerRestartPolicy(ctx interface{}, containerID interface{}, policy interface{}) *MockContainerRuntime_EnsureContainerRestartPolicy_Call {
458- return &MockContainerRuntime_EnsureContainerRestartPolicy_Call{Call: _e.mock.On("EnsureContainerRestartPolicy", ctx, containerID, policy)}
459-}
460-
461-func (_c *MockContainerRuntime_EnsureContainerRestartPolicy_Call) Run(run func(ctx context.Context, containerID string, policy string)) *MockContainerRuntime_EnsureContainerRestartPolicy_Call {
462- _c.Call.Run(func(args mock.Arguments) {
463- var arg0 context.Context
464- if args[0] != nil {
465- arg0 = args[0].(context.Context)
466- }
467- var arg1 string
468- if args[1] != nil {
469- arg1 = args[1].(string)
470- }
471- var arg2 string
472- if args[2] != nil {
473- arg2 = args[2].(string)
474- }
475- run(
476- arg0,
477- arg1,
478- arg2,
479- )
480- })
481- return _c
482-}
483-
484-func (_c *MockContainerRuntime_EnsureContainerRestartPolicy_Call) Return(err error) *MockContainerRuntime_EnsureContainerRestartPolicy_Call {
485- _c.Call.Return(err)
486- return _c
487-}
488-
489-func (_c *MockContainerRuntime_EnsureContainerRestartPolicy_Call) RunAndReturn(run func(ctx context.Context, containerID string, policy string) error) *MockContainerRuntime_EnsureContainerRestartPolicy_Call {
490- _c.Call.Return(run)
491- return _c
492-}
493-
494 // ExecInContainer provides a mock function for the type MockContainerRuntime431 // ExecInContainer provides a mock function for the type MockContainerRuntime
495 func (_mock *MockContainerRuntime) ExecInContainer(ctx context.Context, containerID string, cmd []string) (*out.ExecResult, error) {432 func (_mock *MockContainerRuntime) ExecInContainer(ctx context.Context, containerID string, cmd []string) (*out.ExecResult, error) {
496 ret := _mock.Called(ctx, containerID, cmd)433 ret := _mock.Called(ctx, containerID, cmd)
M internal/boundaries/out/runtime.go +0 -1
@@ -20,7 +20,6 @@ type ContainerRuntime interface {
2020 RestartContainer(ctx context.Context, containerID string) error
2121 RemoveContainer(ctx context.Context, containerID string, force bool) error
2222 RenameContainer(ctx context.Context, containerID, newName string) error
23- EnsureContainerRestartPolicy(ctx context.Context, containerID, policy string) error
2423
2524 // Container inspection
2625 ListContainers(ctx context.Context, all bool) ([]*domain.Container, error)
@@ -20,7 +20,6 @@ type ContainerRuntime interface {
20 RestartContainer(ctx context.Context, containerID string) error20 RestartContainer(ctx context.Context, containerID string) error
21 RemoveContainer(ctx context.Context, containerID string, force bool) error21 RemoveContainer(ctx context.Context, containerID string, force bool) error
22 RenameContainer(ctx context.Context, containerID, newName string) error22 RenameContainer(ctx context.Context, containerID, newName string) error
23- EnsureContainerRestartPolicy(ctx context.Context, containerID, policy string) error
24 23
25 // Container inspection24 // Container inspection
26 ListContainers(ctx context.Context, all bool) ([]*domain.Container, error)25 ListContainers(ctx context.Context, all bool) ([]*domain.Container, error)
M internal/usecase/container/service.go +3 -38
@@ -1401,41 +1401,6 @@ func (s *Service) HealthCheck(ctx context.Context) map[string]bool {
14011401 return health
14021402 }
14031403
1404-// EnsureManagedContainerRestartPolicies updates restart policy for all managed containers.
1405-func (s *Service) EnsureManagedContainerRestartPolicies(ctx context.Context) error {
1406- ctx = zerowrap.CtxWithFields(ctx, map[string]any{
1407- zerowrap.FieldLayer: "usecase",
1408- zerowrap.FieldUseCase: "EnsureManagedContainerRestartPolicies",
1409- })
1410- log := zerowrap.FromCtx(ctx)
1411-
1412- allContainers, err := s.runtime.ListContainers(ctx, true)
1413- if err != nil {
1414- return log.WrapErr(err, "failed to list containers for restart policy migration")
1415- }
1416-
1417- return s.ensureManagedContainerRestartPolicies(ctx, allContainers)
1418-}
1419-
1420-func (s *Service) ensureManagedContainerRestartPolicies(ctx context.Context, allContainers []*domain.Container) error {
1421- log := zerowrap.FromCtx(ctx)
1422-
1423- var errs []error
1424- for _, c := range allContainers {
1425- if c.Labels == nil || c.Labels[domain.LabelManaged] != "true" {
1426- continue
1427- }
1428- if err := s.runtime.EnsureContainerRestartPolicy(ctx, c.ID, domain.RestartPolicyAlways); err != nil {
1429- log.Warn().Err(err).
1430- Str(zerowrap.FieldEntityID, c.ID).
1431- Str("container_name", c.Name).
1432- Msg("failed to ensure managed container restart policy")
1433- errs = append(errs, fmt.Errorf("container %s: %w", c.ID, err))
1434- }
1435- }
1436- return errors.Join(errs...)
1437-}
1438-
14391404 // SyncContainers synchronizes containers with runtime state.
14401405 func (s *Service) SyncContainers(ctx context.Context) error {
14411406 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
@@ -1444,9 +1409,9 @@ func (s *Service) SyncContainers(ctx context.Context) error {
14441409 })
14451410 log := zerowrap.FromCtx(ctx)
14461411
1447- // List containers without holding lock to avoid blocking during Docker API call.
1448- // Use all=true so the same runtime snapshot can migrate stopped containers and
1449- // rebuild the running-container maps without a second Docker list call.
1412+ // List containers without holding lock to avoid blocking during the runtime call.
1413+ // Use all=true so we can rebuild the running-container maps from a single
1414+ // snapshot without a second list call.
14501415 allContainers, err := s.runtime.ListContainers(ctx, true)
14511416 if err != nil {
14521417 return log.WrapErr(err, "failed to list containers")
@@ -1401,41 +1401,6 @@ func (s *Service) HealthCheck(ctx context.Context) map[string]bool {
1401 return health1401 return health
1402 }1402 }
1403 1403
1404-// EnsureManagedContainerRestartPolicies updates restart policy for all managed containers.
1405-func (s *Service) EnsureManagedContainerRestartPolicies(ctx context.Context) error {
1406- ctx = zerowrap.CtxWithFields(ctx, map[string]any{
1407- zerowrap.FieldLayer: "usecase",
1408- zerowrap.FieldUseCase: "EnsureManagedContainerRestartPolicies",
1409- })
1410- log := zerowrap.FromCtx(ctx)
1411-
1412- allContainers, err := s.runtime.ListContainers(ctx, true)
1413- if err != nil {
1414- return log.WrapErr(err, "failed to list containers for restart policy migration")
1415- }
1416-
1417- return s.ensureManagedContainerRestartPolicies(ctx, allContainers)
1418-}
1419-
1420-func (s *Service) ensureManagedContainerRestartPolicies(ctx context.Context, allContainers []*domain.Container) error {
1421- log := zerowrap.FromCtx(ctx)
1422-
1423- var errs []error
1424- for _, c := range allContainers {
1425- if c.Labels == nil || c.Labels[domain.LabelManaged] != "true" {
1426- continue
1427- }
1428- if err := s.runtime.EnsureContainerRestartPolicy(ctx, c.ID, domain.RestartPolicyAlways); err != nil {
1429- log.Warn().Err(err).
1430- Str(zerowrap.FieldEntityID, c.ID).
1431- Str("container_name", c.Name).
1432- Msg("failed to ensure managed container restart policy")
1433- errs = append(errs, fmt.Errorf("container %s: %w", c.ID, err))
1434- }
1435- }
1436- return errors.Join(errs...)
1437-}
1438-
1439 // SyncContainers synchronizes containers with runtime state.1404 // SyncContainers synchronizes containers with runtime state.
1440 func (s *Service) SyncContainers(ctx context.Context) error {1405 func (s *Service) SyncContainers(ctx context.Context) error {
1441 ctx = zerowrap.CtxWithFields(ctx, map[string]any{1406 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
@@ -1444,9 +1409,9 @@ func (s *Service) SyncContainers(ctx context.Context) error {
1444 })1409 })
1445 log := zerowrap.FromCtx(ctx)1410 log := zerowrap.FromCtx(ctx)
1446 1411
1447- // List containers without holding lock to avoid blocking during Docker API call.1412+ // List containers without holding lock to avoid blocking during the runtime call.
1448- // Use all=true so the same runtime snapshot can migrate stopped containers and1413+ // Use all=true so we can rebuild the running-container maps from a single
1449- // rebuild the running-container maps without a second Docker list call.1414+ // snapshot without a second list call.
1450 allContainers, err := s.runtime.ListContainers(ctx, true)1415 allContainers, err := s.runtime.ListContainers(ctx, true)
1451 if err != nil {1416 if err != nil {
1452 return log.WrapErr(err, "failed to list containers")1417 return log.WrapErr(err, "failed to list containers")
M internal/usecase/container/service_test.go +0 -91
@@ -1237,97 +1237,6 @@ func TestService_HealthCheck(t *testing.T) {
12371237 assert.False(t, result["unhealthy.example.com"])
12381238 }
12391239
1240-func TestService_EnsureManagedContainerRestartPolicies_EnsuresRoutesAttachmentsAndStoppedContainers(t *testing.T) {
1241- runtime := mocks.NewMockContainerRuntime(t)
1242- envLoader := mocks.NewMockEnvLoader(t)
1243- eventBus := mocks.NewMockEventPublisher(t)
1244-
1245- svc := NewService(runtime, envLoader, eventBus, nil, Config{}, nil)
1246- ctx := testContext()
1247-
1248- routeContainer := &domain.Container{
1249- ID: "route-1",
1250- Name: "gordon-app.example.com",
1251- Status: string(domain.ContainerStatusRunning),
1252- Labels: map[string]string{
1253- domain.LabelManaged: "true",
1254- domain.LabelDomain: "app.example.com",
1255- },
1256- }
1257- attachmentContainer := &domain.Container{
1258- ID: "attachment-1",
1259- Name: "gordon-app-example-com-postgres",
1260- Status: string(domain.ContainerStatusRunning),
1261- Labels: map[string]string{
1262- domain.LabelManaged: "true",
1263- domain.LabelAttachment: "true",
1264- domain.LabelAttachedTo: "app.example.com",
1265- },
1266- }
1267- stoppedContainer := &domain.Container{
1268- ID: "stopped-1",
1269- Name: "gordon-stopped.example.com",
1270- Status: string(domain.ContainerStatusExited),
1271- Labels: map[string]string{
1272- domain.LabelManaged: "true",
1273- domain.LabelDomain: "stopped.example.com",
1274- },
1275- }
1276- unmanagedContainer := &domain.Container{
1277- ID: "other-1",
1278- Labels: map[string]string{},
1279- }
1280-
1281- runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{
1282- routeContainer,
1283- attachmentContainer,
1284- stoppedContainer,
1285- unmanagedContainer,
1286- }, nil).Once()
1287- runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-1", domain.RestartPolicyAlways).Return(nil).Once()
1288- runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "attachment-1", domain.RestartPolicyAlways).Return(nil).Once()
1289- runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "stopped-1", domain.RestartPolicyAlways).Return(nil).Once()
1290- err := svc.EnsureManagedContainerRestartPolicies(ctx)
1291-
1292- require.NoError(t, err)
1293- runtime.AssertNumberOfCalls(t, "EnsureContainerRestartPolicy", 3)
1294-}
1295-
1296-func TestService_EnsureManagedContainerRestartPolicies_ContinuesAndReturnsError(t *testing.T) {
1297- runtime := mocks.NewMockContainerRuntime(t)
1298- envLoader := mocks.NewMockEnvLoader(t)
1299- eventBus := mocks.NewMockEventPublisher(t)
1300-
1301- svc := NewService(runtime, envLoader, eventBus, nil, Config{}, nil)
1302- ctx := testContext()
1303-
1304- routeContainer := &domain.Container{
1305- ID: "route-1",
1306- Name: "gordon-app.example.com",
1307- Labels: map[string]string{
1308- domain.LabelManaged: "true",
1309- domain.LabelDomain: "app.example.com",
1310- },
1311- }
1312- routeContainer2 := &domain.Container{
1313- ID: "route-2",
1314- Name: "gordon-api.example.com",
1315- Labels: map[string]string{
1316- domain.LabelManaged: "true",
1317- domain.LabelDomain: "api.example.com",
1318- },
1319- }
1320-
1321- runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{routeContainer, routeContainer2}, nil).Once()
1322- runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-1", domain.RestartPolicyAlways).Return(errors.New("update failed")).Once()
1323- runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-2", domain.RestartPolicyAlways).Return(nil).Once()
1324- err := svc.EnsureManagedContainerRestartPolicies(ctx)
1325-
1326- require.Error(t, err)
1327- assert.Contains(t, err.Error(), "route-1")
1328- assert.Contains(t, err.Error(), "update failed")
1329-}
1330-
13311240 func TestService_SyncContainers(t *testing.T) {
13321241 runtime := mocks.NewMockContainerRuntime(t)
13331242 envLoader := mocks.NewMockEnvLoader(t)
@@ -1237,97 +1237,6 @@ func TestService_HealthCheck(t *testing.T) {
1237 assert.False(t, result["unhealthy.example.com"])1237 assert.False(t, result["unhealthy.example.com"])
1238 }1238 }
1239 1239
1240-func TestService_EnsureManagedContainerRestartPolicies_EnsuresRoutesAttachmentsAndStoppedContainers(t *testing.T) {
1241- runtime := mocks.NewMockContainerRuntime(t)
1242- envLoader := mocks.NewMockEnvLoader(t)
1243- eventBus := mocks.NewMockEventPublisher(t)
1244-
1245- svc := NewService(runtime, envLoader, eventBus, nil, Config{}, nil)
1246- ctx := testContext()
1247-
1248- routeContainer := &domain.Container{
1249- ID: "route-1",
1250- Name: "gordon-app.example.com",
1251- Status: string(domain.ContainerStatusRunning),
1252- Labels: map[string]string{
1253- domain.LabelManaged: "true",
1254- domain.LabelDomain: "app.example.com",
1255- },
1256- }
1257- attachmentContainer := &domain.Container{
1258- ID: "attachment-1",
1259- Name: "gordon-app-example-com-postgres",
1260- Status: string(domain.ContainerStatusRunning),
1261- Labels: map[string]string{
1262- domain.LabelManaged: "true",
1263- domain.LabelAttachment: "true",
1264- domain.LabelAttachedTo: "app.example.com",
1265- },
1266- }
1267- stoppedContainer := &domain.Container{
1268- ID: "stopped-1",
1269- Name: "gordon-stopped.example.com",
1270- Status: string(domain.ContainerStatusExited),
1271- Labels: map[string]string{
1272- domain.LabelManaged: "true",
1273- domain.LabelDomain: "stopped.example.com",
1274- },
1275- }
1276- unmanagedContainer := &domain.Container{
1277- ID: "other-1",
1278- Labels: map[string]string{},
1279- }
1280-
1281- runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{
1282- routeContainer,
1283- attachmentContainer,
1284- stoppedContainer,
1285- unmanagedContainer,
1286- }, nil).Once()
1287- runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-1", domain.RestartPolicyAlways).Return(nil).Once()
1288- runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "attachment-1", domain.RestartPolicyAlways).Return(nil).Once()
1289- runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "stopped-1", domain.RestartPolicyAlways).Return(nil).Once()
1290- err := svc.EnsureManagedContainerRestartPolicies(ctx)
1291-
1292- require.NoError(t, err)
1293- runtime.AssertNumberOfCalls(t, "EnsureContainerRestartPolicy", 3)
1294-}
1295-
1296-func TestService_EnsureManagedContainerRestartPolicies_ContinuesAndReturnsError(t *testing.T) {
1297- runtime := mocks.NewMockContainerRuntime(t)
1298- envLoader := mocks.NewMockEnvLoader(t)
1299- eventBus := mocks.NewMockEventPublisher(t)
1300-
1301- svc := NewService(runtime, envLoader, eventBus, nil, Config{}, nil)
1302- ctx := testContext()
1303-
1304- routeContainer := &domain.Container{
1305- ID: "route-1",
1306- Name: "gordon-app.example.com",
1307- Labels: map[string]string{
1308- domain.LabelManaged: "true",
1309- domain.LabelDomain: "app.example.com",
1310- },
1311- }
1312- routeContainer2 := &domain.Container{
1313- ID: "route-2",
1314- Name: "gordon-api.example.com",
1315- Labels: map[string]string{
1316- domain.LabelManaged: "true",
1317- domain.LabelDomain: "api.example.com",
1318- },
1319- }
1320-
1321- runtime.EXPECT().ListContainers(mock.Anything, true).Return([]*domain.Container{routeContainer, routeContainer2}, nil).Once()
1322- runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-1", domain.RestartPolicyAlways).Return(errors.New("update failed")).Once()
1323- runtime.EXPECT().EnsureContainerRestartPolicy(mock.Anything, "route-2", domain.RestartPolicyAlways).Return(nil).Once()
1324- err := svc.EnsureManagedContainerRestartPolicies(ctx)
1325-
1326- require.Error(t, err)
1327- assert.Contains(t, err.Error(), "route-1")
1328- assert.Contains(t, err.Error(), "update failed")
1329-}
1330-
1331 func TestService_SyncContainers(t *testing.T) {1240 func TestService_SyncContainers(t *testing.T) {
1332 runtime := mocks.NewMockContainerRuntime(t)1241 runtime := mocks.NewMockContainerRuntime(t)
1333 envLoader := mocks.NewMockEnvLoader(t)1242 envLoader := mocks.NewMockEnvLoader(t)

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