2023-12-21 16:04:04 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"git.myservermanager.com/varakh/upda/util"
|
|
|
|
"github.com/gin-contrib/cors"
|
|
|
|
ginzap "github.com/gin-contrib/zap"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"go.uber.org/zap"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"os/signal"
|
|
|
|
"syscall"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
func Start() {
|
|
|
|
// configuration init
|
|
|
|
env := bootstrapEnvironment()
|
|
|
|
|
|
|
|
// secure init
|
|
|
|
util.AssertAvailablePRNG()
|
|
|
|
|
2024-01-24 22:21:48 +00:00
|
|
|
// set gin mode derived
|
|
|
|
if env.appConfig.isDevelopment {
|
2023-12-21 16:04:04 +00:00
|
|
|
gin.SetMode(gin.DebugMode)
|
|
|
|
} else {
|
|
|
|
gin.SetMode(gin.ReleaseMode)
|
|
|
|
}
|
|
|
|
|
|
|
|
router := gin.New()
|
|
|
|
router.Use(ginzap.Ginzap(zap.L(), time.RFC3339, false))
|
|
|
|
router.Use(ginzap.RecoveryWithZap(zap.L(), true))
|
|
|
|
|
|
|
|
// metrics
|
|
|
|
prometheusService := newPrometheusService(router, env.prometheusConfig)
|
|
|
|
|
|
|
|
if env.prometheusConfig.enabled {
|
|
|
|
prometheusService.init()
|
|
|
|
router.Use(prometheusService.prometheus.Instrument())
|
|
|
|
}
|
|
|
|
|
|
|
|
updateRepo := newUpdateDbRepo(env.db)
|
|
|
|
webhookRepo := newWebhookDbRepo(env.db)
|
|
|
|
eventRepo := newEventDbRepo(env.db)
|
|
|
|
|
2023-12-22 12:13:44 +00:00
|
|
|
lockService := newLockMemService()
|
|
|
|
|
2023-12-21 16:04:04 +00:00
|
|
|
eventService := newEventService(eventRepo)
|
2023-12-22 12:13:44 +00:00
|
|
|
updateService := newUpdateService(updateRepo, eventService, prometheusService)
|
2023-12-21 16:04:04 +00:00
|
|
|
webhookService := newWebhookService(webhookRepo, env.webhookConfig, eventService)
|
|
|
|
webhookInvocationService := newWebhookInvocationService(webhookService, updateService, env.webhookConfig)
|
|
|
|
|
2023-12-22 12:13:44 +00:00
|
|
|
taskService := newTaskService(updateService, eventService, webhookService, lockService, prometheusService, env.appConfig, env.taskConfig, env.lockConfig, env.prometheusConfig)
|
2023-12-21 16:04:04 +00:00
|
|
|
taskService.init()
|
|
|
|
taskService.start()
|
|
|
|
|
|
|
|
updateHandler := newUpdateHandler(updateService, env.appConfig)
|
|
|
|
webhookHandler := newWebhookHandler(webhookService)
|
|
|
|
webhookInvocationHandler := newWebhookInvocationHandler(webhookInvocationService, webhookService)
|
|
|
|
eventHandler := newEventHandler(eventService)
|
|
|
|
infoHandler := newInfoHandler(env.appConfig)
|
|
|
|
healthHandler := newHealthHandler()
|
|
|
|
authHandler := newAuthHandler()
|
|
|
|
|
|
|
|
router.Use(middlewareAppName())
|
|
|
|
router.Use(middlewareAppVersion())
|
|
|
|
router.Use(middlewareAppContentType())
|
|
|
|
router.Use(middlewareErrorHandler())
|
|
|
|
router.Use(middlewareAppErrorRecoveryHandler())
|
|
|
|
|
|
|
|
router.Use(cors.New(cors.Config{
|
|
|
|
AllowOrigins: env.serverConfig.corsAllowOrigin,
|
|
|
|
AllowMethods: env.serverConfig.corsAllowMethods,
|
|
|
|
AllowHeaders: env.serverConfig.corsAllowHeaders,
|
|
|
|
AllowCredentials: true,
|
|
|
|
}))
|
|
|
|
|
|
|
|
apiPublicGroup := router.Group("/api/v1")
|
|
|
|
apiPublicGroup.GET("/health", healthHandler.showHealth)
|
|
|
|
apiPublicGroup.GET("/info", infoHandler.showInfo)
|
|
|
|
|
|
|
|
apiPublicGroup.POST("/webhooks/:id", webhookInvocationHandler.executeWebhookGeneric)
|
|
|
|
|
|
|
|
apiAuthGroup := router.Group("/api/v1", gin.BasicAuth(gin.Accounts{
|
|
|
|
env.authConfig.adminUser: env.authConfig.adminPassword,
|
|
|
|
}))
|
|
|
|
|
|
|
|
apiAuthGroup.GET("/login", authHandler.login)
|
|
|
|
|
|
|
|
apiAuthGroup.GET("/updates", updateHandler.paginate)
|
|
|
|
apiAuthGroup.GET("/updates/:id", updateHandler.get)
|
|
|
|
apiAuthGroup.PATCH("/updates/:id/state", updateHandler.updateState)
|
|
|
|
apiAuthGroup.DELETE("/updates/:id", updateHandler.delete)
|
|
|
|
|
|
|
|
apiAuthGroup.GET("/webhooks", webhookHandler.paginate)
|
|
|
|
apiAuthGroup.POST("/webhooks", webhookHandler.create)
|
|
|
|
apiAuthGroup.PATCH("/webhooks/:id/label", webhookHandler.updateLabel)
|
|
|
|
apiAuthGroup.PATCH("/webhooks/:id/ignore-host", webhookHandler.updateIgnoreHost)
|
|
|
|
apiAuthGroup.DELETE("/webhooks/:id", webhookHandler.delete)
|
|
|
|
|
|
|
|
apiAuthGroup.GET("/events", eventHandler.window)
|
|
|
|
apiAuthGroup.DELETE("/events/:id", eventHandler.delete)
|
|
|
|
|
|
|
|
// start server
|
|
|
|
serverAddress := fmt.Sprintf("%s:%d", env.serverConfig.listen, env.serverConfig.port)
|
|
|
|
srv := &http.Server{
|
|
|
|
Addr: serverAddress,
|
|
|
|
Handler: router,
|
|
|
|
}
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
if env.serverConfig.tlsEnabled {
|
|
|
|
err = srv.ListenAndServeTLS(env.serverConfig.tlsCertPath, env.serverConfig.tlsKeyPath)
|
|
|
|
} else {
|
|
|
|
err = srv.ListenAndServe()
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
|
|
zap.L().Sugar().Fatalf("Application cannot be started: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// gracefully handle shut down
|
|
|
|
// Wait for interrupt signal to gracefully shut down the server with
|
|
|
|
// a timeout of x seconds.
|
|
|
|
quit := make(chan os.Signal)
|
|
|
|
// kill (no param) default send syscall.SIGTERM
|
|
|
|
// kill -2 is syscall.SIGINT
|
|
|
|
// kill -9 is syscall. SIGKILL but cannot be caught, thus no need to add
|
|
|
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
<-quit
|
|
|
|
zap.L().Info("Shutting down...")
|
|
|
|
taskService.stop()
|
|
|
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), env.serverConfig.timeout)
|
|
|
|
defer cancel()
|
|
|
|
if err := srv.Shutdown(ctx); err != nil {
|
|
|
|
zap.L().Sugar().Fatalf("Shutdown failed, exited directly: %v", err)
|
|
|
|
}
|
|
|
|
// catching ctx.Done() for configured timeout
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
zap.L().Sugar().Infof("Shutdown timeout of '%v' expired, exiting...", env.serverConfig.timeout)
|
|
|
|
|
|
|
|
}
|
|
|
|
zap.L().Info("Exited")
|
|
|
|
}
|