detached 470ef27

refactor(app): extract preview teardown functions to reduce complexity

Extract inline closures from initPreviewService into named functions
(teardownTrackedPreview, gcOrphanedPreviews, cleanupOrphanDomainResources)
to satisfy gocyclo max 15 lint rule.
commit
470ef278ae82be03fc4d895f7ee0fa23a520ca9e
author
brice · · 2026-03-24 08:39 UTC
committer
brice · · 2026-03-25 05:38 UTC
parent
a67455d
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
1 file · +87 -72
M internal/app/run.go +87 -72
@@ -588,88 +588,103 @@ func initPreviewService(ctx context.Context, cfg Config, svc *services, log zero
588588 }
589589
590590 svc.previewService.StartTicker(ctx, 1*time.Hour, func(ctx context.Context, p domain.PreviewRoute) {
591- // Tracked preview teardown.
592- log := zerowrap.FromCtx(ctx)
593- for _, containerName := range p.Containers {
594- if err := svc.runtime.StopContainer(ctx, containerName); err != nil {
595- log.Warn().Err(err).Str("container", containerName).Str("preview", p.Domain).Msg("failed to stop preview container")
596- }
597- if err := svc.runtime.RemoveContainer(ctx, containerName, true); err != nil {
598- log.Warn().Err(err).Str("container", containerName).Str("preview", p.Domain).Msg("failed to remove preview container")
599- }
591+ teardownTrackedPreview(ctx, svc, p)
592+ }, func(ctx context.Context) {
593+ gcOrphanedPreviews(ctx, svc, previewConfig)
594+ })
595+}
596+
597+// teardownTrackedPreview removes all resources for a tracked preview that has expired.
598+func teardownTrackedPreview(ctx context.Context, svc *services, p domain.PreviewRoute) {
599+ log := zerowrap.FromCtx(ctx)
600+ for _, containerName := range p.Containers {
601+ if err := svc.runtime.StopContainer(ctx, containerName); err != nil {
602+ log.Warn().Err(err).Str("container", containerName).Str("preview", p.Domain).Msg("failed to stop preview container")
600603 }
601- for _, volName := range p.Volumes {
602- if err := svc.runtime.RemoveVolume(ctx, volName, true); err != nil {
603- log.Warn().Err(err).Str("volume", volName).Str("preview", p.Domain).Msg("failed to remove preview volume")
604- }
604+ if err := svc.runtime.RemoveContainer(ctx, containerName, true); err != nil {
605+ log.Warn().Err(err).Str("container", containerName).Str("preview", p.Domain).Msg("failed to remove preview container")
605606 }
606- // Remove network (naming convention: {networkPrefix}-{domain-sanitized}).
607- networkPrefix := svc.configSvc.GetNetworkPrefix()
608- networkName := networkPrefix + "-" + strings.ReplaceAll(p.Domain, ".", "-")
609- if err := svc.runtime.RemoveNetwork(ctx, networkName); err != nil {
610- log.Warn().Err(err).Str("network", networkName).Str("preview", p.Domain).Msg("failed to remove preview network")
611- }
612- // Remove route from config so proxy stops routing to this domain.
613- if err := svc.configSvc.RemoveRoute(ctx, p.Domain); err != nil {
614- if errors.Is(err, domain.ErrRouteNotFound) {
615- log.Debug().Str("domain", p.Domain).Msg("preview route already removed from config")
616- } else {
617- log.Warn().Err(err).Str("domain", p.Domain).Msg("failed to remove preview route from config")
618- }
607+ }
608+ for _, volName := range p.Volumes {
609+ if err := svc.runtime.RemoveVolume(ctx, volName, true); err != nil {
610+ log.Warn().Err(err).Str("volume", volName).Str("preview", p.Domain).Msg("failed to remove preview volume")
619611 }
620- svc.proxySvc.InvalidateTarget(ctx, p.Domain)
621- }, func(ctx context.Context) {
622- // Orphan GC: find and tear down untracked preview containers.
623- log := zerowrap.FromCtx(ctx)
624- orphans := svc.previewService.CollectOrphans(ctx, svc.runtime, previewConfig.TagPatterns, previewConfig.Separator)
625- for _, c := range orphans {
626- orphanDomain := c.Labels[domain.LabelDomain]
627- log.Warn().Str("container", c.Name).Str("image", c.Image).Str("domain", orphanDomain).
628- Time("created", c.Created).Msg("orphaned preview container detected, cleaning up")
629-
630- // Stop and remove container.
631- if err := svc.runtime.StopContainer(ctx, c.Name); err != nil {
632- log.Warn().Err(err).Str("container", c.Name).Msg("failed to stop orphan container")
633- }
634- if err := svc.runtime.RemoveContainer(ctx, c.Name, true); err != nil {
635- log.Warn().Err(err).Str("container", c.Name).Msg("failed to remove orphan container")
636- }
612+ }
613+ // Remove network (naming convention: {networkPrefix}-{domain-sanitized}).
614+ networkPrefix := svc.configSvc.GetNetworkPrefix()
615+ networkName := networkPrefix + "-" + strings.ReplaceAll(p.Domain, ".", "-")
616+ if err := svc.runtime.RemoveNetwork(ctx, networkName); err != nil {
617+ log.Warn().Err(err).Str("network", networkName).Str("preview", p.Domain).Msg("failed to remove preview network")
618+ }
619+ // Remove route from config so proxy stops routing to this domain.
620+ if err := svc.configSvc.RemoveRoute(ctx, p.Domain); err != nil {
621+ if errors.Is(err, domain.ErrRouteNotFound) {
622+ log.Debug().Str("domain", p.Domain).Msg("preview route already removed from config")
623+ } else {
624+ log.Warn().Err(err).Str("domain", p.Domain).Msg("failed to remove preview route from config")
625+ }
626+ }
627+ svc.proxySvc.InvalidateTarget(ctx, p.Domain)
628+}
637629
638- if orphanDomain != "" {
639- // Remove volumes matching domain prefix (convention: {volPrefix}-{domain-sanitized}-).
640- _, volPrefix, _ := svc.configSvc.GetVolumeConfig()
641- domainSanitized := strings.ReplaceAll(orphanDomain, ".", "-")
642- fullVolumePrefix := volPrefix + "-" + domainSanitized + "-"
643- volumes, err := svc.runtime.ListVolumes(ctx)
644- if err == nil {
645- for _, v := range volumes {
646- if strings.HasPrefix(v.Name, fullVolumePrefix) {
647- if err := svc.runtime.RemoveVolume(ctx, v.Name, true); err != nil {
648- log.Warn().Err(err).Str("volume", v.Name).Msg("failed to remove orphan volume")
649- }
650- }
651- }
652- }
630+// gcOrphanedPreviews finds and tears down untracked preview containers.
631+func gcOrphanedPreviews(ctx context.Context, svc *services, previewConfig domain.PreviewConfig) {
632+ log := zerowrap.FromCtx(ctx)
633+ orphans := svc.previewService.CollectOrphans(ctx, svc.runtime, previewConfig.TagPatterns, previewConfig.Separator)
634+ for _, c := range orphans {
635+ orphanDomain := c.Labels[domain.LabelDomain]
636+ log.Warn().Str("container", c.Name).Str("image", c.Image).Str("domain", orphanDomain).
637+ Time("created", c.Created).Msg("orphaned preview container detected, cleaning up")
653638
654- // Remove network (convention: {networkPrefix}-{domain-sanitized}).
655- networkPrefix := svc.configSvc.GetNetworkPrefix()
656- networkName := networkPrefix + "-" + domainSanitized
657- if err := svc.runtime.RemoveNetwork(ctx, networkName); err != nil {
658- log.Warn().Err(err).Str("network", networkName).Msg("failed to remove orphan network")
659- }
639+ // Stop and remove container.
640+ if err := svc.runtime.StopContainer(ctx, c.Name); err != nil {
641+ log.Warn().Err(err).Str("container", c.Name).Msg("failed to stop orphan container")
642+ }
643+ if err := svc.runtime.RemoveContainer(ctx, c.Name, true); err != nil {
644+ log.Warn().Err(err).Str("container", c.Name).Msg("failed to remove orphan container")
645+ }
646+
647+ if orphanDomain != "" {
648+ cleanupOrphanDomainResources(ctx, svc, orphanDomain)
649+ }
650+
651+ log.Info().Str("container", c.Name).Str("domain", orphanDomain).Msg("orphaned preview container cleaned up")
652+ }
653+}
660654
661- // Remove route and invalidate proxy.
662- if err := svc.configSvc.RemoveRoute(ctx, orphanDomain); err != nil {
663- if !errors.Is(err, domain.ErrRouteNotFound) {
664- log.Warn().Err(err).Str("domain", orphanDomain).Msg("failed to remove orphan route")
665- }
655+// cleanupOrphanDomainResources removes volumes, network, and route for an orphaned preview domain.
656+func cleanupOrphanDomainResources(ctx context.Context, svc *services, orphanDomain string) {
657+ log := zerowrap.FromCtx(ctx)
658+ domainSanitized := strings.ReplaceAll(orphanDomain, ".", "-")
659+
660+ // Remove volumes matching domain prefix (convention: {volPrefix}-{domain-sanitized}-).
661+ _, volPrefix, _ := svc.configSvc.GetVolumeConfig()
662+ fullVolumePrefix := volPrefix + "-" + domainSanitized + "-"
663+ volumes, err := svc.runtime.ListVolumes(ctx)
664+ if err == nil {
665+ for _, v := range volumes {
666+ if strings.HasPrefix(v.Name, fullVolumePrefix) {
667+ if err := svc.runtime.RemoveVolume(ctx, v.Name, true); err != nil {
668+ log.Warn().Err(err).Str("volume", v.Name).Msg("failed to remove orphan volume")
666669 }
667- svc.proxySvc.InvalidateTarget(ctx, orphanDomain)
668670 }
671+ }
672+ }
669673
670- log.Info().Str("container", c.Name).Str("domain", orphanDomain).Msg("orphaned preview container cleaned up")
674+ // Remove network (convention: {networkPrefix}-{domain-sanitized}).
675+ networkPrefix := svc.configSvc.GetNetworkPrefix()
676+ networkName := networkPrefix + "-" + domainSanitized
677+ if err := svc.runtime.RemoveNetwork(ctx, networkName); err != nil {
678+ log.Warn().Err(err).Str("network", networkName).Msg("failed to remove orphan network")
679+ }
680+
681+ // Remove route and invalidate proxy.
682+ if err := svc.configSvc.RemoveRoute(ctx, orphanDomain); err != nil {
683+ if !errors.Is(err, domain.ErrRouteNotFound) {
684+ log.Warn().Err(err).Str("domain", orphanDomain).Msg("failed to remove orphan route")
671685 }
672- })
686+ }
687+ svc.proxySvc.InvalidateTarget(ctx, orphanDomain)
673688 }
674689
675690 // injectTelemetryMetrics creates and injects OTel metrics into services when
@@ -588,88 +588,103 @@ func initPreviewService(ctx context.Context, cfg Config, svc *services, log zero
588 }588 }
589 589
590 svc.previewService.StartTicker(ctx, 1*time.Hour, func(ctx context.Context, p domain.PreviewRoute) {590 svc.previewService.StartTicker(ctx, 1*time.Hour, func(ctx context.Context, p domain.PreviewRoute) {
591- // Tracked preview teardown.591+ teardownTrackedPreview(ctx, svc, p)
592- log := zerowrap.FromCtx(ctx)592+ }, func(ctx context.Context) {
593- for _, containerName := range p.Containers {593+ gcOrphanedPreviews(ctx, svc, previewConfig)
594- if err := svc.runtime.StopContainer(ctx, containerName); err != nil {594+ })
595- log.Warn().Err(err).Str("container", containerName).Str("preview", p.Domain).Msg("failed to stop preview container")595+}
596- }596+
597- if err := svc.runtime.RemoveContainer(ctx, containerName, true); err != nil {597+// teardownTrackedPreview removes all resources for a tracked preview that has expired.
598- log.Warn().Err(err).Str("container", containerName).Str("preview", p.Domain).Msg("failed to remove preview container")598+func teardownTrackedPreview(ctx context.Context, svc *services, p domain.PreviewRoute) {
599- }599+ log := zerowrap.FromCtx(ctx)
600+ for _, containerName := range p.Containers {
601+ if err := svc.runtime.StopContainer(ctx, containerName); err != nil {
602+ log.Warn().Err(err).Str("container", containerName).Str("preview", p.Domain).Msg("failed to stop preview container")
600 }603 }
601- for _, volName := range p.Volumes {604+ if err := svc.runtime.RemoveContainer(ctx, containerName, true); err != nil {
602- if err := svc.runtime.RemoveVolume(ctx, volName, true); err != nil {605+ log.Warn().Err(err).Str("container", containerName).Str("preview", p.Domain).Msg("failed to remove preview container")
603- log.Warn().Err(err).Str("volume", volName).Str("preview", p.Domain).Msg("failed to remove preview volume")
604- }
605 }606 }
606- // Remove network (naming convention: {networkPrefix}-{domain-sanitized}).607+ }
607- networkPrefix := svc.configSvc.GetNetworkPrefix()608+ for _, volName := range p.Volumes {
608- networkName := networkPrefix + "-" + strings.ReplaceAll(p.Domain, ".", "-")609+ if err := svc.runtime.RemoveVolume(ctx, volName, true); err != nil {
609- if err := svc.runtime.RemoveNetwork(ctx, networkName); err != nil {610+ log.Warn().Err(err).Str("volume", volName).Str("preview", p.Domain).Msg("failed to remove preview volume")
610- log.Warn().Err(err).Str("network", networkName).Str("preview", p.Domain).Msg("failed to remove preview network")
611- }
612- // Remove route from config so proxy stops routing to this domain.
613- if err := svc.configSvc.RemoveRoute(ctx, p.Domain); err != nil {
614- if errors.Is(err, domain.ErrRouteNotFound) {
615- log.Debug().Str("domain", p.Domain).Msg("preview route already removed from config")
616- } else {
617- log.Warn().Err(err).Str("domain", p.Domain).Msg("failed to remove preview route from config")
618- }
619 }611 }
620- svc.proxySvc.InvalidateTarget(ctx, p.Domain)612+ }
621- }, func(ctx context.Context) {613+ // Remove network (naming convention: {networkPrefix}-{domain-sanitized}).
622- // Orphan GC: find and tear down untracked preview containers.614+ networkPrefix := svc.configSvc.GetNetworkPrefix()
623- log := zerowrap.FromCtx(ctx)615+ networkName := networkPrefix + "-" + strings.ReplaceAll(p.Domain, ".", "-")
624- orphans := svc.previewService.CollectOrphans(ctx, svc.runtime, previewConfig.TagPatterns, previewConfig.Separator)616+ if err := svc.runtime.RemoveNetwork(ctx, networkName); err != nil {
625- for _, c := range orphans {617+ log.Warn().Err(err).Str("network", networkName).Str("preview", p.Domain).Msg("failed to remove preview network")
626- orphanDomain := c.Labels[domain.LabelDomain]618+ }
627- log.Warn().Str("container", c.Name).Str("image", c.Image).Str("domain", orphanDomain).619+ // Remove route from config so proxy stops routing to this domain.
628- Time("created", c.Created).Msg("orphaned preview container detected, cleaning up")620+ if err := svc.configSvc.RemoveRoute(ctx, p.Domain); err != nil {
629-621+ if errors.Is(err, domain.ErrRouteNotFound) {
630- // Stop and remove container.622+ log.Debug().Str("domain", p.Domain).Msg("preview route already removed from config")
631- if err := svc.runtime.StopContainer(ctx, c.Name); err != nil {623+ } else {
632- log.Warn().Err(err).Str("container", c.Name).Msg("failed to stop orphan container")624+ log.Warn().Err(err).Str("domain", p.Domain).Msg("failed to remove preview route from config")
633- }625+ }
634- if err := svc.runtime.RemoveContainer(ctx, c.Name, true); err != nil {626+ }
635- log.Warn().Err(err).Str("container", c.Name).Msg("failed to remove orphan container")627+ svc.proxySvc.InvalidateTarget(ctx, p.Domain)
636- }628+}
637 629
638- if orphanDomain != "" {630+// gcOrphanedPreviews finds and tears down untracked preview containers.
639- // Remove volumes matching domain prefix (convention: {volPrefix}-{domain-sanitized}-).631+func gcOrphanedPreviews(ctx context.Context, svc *services, previewConfig domain.PreviewConfig) {
640- _, volPrefix, _ := svc.configSvc.GetVolumeConfig()632+ log := zerowrap.FromCtx(ctx)
641- domainSanitized := strings.ReplaceAll(orphanDomain, ".", "-")633+ orphans := svc.previewService.CollectOrphans(ctx, svc.runtime, previewConfig.TagPatterns, previewConfig.Separator)
642- fullVolumePrefix := volPrefix + "-" + domainSanitized + "-"634+ for _, c := range orphans {
643- volumes, err := svc.runtime.ListVolumes(ctx)635+ orphanDomain := c.Labels[domain.LabelDomain]
644- if err == nil {636+ log.Warn().Str("container", c.Name).Str("image", c.Image).Str("domain", orphanDomain).
645- for _, v := range volumes {637+ Time("created", c.Created).Msg("orphaned preview container detected, cleaning up")
646- if strings.HasPrefix(v.Name, fullVolumePrefix) {
647- if err := svc.runtime.RemoveVolume(ctx, v.Name, true); err != nil {
648- log.Warn().Err(err).Str("volume", v.Name).Msg("failed to remove orphan volume")
649- }
650- }
651- }
652- }
653 638
654- // Remove network (convention: {networkPrefix}-{domain-sanitized}).639+ // Stop and remove container.
655- networkPrefix := svc.configSvc.GetNetworkPrefix()640+ if err := svc.runtime.StopContainer(ctx, c.Name); err != nil {
656- networkName := networkPrefix + "-" + domainSanitized641+ log.Warn().Err(err).Str("container", c.Name).Msg("failed to stop orphan container")
657- if err := svc.runtime.RemoveNetwork(ctx, networkName); err != nil {642+ }
658- log.Warn().Err(err).Str("network", networkName).Msg("failed to remove orphan network")643+ if err := svc.runtime.RemoveContainer(ctx, c.Name, true); err != nil {
659- }644+ log.Warn().Err(err).Str("container", c.Name).Msg("failed to remove orphan container")
645+ }
646+
647+ if orphanDomain != "" {
648+ cleanupOrphanDomainResources(ctx, svc, orphanDomain)
649+ }
650+
651+ log.Info().Str("container", c.Name).Str("domain", orphanDomain).Msg("orphaned preview container cleaned up")
652+ }
653+}
660 654
661- // Remove route and invalidate proxy.655+// cleanupOrphanDomainResources removes volumes, network, and route for an orphaned preview domain.
662- if err := svc.configSvc.RemoveRoute(ctx, orphanDomain); err != nil {656+func cleanupOrphanDomainResources(ctx context.Context, svc *services, orphanDomain string) {
663- if !errors.Is(err, domain.ErrRouteNotFound) {657+ log := zerowrap.FromCtx(ctx)
664- log.Warn().Err(err).Str("domain", orphanDomain).Msg("failed to remove orphan route")658+ domainSanitized := strings.ReplaceAll(orphanDomain, ".", "-")
665- }659+
660+ // Remove volumes matching domain prefix (convention: {volPrefix}-{domain-sanitized}-).
661+ _, volPrefix, _ := svc.configSvc.GetVolumeConfig()
662+ fullVolumePrefix := volPrefix + "-" + domainSanitized + "-"
663+ volumes, err := svc.runtime.ListVolumes(ctx)
664+ if err == nil {
665+ for _, v := range volumes {
666+ if strings.HasPrefix(v.Name, fullVolumePrefix) {
667+ if err := svc.runtime.RemoveVolume(ctx, v.Name, true); err != nil {
668+ log.Warn().Err(err).Str("volume", v.Name).Msg("failed to remove orphan volume")
666 }669 }
667- svc.proxySvc.InvalidateTarget(ctx, orphanDomain)
668 }670 }
671+ }
672+ }
669 673
670- log.Info().Str("container", c.Name).Str("domain", orphanDomain).Msg("orphaned preview container cleaned up")674+ // Remove network (convention: {networkPrefix}-{domain-sanitized}).
675+ networkPrefix := svc.configSvc.GetNetworkPrefix()
676+ networkName := networkPrefix + "-" + domainSanitized
677+ if err := svc.runtime.RemoveNetwork(ctx, networkName); err != nil {
678+ log.Warn().Err(err).Str("network", networkName).Msg("failed to remove orphan network")
679+ }
680+
681+ // Remove route and invalidate proxy.
682+ if err := svc.configSvc.RemoveRoute(ctx, orphanDomain); err != nil {
683+ if !errors.Is(err, domain.ErrRouteNotFound) {
684+ log.Warn().Err(err).Str("domain", orphanDomain).Msg("failed to remove orphan route")
671 }685 }
672- })686+ }
687+ svc.proxySvc.InvalidateTarget(ctx, orphanDomain)
673 }688 }
674 689
675 // injectTelemetryMetrics creates and injects OTel metrics into services when690 // injectTelemetryMetrics creates and injects OTel metrics into services when

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