This commit is contained in:
hhftechnologies
2025-05-19 09:31:07 +05:30
parent f5f870559e
commit 8f777cdafa

146
main.go
View File

@@ -25,43 +25,40 @@ type Plugin struct {
IconPath string `json:"iconPath"` IconPath string `json:"iconPath"`
Import string `json:"import"` Import string `json:"import"`
Summary string `json:"summary"` Summary string `json:"summary"`
// Add any other fields that might be in your JSON, e.g., author, version
Author string `json:"author,omitempty"` Author string `json:"author,omitempty"`
Version string `json:"version,omitempty"` Version string `json:"version,omitempty"`
TestedWith string `json:"tested_with,omitempty"` // Example: "Traefik v2.10+" TestedWith string `json:"tested_with,omitempty"`
Stars int `json:"stars,omitempty"` // Example: GitHub stars Stars int `json:"stars,omitempty"`
Homepage string `json:"homepage,omitempty"` // Example: Link to plugin's GitHub repo Homepage string `json:"homepage,omitempty"`
Docs string `json:"docs,omitempty"` // Example: Link to plugin's documentation Docs string `json:"docs,omitempty"`
} }
// Configuration represents the application configuration // Configuration represents the application configuration
type Configuration struct { type Configuration struct {
PangolinAPIURL string PangolinAPIURL string
TraefikAPIURL string TraefikAPIURL string
TraefikConfDir string TraefikConfDir string
DBPath string DBPath string
Port string Port string
UIPath string UIPath string
ConfigDir string ConfigDir string
CheckInterval time.Duration CheckInterval time.Duration
GenerateInterval time.Duration GenerateInterval time.Duration
ServiceInterval time.Duration // New field for service check interval ServiceInterval time.Duration
Debug bool Debug bool
AllowCORS bool AllowCORS bool
CORSOrigin string CORSOrigin string
ActiveDataSource string ActiveDataSource string
TraefikStaticConfigPath string // New: Path to traefik_config.yml or traefik.yml TraefikStaticConfigPath string
PluginsJSONURL string // New: URL to the plugins.json file PluginsJSONURL string
} }
// DiscoverTraefikAPI attempts to discover the Traefik API by trying common URLs // DiscoverTraefikAPI attempts to discover the Traefik API by trying common URLs
func DiscoverTraefikAPI() (string, error) { func DiscoverTraefikAPI() (string, error) {
client := &http.Client{ client := &http.Client{
Timeout: 2 * time.Second, // Short timeout for discovery Timeout: 2 * time.Second,
} }
// Common URLs to try
urls := []string{ urls := []string{
"http://host.docker.internal:8080", "http://host.docker.internal:8080",
"http://localhost:8080", "http://localhost:8080",
@@ -81,22 +78,18 @@ func DiscoverTraefikAPI() (string, error) {
resp.Body.Close() resp.Body.Close()
} }
} }
return "", nil
return "", nil // Return empty string without error to allow fallbacks
} }
func main() { func main() {
log.Println("Starting Middleware Manager...") log.Println("Starting Middleware Manager...")
// Parse command line flags
var debug bool var debug bool
flag.BoolVar(&debug, "debug", false, "Enable debug mode") flag.BoolVar(&debug, "debug", false, "Enable debug mode")
flag.Parse() flag.Parse()
// Load configuration
cfg := loadConfiguration(debug) cfg := loadConfiguration(debug)
// Try to discover Traefik API URL if not set in environment
if os.Getenv("TRAEFIK_API_URL") == "" { if os.Getenv("TRAEFIK_API_URL") == "" {
if discoveredURL, err := DiscoverTraefikAPI(); err == nil && discoveredURL != "" { if discoveredURL, err := DiscoverTraefikAPI(); err == nil && discoveredURL != "" {
log.Printf("Auto-discovered Traefik API URL: %s", discoveredURL) log.Printf("Auto-discovered Traefik API URL: %s", discoveredURL)
@@ -104,53 +97,51 @@ func main() {
} }
} }
// Initialize database
db, err := database.InitDB(cfg.DBPath) db, err := database.InitDB(cfg.DBPath)
if err != nil { if err != nil {
log.Fatalf("Failed to initialize database: %v", err) log.Fatalf("Failed to initialize database: %v", err)
} }
defer db.Close() defer db.Close()
// Ensure config directory exists
configDir := cfg.ConfigDir configDir := cfg.ConfigDir
if err := config.EnsureConfigDirectory(configDir); err != nil { if err := config.EnsureConfigDirectory(configDir); err != nil {
log.Printf("Warning: Failed to create config directory: %v", err) log.Printf("Warning: Failed to create config directory: %v", err)
} }
// Save default templates file if it doesn't exist
if err := config.SaveTemplateFile(configDir); err != nil { if err := config.SaveTemplateFile(configDir); err != nil {
log.Printf("Warning: Failed to save default templates: %v", err) log.Printf("Warning: Failed to save default middleware templates: %v", err)
} }
// Load default middleware templates
if err := config.LoadDefaultTemplates(db); err != nil { if err := config.LoadDefaultTemplates(db); err != nil {
log.Printf("Warning: Failed to load default templates: %v", err) log.Printf("Warning: Failed to load default middleware templates: %v", err)
}
if err := config.SaveTemplateServicesFile(configDir); err != nil {
log.Printf("Warning: Failed to save default service templates: %v", err)
}
if err := config.LoadDefaultServiceTemplates(db); err != nil {
log.Printf("Warning: Failed to load default service templates: %v", err)
} }
// Initialize config manager
configManager, err := services.NewConfigManager(filepath.Join(configDir, "config.json")) configManager, err := services.NewConfigManager(filepath.Join(configDir, "config.json"))
if err != nil { if err != nil {
log.Fatalf("Failed to initialize config manager: %v", err) log.Fatalf("Failed to initialize config manager: %v", err)
} }
// Ensure default data sources are configured with potentially discovered URL
configManager.EnsureDefaultDataSources(cfg.PangolinAPIURL, cfg.TraefikAPIURL) configManager.EnsureDefaultDataSources(cfg.PangolinAPIURL, cfg.TraefikAPIURL)
// Create stop channel for graceful shutdown
stopChan := make(chan struct{}) stopChan := make(chan struct{})
// Start resource watcher with config manager
resourceWatcher, err := services.NewResourceWatcher(db, configManager) resourceWatcher, err := services.NewResourceWatcher(db, configManager)
if err != nil { if err != nil {
log.Fatalf("Failed to create resource watcher: %v", err) log.Fatalf("Failed to create resource watcher: %v", err)
} }
go resourceWatcher.Start(cfg.CheckInterval) go resourceWatcher.Start(cfg.CheckInterval)
// Start configuration generator
configGenerator := services.NewConfigGenerator(db, cfg.TraefikConfDir, configManager) configGenerator := services.NewConfigGenerator(db, cfg.TraefikConfDir, configManager)
go configGenerator.Start(cfg.GenerateInterval) go configGenerator.Start(cfg.GenerateInterval)
// Start API server
serverConfig := api.ServerConfig{ serverConfig := api.ServerConfig{
Port: cfg.Port, Port: cfg.Port,
UIPath: cfg.UIPath, UIPath: cfg.UIPath,
@@ -159,19 +150,25 @@ func main() {
CORSOrigin: cfg.CORSOrigin, CORSOrigin: cfg.CORSOrigin,
} }
// so handlers can access TraefikStaticConfigPath and PluginsJSONURL
server := api.NewServer(db.DB, serverConfig, configManager, cfg.TraefikStaticConfigPath, cfg.PluginsJSONURL) server := api.NewServer(db.DB, serverConfig, configManager, cfg.TraefikStaticConfigPath, cfg.PluginsJSONURL)
go func() { go func() {
if err := server.Start(); err != nil { if err := server.Start(); err != nil {
log.Printf("Server error: %v", err) log.Printf("Server error: %v", err)
close(stopChan) // Ensure stopChan is closed on server error close(stopChan)
} }
}() }()
// Wait for shutdown signal or server error
signalChan := make(chan os.Signal, 1) signalChan := make(chan os.Signal, 1)
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM) signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
serviceWatcher, err := services.NewServiceWatcher(db, configManager)
if err != nil {
log.Printf("Warning: Failed to create service watcher: %v", err)
serviceWatcher = nil
} else {
go serviceWatcher.Start(cfg.ServiceInterval)
}
select { select {
case <-signalChan: case <-signalChan:
log.Println("Received shutdown signal") log.Println("Received shutdown signal")
@@ -179,29 +176,17 @@ func main() {
log.Println("Received stop signal from server") log.Println("Received stop signal from server")
} }
// Start service watcher with config manager
serviceWatcher, err := services.NewServiceWatcher(db, configManager)
if err != nil {
log.Printf("Warning: Failed to create service watcher: %v", err)
} else {
// Use the same interval as the resource watcher, or a different one if preferred
go serviceWatcher.Start(cfg.CheckInterval)
}
// Graceful shutdown
log.Println("Shutting down...") log.Println("Shutting down...")
resourceWatcher.Stop() resourceWatcher.Stop()
configGenerator.Stop() if serviceWatcher != nil {
if serviceWatcher != nil { // Check if serviceWatcher was initialized
serviceWatcher.Stop() serviceWatcher.Stop()
} }
server.Stop() // Ensure server has a Stop method // Add this line configGenerator.Stop()
server.Stop()
log.Println("Middleware Manager stopped") log.Println("Middleware Manager stopped")
} }
// loadConfiguration loads configuration from environment variables
func loadConfiguration(debug bool) Configuration { func loadConfiguration(debug bool) Configuration {
// Default check interval is 30 seconds
checkInterval := 30 * time.Second checkInterval := 30 * time.Second
if intervalStr := getEnv("CHECK_INTERVAL_SECONDS", "30"); intervalStr != "" { if intervalStr := getEnv("CHECK_INTERVAL_SECONDS", "30"); intervalStr != "" {
if interval, err := strconv.Atoi(intervalStr); err == nil && interval > 0 { if interval, err := strconv.Atoi(intervalStr); err == nil && interval > 0 {
@@ -209,7 +194,6 @@ func loadConfiguration(debug bool) Configuration {
} }
} }
// Default generate interval is 10 seconds
generateInterval := 10 * time.Second generateInterval := 10 * time.Second
if intervalStr := getEnv("GENERATE_INTERVAL_SECONDS", "10"); intervalStr != "" { if intervalStr := getEnv("GENERATE_INTERVAL_SECONDS", "10"); intervalStr != "" {
if interval, err := strconv.Atoi(intervalStr); err == nil && interval > 0 { if interval, err := strconv.Atoi(intervalStr); err == nil && interval > 0 {
@@ -217,38 +201,42 @@ func loadConfiguration(debug bool) Configuration {
} }
} }
// Allow CORS if specified parsedServiceInterval := 30 * time.Second
if intervalStr := getEnv("SERVICE_INTERVAL_SECONDS", "30"); intervalStr != "" {
if interval, err := strconv.Atoi(intervalStr); err == nil && interval > 0 {
parsedServiceInterval = time.Duration(interval) * time.Second
}
}
allowCORS := false allowCORS := false
if corsStr := getEnv("ALLOW_CORS", "false"); corsStr != "" { if corsStr := getEnv("ALLOW_CORS", "false"); corsStr != "" {
allowCORS = strings.ToLower(corsStr) == "true" allowCORS = strings.ToLower(corsStr) == "true"
} }
// Override debug mode from environment if specified
if debugStr := getEnv("DEBUG", ""); debugStr != "" { if debugStr := getEnv("DEBUG", ""); debugStr != "" {
debug = strings.ToLower(debugStr) == "true" debug = strings.ToLower(debugStr) == "true"
} }
return Configuration{ return Configuration{
PangolinAPIURL: getEnv("PANGOLIN_API_URL", "http://pangolin:3001/api/v1"), PangolinAPIURL: getEnv("PANGOLIN_API_URL", "http://pangolin:3001/api/v1"),
// Changed to use host.docker.internal as first default to better support Docker environments TraefikAPIURL: getEnv("TRAEFIK_API_URL", "http://host.docker.internal:8080"),
TraefikAPIURL: getEnv("TRAEFIK_API_URL", "http://host.docker.internal:8080"), TraefikConfDir: getEnv("TRAEFIK_CONF_DIR", "/conf"),
TraefikConfDir: getEnv("TRAEFIK_CONF_DIR", "/conf"), DBPath: getEnv("DB_PATH", "/data/middleware.db"),
DBPath: getEnv("DB_PATH", "/data/middleware.db"), Port: getEnv("PORT", "3456"),
Port: getEnv("PORT", "3456"), UIPath: getEnv("UI_PATH", "/app/ui/build"),
UIPath: getEnv("UI_PATH", "/app/ui/build"), ConfigDir: getEnv("CONFIG_DIR", "/app/config"),
ConfigDir: getEnv("CONFIG_DIR", "/app/config"), ActiveDataSource: getEnv("ACTIVE_DATA_SOURCE", "pangolin"),
ActiveDataSource: getEnv("ACTIVE_DATA_SOURCE", "pangolin"), CheckInterval: checkInterval,
CheckInterval: checkInterval, GenerateInterval: generateInterval,
GenerateInterval: generateInterval, ServiceInterval: parsedServiceInterval,
Debug: debug, Debug: debug,
AllowCORS: allowCORS, AllowCORS: allowCORS,
CORSOrigin: getEnv("CORS_ORIGIN", ""), CORSOrigin: getEnv("CORS_ORIGIN", ""),
TraefikStaticConfigPath: getEnv("TRAEFIK_STATIC_CONFIG_PATH", "/etc/traefik/traefik.yml"), // New TraefikStaticConfigPath: getEnv("TRAEFIK_STATIC_CONFIG_PATH", "/etc/traefik/traefik.yml"),
PluginsJSONURL: getEnv("PLUGINS_JSON_URL", "https://raw.githubusercontent.com/hhftechnology/middleware-manager/traefik-int/plugin/plugins.json"), // New PluginsJSONURL: getEnv("PLUGINS_JSON_URL", "https://raw.githubusercontent.com/hhftechnology/middleware-manager/traefik-int/plugin/plugins.json"),
} }
} }
// getEnv gets an environment variable or returns a default value
func getEnv(key, fallback string) string { func getEnv(key, fallback string) string {
if value, exists := os.LookupEnv(key); exists { if value, exists := os.LookupEnv(key); exists {
return value return value