detached ae32066

feat(preview): add deployer, route manager, volume cloner deps to service

commit
ae320663f1337bac5f91900e5341cb06a05cd2df
author
brice · · 2026-03-18 17:07 UTC
committer
brice · · 2026-03-18 20:39 UTC
parent
fe012a8
signature
unsigned
2 files · +58 -10
M internal/usecase/auto/preview/service.go +43 -6
@@ -17,14 +17,31 @@ type nameLockEntry struct {
1717 refs int
1818 }
1919
20+// Deployer abstracts the container service deploy method.
21+type Deployer interface {
22+ Deploy(ctx context.Context, route domain.Route) (*domain.Container, error)
23+}
24+
25+// RouteManager abstracts config service methods needed for preview route lifecycle.
26+type RouteManager interface {
27+ AddRoute(ctx context.Context, route domain.Route) error
28+ RemoveRoute(ctx context.Context, domain string) error
29+ GetRoute(ctx context.Context, domain string) (*domain.Route, error)
30+ GetVolumeConfig() (autoCreate bool, prefix string, preserve bool)
31+}
32+
2033 // Service manages preview lifecycle: CRUD, TTL, persistence.
2134 type Service struct {
22- store out.PreviewStore
23- defaultTTL time.Duration
24- previews []domain.PreviewRoute
25- mu sync.RWMutex
26- nameLocks map[string]*nameLockEntry
27- nameLockMu sync.Mutex
35+ store out.PreviewStore
36+ defaultTTL time.Duration
37+ deployer Deployer
38+ routeManager RouteManager
39+ volumeCloner VolumeCloner
40+ registryDomain string
41+ previews []domain.PreviewRoute
42+ mu sync.RWMutex
43+ nameLocks map[string]*nameLockEntry
44+ nameLockMu sync.Mutex
2845 }
2946
3047 func NewService(store out.PreviewStore, defaultTTL time.Duration) *Service {
@@ -35,6 +52,26 @@ func NewService(store out.PreviewStore, defaultTTL time.Duration) *Service {
3552 }
3653 }
3754
55+func (s *Service) WithDeployer(d Deployer) *Service {
56+ s.deployer = d
57+ return s
58+}
59+
60+func (s *Service) WithRouteManager(rm RouteManager) *Service {
61+ s.routeManager = rm
62+ return s
63+}
64+
65+func (s *Service) WithVolumeCloner(vc VolumeCloner) *Service {
66+ s.volumeCloner = vc
67+ return s
68+}
69+
70+func (s *Service) WithRegistryDomain(d string) *Service {
71+ s.registryDomain = d
72+ return s
73+}
74+
3875 func (s *Service) Load(ctx context.Context) error {
3976 previews, err := s.store.Load(ctx)
4077 if err != nil {
@@ -17,14 +17,31 @@ type nameLockEntry struct {
17 refs int17 refs int
18 }18 }
19 19
20+// Deployer abstracts the container service deploy method.
21+type Deployer interface {
22+ Deploy(ctx context.Context, route domain.Route) (*domain.Container, error)
23+}
24+
25+// RouteManager abstracts config service methods needed for preview route lifecycle.
26+type RouteManager interface {
27+ AddRoute(ctx context.Context, route domain.Route) error
28+ RemoveRoute(ctx context.Context, domain string) error
29+ GetRoute(ctx context.Context, domain string) (*domain.Route, error)
30+ GetVolumeConfig() (autoCreate bool, prefix string, preserve bool)
31+}
32+
20 // Service manages preview lifecycle: CRUD, TTL, persistence.33 // Service manages preview lifecycle: CRUD, TTL, persistence.
21 type Service struct {34 type Service struct {
22- store out.PreviewStore35+ store out.PreviewStore
23- defaultTTL time.Duration36+ defaultTTL time.Duration
24- previews []domain.PreviewRoute37+ deployer Deployer
25- mu sync.RWMutex38+ routeManager RouteManager
26- nameLocks map[string]*nameLockEntry39+ volumeCloner VolumeCloner
27- nameLockMu sync.Mutex40+ registryDomain string
41+ previews []domain.PreviewRoute
42+ mu sync.RWMutex
43+ nameLocks map[string]*nameLockEntry
44+ nameLockMu sync.Mutex
28 }45 }
29 46
30 func NewService(store out.PreviewStore, defaultTTL time.Duration) *Service {47 func NewService(store out.PreviewStore, defaultTTL time.Duration) *Service {
@@ -35,6 +52,26 @@ func NewService(store out.PreviewStore, defaultTTL time.Duration) *Service {
35 }52 }
36 }53 }
37 54
55+func (s *Service) WithDeployer(d Deployer) *Service {
56+ s.deployer = d
57+ return s
58+}
59+
60+func (s *Service) WithRouteManager(rm RouteManager) *Service {
61+ s.routeManager = rm
62+ return s
63+}
64+
65+func (s *Service) WithVolumeCloner(vc VolumeCloner) *Service {
66+ s.volumeCloner = vc
67+ return s
68+}
69+
70+func (s *Service) WithRegistryDomain(d string) *Service {
71+ s.registryDomain = d
72+ return s
73+}
74+
38 func (s *Service) Load(ctx context.Context) error {75 func (s *Service) Load(ctx context.Context) error {
39 previews, err := s.store.Load(ctx)76 previews, err := s.store.Load(ctx)
40 if err != nil {77 if err != nil {
M internal/usecase/auto/preview/volumes.go +15 -4
@@ -16,6 +16,17 @@ type VolumeCloner interface {
1616 StartContainer(ctx context.Context, containerID string) error
1717 WaitForContainer(ctx context.Context, containerID string) error
1818 RemoveContainer(ctx context.Context, containerID string, force bool) error
19+ ListVolumes(ctx context.Context) ([]*domain.VolumeInfo, error)
20+}
21+
22+// VolumeNamer is a function that maps a source volume name to a destination volume name.
23+type VolumeNamer func(sourceVolName string) string
24+
25+// DefaultNamer returns a VolumeNamer that uses BuildCloneVolumeName with the given preview name.
26+func DefaultNamer(previewName string) VolumeNamer {
27+ return func(sourceVolName string) string {
28+ return BuildCloneVolumeName(previewName, sourceVolName)
29+ }
1930 }
2031
2132 // BuildCloneVolumeName generates a preview volume name.
@@ -36,11 +47,11 @@ func BuildCloneContainerName(previewName, image string) string {
3647 }
3748
3849 // CloneVolumes copies volumes from source using a read-only helper container.
39-// sourceVolumes maps volume display names to actual Docker volume names.
40-func CloneVolumes(ctx context.Context, cloner VolumeCloner, previewName string, sourceVolumes map[string]string) ([]string, error) {
50+// sourceVolumes is a list of actual Docker volume names to clone.
51+func CloneVolumes(ctx context.Context, cloner VolumeCloner, namer VolumeNamer, sourceVolumes []string) ([]string, error) {
4152 var clonedNames []string
42- for volName, sourceVolName := range sourceVolumes {
43- destName := BuildCloneVolumeName(previewName, volName)
53+ for _, sourceVolName := range sourceVolumes {
54+ destName := namer(sourceVolName)
4455
4556 if err := cloner.CreateVolume(ctx, destName); err != nil {
4657 cleanupVolumes(ctx, cloner, clonedNames)
@@ -16,6 +16,17 @@ type VolumeCloner interface {
16 StartContainer(ctx context.Context, containerID string) error16 StartContainer(ctx context.Context, containerID string) error
17 WaitForContainer(ctx context.Context, containerID string) error17 WaitForContainer(ctx context.Context, containerID string) error
18 RemoveContainer(ctx context.Context, containerID string, force bool) error18 RemoveContainer(ctx context.Context, containerID string, force bool) error
19+ ListVolumes(ctx context.Context) ([]*domain.VolumeInfo, error)
20+}
21+
22+// VolumeNamer is a function that maps a source volume name to a destination volume name.
23+type VolumeNamer func(sourceVolName string) string
24+
25+// DefaultNamer returns a VolumeNamer that uses BuildCloneVolumeName with the given preview name.
26+func DefaultNamer(previewName string) VolumeNamer {
27+ return func(sourceVolName string) string {
28+ return BuildCloneVolumeName(previewName, sourceVolName)
29+ }
19 }30 }
20 31
21 // BuildCloneVolumeName generates a preview volume name.32 // BuildCloneVolumeName generates a preview volume name.
@@ -36,11 +47,11 @@ func BuildCloneContainerName(previewName, image string) string {
36 }47 }
37 48
38 // CloneVolumes copies volumes from source using a read-only helper container.49 // CloneVolumes copies volumes from source using a read-only helper container.
39-// sourceVolumes maps volume display names to actual Docker volume names.50+// sourceVolumes is a list of actual Docker volume names to clone.
40-func CloneVolumes(ctx context.Context, cloner VolumeCloner, previewName string, sourceVolumes map[string]string) ([]string, error) {51+func CloneVolumes(ctx context.Context, cloner VolumeCloner, namer VolumeNamer, sourceVolumes []string) ([]string, error) {
41 var clonedNames []string52 var clonedNames []string
42- for volName, sourceVolName := range sourceVolumes {53+ for _, sourceVolName := range sourceVolumes {
43- destName := BuildCloneVolumeName(previewName, volName)54+ destName := namer(sourceVolName)
44 55
45 if err := cloner.CreateVolume(ctx, destName); err != nil {56 if err := cloner.CreateVolume(ctx, destName); err != nil {
46 cleanupVolumes(ctx, cloner, clonedNames)57 cleanupVolumes(ctx, cloner, clonedNames)

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