feat(app): wire preview service, dispatcher, and TTL ticker in run.go
- commit
- ad3801f1c2b20eec66910f39b50a6c12a3ea9b83
- author
- brice · · 2026-03-19 05:10 UTC
- committer
- brice · · 2026-03-19 05:13 UTC
- parent
- 458a175
- signature
- unsigned
1 file · +40 -3
M internal/app/run.go +40 -3
| @@ -64,6 +64,8 @@ import ( | |||
| 64 | 64 | ||
| 65 | 65 | // Use cases | |
| 66 | 66 | "github.com/bnema/gordon/internal/usecase/auth" | |
| 67 | + | "github.com/bnema/gordon/internal/usecase/auto" | |
| 68 | + | "github.com/bnema/gordon/internal/usecase/auto/preview" | |
| 67 | 69 | "github.com/bnema/gordon/internal/usecase/backup" | |
| 68 | 70 | "github.com/bnema/gordon/internal/usecase/config" | |
| 69 | 71 | "github.com/bnema/gordon/internal/usecase/container" | |
| @@ -192,6 +194,8 @@ type services struct { | |||
| 192 | 194 | adminHandler *admin.Handler | |
| 193 | 195 | internalRegUser string | |
| 194 | 196 | internalRegPass string | |
| 197 | + | previewStore *filesystem.PreviewStore | |
| 198 | + | previewService *preview.Service | |
| 195 | 199 | envDir string | |
| 196 | 200 | maxBlobChunkSize int64 | |
| 197 | 201 | } | |
| @@ -541,6 +545,27 @@ func createServices(ctx context.Context, v *viper.Viper, cfg Config, log zerowra | |||
| 541 | 545 | // Create log service for accessing logs via admin API | |
| 542 | 546 | svc.logSvc = logs.NewService(resolveLogFilePath(cfg), cfg.Logging.File.Enabled, svc.containerSvc, svc.runtime, log) | |
| 543 | 547 | ||
| 548 | + | // Preview store and service | |
| 549 | + | previewStorePath := filepath.Join(resolveDataDir(cfg.Server.DataDir), "previews.json") | |
| 550 | + | svc.previewStore = filesystem.NewPreviewStore(previewStorePath) | |
| 551 | + | previewConfig := svc.configSvc.GetPreviewConfig() | |
| 552 | + | svc.previewService = preview.NewService(svc.previewStore, previewConfig.TTL) | |
| 553 | + | if err := svc.previewService.Load(ctx); err != nil { | |
| 554 | + | log.Warn().Err(err).Msg("failed to load previews") | |
| 555 | + | } | |
| 556 | + | ||
| 557 | + | // Start TTL ticker to expire and clean up preview environments | |
| 558 | + | svc.previewService.StartTicker(ctx, 60*time.Second, func(ctx context.Context, p domain.PreviewRoute) { | |
| 559 | + | for _, containerName := range p.Containers { | |
| 560 | + | _ = svc.runtime.StopContainer(ctx, containerName) | |
| 561 | + | _ = svc.runtime.RemoveContainer(ctx, containerName, true) | |
| 562 | + | } | |
| 563 | + | for _, volName := range p.Volumes { | |
| 564 | + | _ = svc.runtime.RemoveVolume(ctx, volName, true) | |
| 565 | + | } | |
| 566 | + | svc.proxySvc.InvalidateTarget(ctx, p.Domain) | |
| 567 | + | }) | |
| 568 | + | ||
| 544 | 569 | // Create admin handler for admin API | |
| 545 | 570 | svc.adminHandler = admin.NewHandler( | |
| 546 | 571 | svc.configSvc, | |
| @@ -553,7 +578,7 @@ func createServices(ctx context.Context, v *viper.Viper, cfg Config, log zerowra | |||
| 553 | 578 | svc.eventBus, | |
| 554 | 579 | log, | |
| 555 | 580 | svc.backupSvc, | |
| 556 | - | nil, // previewSvc: wired in Task 13 | |
| 581 | + | svc.previewService, | |
| 557 | 582 | svc.imageSvc, | |
| 558 | 583 | ).WithVolumeService(svc.volumeSvc) | |
| 559 | 584 | ||
| @@ -1350,8 +1375,20 @@ func registerEventHandlers(ctx context.Context, svc *services, cfg Config) (func | |||
| 1350 | 1375 | // Auto-route handler for creating routes from image labels | |
| 1351 | 1376 | autoRouteHandler := container.NewAutoRouteHandler(ctx, svc.configSvc, svc.containerSvc, svc.blobStorage, cfg.Server.RegistryDomain). | |
| 1352 | 1377 | WithEnvExtractor(svc.runtime, svc.envDir) | |
| 1353 | - | if err := svc.eventBus.Subscribe(autoRouteHandler); err != nil { | |
| 1354 | - | return nil, fmt.Errorf("failed to subscribe auto-route handler: %w", err) | |
| 1378 | + | ||
| 1379 | + | // Preview handler for creating preview environments from tagged images | |
| 1380 | + | autoPreviewHandler := preview.NewAutoPreviewHandler( | |
| 1381 | + | ctx, | |
| 1382 | + | svc.configSvc, | |
| 1383 | + | svc.blobStorage, | |
| 1384 | + | svc.previewService, | |
| 1385 | + | cfg.Server.RegistryDomain, | |
| 1386 | + | ) | |
| 1387 | + | ||
| 1388 | + | // Dispatcher routes image push events to either auto-route or preview handler | |
| 1389 | + | dispatcher := auto.NewImagePushDispatcher(svc.configSvc, autoRouteHandler, autoPreviewHandler) | |
| 1390 | + | if err := svc.eventBus.Subscribe(dispatcher); err != nil { | |
| 1391 | + | return nil, fmt.Errorf("subscribe image push dispatcher: %w", err) | |
| 1355 | 1392 | } | |
| 1356 | 1393 | ||
| 1357 | 1394 | configReloadHandler := container.NewConfigReloadHandler(ctx, svc.containerSvc, svc.configSvc) | |
| @@ -64,6 +64,8 @@ import ( | |||||
| 64 | 64 | ||||
| 65 | // Use cases | 65 | // Use cases | ||
| 66 | "github.com/bnema/gordon/internal/usecase/auth" | 66 | "github.com/bnema/gordon/internal/usecase/auth" | ||
| 67 | + | "github.com/bnema/gordon/internal/usecase/auto" | |||
| 68 | + | "github.com/bnema/gordon/internal/usecase/auto/preview" | |||
| 67 | "github.com/bnema/gordon/internal/usecase/backup" | 69 | "github.com/bnema/gordon/internal/usecase/backup" | ||
| 68 | "github.com/bnema/gordon/internal/usecase/config" | 70 | "github.com/bnema/gordon/internal/usecase/config" | ||
| 69 | "github.com/bnema/gordon/internal/usecase/container" | 71 | "github.com/bnema/gordon/internal/usecase/container" | ||
| @@ -192,6 +194,8 @@ type services struct { | |||||
| 192 | adminHandler *admin.Handler | 194 | adminHandler *admin.Handler | ||
| 193 | internalRegUser string | 195 | internalRegUser string | ||
| 194 | internalRegPass string | 196 | internalRegPass string | ||
| 197 | + | previewStore *filesystem.PreviewStore | |||
| 198 | + | previewService *preview.Service | |||
| 195 | envDir string | 199 | envDir string | ||
| 196 | maxBlobChunkSize int64 | 200 | maxBlobChunkSize int64 | ||
| 197 | } | 201 | } | ||
| @@ -541,6 +545,27 @@ func createServices(ctx context.Context, v *viper.Viper, cfg Config, log zerowra | |||||
| 541 | // Create log service for accessing logs via admin API | 545 | // Create log service for accessing logs via admin API | ||
| 542 | svc.logSvc = logs.NewService(resolveLogFilePath(cfg), cfg.Logging.File.Enabled, svc.containerSvc, svc.runtime, log) | 546 | svc.logSvc = logs.NewService(resolveLogFilePath(cfg), cfg.Logging.File.Enabled, svc.containerSvc, svc.runtime, log) | ||
| 543 | 547 | ||||
| 548 | + | // Preview store and service | |||
| 549 | + | previewStorePath := filepath.Join(resolveDataDir(cfg.Server.DataDir), "previews.json") | |||
| 550 | + | svc.previewStore = filesystem.NewPreviewStore(previewStorePath) | |||
| 551 | + | previewConfig := svc.configSvc.GetPreviewConfig() | |||
| 552 | + | svc.previewService = preview.NewService(svc.previewStore, previewConfig.TTL) | |||
| 553 | + | if err := svc.previewService.Load(ctx); err != nil { | |||
| 554 | + | log.Warn().Err(err).Msg("failed to load previews") | |||
| 555 | + | } | |||
| 556 | + | ||||
| 557 | + | // Start TTL ticker to expire and clean up preview environments | |||
| 558 | + | svc.previewService.StartTicker(ctx, 60*time.Second, func(ctx context.Context, p domain.PreviewRoute) { | |||
| 559 | + | for _, containerName := range p.Containers { | |||
| 560 | + | _ = svc.runtime.StopContainer(ctx, containerName) | |||
| 561 | + | _ = svc.runtime.RemoveContainer(ctx, containerName, true) | |||
| 562 | + | } | |||
| 563 | + | for _, volName := range p.Volumes { | |||
| 564 | + | _ = svc.runtime.RemoveVolume(ctx, volName, true) | |||
| 565 | + | } | |||
| 566 | + | svc.proxySvc.InvalidateTarget(ctx, p.Domain) | |||
| 567 | + | }) | |||
| 568 | + | ||||
| 544 | // Create admin handler for admin API | 569 | // Create admin handler for admin API | ||
| 545 | svc.adminHandler = admin.NewHandler( | 570 | svc.adminHandler = admin.NewHandler( | ||
| 546 | svc.configSvc, | 571 | svc.configSvc, | ||
| @@ -553,7 +578,7 @@ func createServices(ctx context.Context, v *viper.Viper, cfg Config, log zerowra | |||||
| 553 | svc.eventBus, | 578 | svc.eventBus, | ||
| 554 | log, | 579 | log, | ||
| 555 | svc.backupSvc, | 580 | svc.backupSvc, | ||
| 556 | - | nil, // previewSvc: wired in Task 13 | 581 | + | svc.previewService, |
| 557 | svc.imageSvc, | 582 | svc.imageSvc, | ||
| 558 | ).WithVolumeService(svc.volumeSvc) | 583 | ).WithVolumeService(svc.volumeSvc) | ||
| 559 | 584 | ||||
| @@ -1350,8 +1375,20 @@ func registerEventHandlers(ctx context.Context, svc *services, cfg Config) (func | |||||
| 1350 | // Auto-route handler for creating routes from image labels | 1375 | // Auto-route handler for creating routes from image labels | ||
| 1351 | autoRouteHandler := container.NewAutoRouteHandler(ctx, svc.configSvc, svc.containerSvc, svc.blobStorage, cfg.Server.RegistryDomain). | 1376 | autoRouteHandler := container.NewAutoRouteHandler(ctx, svc.configSvc, svc.containerSvc, svc.blobStorage, cfg.Server.RegistryDomain). | ||
| 1352 | WithEnvExtractor(svc.runtime, svc.envDir) | 1377 | WithEnvExtractor(svc.runtime, svc.envDir) | ||
| 1353 | - | if err := svc.eventBus.Subscribe(autoRouteHandler); err != nil { | 1378 | + | |
| 1354 | - | return nil, fmt.Errorf("failed to subscribe auto-route handler: %w", err) | 1379 | + | // Preview handler for creating preview environments from tagged images |
| 1380 | + | autoPreviewHandler := preview.NewAutoPreviewHandler( | |||
| 1381 | + | ctx, | |||
| 1382 | + | svc.configSvc, | |||
| 1383 | + | svc.blobStorage, | |||
| 1384 | + | svc.previewService, | |||
| 1385 | + | cfg.Server.RegistryDomain, | |||
| 1386 | + | ) | |||
| 1387 | + | ||||
| 1388 | + | // Dispatcher routes image push events to either auto-route or preview handler | |||
| 1389 | + | dispatcher := auto.NewImagePushDispatcher(svc.configSvc, autoRouteHandler, autoPreviewHandler) | |||
| 1390 | + | if err := svc.eventBus.Subscribe(dispatcher); err != nil { | |||
| 1391 | + | return nil, fmt.Errorf("subscribe image push dispatcher: %w", err) | |||
| 1355 | } | 1392 | } | ||
| 1356 | 1393 | ||||
| 1357 | configReloadHandler := container.NewConfigReloadHandler(ctx, svc.containerSvc, svc.configSvc) | 1394 | configReloadHandler := container.NewConfigReloadHandler(ctx, svc.containerSvc, svc.configSvc) | ||