detached e150f7b

refactor: split deploy coordination from registry boundary

commit
e150f7b9a7c597f6647e4a2f56ad515c163c7bde
author
brice · · 2026-03-04 13:05 UTC
parent
b89ced4
signature
unsigned
5 files · +140 -9
M internal/adapters/in/cli/controlplane_local.go +13 -3
@@ -18,6 +18,7 @@ type localControlPlane struct {
1818 containerSvc in.ContainerService
1919 backupSvc in.BackupService
2020 registrySvc in.RegistryService
21+ deployCoord in.DeployCoordinator
2122 healthSvc in.HealthService
2223 logSvc in.LogService
2324 }
@@ -27,12 +28,21 @@ func NewLocalControlPlane(kernel *app.Kernel) ControlPlane {
2728 return &localControlPlane{}
2829 }
2930
31+ registrySvc := kernel.Registry()
32+ var deployCoord in.DeployCoordinator
33+ if registrySvc != nil {
34+ if coordinator, ok := any(registrySvc).(in.DeployCoordinator); ok {
35+ deployCoord = coordinator
36+ }
37+ }
38+
3039 return &localControlPlane{
3140 configSvc: kernel.Config(),
3241 secretSvc: kernel.Secrets(),
3342 containerSvc: kernel.Container(),
3443 backupSvc: kernel.Backup(),
35- registrySvc: kernel.Registry(),
44+ registrySvc: registrySvc,
45+ deployCoord: deployCoord,
3646 healthSvc: kernel.Health(),
3747 logSvc: kernel.Logs(),
3848 }
@@ -224,8 +234,8 @@ func (l *localControlPlane) Reload(_ context.Context) error {
224234 }
225235
226236 func (l *localControlPlane) DeployIntent(_ context.Context, imageName string) error {
227- if l.registrySvc != nil {
228- l.registrySvc.SuppressDeployEvent(imageName)
237+ if l.deployCoord != nil {
238+ l.deployCoord.SuppressDeployEvent(imageName)
229239 }
230240 return nil
231241 }
@@ -18,6 +18,7 @@ type localControlPlane struct {
18 containerSvc in.ContainerService18 containerSvc in.ContainerService
19 backupSvc in.BackupService19 backupSvc in.BackupService
20 registrySvc in.RegistryService20 registrySvc in.RegistryService
21+ deployCoord in.DeployCoordinator
21 healthSvc in.HealthService22 healthSvc in.HealthService
22 logSvc in.LogService23 logSvc in.LogService
23 }24 }
@@ -27,12 +28,21 @@ func NewLocalControlPlane(kernel *app.Kernel) ControlPlane {
27 return &localControlPlane{}28 return &localControlPlane{}
28 }29 }
29 30
31+ registrySvc := kernel.Registry()
32+ var deployCoord in.DeployCoordinator
33+ if registrySvc != nil {
34+ if coordinator, ok := any(registrySvc).(in.DeployCoordinator); ok {
35+ deployCoord = coordinator
36+ }
37+ }
38+
30 return &localControlPlane{39 return &localControlPlane{
31 configSvc: kernel.Config(),40 configSvc: kernel.Config(),
32 secretSvc: kernel.Secrets(),41 secretSvc: kernel.Secrets(),
33 containerSvc: kernel.Container(),42 containerSvc: kernel.Container(),
34 backupSvc: kernel.Backup(),43 backupSvc: kernel.Backup(),
35- registrySvc: kernel.Registry(),44+ registrySvc: registrySvc,
45+ deployCoord: deployCoord,
36 healthSvc: kernel.Health(),46 healthSvc: kernel.Health(),
37 logSvc: kernel.Logs(),47 logSvc: kernel.Logs(),
38 }48 }
@@ -224,8 +234,8 @@ func (l *localControlPlane) Reload(_ context.Context) error {
224 }234 }
225 235
226 func (l *localControlPlane) DeployIntent(_ context.Context, imageName string) error {236 func (l *localControlPlane) DeployIntent(_ context.Context, imageName string) error {
227- if l.registrySvc != nil {237+ if l.deployCoord != nil {
228- l.registrySvc.SuppressDeployEvent(imageName)238+ l.deployCoord.SuppressDeployEvent(imageName)
229 }239 }
230 return nil240 return nil
231 }241 }
M internal/adapters/in/http/admin/handler.go +7 -2
@@ -28,6 +28,11 @@ const maxAdminRequestSize = 1 << 20 // 1MB
2828 // maxLogLines is the maximum allowed number of log lines that can be requested.
2929 const maxLogLines = 10000
3030
31+type registryDeployService interface {
32+ in.RegistryService
33+ in.DeployCoordinator
34+}
35+
3136 // Handler implements the HTTP handler for the admin API.
3237 type Handler struct {
3338 configSvc in.ConfigService
@@ -38,7 +43,7 @@ type Handler struct {
3843 healthSvc in.HealthService
3944 secretSvc in.SecretService
4045 logSvc in.LogService
41- registrySvc in.RegistryService
46+ registrySvc registryDeployService
4247 eventBus out.EventPublisher
4348 log zerowrap.Logger
4449 }
@@ -130,7 +135,7 @@ func NewHandler(
130135 healthSvc in.HealthService,
131136 secretSvc in.SecretService,
132137 logSvc in.LogService,
133- registrySvc in.RegistryService,
138+ registrySvc registryDeployService,
134139 eventBus out.EventPublisher,
135140 log zerowrap.Logger,
136141 backupSvc in.BackupService,
@@ -28,6 +28,11 @@ const maxAdminRequestSize = 1 << 20 // 1MB
28 // maxLogLines is the maximum allowed number of log lines that can be requested.28 // maxLogLines is the maximum allowed number of log lines that can be requested.
29 const maxLogLines = 1000029 const maxLogLines = 10000
30 30
31+type registryDeployService interface {
32+ in.RegistryService
33+ in.DeployCoordinator
34+}
35+
31 // Handler implements the HTTP handler for the admin API.36 // Handler implements the HTTP handler for the admin API.
32 type Handler struct {37 type Handler struct {
33 configSvc in.ConfigService38 configSvc in.ConfigService
@@ -38,7 +43,7 @@ type Handler struct {
38 healthSvc in.HealthService43 healthSvc in.HealthService
39 secretSvc in.SecretService44 secretSvc in.SecretService
40 logSvc in.LogService45 logSvc in.LogService
41- registrySvc in.RegistryService46+ registrySvc registryDeployService
42 eventBus out.EventPublisher47 eventBus out.EventPublisher
43 log zerowrap.Logger48 log zerowrap.Logger
44 }49 }
@@ -130,7 +135,7 @@ func NewHandler(
130 healthSvc in.HealthService,135 healthSvc in.HealthService,
131 secretSvc in.SecretService,136 secretSvc in.SecretService,
132 logSvc in.LogService,137 logSvc in.LogService,
133- registrySvc in.RegistryService,138+ registrySvc registryDeployService,
134 eventBus out.EventPublisher,139 eventBus out.EventPublisher,
135 log zerowrap.Logger,140 log zerowrap.Logger,
136 backupSvc in.BackupService,141 backupSvc in.BackupService,
A internal/boundaries/in/deploy.go +10 -0
@@ -0,0 +1,10 @@
1+package in
2+
3+// DeployCoordinator defines deploy coordination operations.
4+//
5+// These methods let the CLI/admin API suppress image-pushed deploy events
6+// while an explicit deploy flow is in progress.
7+type DeployCoordinator interface {
8+ SuppressDeployEvent(imageName string)
9+ ClearDeployEventSuppression(imageName string)
10+}
@@ -0,0 +1,10 @@
1+package in
2+
3+// DeployCoordinator defines deploy coordination operations.
4+//
5+// These methods let the CLI/admin API suppress image-pushed deploy events
6+// while an explicit deploy flow is in progress.
7+type DeployCoordinator interface {
8+ SuppressDeployEvent(imageName string)
9+ ClearDeployEventSuppression(imageName string)
10+}
A internal/boundaries/in/mocks/mock_deploy_coordinator.go +110 -0
@@ -0,0 +1,110 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ mock "github.com/stretchr/testify/mock"
9+)
10+
11+// NewMockDeployCoordinator creates a new instance of MockDeployCoordinator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
12+// The first argument is typically a *testing.T value.
13+func NewMockDeployCoordinator(t interface {
14+ mock.TestingT
15+ Cleanup(func())
16+}) *MockDeployCoordinator {
17+ mock := &MockDeployCoordinator{}
18+ mock.Mock.Test(t)
19+
20+ t.Cleanup(func() { mock.AssertExpectations(t) })
21+
22+ return mock
23+}
24+
25+// MockDeployCoordinator is an autogenerated mock type for the DeployCoordinator type.
26+type MockDeployCoordinator struct {
27+ mock.Mock
28+}
29+
30+type MockDeployCoordinator_Expecter struct {
31+ mock *mock.Mock
32+}
33+
34+func (_m *MockDeployCoordinator) EXPECT() *MockDeployCoordinator_Expecter {
35+ return &MockDeployCoordinator_Expecter{mock: &_m.Mock}
36+}
37+
38+// ClearDeployEventSuppression provides a mock function for the type MockDeployCoordinator.
39+func (_mock *MockDeployCoordinator) ClearDeployEventSuppression(imageName string) {
40+ _mock.Called(imageName)
41+}
42+
43+// MockDeployCoordinator_ClearDeployEventSuppression_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClearDeployEventSuppression'.
44+type MockDeployCoordinator_ClearDeployEventSuppression_Call struct {
45+ *mock.Call
46+}
47+
48+// ClearDeployEventSuppression is a helper method to define mock.On call.
49+// - imageName string
50+func (_e *MockDeployCoordinator_Expecter) ClearDeployEventSuppression(imageName interface{}) *MockDeployCoordinator_ClearDeployEventSuppression_Call {
51+ return &MockDeployCoordinator_ClearDeployEventSuppression_Call{Call: _e.mock.On("ClearDeployEventSuppression", imageName)}
52+}
53+
54+func (_c *MockDeployCoordinator_ClearDeployEventSuppression_Call) Run(run func(imageName string)) *MockDeployCoordinator_ClearDeployEventSuppression_Call {
55+ _c.Call.Run(func(args mock.Arguments) {
56+ var arg0 string
57+ if args[0] != nil {
58+ arg0 = args[0].(string)
59+ }
60+ run(arg0)
61+ })
62+ return _c
63+}
64+
65+func (_c *MockDeployCoordinator_ClearDeployEventSuppression_Call) Return() *MockDeployCoordinator_ClearDeployEventSuppression_Call {
66+ _c.Call.Return()
67+ return _c
68+}
69+
70+func (_c *MockDeployCoordinator_ClearDeployEventSuppression_Call) RunAndReturn(run func(imageName string)) *MockDeployCoordinator_ClearDeployEventSuppression_Call {
71+ _c.Run(run)
72+ return _c
73+}
74+
75+// SuppressDeployEvent provides a mock function for the type MockDeployCoordinator.
76+func (_mock *MockDeployCoordinator) SuppressDeployEvent(imageName string) {
77+ _mock.Called(imageName)
78+}
79+
80+// MockDeployCoordinator_SuppressDeployEvent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SuppressDeployEvent'.
81+type MockDeployCoordinator_SuppressDeployEvent_Call struct {
82+ *mock.Call
83+}
84+
85+// SuppressDeployEvent is a helper method to define mock.On call.
86+// - imageName string
87+func (_e *MockDeployCoordinator_Expecter) SuppressDeployEvent(imageName interface{}) *MockDeployCoordinator_SuppressDeployEvent_Call {
88+ return &MockDeployCoordinator_SuppressDeployEvent_Call{Call: _e.mock.On("SuppressDeployEvent", imageName)}
89+}
90+
91+func (_c *MockDeployCoordinator_SuppressDeployEvent_Call) Run(run func(imageName string)) *MockDeployCoordinator_SuppressDeployEvent_Call {
92+ _c.Call.Run(func(args mock.Arguments) {
93+ var arg0 string
94+ if args[0] != nil {
95+ arg0 = args[0].(string)
96+ }
97+ run(arg0)
98+ })
99+ return _c
100+}
101+
102+func (_c *MockDeployCoordinator_SuppressDeployEvent_Call) Return() *MockDeployCoordinator_SuppressDeployEvent_Call {
103+ _c.Call.Return()
104+ return _c
105+}
106+
107+func (_c *MockDeployCoordinator_SuppressDeployEvent_Call) RunAndReturn(run func(imageName string)) *MockDeployCoordinator_SuppressDeployEvent_Call {
108+ _c.Run(run)
109+ return _c
110+}
@@ -0,0 +1,110 @@
1+// Code generated by mockery; DO NOT EDIT.
2+// github.com/vektra/mockery
3+// template: testify
4+
5+package mocks
6+
7+import (
8+ mock "github.com/stretchr/testify/mock"
9+)
10+
11+// NewMockDeployCoordinator creates a new instance of MockDeployCoordinator. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
12+// The first argument is typically a *testing.T value.
13+func NewMockDeployCoordinator(t interface {
14+ mock.TestingT
15+ Cleanup(func())
16+}) *MockDeployCoordinator {
17+ mock := &MockDeployCoordinator{}
18+ mock.Mock.Test(t)
19+
20+ t.Cleanup(func() { mock.AssertExpectations(t) })
21+
22+ return mock
23+}
24+
25+// MockDeployCoordinator is an autogenerated mock type for the DeployCoordinator type.
26+type MockDeployCoordinator struct {
27+ mock.Mock
28+}
29+
30+type MockDeployCoordinator_Expecter struct {
31+ mock *mock.Mock
32+}
33+
34+func (_m *MockDeployCoordinator) EXPECT() *MockDeployCoordinator_Expecter {
35+ return &MockDeployCoordinator_Expecter{mock: &_m.Mock}
36+}
37+
38+// ClearDeployEventSuppression provides a mock function for the type MockDeployCoordinator.
39+func (_mock *MockDeployCoordinator) ClearDeployEventSuppression(imageName string) {
40+ _mock.Called(imageName)
41+}
42+
43+// MockDeployCoordinator_ClearDeployEventSuppression_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'ClearDeployEventSuppression'.
44+type MockDeployCoordinator_ClearDeployEventSuppression_Call struct {
45+ *mock.Call
46+}
47+
48+// ClearDeployEventSuppression is a helper method to define mock.On call.
49+// - imageName string
50+func (_e *MockDeployCoordinator_Expecter) ClearDeployEventSuppression(imageName interface{}) *MockDeployCoordinator_ClearDeployEventSuppression_Call {
51+ return &MockDeployCoordinator_ClearDeployEventSuppression_Call{Call: _e.mock.On("ClearDeployEventSuppression", imageName)}
52+}
53+
54+func (_c *MockDeployCoordinator_ClearDeployEventSuppression_Call) Run(run func(imageName string)) *MockDeployCoordinator_ClearDeployEventSuppression_Call {
55+ _c.Call.Run(func(args mock.Arguments) {
56+ var arg0 string
57+ if args[0] != nil {
58+ arg0 = args[0].(string)
59+ }
60+ run(arg0)
61+ })
62+ return _c
63+}
64+
65+func (_c *MockDeployCoordinator_ClearDeployEventSuppression_Call) Return() *MockDeployCoordinator_ClearDeployEventSuppression_Call {
66+ _c.Call.Return()
67+ return _c
68+}
69+
70+func (_c *MockDeployCoordinator_ClearDeployEventSuppression_Call) RunAndReturn(run func(imageName string)) *MockDeployCoordinator_ClearDeployEventSuppression_Call {
71+ _c.Run(run)
72+ return _c
73+}
74+
75+// SuppressDeployEvent provides a mock function for the type MockDeployCoordinator.
76+func (_mock *MockDeployCoordinator) SuppressDeployEvent(imageName string) {
77+ _mock.Called(imageName)
78+}
79+
80+// MockDeployCoordinator_SuppressDeployEvent_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SuppressDeployEvent'.
81+type MockDeployCoordinator_SuppressDeployEvent_Call struct {
82+ *mock.Call
83+}
84+
85+// SuppressDeployEvent is a helper method to define mock.On call.
86+// - imageName string
87+func (_e *MockDeployCoordinator_Expecter) SuppressDeployEvent(imageName interface{}) *MockDeployCoordinator_SuppressDeployEvent_Call {
88+ return &MockDeployCoordinator_SuppressDeployEvent_Call{Call: _e.mock.On("SuppressDeployEvent", imageName)}
89+}
90+
91+func (_c *MockDeployCoordinator_SuppressDeployEvent_Call) Run(run func(imageName string)) *MockDeployCoordinator_SuppressDeployEvent_Call {
92+ _c.Call.Run(func(args mock.Arguments) {
93+ var arg0 string
94+ if args[0] != nil {
95+ arg0 = args[0].(string)
96+ }
97+ run(arg0)
98+ })
99+ return _c
100+}
101+
102+func (_c *MockDeployCoordinator_SuppressDeployEvent_Call) Return() *MockDeployCoordinator_SuppressDeployEvent_Call {
103+ _c.Call.Return()
104+ return _c
105+}
106+
107+func (_c *MockDeployCoordinator_SuppressDeployEvent_Call) RunAndReturn(run func(imageName string)) *MockDeployCoordinator_SuppressDeployEvent_Call {
108+ _c.Run(run)
109+ return _c
110+}
M internal/boundaries/in/registry.go +0 -4
@@ -29,8 +29,4 @@ type RegistryService interface {
2929 // Tag operations
3030 ListTags(ctx context.Context, name string) ([]string, error)
3131 ListRepositories(ctx context.Context) ([]string, error)
32-
33- // Deploy event suppression (prevents double-deploy when CLI manages the push)
34- SuppressDeployEvent(imageName string)
35- ClearDeployEventSuppression(imageName string)
3632 }
@@ -29,8 +29,4 @@ type RegistryService interface {
29 // Tag operations29 // Tag operations
30 ListTags(ctx context.Context, name string) ([]string, error)30 ListTags(ctx context.Context, name string) ([]string, error)
31 ListRepositories(ctx context.Context) ([]string, error)31 ListRepositories(ctx context.Context) ([]string, error)
32-
33- // Deploy event suppression (prevents double-deploy when CLI manages the push)
34- SuppressDeployEvent(imageName string)
35- ClearDeployEventSuppression(imageName string)
36 }32 }

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