Skip to content

Commit db18716

Browse files
Merge pull request #35 from hhftechnology/traefik-int
Integrate Traefik Services and Plugin Management
2 parents b6e04c8 + 9ee4a39 commit db18716

29 files changed

+1753
-506
lines changed

README.md

Lines changed: 287 additions & 406 deletions
Large diffs are not rendered by default.

api/handlers/plugin_handler.go

Lines changed: 416 additions & 0 deletions
Large diffs are not rendered by default.

api/server.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ type Server struct {
2727
configHandler *handlers.ConfigHandler
2828
dataSourceHandler *handlers.DataSourceHandler
2929
serviceHandler *handlers.ServiceHandler
30+
pluginHandler *handlers.PluginHandler // New handler
3031
configManager *services.ConfigManager
32+
traefikStaticConfigPath string // New
33+
pluginsJSONURL string // New
3134
}
3235

3336
// ServerConfig contains configuration options for the server
@@ -40,7 +43,7 @@ type ServerConfig struct {
4043
}
4144

4245
// NewServer creates a new API server
43-
func NewServer(db *sql.DB, config ServerConfig, configManager *services.ConfigManager) *Server {
46+
func NewServer(db *sql.DB, config ServerConfig, configManager *services.ConfigManager, traefikStaticConfigPath string, pluginsJSONURL string) *Server {
4447
// Set gin mode based on debug flag
4548
if !config.Debug {
4649
gin.SetMode(gin.ReleaseMode)
@@ -83,6 +86,8 @@ func NewServer(db *sql.DB, config ServerConfig, configManager *services.ConfigMa
8386
configHandler := handlers.NewConfigHandler(db)
8487
dataSourceHandler := handlers.NewDataSourceHandler(configManager)
8588
serviceHandler := handlers.NewServiceHandler(db)
89+
// Initialize PluginHandler, passing the path to traefik.yml and the plugins.json URL
90+
pluginHandler := handlers.NewPluginHandler(db, traefikStaticConfigPath, pluginsJSONURL)
8691

8792
// Setup server with all handlers
8893
server := &Server{
@@ -93,7 +98,10 @@ func NewServer(db *sql.DB, config ServerConfig, configManager *services.ConfigMa
9398
configHandler: configHandler,
9499
dataSourceHandler: dataSourceHandler,
95100
serviceHandler: serviceHandler,
101+
pluginHandler: pluginHandler, // Add to server struct
96102
configManager: configManager,
103+
traefikStaticConfigPath: traefikStaticConfigPath, // Store the path
104+
pluginsJSONURL: pluginsJSONURL, // Store the URL
97105
srv: &http.Server{
98106
Addr: ":" + config.Port,
99107
Handler: router,
@@ -174,6 +182,17 @@ func (s *Server) setupRoutes(uiPath string) {
174182
datasource.PUT("/:name", s.dataSourceHandler.UpdateDataSource)
175183
datasource.POST("/:name/test", s.dataSourceHandler.TestDataSourceConnection)
176184
}
185+
186+
// Plugin Hub Routes
187+
pluginsGroup := api.Group("/plugins")
188+
{
189+
pluginsGroup.GET("", s.pluginHandler.GetPlugins) // Endpoint to list plugins
190+
pluginsGroup.POST("/install", s.pluginHandler.InstallPlugin) // Endpoint to install a plugin
191+
pluginsGroup.DELETE("/remove", s.pluginHandler.RemovePlugin) // New Remove Endpoint
192+
pluginsGroup.GET("/configpath", s.pluginHandler.GetTraefikStaticConfigPath) // Endpoint to get current path
193+
pluginsGroup.PUT("/configpath", s.pluginHandler.UpdateTraefikStaticConfigPath) // Endpoint to update path
194+
195+
}
177196
}
178197

179198
// Serve the React app

main.go

Lines changed: 88 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -18,31 +18,47 @@ import (
1818
"github.com/hhftechnology/middleware-manager/services"
1919
)
2020

21+
// Plugin represents the structure of a plugin in the JSON file
22+
type Plugin struct {
23+
DisplayName string `json:"displayName"`
24+
Type string `json:"type"`
25+
IconPath string `json:"iconPath"`
26+
Import string `json:"import"`
27+
Summary string `json:"summary"`
28+
Author string `json:"author,omitempty"`
29+
Version string `json:"version,omitempty"`
30+
TestedWith string `json:"tested_with,omitempty"`
31+
Stars int `json:"stars,omitempty"`
32+
Homepage string `json:"homepage,omitempty"`
33+
Docs string `json:"docs,omitempty"`
34+
}
35+
2136
// Configuration represents the application configuration
2237
type Configuration struct {
23-
PangolinAPIURL string
24-
TraefikAPIURL string
25-
TraefikConfDir string
26-
DBPath string
27-
Port string
28-
UIPath string
29-
ConfigDir string
30-
CheckInterval time.Duration
31-
GenerateInterval time.Duration
32-
ServiceInterval time.Duration // New field for service check interval
33-
Debug bool
34-
AllowCORS bool
35-
CORSOrigin string
36-
ActiveDataSource string
38+
PangolinAPIURL string
39+
TraefikAPIURL string
40+
TraefikConfDir string
41+
DBPath string
42+
Port string
43+
UIPath string
44+
ConfigDir string
45+
CheckInterval time.Duration
46+
GenerateInterval time.Duration
47+
ServiceInterval time.Duration
48+
Debug bool
49+
AllowCORS bool
50+
CORSOrigin string
51+
ActiveDataSource string
52+
TraefikStaticConfigPath string
53+
PluginsJSONURL string
3754
}
3855

3956
// DiscoverTraefikAPI attempts to discover the Traefik API by trying common URLs
4057
func DiscoverTraefikAPI() (string, error) {
4158
client := &http.Client{
42-
Timeout: 2 * time.Second, // Short timeout for discovery
59+
Timeout: 2 * time.Second,
4360
}
4461

45-
// Common URLs to try
4662
urls := []string{
4763
"http://host.docker.internal:8080",
4864
"http://localhost:8080",
@@ -62,169 +78,165 @@ func DiscoverTraefikAPI() (string, error) {
6278
resp.Body.Close()
6379
}
6480
}
65-
66-
return "", nil // Return empty string without error to allow fallbacks
81+
return "", nil
6782
}
6883

6984
func main() {
7085
log.Println("Starting Middleware Manager...")
7186

72-
// Parse command line flags
7387
var debug bool
7488
flag.BoolVar(&debug, "debug", false, "Enable debug mode")
7589
flag.Parse()
7690

77-
// Load configuration
7891
cfg := loadConfiguration(debug)
7992

80-
// Try to discover Traefik API URL if not set in environment
8193
if os.Getenv("TRAEFIK_API_URL") == "" {
8294
if discoveredURL, err := DiscoverTraefikAPI(); err == nil && discoveredURL != "" {
8395
log.Printf("Auto-discovered Traefik API URL: %s", discoveredURL)
8496
cfg.TraefikAPIURL = discoveredURL
8597
}
8698
}
8799

88-
// Initialize database
89100
db, err := database.InitDB(cfg.DBPath)
90101
if err != nil {
91102
log.Fatalf("Failed to initialize database: %v", err)
92103
}
93104
defer db.Close()
94-
95-
// Ensure config directory exists
105+
96106
configDir := cfg.ConfigDir
97107
if err := config.EnsureConfigDirectory(configDir); err != nil {
98108
log.Printf("Warning: Failed to create config directory: %v", err)
99109
}
100-
101-
// Save default templates file if it doesn't exist
110+
102111
if err := config.SaveTemplateFile(configDir); err != nil {
103-
log.Printf("Warning: Failed to save default templates: %v", err)
112+
log.Printf("Warning: Failed to save default middleware templates: %v", err)
104113
}
105-
106-
// Load default middleware templates
114+
107115
if err := config.LoadDefaultTemplates(db); err != nil {
108-
log.Printf("Warning: Failed to load default templates: %v", err)
116+
log.Printf("Warning: Failed to load default middleware templates: %v", err)
117+
}
118+
119+
if err := config.SaveTemplateServicesFile(configDir); err != nil {
120+
log.Printf("Warning: Failed to save default service templates: %v", err)
121+
}
122+
123+
if err := config.LoadDefaultServiceTemplates(db); err != nil {
124+
log.Printf("Warning: Failed to load default service templates: %v", err)
109125
}
110126

111-
// Initialize config manager
112127
configManager, err := services.NewConfigManager(filepath.Join(configDir, "config.json"))
113128
if err != nil {
114129
log.Fatalf("Failed to initialize config manager: %v", err)
115130
}
116131

117-
// Ensure default data sources are configured with potentially discovered URL
118132
configManager.EnsureDefaultDataSources(cfg.PangolinAPIURL, cfg.TraefikAPIURL)
119133

120-
// Create stop channel for graceful shutdown
121134
stopChan := make(chan struct{})
122-
123-
// Start resource watcher with config manager
135+
124136
resourceWatcher, err := services.NewResourceWatcher(db, configManager)
125137
if err != nil {
126138
log.Fatalf("Failed to create resource watcher: %v", err)
127139
}
128140
go resourceWatcher.Start(cfg.CheckInterval)
129141

130-
// Start configuration generator
131142
configGenerator := services.NewConfigGenerator(db, cfg.TraefikConfDir, configManager)
132143
go configGenerator.Start(cfg.GenerateInterval)
133144

134-
// Start API server
135145
serverConfig := api.ServerConfig{
136146
Port: cfg.Port,
137147
UIPath: cfg.UIPath,
138148
Debug: cfg.Debug,
139149
AllowCORS: cfg.AllowCORS,
140150
CORSOrigin: cfg.CORSOrigin,
141151
}
142-
143-
server := api.NewServer(db.DB, serverConfig, configManager)
152+
153+
server := api.NewServer(db.DB, serverConfig, configManager, cfg.TraefikStaticConfigPath, cfg.PluginsJSONURL)
144154
go func() {
145155
if err := server.Start(); err != nil {
146156
log.Printf("Server error: %v", err)
147157
close(stopChan)
148158
}
149159
}()
150160

151-
// Wait for shutdown signal or server error
152161
signalChan := make(chan os.Signal, 1)
153162
signal.Notify(signalChan, os.Interrupt, syscall.SIGTERM)
154-
163+
164+
serviceWatcher, err := services.NewServiceWatcher(db, configManager)
165+
if err != nil {
166+
log.Printf("Warning: Failed to create service watcher: %v", err)
167+
serviceWatcher = nil
168+
} else {
169+
go serviceWatcher.Start(cfg.ServiceInterval)
170+
}
171+
155172
select {
156173
case <-signalChan:
157174
log.Println("Received shutdown signal")
158175
case <-stopChan:
159176
log.Println("Received stop signal from server")
160177
}
161178

162-
// Start service watcher with config manager
163-
serviceWatcher, err := services.NewServiceWatcher(db, configManager)
164-
if err != nil {
165-
log.Printf("Warning: Failed to create service watcher: %v", err)
166-
} else {
167-
// Use the same interval as the resource watcher, or a different one if preferred
168-
go serviceWatcher.Start(cfg.CheckInterval)
169-
}
170-
171-
// Graceful shutdown
172179
log.Println("Shutting down...")
173180
resourceWatcher.Stop()
174-
serviceWatcher.Stop() // Add this line
181+
if serviceWatcher != nil {
182+
serviceWatcher.Stop()
183+
}
175184
configGenerator.Stop()
176185
server.Stop()
177186
log.Println("Middleware Manager stopped")
178187
}
179188

180-
// loadConfiguration loads configuration from environment variables
181189
func loadConfiguration(debug bool) Configuration {
182-
// Default check interval is 30 seconds
183190
checkInterval := 30 * time.Second
184191
if intervalStr := getEnv("CHECK_INTERVAL_SECONDS", "30"); intervalStr != "" {
185192
if interval, err := strconv.Atoi(intervalStr); err == nil && interval > 0 {
186193
checkInterval = time.Duration(interval) * time.Second
187194
}
188195
}
189-
190-
// Default generate interval is 10 seconds
196+
191197
generateInterval := 10 * time.Second
192198
if intervalStr := getEnv("GENERATE_INTERVAL_SECONDS", "10"); intervalStr != "" {
193199
if interval, err := strconv.Atoi(intervalStr); err == nil && interval > 0 {
194200
generateInterval = time.Duration(interval) * time.Second
195201
}
196202
}
197-
198-
// Allow CORS if specified
203+
204+
parsedServiceInterval := 30 * time.Second
205+
if intervalStr := getEnv("SERVICE_INTERVAL_SECONDS", "30"); intervalStr != "" {
206+
if interval, err := strconv.Atoi(intervalStr); err == nil && interval > 0 {
207+
parsedServiceInterval = time.Duration(interval) * time.Second
208+
}
209+
}
210+
199211
allowCORS := false
200212
if corsStr := getEnv("ALLOW_CORS", "false"); corsStr != "" {
201213
allowCORS = strings.ToLower(corsStr) == "true"
202214
}
203-
204-
// Override debug mode from environment if specified
215+
205216
if debugStr := getEnv("DEBUG", ""); debugStr != "" {
206217
debug = strings.ToLower(debugStr) == "true"
207218
}
208-
219+
209220
return Configuration{
210-
PangolinAPIURL: getEnv("PANGOLIN_API_URL", "http://pangolin:3001/api/v1"),
211-
// Changed to use host.docker.internal as first default to better support Docker environments
212-
TraefikAPIURL: getEnv("TRAEFIK_API_URL", "http://host.docker.internal:8080"),
213-
TraefikConfDir: getEnv("TRAEFIK_CONF_DIR", "/conf"),
214-
DBPath: getEnv("DB_PATH", "/data/middleware.db"),
215-
Port: getEnv("PORT", "3456"),
216-
UIPath: getEnv("UI_PATH", "/app/ui/build"),
217-
ConfigDir: getEnv("CONFIG_DIR", "/app/config"),
218-
ActiveDataSource: getEnv("ACTIVE_DATA_SOURCE", "pangolin"),
219-
CheckInterval: checkInterval,
220-
GenerateInterval: generateInterval,
221-
Debug: debug,
222-
AllowCORS: allowCORS,
223-
CORSOrigin: getEnv("CORS_ORIGIN", ""),
221+
PangolinAPIURL: getEnv("PANGOLIN_API_URL", "http://pangolin:3001/api/v1"),
222+
TraefikAPIURL: getEnv("TRAEFIK_API_URL", "http://host.docker.internal:8080"),
223+
TraefikConfDir: getEnv("TRAEFIK_CONF_DIR", "/conf"),
224+
DBPath: getEnv("DB_PATH", "/data/middleware.db"),
225+
Port: getEnv("PORT", "3456"),
226+
UIPath: getEnv("UI_PATH", "/app/ui/build"),
227+
ConfigDir: getEnv("CONFIG_DIR", "/app/config"),
228+
ActiveDataSource: getEnv("ACTIVE_DATA_SOURCE", "pangolin"),
229+
CheckInterval: checkInterval,
230+
GenerateInterval: generateInterval,
231+
ServiceInterval: parsedServiceInterval,
232+
Debug: debug,
233+
AllowCORS: allowCORS,
234+
CORSOrigin: getEnv("CORS_ORIGIN", ""),
235+
TraefikStaticConfigPath: getEnv("TRAEFIK_STATIC_CONFIG_PATH", "/etc/traefik/traefik.yml"),
236+
PluginsJSONURL: getEnv("PLUGINS_JSON_URL", "https://raw.githubusercontent.com/hhftechnology/middleware-manager/traefik-int/plugin/plugins.json"),
224237
}
225238
}
226239

227-
// getEnv gets an environment variable or returns a default value
228240
func getEnv(key, fallback string) string {
229241
if value, exists := os.LookupEnv(key); exists {
230242
return value

0 commit comments

Comments
 (0)