- Changed the build process to include a web UI build stage using Node.js. - Updated Go build stage to copy web UI files to the correct location. - Removed the main.go file as it is no longer needed. - Added SQLite database configuration to example config. - Updated dependencies in go.mod and go.sum, including new packages for JWT and SQLite. - Modified .gitignore to include new database and configuration files. Signed-off-by: zhenyus <zhenyus@mathmast.com>
266 lines
7.2 KiB
Go
266 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"syscall"
|
|
"time"
|
|
|
|
"freeleaps.com/gitea-webhook-ambassador/internal/auth"
|
|
"freeleaps.com/gitea-webhook-ambassador/internal/config"
|
|
"freeleaps.com/gitea-webhook-ambassador/internal/database"
|
|
"freeleaps.com/gitea-webhook-ambassador/internal/handler"
|
|
"freeleaps.com/gitea-webhook-ambassador/internal/jenkins"
|
|
"freeleaps.com/gitea-webhook-ambassador/internal/logger"
|
|
"freeleaps.com/gitea-webhook-ambassador/internal/web"
|
|
webhandler "freeleaps.com/gitea-webhook-ambassador/internal/web/handler"
|
|
"freeleaps.com/gitea-webhook-ambassador/internal/worker"
|
|
)
|
|
|
|
var (
|
|
configFile = flag.String("config", "config.yaml", "Path to configuration file")
|
|
)
|
|
|
|
func main() {
|
|
flag.Parse()
|
|
|
|
// Initialize logger with default configuration
|
|
logger.Configure(logger.Config{
|
|
Level: "info",
|
|
Format: "text",
|
|
})
|
|
|
|
// Load initial configuration
|
|
if err := config.Load(*configFile); err != nil {
|
|
logger.Error("Failed to load configuration: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Setup application
|
|
app, err := setupApplication()
|
|
if err != nil {
|
|
logger.Error("Failed to setup application: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
defer app.cleanup()
|
|
|
|
// Start HTTP server
|
|
go app.startServer()
|
|
|
|
// Handle graceful shutdown
|
|
app.handleShutdown()
|
|
}
|
|
|
|
type application struct {
|
|
server *http.Server
|
|
workerPool *worker.Pool
|
|
db *database.DB
|
|
watcher *config.Watcher
|
|
}
|
|
|
|
func setupApplication() (*application, error) {
|
|
cfg := config.Get()
|
|
|
|
// Configure logger based on configuration
|
|
logger.Configure(logger.Config{
|
|
Level: cfg.Logging.Level,
|
|
Format: cfg.Logging.Format,
|
|
File: cfg.Logging.File,
|
|
})
|
|
|
|
// Ensure database directory exists
|
|
dbDir := filepath.Dir(cfg.Database.Path)
|
|
if err := os.MkdirAll(dbDir, 0755); err != nil {
|
|
return nil, fmt.Errorf("failed to create database directory: %v", err)
|
|
}
|
|
|
|
// Initialize database
|
|
db, err := setupDatabase(cfg)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to setup database: %v", err)
|
|
}
|
|
|
|
// Create Jenkins client
|
|
jenkinsClient := jenkins.New(jenkins.Config{
|
|
URL: cfg.Jenkins.URL,
|
|
Username: cfg.Jenkins.Username,
|
|
Token: cfg.Jenkins.Token,
|
|
Timeout: time.Duration(cfg.Jenkins.Timeout) * time.Second,
|
|
})
|
|
|
|
// Create worker pool
|
|
workerPool, err := setupWorkerPool(cfg, jenkinsClient, db)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to setup worker pool: %v", err)
|
|
}
|
|
|
|
// Setup config watcher
|
|
watcher, err := setupConfigWatcher(*configFile)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to setup config watcher: %v", err)
|
|
}
|
|
|
|
if err := watcher.Start(); err != nil {
|
|
return nil, fmt.Errorf("failed to start config watcher: %v", err)
|
|
}
|
|
|
|
// Create HTTP server
|
|
server := setupHTTPServer(cfg, workerPool, db)
|
|
|
|
return &application{
|
|
server: server,
|
|
workerPool: workerPool,
|
|
db: db,
|
|
watcher: watcher,
|
|
}, nil
|
|
}
|
|
|
|
func setupDatabase(cfg config.Configuration) (*database.DB, error) {
|
|
return database.New(database.Config{
|
|
Path: cfg.Database.Path,
|
|
})
|
|
}
|
|
|
|
func setupWorkerPool(cfg config.Configuration, jenkinsClient *jenkins.Client, db *database.DB) (*worker.Pool, error) {
|
|
pool, err := worker.New(worker.Config{
|
|
PoolSize: cfg.Worker.PoolSize,
|
|
QueueSize: cfg.Worker.QueueSize,
|
|
MaxRetries: cfg.Worker.MaxRetries,
|
|
RetryBackoff: time.Duration(cfg.Worker.RetryBackoff) * time.Second,
|
|
Client: jenkinsClient,
|
|
DB: db,
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Start event cleanup
|
|
go worker.CleanupEvents(time.Duration(cfg.EventCleanup.ExpireAfter) * time.Second)
|
|
|
|
return pool, nil
|
|
}
|
|
|
|
func setupConfigWatcher(configPath string) (*config.Watcher, error) {
|
|
return config.NewWatcher(configPath, func() error {
|
|
if err := config.Load(configPath); err != nil {
|
|
return err
|
|
}
|
|
newCfg := config.Get()
|
|
|
|
// Update logger configuration
|
|
logger.Configure(logger.Config{
|
|
Level: newCfg.Logging.Level,
|
|
Format: newCfg.Logging.Format,
|
|
File: newCfg.Logging.File,
|
|
})
|
|
|
|
logger.Info("Configuration reloaded successfully")
|
|
return nil
|
|
})
|
|
}
|
|
|
|
func setupHTTPServer(cfg config.Configuration, workerPool *worker.Pool, db *database.DB) *http.Server {
|
|
// Create handlers
|
|
webhookHandler := handler.NewWebhookHandler(workerPool, db, &cfg)
|
|
healthHandler := handler.NewHealthHandler(workerPool, &cfg)
|
|
adminHandler := handler.NewAdminHandler(db, &cfg)
|
|
projectHandler := handler.NewProjectHandler(db, &cfg)
|
|
logsHandler := handler.NewLogsHandler(db, &cfg)
|
|
|
|
// Create auth middleware
|
|
authMiddleware := auth.NewMiddleware(cfg.Server.SecretKey)
|
|
|
|
// Create dashboard handler
|
|
dashboardHandler, err := webhandler.NewDashboardHandler(
|
|
web.WebAssets,
|
|
projectHandler,
|
|
adminHandler,
|
|
logsHandler,
|
|
healthHandler,
|
|
)
|
|
if err != nil {
|
|
logger.Error("Failed to create dashboard handler: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Setup HTTP routes
|
|
mux := http.NewServeMux()
|
|
|
|
// Static file handlers (not protected by auth)
|
|
mux.HandleFunc("/css/", dashboardHandler.ServeHTTP)
|
|
mux.HandleFunc("/js/", dashboardHandler.ServeHTTP)
|
|
mux.HandleFunc("/img/", dashboardHandler.ServeHTTP)
|
|
|
|
// Webhook endpoint (not protected by auth, uses its own validation)
|
|
mux.HandleFunc(cfg.Server.WebhookPath, webhookHandler.HandleWebhook)
|
|
|
|
// Login routes - must be defined before protected routes
|
|
mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodGet {
|
|
dashboardHandler.ServeHTTP(w, r)
|
|
} else {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
})
|
|
mux.HandleFunc("/api/auth/login", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodPost {
|
|
authMiddleware.HandleLogin(w, r)
|
|
} else {
|
|
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
}
|
|
})
|
|
|
|
// Protected routes
|
|
mux.Handle("/", authMiddleware.Authenticate(dashboardHandler))
|
|
mux.Handle("/dashboard", authMiddleware.Authenticate(dashboardHandler))
|
|
|
|
// Protected API routes
|
|
mux.Handle("/api/projects", authMiddleware.Authenticate(http.HandlerFunc(projectHandler.HandleGetProjectMapping)))
|
|
mux.Handle("/api/admin/api-keys", authMiddleware.Authenticate(http.HandlerFunc(adminHandler.HandleListAPIKeys)))
|
|
mux.Handle("/api/admin/api-keys/delete", authMiddleware.Authenticate(http.HandlerFunc(adminHandler.HandleDeleteAPIKey)))
|
|
mux.Handle("/api/logs", authMiddleware.Authenticate(http.HandlerFunc(logsHandler.HandleGetTriggerLogs)))
|
|
mux.Handle("/api/health", authMiddleware.Authenticate(http.HandlerFunc(healthHandler.HandleHealth)))
|
|
|
|
return &http.Server{
|
|
Addr: fmt.Sprintf(":%d", cfg.Server.Port),
|
|
Handler: mux,
|
|
ReadTimeout: 30 * time.Second,
|
|
WriteTimeout: 30 * time.Second,
|
|
IdleTimeout: 60 * time.Second,
|
|
}
|
|
}
|
|
|
|
func (app *application) startServer() {
|
|
logger.Info("Server listening on %s", app.server.Addr)
|
|
if err := app.server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
|
|
logger.Error("HTTP server error: %v", err)
|
|
os.Exit(1)
|
|
}
|
|
}
|
|
|
|
func (app *application) handleShutdown() {
|
|
stop := make(chan os.Signal, 1)
|
|
signal.Notify(stop, os.Interrupt, syscall.SIGTERM)
|
|
|
|
<-stop
|
|
logger.Info("Shutting down server...")
|
|
app.cleanup()
|
|
logger.Info("Server shutdown complete")
|
|
}
|
|
|
|
func (app *application) cleanup() {
|
|
if app.workerPool != nil {
|
|
app.workerPool.Release()
|
|
}
|
|
if app.db != nil {
|
|
app.db.Close()
|
|
}
|
|
if app.watcher != nil {
|
|
app.watcher.Stop()
|
|
}
|
|
}
|