Merge pull request #104 from hhftechnology/dev
Some checks failed
Build and Push Docker Image / build-and-push (push) Has been cancelled
Tests / Run Tests (push) Has been cancelled
Tests / Lint (push) Has been cancelled
Tests / Build (push) Has been cancelled

Fix: serversTransports from Pangolin config not passed through to Tra…
This commit is contained in:
HHF Technology 2026-03-24 10:27:16 +05:30 committed by GitHub
commit 7eb1378d82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 132 additions and 44 deletions

View file

@ -6,36 +6,36 @@ import (
// Resource represents a Pangolin resource
type Resource struct {
ID string `json:"id"` // Internal UUID (stable, never changes)
PangolinRouterID string `json:"pangolin_router_id"` // Pangolin's router ID (can change)
Host string `json:"host"`
ServiceID string `json:"service_id"`
OrgID string `json:"org_id"`
SiteID string `json:"site_id"`
Status string `json:"status"`
ID string `json:"id"` // Internal UUID (stable, never changes)
PangolinRouterID string `json:"pangolin_router_id"` // Pangolin's router ID (can change)
Host string `json:"host"`
ServiceID string `json:"service_id"`
OrgID string `json:"org_id"`
SiteID string `json:"site_id"`
Status string `json:"status"`
// HTTP router configuration
Entrypoints string `json:"entrypoints"`
Entrypoints string `json:"entrypoints"`
// TLS certificate configuration
TLSDomains string `json:"tls_domains"`
TLSDomains string `json:"tls_domains"`
// TCP SNI routing configuration
TCPEnabled bool `json:"tcp_enabled"`
TCPEntrypoints string `json:"tcp_entrypoints"`
TCPSNIRule string `json:"tcp_sni_rule"`
TCPEnabled bool `json:"tcp_enabled"`
TCPEntrypoints string `json:"tcp_entrypoints"`
TCPSNIRule string `json:"tcp_sni_rule"`
// Custom headers configuration
CustomHeaders string `json:"custom_headers"`
CustomHeaders string `json:"custom_headers"`
// Router priority configuration
RouterPriority int `json:"router_priority"`
RouterPriority int `json:"router_priority"`
// Source type for tracking data origin
SourceType string `json:"source_type"`
SourceType string `json:"source_type"`
// mTLS configuration
MTLSEnabled bool `json:"mtls_enabled"`
MTLSEnabled bool `json:"mtls_enabled"`
// TLS Hardening configuration (standalone, disabled when mTLS is active)
TLSHardeningEnabled bool `json:"tls_hardening_enabled"`
@ -43,8 +43,8 @@ type Resource struct {
// Secure Headers configuration
SecureHeadersEnabled bool `json:"secure_headers_enabled"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
// PangolinResource represents the format of a resource from Pangolin API
@ -58,9 +58,10 @@ type PangolinResource struct {
// PangolinTraefikConfig represents the Traefik configuration from Pangolin API
type PangolinTraefikConfig struct {
HTTP struct {
Routers map[string]PangolinRouter `json:"routers"`
Services map[string]PangolinService `json:"services"`
Middlewares map[string]map[string]interface{} `json:"middlewares"`
Routers map[string]PangolinRouter `json:"routers"`
Services map[string]PangolinService `json:"services"`
Middlewares map[string]map[string]interface{} `json:"middlewares"`
ServersTransports map[string]interface{} `json:"serversTransports"`
} `json:"http"`
}
@ -96,7 +97,7 @@ type PangolinServiceConfig struct {
type TraefikService struct {
Name string `json:"name"`
Provider string `json:"provider"`
// Service types - only one will be populated based on service type
LoadBalancer *struct {
Servers []struct {
@ -108,7 +109,7 @@ type TraefikService struct {
Sticky interface{} `json:"sticky,omitempty"`
HealthCheck interface{} `json:"healthCheck,omitempty"`
} `json:"loadBalancer,omitempty"`
Weighted *struct {
Services []struct {
Name string `json:"name"`
@ -117,10 +118,10 @@ type TraefikService struct {
Sticky interface{} `json:"sticky,omitempty"`
HealthCheck interface{} `json:"healthCheck,omitempty"`
} `json:"weighted,omitempty"`
Mirroring *struct {
Service string `json:"service"`
Mirrors []struct {
Service string `json:"service"`
Mirrors []struct {
Name string `json:"name"`
Percent int `json:"percent"`
} `json:"mirrors,omitempty"`
@ -128,10 +129,10 @@ type TraefikService struct {
MirrorBody *bool `json:"mirrorBody,omitempty"`
HealthCheck interface{} `json:"healthCheck,omitempty"`
} `json:"mirroring,omitempty"`
Failover *struct {
Service string `json:"service"`
Fallback string `json:"fallback"`
HealthCheck interface{} `json:"healthCheck,omitempty"`
} `json:"failover,omitempty"`
}
}

View file

@ -28,9 +28,10 @@ type ProxiedTraefikConfig struct {
// HTTPConfig represents HTTP configuration section
type HTTPConfig struct {
Middlewares map[string]interface{} `json:"middlewares,omitempty"`
Routers map[string]interface{} `json:"routers,omitempty"`
Services map[string]interface{} `json:"services,omitempty"`
Middlewares map[string]interface{} `json:"middlewares,omitempty"`
Routers map[string]interface{} `json:"routers,omitempty"`
Services map[string]interface{} `json:"services,omitempty"`
ServersTransports map[string]interface{} `json:"serversTransports,omitempty"`
}
// TCPConfig represents TCP configuration section
@ -53,12 +54,12 @@ type TLSConfig struct {
// OrderedRouter represents a Traefik HTTP router with fields in Pangolin's order.
// The JSON field order matches Pangolin API output for consistency.
type OrderedRouter struct {
EntryPoints []string `json:"entryPoints,omitempty"`
Middlewares []string `json:"middlewares,omitempty"`
Service string `json:"service,omitempty"`
Rule string `json:"rule,omitempty"`
Priority int `json:"priority,omitempty"`
TLS *OrderedTLSConfig `json:"tls,omitempty"`
EntryPoints []string `json:"entryPoints,omitempty"`
Middlewares []string `json:"middlewares,omitempty"`
Service string `json:"service,omitempty"`
Rule string `json:"rule,omitempty"`
Priority int `json:"priority,omitempty"`
TLS *OrderedTLSConfig `json:"tls,omitempty"`
}
// OrderedTLSConfig represents TLS config for a router with Pangolin's field order.
@ -146,7 +147,7 @@ func NewConfigProxy(db *database.DB, configManager *ConfigManager, pangolinURL s
db: db,
configManager: configManager,
pangolinURL: pangolinURL,
httpClient: HTTPClientWithTimeout(10 * time.Second),
httpClient: HTTPClientWithTimeout(10 * time.Second),
cacheDuration: 5 * time.Second, // Match typical Traefik poll interval
}
}
@ -266,6 +267,9 @@ func (cp *ConfigProxy) initializeConfigMaps(config *ProxiedTraefikConfig) {
if config.HTTP.Services == nil {
config.HTTP.Services = make(map[string]interface{})
}
if config.HTTP.ServersTransports == nil {
config.HTTP.ServersTransports = make(map[string]interface{})
}
if config.TCP == nil {
config.TCP = &TCPConfig{}

View file

@ -55,6 +55,89 @@ func TestConfigProxyCachesAndInvalidates(t *testing.T) {
}
}
func TestConfigProxyPreservesServersTransports(t *testing.T) {
db := newTestDB(t)
cm := newTestConfigManager(t)
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"http": map[string]interface{}{
"middlewares": map[string]interface{}{},
"routers": map[string]interface{}{},
"services": map[string]interface{}{
"14-example-service": map[string]interface{}{
"loadBalancer": map[string]interface{}{
"servers": []map[string]interface{}{
{"url": "https://10.0.0.1:12345"},
},
"serversTransport": "14-transport",
},
},
},
"serversTransports": map[string]interface{}{
"14-transport": map[string]interface{}{
"serverName": "example.com",
"insecureSkipVerify": true,
},
},
},
})
}))
defer server.Close()
cp := NewConfigProxy(db, cm, server.URL)
cp.httpClient = server.Client()
config, err := cp.GetMergedConfig()
if err != nil {
t.Fatalf("GetMergedConfig() error = %v", err)
}
if config.HTTP == nil {
t.Fatal("config.HTTP is nil")
}
transportRaw, exists := config.HTTP.ServersTransports["14-transport"]
if !exists {
t.Fatalf("serversTransport %q was not preserved", "14-transport")
}
transport, ok := transportRaw.(map[string]interface{})
if !ok {
t.Fatalf("serversTransport has type %T, want map[string]interface{}", transportRaw)
}
if got, ok := transport["serverName"].(string); !ok || got != "example.com" {
t.Fatalf("serverName = %#v, want %q", transport["serverName"], "example.com")
}
if got, ok := transport["insecureSkipVerify"].(bool); !ok || !got {
t.Fatalf("insecureSkipVerify = %#v, want true", transport["insecureSkipVerify"])
}
serviceRaw, exists := config.HTTP.Services["14-example-service"]
if !exists {
t.Fatalf("service %q not found", "14-example-service")
}
service, ok := serviceRaw.(map[string]interface{})
if !ok {
t.Fatalf("service has type %T, want map[string]interface{}", serviceRaw)
}
loadBalancerRaw, exists := service["loadBalancer"]
if !exists {
t.Fatalf("service %q missing loadBalancer", "14-example-service")
}
loadBalancer, ok := loadBalancerRaw.(map[string]interface{})
if !ok {
t.Fatalf("loadBalancer has type %T, want map[string]interface{}", loadBalancerRaw)
}
if got, ok := loadBalancer["serversTransport"].(string); !ok || got != "14-transport" {
t.Fatalf("loadBalancer.serversTransport = %#v, want %q", loadBalancer["serversTransport"], "14-transport")
}
}
func TestConfigGeneratorWritesConfigFile(t *testing.T) {
db := newTestDB(t)
cm := newTestConfigManager(t)

View file

@ -101,7 +101,7 @@ export function DataSourceSettings() {
Data Source Settings
</DialogTitle>
<DialogDescription>
Configure your data source connections for Pangolin or Traefik API
Configure your data source connections for Pangolin or Traefik API --Traefik api will not function on pangolin stack/api,dont switch
</DialogDescription>
</DialogHeader>
@ -178,7 +178,7 @@ export function DataSourceSettings() {
<Input
value={editUrl}
onChange={(e) => setEditUrl(e.target.value)}
placeholder="http://localhost:8080"
placeholder="http://gerbil:8080"
/>
</div>
<div className="grid grid-cols-2 gap-2">