Skip to content

Commit 88a2a02

Browse files
committed
update
1 parent 7156fdc commit 88a2a02

23 files changed

+1700
-95
lines changed

.gitattributes

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Auto detect text files and perform LF normalization
2+
* text=auto

api/handlers/datasource.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package handlers
22

33
import (
4+
"context"
5+
"fmt"
6+
"log"
47
"net/http"
8+
"time"
59

610
"github.com/gin-gonic/gin"
711
"github.com/hhftechnology/middleware-manager/models"
@@ -25,6 +29,12 @@ func (h *DataSourceHandler) GetDataSources(c *gin.Context) {
2529
sources := h.ConfigManager.GetDataSources()
2630
activeSource := h.ConfigManager.GetActiveSourceName()
2731

32+
// Format sources to mask passwords
33+
for key, source := range sources {
34+
source.FormatBasicAuth()
35+
sources[key] = source
36+
}
37+
2838
c.JSON(http.StatusOK, gin.H{
2939
"active_source": activeSource,
3040
"sources": sources,
@@ -91,4 +101,78 @@ func (h *DataSourceHandler) UpdateDataSource(c *gin.Context) {
91101
"name": name,
92102
"config": config,
93103
})
104+
}
105+
106+
// TestDataSourceConnection tests the connection to a data source
107+
func (h *DataSourceHandler) TestDataSourceConnection(c *gin.Context) {
108+
name := c.Param("name")
109+
if name == "" {
110+
ResponseWithError(c, http.StatusBadRequest, "Data source name is required")
111+
return
112+
}
113+
114+
var config models.DataSourceConfig
115+
if err := c.ShouldBindJSON(&config); err != nil {
116+
ResponseWithError(c, http.StatusBadRequest, fmt.Sprintf("Invalid request: %v", err))
117+
return
118+
}
119+
120+
// Create a context with timeout
121+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
122+
defer cancel()
123+
124+
// Test the connection with endpoints that work
125+
err := testDataSourceConnection(ctx, config)
126+
if err != nil {
127+
log.Printf("Connection test failed for %s: %v", name, err)
128+
ResponseWithError(c, http.StatusBadRequest, fmt.Sprintf("Connection test failed: %v", err))
129+
return
130+
}
131+
132+
c.JSON(http.StatusOK, gin.H{
133+
"message": "Connection test successful",
134+
"name": name,
135+
})
136+
}
137+
138+
// testDataSourceConnection tests the connection to a data source using different endpoints
139+
// based on the data source type
140+
func testDataSourceConnection(ctx context.Context, config models.DataSourceConfig) error {
141+
client := &http.Client{
142+
Timeout: 5 * time.Second,
143+
}
144+
145+
var url string
146+
switch config.Type {
147+
case models.PangolinAPI:
148+
// Use traefik-config endpoint instead of status to test Pangolin
149+
url = config.URL + "/traefik-config"
150+
case models.TraefikAPI:
151+
// Use http/routers endpoint to test Traefik
152+
url = config.URL + "/api/http/routers"
153+
default:
154+
return fmt.Errorf("unsupported data source type: %s", config.Type)
155+
}
156+
157+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
158+
if err != nil {
159+
return fmt.Errorf("failed to create request: %w", err)
160+
}
161+
162+
// Add basic auth if configured
163+
if config.BasicAuth.Username != "" {
164+
req.SetBasicAuth(config.BasicAuth.Username, config.BasicAuth.Password)
165+
}
166+
167+
resp, err := client.Do(req)
168+
if err != nil {
169+
return fmt.Errorf("connection failed: %w", err)
170+
}
171+
defer resp.Body.Close()
172+
173+
if resp.StatusCode >= 400 {
174+
return fmt.Errorf("API returned status code: %d", resp.StatusCode)
175+
}
176+
177+
return nil
94178
}

api/server.go

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/gin-contrib/static"
1515
"github.com/gin-gonic/gin"
1616
"github.com/hhftechnology/middleware-manager/api/handlers"
17+
"github.com/hhftechnology/middleware-manager/services"
1718
)
1819

1920
// Server represents the API server
@@ -24,6 +25,8 @@ type Server struct {
2425
middlewareHandler *handlers.MiddlewareHandler
2526
resourceHandler *handlers.ResourceHandler
2627
configHandler *handlers.ConfigHandler
28+
dataSourceHandler *handlers.DataSourceHandler
29+
configManager *services.ConfigManager
2730
}
2831

2932
// ServerConfig contains configuration options for the server
@@ -36,7 +39,7 @@ type ServerConfig struct {
3639
}
3740

3841
// NewServer creates a new API server
39-
func NewServer(db *sql.DB, config ServerConfig) *Server {
42+
func NewServer(db *sql.DB, config ServerConfig, configManager *services.ConfigManager) *Server {
4043
// Set gin mode based on debug flag
4144
if !config.Debug {
4245
gin.SetMode(gin.ReleaseMode)
@@ -77,6 +80,7 @@ func NewServer(db *sql.DB, config ServerConfig) *Server {
7780
middlewareHandler := handlers.NewMiddlewareHandler(db)
7881
resourceHandler := handlers.NewResourceHandler(db)
7982
configHandler := handlers.NewConfigHandler(db)
83+
dataSourceHandler := handlers.NewDataSourceHandler(configManager)
8084

8185
// Setup server
8286
server := &Server{
@@ -85,6 +89,8 @@ func NewServer(db *sql.DB, config ServerConfig) *Server {
8589
middlewareHandler: middlewareHandler,
8690
resourceHandler: resourceHandler,
8791
configHandler: configHandler,
92+
dataSourceHandler: dataSourceHandler,
93+
configManager: configManager,
8894
srv: &http.Server{
8995
Addr: ":" + config.Port,
9096
Handler: router,
@@ -132,20 +138,22 @@ func (s *Server) setupRoutes(uiPath string) {
132138
resources.DELETE("/:id/middlewares/:middlewareId", s.resourceHandler.RemoveMiddleware)
133139

134140
// Router configuration routes
135-
resources.PUT("/:id/config/http", s.configHandler.UpdateHTTPConfig) // HTTP entrypoints
136-
resources.PUT("/:id/config/tls", s.configHandler.UpdateTLSConfig) // TLS certificate domains
137-
resources.PUT("/:id/config/tcp", s.configHandler.UpdateTCPConfig) // TCP SNI routing
138-
resources.PUT("/:id/config/headers", s.configHandler.UpdateHeadersConfig) // Custom Host headers
139-
resources.PUT("/:id/config/priority", s.configHandler.UpdateRouterPriority) // Router priority
141+
resources.PUT("/:id/config/http", s.configHandler.UpdateHTTPConfig)
142+
resources.PUT("/:id/config/tls", s.configHandler.UpdateTLSConfig)
143+
resources.PUT("/:id/config/tcp", s.configHandler.UpdateTCPConfig)
144+
resources.PUT("/:id/config/headers", s.configHandler.UpdateHeadersConfig)
145+
resources.PUT("/:id/config/priority", s.configHandler.UpdateRouterPriority)
146+
}
147+
148+
// Data source routes
149+
datasource := api.Group("/datasource")
150+
{
151+
datasource.GET("", s.dataSourceHandler.GetDataSources)
152+
datasource.GET("/active", s.dataSourceHandler.GetActiveDataSource)
153+
datasource.PUT("/active", s.dataSourceHandler.SetActiveDataSource)
154+
datasource.PUT("/:name", s.dataSourceHandler.UpdateDataSource)
155+
datasource.POST("/:name/test", s.dataSourceHandler.TestDataSourceConnection)
140156
}
141-
// Data source routes
142-
datasource := api.Group("/datasource")
143-
{
144-
datasource.GET("", s.dataSourceHandler.GetDataSources)
145-
datasource.GET("/active", s.dataSourceHandler.GetActiveDataSource)
146-
datasource.PUT("/active", s.dataSourceHandler.SetActiveDataSource)
147-
datasource.PUT("/:name", s.dataSourceHandler.UpdateDataSource)
148-
}
149157
}
150158

151159
// Serve the React app

database/db.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,29 @@ func runPostMigrationUpdates(db *sql.DB) error {
158158
if err != nil {
159159
return fmt.Errorf("failed to check if entrypoints column exists: %w", err)
160160
}
161+
162+
// Check for source_type column
163+
var hasSourceTypeColumn bool
164+
err = db.QueryRow(`
165+
SELECT COUNT(*) > 0
166+
FROM pragma_table_info('resources')
167+
WHERE name = 'source_type'
168+
`).Scan(&hasSourceTypeColumn)
169+
170+
if err != nil {
171+
return fmt.Errorf("failed to check if source_type column exists: %w", err)
172+
}
173+
174+
// If the column doesn't exist, add it
175+
if !hasSourceTypeColumn {
176+
log.Println("Adding source_type column to resources table")
177+
178+
if _, err := db.Exec("ALTER TABLE resources ADD COLUMN source_type TEXT DEFAULT ''"); err != nil {
179+
return fmt.Errorf("failed to add source_type column: %w", err)
180+
}
181+
182+
log.Println("Successfully added source_type column")
183+
}
161184

162185
// If the column doesn't exist, add the routing columns too
163186
if !hasEntrypointsColumn {

database/migrations.sql

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ CREATE TABLE IF NOT EXISTS resources (
3535
-- Router priority configuration
3636
router_priority INTEGER DEFAULT 100,
3737

38+
-- Source type for tracking data origin
39+
source_type TEXT DEFAULT '',
40+
3841
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
3942
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
4043
);

database/transaction.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,4 +95,5 @@ func GetLastInsertID(result sql.Result) (int64, error) {
9595
return 0, fmt.Errorf("failed to get last insert ID: %w", err)
9696
}
9797
return id, nil
98-
}
98+
}
99+

main.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"flag"
55
"log"
6+
"net/http"
67
"os"
78
"os/signal"
89
"path/filepath"
@@ -20,6 +21,7 @@ import (
2021
// Configuration represents the application configuration
2122
type Configuration struct {
2223
PangolinAPIURL string
24+
TraefikAPIURL string
2325
TraefikConfDir string
2426
DBPath string
2527
Port string
@@ -30,6 +32,37 @@ type Configuration struct {
3032
Debug bool
3133
AllowCORS bool
3234
CORSOrigin string
35+
ActiveDataSource string
36+
}
37+
38+
// DiscoverTraefikAPI attempts to discover the Traefik API by trying common URLs
39+
func DiscoverTraefikAPI() (string, error) {
40+
client := &http.Client{
41+
Timeout: 2 * time.Second, // Short timeout for discovery
42+
}
43+
44+
// Common URLs to try
45+
urls := []string{
46+
"http://host.docker.internal:8080",
47+
"http://localhost:8080",
48+
"http://127.0.0.1:8080",
49+
"http://traefik:8080",
50+
}
51+
52+
for _, url := range urls {
53+
testURL := url + "/api/version"
54+
resp, err := client.Get(testURL)
55+
if err == nil && resp.StatusCode == http.StatusOK {
56+
resp.Body.Close()
57+
log.Printf("Discovered Traefik API at %s", url)
58+
return url, nil
59+
}
60+
if resp != nil {
61+
resp.Body.Close()
62+
}
63+
}
64+
65+
return "", nil // Return empty string without error to allow fallbacks
3366
}
3467

3568
func main() {
@@ -43,6 +76,14 @@ func main() {
4376
// Load configuration
4477
cfg := loadConfiguration(debug)
4578

79+
// Try to discover Traefik API URL if not set in environment
80+
if os.Getenv("TRAEFIK_API_URL") == "" {
81+
if discoveredURL, err := DiscoverTraefikAPI(); err == nil && discoveredURL != "" {
82+
log.Printf("Auto-discovered Traefik API URL: %s", discoveredURL)
83+
cfg.TraefikAPIURL = discoveredURL
84+
}
85+
}
86+
4687
// Initialize database
4788
db, err := database.InitDB(cfg.DBPath)
4889
if err != nil {
@@ -72,6 +113,9 @@ func main() {
72113
log.Fatalf("Failed to initialize config manager: %v", err)
73114
}
74115

116+
// Ensure default data sources are configured with potentially discovered URL
117+
configManager.EnsureDefaultDataSources(cfg.PangolinAPIURL, cfg.TraefikAPIURL)
118+
75119
// Create stop channel for graceful shutdown
76120
stopChan := make(chan struct{})
77121

@@ -153,11 +197,14 @@ func loadConfiguration(debug bool) Configuration {
153197

154198
return Configuration{
155199
PangolinAPIURL: getEnv("PANGOLIN_API_URL", "http://pangolin:3001/api/v1"),
200+
// Changed to use host.docker.internal as first default to better support Docker environments
201+
TraefikAPIURL: getEnv("TRAEFIK_API_URL", "http://host.docker.internal:8080"),
156202
TraefikConfDir: getEnv("TRAEFIK_CONF_DIR", "/conf"),
157203
DBPath: getEnv("DB_PATH", "/data/middleware.db"),
158204
Port: getEnv("PORT", "3456"),
159205
UIPath: getEnv("UI_PATH", "/app/ui/build"),
160206
ConfigDir: getEnv("CONFIG_DIR", "/app/config"),
207+
ActiveDataSource: getEnv("ACTIVE_DATA_SOURCE", "pangolin"),
161208
CheckInterval: checkInterval,
162209
GenerateInterval: generateInterval,
163210
Debug: debug,

0 commit comments

Comments
 (0)