@@ -11,7 +11,6 @@ import (
11
11
"github.com/cloudflare/cloudflare-go"
12
12
"github.com/go-resty/resty/v2"
13
13
"github.com/traefik/traefik/v3/pkg/muxer/http"
14
-
15
14
log "github.com/sirupsen/logrus"
16
15
)
17
16
@@ -20,11 +19,9 @@ func init() {
20
19
DisableColors : true ,
21
20
FullTimestamp : true ,
22
21
})
23
- // Set log level based on environment variable or default to info
24
- logLevel := os .Getenv ("LOG_LEVEL" )
25
- if logLevel != "" {
26
- level , err := log .ParseLevel (logLevel )
27
- if err == nil {
22
+ // Set log level from environment variable, default to info
23
+ if logLevel := os .Getenv ("LOG_LEVEL" ); logLevel != "" {
24
+ if level , err := log .ParseLevel (logLevel ); err == nil {
28
25
log .SetLevel (level )
29
26
}
30
27
}
@@ -40,20 +37,21 @@ func main() {
40
37
"TRAEFIK_SERVICE_ENDPOINT" ,
41
38
"TRAEFIK_API_ENDPOINT" ,
42
39
}
43
-
44
40
for _ , envVar := range requiredEnvVars {
45
41
if os .Getenv (envVar ) == "" {
46
42
log .Fatalf ("Required environment variable %s is not set" , envVar )
47
43
}
48
44
}
49
45
46
+ // Initialize Cloudflare client
50
47
cf , err := cloudflare .NewWithAPIToken (os .Getenv ("CLOUDFLARE_API_TOKEN" ))
51
48
if err != nil {
52
- log .Fatal ( err )
49
+ log .Fatalf ( "Failed to initialize Cloudflare client: %v" , err )
53
50
}
54
51
55
52
ctx := context .Background ()
56
53
54
+ // Initialize HTTP client for Traefik API
57
55
client := resty .New ().
58
56
SetBaseURL (os .Getenv ("TRAEFIK_API_ENDPOINT" )).
59
57
SetTimeout (10 * time .Second ).
@@ -63,80 +61,78 @@ func main() {
63
61
var cache []Router
64
62
for poll := range pollCh {
65
63
if poll .Err != nil {
66
- log .Fatal (poll .Err )
64
+ log .Errorf ("Polling error: %v" , poll .Err )
65
+ continue // Continue instead of fatal to keep the loop running
67
66
}
68
67
69
- // skip if no changes to traefik routers
68
+ // Skip if no changes detected
70
69
if reflect .DeepEqual (cache , poll .Routers ) {
71
70
continue
72
71
}
73
72
74
- log .Info ("changes detected" )
75
-
76
- // update the cache
73
+ log .Info ("Traefik router changes detected" )
77
74
cache = poll .Routers
78
75
79
- // Group routes by domain to avoid duplicates
76
+ // Group routes by domain, avoiding duplicates
80
77
domainRoutes := make (map [string ]bool )
81
-
82
78
for _ , r := range poll .Routers {
83
- // Only enabled routes
84
- if r .Status != "enabled" {
85
- log .Debugf ("Skipping disabled route: %s" , r .Rule )
79
+ // Skip disabled or internal routers
80
+ if r .Status != "enabled" || r . Provider == "internal" {
81
+ log .Debugf ("Skipping route: %s (status: %s, provider: %s) " , r .Rule , r . Status , r . Provider )
86
82
continue
87
83
}
88
84
89
- // Handle both HTTP and HTTPS routes
90
- // We want to include all routes, including those with TLS configured
91
- if os .Getenv ("TRAEFIK_ENTRYPOINT" ) != "" {
92
- // If TRAEFIK_ENTRYPOINT is specified, only use routes with that entrypoint
93
- if ! contains (r .EntryPoints , os .Getenv ("TRAEFIK_ENTRYPOINT" )) {
94
- log .Debugf ("Skipping route with different entrypoint: %s" , r .Rule )
85
+ // Filter by entrypoint if specified
86
+ if entrypoint := os .Getenv ("TRAEFIK_ENTRYPOINT" ); entrypoint != "" {
87
+ if ! contains (r .EntryPoints , entrypoint ) {
88
+ log .Debugf ("Skipping route with mismatched entrypoint: %s" , r .Rule )
95
89
continue
96
90
}
97
91
}
98
92
93
+ // Parse domains from the rule
99
94
domains , err := http .ParseDomains (r .Rule )
100
95
if err != nil {
101
96
log .Errorf ("Failed to parse domains from rule %s: %v" , r .Rule , err )
102
97
continue
103
98
}
104
99
105
- // Add each domain only once
100
+ // Log and add unique domains
106
101
for _ , domain := range domains {
107
- if _ , exists := domainRoutes [domain ]; ! exists {
102
+ if ! domainRoutes [domain ] {
108
103
domainRoutes [domain ] = true
104
+ log .Debugf ("Processing domain: %s from rule: %s" , domain , r .Rule )
109
105
}
110
106
}
111
107
}
112
108
113
- // Create tunnel configuration with unique domains
109
+ // Build Cloudflare tunnel ingress rules
114
110
ingress := []cloudflare.UnvalidatedIngressRule {}
115
111
for domain := range domainRoutes {
116
112
log .WithFields (log.Fields {
117
113
"domain" : domain ,
118
114
"service" : os .Getenv ("TRAEFIK_SERVICE_ENDPOINT" ),
119
- }).Info ("upserting tunnel" )
115
+ }).Info ("Adding tunnel ingress rule " )
120
116
121
- // Create a single ingress rule for the domain that handles all routes
122
117
ingress = append (ingress , cloudflare.UnvalidatedIngressRule {
123
118
Hostname : domain ,
124
119
Service : os .Getenv ("TRAEFIK_SERVICE_ENDPOINT" ),
125
120
OriginRequest : & cloudflare.OriginRequestConfig {
126
121
HTTPHostHeader : & domain ,
127
- NoTLSVerify : boolPtr (true ), // Skip TLS verification for internal traffic
122
+ NoTLSVerify : boolPtr (true ), // Skip TLS for internal traffic
128
123
},
129
124
})
130
125
}
131
126
132
- // add catch-all rule
127
+ // Add catch-all rule
133
128
ingress = append (ingress , cloudflare.UnvalidatedIngressRule {
134
129
Service : "http_status:404" ,
135
130
})
136
131
137
- err = updateTunnels (ctx , cf , ingress )
138
- if err != nil {
139
- log .Fatal (err )
132
+ // Update tunnel configuration
133
+ if err := updateTunnels (ctx , cf , ingress ); err != nil {
134
+ log .Errorf ("Failed to update tunnels: %v" , err )
135
+ continue
140
136
}
141
137
}
142
138
}
@@ -146,18 +142,16 @@ func boolPtr(b bool) *bool {
146
142
return & b
147
143
}
148
144
149
- func pollTraefikRouters (client * resty.Client ) (ch chan PollResponse ) {
150
- ch = make (chan PollResponse )
145
+ // Poll Traefik routers periodically
146
+ func pollTraefikRouters (client * resty.Client ) chan PollResponse {
147
+ ch := make (chan PollResponse )
151
148
go func () {
152
- defer func () {
153
- close (ch )
154
- }()
149
+ defer close (ch )
155
150
r := rand .New (rand .NewSource (time .Now ().UnixNano ()))
156
- c := time .Tick (10 * time .Second )
151
+ ticker := time .Tick (10 * time .Second )
157
152
158
- for range c {
153
+ for range ticker {
159
154
var pollRes PollResponse
160
-
161
155
resp , err := client .R ().
162
156
EnableTrace ().
163
157
SetResult (& pollRes .Routers ).
@@ -167,70 +161,69 @@ func pollTraefikRouters(client *resty.Client) (ch chan PollResponse) {
167
161
if err != nil {
168
162
log .Errorf ("Error polling Traefik API: %v" , err )
169
163
ch <- pollRes
170
- time .Sleep (5 * time .Second ) // Wait before retrying
164
+ time .Sleep (5 * time .Second )
171
165
continue
172
166
}
173
167
174
168
if resp .StatusCode () != 200 {
175
169
log .Errorf ("Unexpected status code from Traefik API: %d" , resp .StatusCode ())
176
170
pollRes .Err = fmt .Errorf ("unexpected status code: %d" , resp .StatusCode ())
177
171
ch <- pollRes
178
- time .Sleep (5 * time .Second ) // Wait before retrying
172
+ time .Sleep (5 * time .Second )
179
173
continue
180
174
}
181
175
182
176
ch <- pollRes
183
-
184
177
jitter := time .Duration (r .Int31n (5000 )) * time .Millisecond
185
178
time .Sleep (jitter )
186
179
}
187
180
}()
188
- return
181
+ return ch
189
182
}
190
183
184
+ // Update Cloudflare tunnel configuration and DNS records
191
185
func updateTunnels (ctx context.Context , cf * cloudflare.API , ingress []cloudflare.UnvalidatedIngressRule ) error {
192
- // Get Current tunnel config
193
186
aid := cloudflare .AccountIdentifier (os .Getenv ("CLOUDFLARE_ACCOUNT_ID" ))
187
+
188
+ // Fetch current tunnel config
194
189
cfg , err := cf .GetTunnelConfiguration (ctx , aid , os .Getenv ("CLOUDFLARE_TUNNEL_ID" ))
195
190
if err != nil {
196
- return fmt .Errorf ("unable to pull current tunnel config, %s " , err . Error () )
191
+ return fmt .Errorf ("failed to fetch tunnel config: %v " , err )
197
192
}
198
193
199
- // Update config with new ingress rules
194
+ // Update with new ingress rules
200
195
cfg .Config .Ingress = ingress
201
- cfg , err = cf .UpdateTunnelConfiguration (ctx , aid , cloudflare.TunnelConfigurationParams {
196
+ _ , err = cf .UpdateTunnelConfiguration (ctx , aid , cloudflare.TunnelConfigurationParams {
202
197
TunnelID : os .Getenv ("CLOUDFLARE_TUNNEL_ID" ),
203
198
Config : cfg .Config ,
204
199
})
205
200
if err != nil {
206
- return fmt .Errorf ("unable to update tunnel config, %s " , err . Error () )
201
+ return fmt .Errorf ("failed to update tunnel config: %v " , err )
207
202
}
203
+ log .Info ("Tunnel configuration updated successfully" )
208
204
209
- log .Info ("tunnel config updated" )
210
-
211
- // Update DNS to point to new tunnel
212
- for _ , i := range ingress {
213
- if i .Hostname == "" {
205
+ // Update DNS records
206
+ for _ , rule := range ingress {
207
+ if rule .Hostname == "" {
214
208
continue
215
209
}
216
210
217
- var proxied bool = true
218
-
211
+ proxied := true
219
212
record := cloudflare.DNSRecord {
220
213
Type : "CNAME" ,
221
- Name : i .Hostname ,
214
+ Name : rule .Hostname ,
222
215
Content : fmt .Sprintf ("%s.cfargotunnel.com" , os .Getenv ("CLOUDFLARE_TUNNEL_ID" )),
223
216
TTL : 1 ,
224
217
Proxied : & proxied ,
225
218
}
226
219
227
220
zid := cloudflare .ZoneIdentifier (os .Getenv ("CLOUDFLARE_ZONE_ID" ))
228
- r , _ , err := cf .ListDNSRecords (ctx , zid , cloudflare.ListDNSRecordsParams {Name : i .Hostname })
221
+ records , _ , err := cf .ListDNSRecords (ctx , zid , cloudflare.ListDNSRecordsParams {Name : rule .Hostname })
229
222
if err != nil {
230
- return fmt .Errorf ("err checking DNS records, %s " , err . Error () )
223
+ return fmt .Errorf ("failed to list DNS records for %s: %v " , rule . Hostname , err )
231
224
}
232
225
233
- if len (r ) == 0 {
226
+ if len (records ) == 0 {
234
227
_ , err := cf .CreateDNSRecord (ctx , zid , cloudflare.CreateDNSRecordParams {
235
228
Name : record .Name ,
236
229
Type : record .Type ,
@@ -239,35 +232,31 @@ func updateTunnels(ctx context.Context, cf *cloudflare.API, ingress []cloudflare
239
232
Proxied : record .Proxied ,
240
233
})
241
234
if err != nil {
242
- return fmt .Errorf ("unable to create DNS record, %s " , err . Error () )
235
+ return fmt .Errorf ("failed to create DNS record for %s: %v " , rule . Hostname , err )
243
236
}
244
- log .WithFields (log.Fields {
245
- "domain" : record .Name ,
246
- }).Info ("DNS created" )
237
+ log .WithFields (log.Fields {"domain" : record .Name }).Info ("DNS record created" )
247
238
continue
248
239
}
249
240
250
- if r [0 ].Content != record .Content {
241
+ if records [0 ].Content != record .Content {
251
242
_ , err = cf .UpdateDNSRecord (ctx , zid , cloudflare.UpdateDNSRecordParams {
252
- ID : r [0 ].ID , // Use the actual ID from the retrieved record
243
+ ID : records [0 ].ID ,
253
244
Name : record .Name ,
254
245
Type : record .Type ,
255
246
Content : record .Content ,
256
247
TTL : record .TTL ,
257
248
Proxied : record .Proxied ,
258
249
})
259
250
if err != nil {
260
- return fmt .Errorf ("could not update record for %s, %s " , i .Hostname , err )
251
+ return fmt .Errorf ("failed to update DNS record for %s: %v " , rule .Hostname , err )
261
252
}
262
- log .WithFields (log.Fields {
263
- "domain" : record .Name ,
264
- }).Info ("DNS updated" )
253
+ log .WithFields (log.Fields {"domain" : record .Name }).Info ("DNS record updated" )
265
254
}
266
255
}
267
-
268
256
return nil
269
257
}
270
258
259
+ // Check if a slice contains a string
271
260
func contains (s []string , e string ) bool {
272
261
for _ , a := range s {
273
262
if a == e {
0 commit comments