123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- package app
- import (
- "context"
- "fmt"
- "net/http"
- "sync"
- "time"
- "git.linuxforward.com/byop/byop-engine/auth"
- "git.linuxforward.com/byop/byop-engine/clients"
- "git.linuxforward.com/byop/byop-engine/config"
- "git.linuxforward.com/byop/byop-engine/dbstore"
- "git.linuxforward.com/byop/byop-engine/handlers"
- mw "git.linuxforward.com/byop/byop-engine/middleware"
- "git.linuxforward.com/byop/byop-engine/services"
- "github.com/gin-gonic/gin"
- "github.com/pkg/errors"
- "github.com/sirupsen/logrus"
- )
- type App struct {
- entry *logrus.Entry
- cnf *config.Config
- ctx context.Context
- cancelFunc context.CancelFunc
- rtr *gin.Engine
- // Database
- database *dbstore.SQLiteStore
- // Services
- authService auth.Service
- tokenStore auth.TokenStore
- // Clients
- buildkitClient clients.BuildMachineClient // BuildKit client for build operations
- registryClient clients.RegistryClient // Docker registry client for pushing images
- // Handlers
- authHandler *handlers.AuthHandler
- userHandler *handlers.UserHandler
- clientHandler *handlers.ClientHandler
- componentHandler *handlers.ComponentHandler // Renamed from appHandler
- appHandler *handlers.AppsHandler // Renamed from templateHandler
- deploymentHandler *handlers.DeploymentHandler
- previewHandler *handlers.PreviewHandler
- // Preview Service
- previewService services.PreviewService
- builderService *services.Builder
- appImporter *services.AppImporter
- // Resource Handlers
- providerHandler *handlers.ProviderHandler
- // ticketHandler *handlers.TicketHandler
- stopped bool
- wg sync.WaitGroup
- // monitoringHandler *handlers.MonitoringHandler
- }
- func NewApp(cnf *config.Config) (*App, error) {
- ctx, cancelFunc := context.WithCancel(context.Background())
- app := &App{
- entry: logrus.WithField("component", "app"),
- cnf: cnf,
- ctx: ctx,
- cancelFunc: cancelFunc,
- }
- // Initialize router first
- if cnf.Debug {
- // gin.SetMode(gin.ReleaseMode)
- // gin.SetMode(gin.DebugMode)
- logrus.SetLevel(logrus.DebugLevel)
- } else {
- // Set gin to release mode for production
- // This will disable debug logs and enable performance optimizations
- gin.SetMode(gin.ReleaseMode)
- }
- app.rtr = gin.New()
- // Disable automatic redirection of trailing slashes
- // This prevents 301 redirects that can cause CORS issues
- app.rtr.RedirectTrailingSlash = false
- app.rtr.RedirectFixedPath = false
- app.rtr.Use(gin.Recovery())
- // app.rtr.Use(mw.Logger)
- // Add CORS middleware to handle cross-origin requests
- app.rtr.Use(mw.CORS())
- // Initialize clients
- app.buildkitClient = clients.NewDockerfileBuilder(cnf.BuildkitHost)
- app.entry.Info("Dockerfile builder client initialized")
- app.registryClient = clients.NewSimpleRegistryClient(app.buildkitClient)
- app.entry.Info("Registry client initialized")
- // Initialize services and handlers
- if err := app.initCommonServices(); err != nil {
- return nil, errors.Wrap(err, "initialize services")
- }
- if err := app.initHandlers(); err != nil {
- return nil, errors.Wrap(err, "initialize handlers")
- }
- // Set up routes after all handlers are initialized
- app.setupRoutes()
- return app, nil
- }
- // Shutdown gracefully shuts down the application and its resources.
- func (a *App) Shutdown(ctx context.Context) error {
- a.entry.Info("Initiating graceful shutdown...")
- // Signal all goroutines to stop
- a.cancelFunc() // This will close a.ctx
- // Close services
- if a.previewService != nil {
- a.entry.Info("Closing preview service...")
- a.previewService.Close(ctx) // Assuming Close() is idempotent or handles multiple calls gracefully
- a.entry.Info("Preview service closed.")
- }
- if a.tokenStore != nil {
- if closer, ok := a.tokenStore.(interface{ Close() error }); ok {
- a.entry.Info("Closing token store...")
- if err := closer.Close(); err != nil {
- a.entry.WithError(err).Error("Failed to close token store")
- // Potentially return this error or aggregate errors
- } else {
- a.entry.Info("Token store closed.")
- }
- }
- }
- // Close database connection
- if a.database != nil {
- a.entry.Info("Closing database connection...")
- if err := a.database.Close(); err != nil {
- a.entry.WithError(err).Error("Failed to close database connection")
- // Potentially return this error or aggregate errors
- } else {
- a.entry.Info("Database connection closed.")
- }
- }
- // Wait for any other background goroutines managed by the app to finish
- // a.wg.Wait() // Uncomment if you use sync.WaitGroup for other app-managed goroutines
- a.entry.Info("Graceful shutdown completed.")
- return nil
- }
- func (a *App) Run() error {
- // The main HTTP server instance for the application.
- // This will be shut down by the logic in main.go.
- srv := &http.Server{
- Addr: fmt.Sprintf(":%d", a.cnf.Server.Port),
- Handler: a.rtr,
- }
- // This goroutine will block until the server is shut down or an error occurs.
- // The actual shutdown signal is handled in main.go.
- go func() {
- select {
- case <-a.ctx.Done(): // Listen for the app context cancellation
- a.entry.Info("App context cancelled, initiating server shutdown from Run()...")
- // Context for server shutdown, can be different from app context if needed
- shutdownCtx, cancelShutdown := context.WithTimeout(context.Background(), 15*time.Second)
- defer cancelShutdown()
- if err := srv.Shutdown(shutdownCtx); err != nil {
- a.entry.WithError(err).Error("HTTP server shutdown error in Run() after context cancellation")
- }
- return
- }
- }()
- a.entry.WithField("address", srv.Addr).Infof("Starting server on port %d", a.cnf.Server.Port)
- var err error
- if a.cnf.Server.Tls.Enabled {
- a.entry.Info("Starting server with TLS...")
- err = srv.ListenAndServeTLS(a.cnf.Server.Tls.CertFile, a.cnf.Server.Tls.KeyFile)
- } else {
- a.entry.Info("Starting server without TLS...")
- err = srv.ListenAndServe()
- }
- if err != nil && err != http.ErrServerClosed {
- a.entry.WithError(err).Errorf("Server ListenAndServe error")
- return fmt.Errorf("failed to start server: %w", err)
- }
- a.entry.Info("Server Run() method finished.")
- return nil // http.ErrServerClosed is a normal error on shutdown
- }
|