@@ -20,20 +20,44 @@ func init() {
20
20
DisableColors : true ,
21
21
FullTimestamp : true ,
22
22
})
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 {
28
+ log .SetLevel (level )
29
+ }
30
+ }
23
31
}
24
32
25
33
func main () {
26
- // Initialize Cloudflare API client
34
+ // Validate required environment variables
35
+ requiredEnvVars := []string {
36
+ "CLOUDFLARE_API_TOKEN" ,
37
+ "CLOUDFLARE_ACCOUNT_ID" ,
38
+ "CLOUDFLARE_TUNNEL_ID" ,
39
+ "CLOUDFLARE_ZONE_ID" ,
40
+ "TRAEFIK_SERVICE_ENDPOINT" ,
41
+ "TRAEFIK_API_ENDPOINT" ,
42
+ }
43
+
44
+ for _ , envVar := range requiredEnvVars {
45
+ if os .Getenv (envVar ) == "" {
46
+ log .Fatalf ("Required environment variable %s is not set" , envVar )
47
+ }
48
+ }
49
+
27
50
cf , err := cloudflare .NewWithAPIToken (os .Getenv ("CLOUDFLARE_API_TOKEN" ))
28
51
if err != nil {
29
52
log .Fatal (err )
30
53
}
31
54
32
55
ctx := context .Background ()
33
56
34
- // Set up Traefik API client
35
57
client := resty .New ().
36
- SetBaseURL (os .Getenv ("TRAEFIK_API_ENDPOINT" ))
58
+ SetBaseURL (os .Getenv ("TRAEFIK_API_ENDPOINT" )).
59
+ SetTimeout (10 * time .Second ).
60
+ SetRetryCount (3 )
37
61
38
62
pollCh := pollTraefikRouters (client )
39
63
var cache []Router
@@ -42,95 +66,108 @@ func main() {
42
66
log .Fatal (poll .Err )
43
67
}
44
68
45
- // Skip if no changes to Traefik routers
69
+ // skip if no changes to traefik routers
46
70
if reflect .DeepEqual (cache , poll .Routers ) {
47
71
continue
48
72
}
49
73
50
74
log .Info ("changes detected" )
51
75
52
- // Update the cache
76
+ // update the cache
53
77
cache = poll .Routers
54
78
55
- // Collect unique domains from applicable routers
56
- uniqueDomains := make ( map [ string ] struct {})
79
+ ingress := []cloudflare. UnvalidatedIngressRule {}
80
+
57
81
for _ , r := range poll .Routers {
58
- // Filter routers
82
+ // Only enabled routes
59
83
if r .Status != "enabled" {
60
- continue
61
- }
62
- if ! contains (r .EntryPoints , os .Getenv ("TRAEFIK_ENTRYPOINT" )) {
84
+ log .Debugf ("Skipping disabled route: %s" , r .Rule )
63
85
continue
64
86
}
65
87
66
- // Log if router has TLS configured for debugging
67
- if r .TLS .CertResolver != "" {
68
- log .WithFields (log.Fields {
69
- "router" : r .ServiceName ,
70
- "rule" : r .Rule ,
71
- }).Warn ("router has TLS configured but is included for tunnel" )
88
+ // Handle both HTTP and HTTPS routes
89
+ // We want to include all routes, including those with TLS configured
90
+ if os .Getenv ("TRAEFIK_ENTRYPOINT" ) != "" {
91
+ // If TRAEFIK_ENTRYPOINT is specified, only use routes with that entrypoint
92
+ if ! contains (r .EntryPoints , os .Getenv ("TRAEFIK_ENTRYPOINT" )) {
93
+ log .Debugf ("Skipping route with different entrypoint: %s" , r .Rule )
94
+ continue
95
+ }
72
96
}
73
97
74
98
domains , err := http .ParseDomains (r .Rule )
75
99
if err != nil {
76
- log .WithFields (log.Fields {
77
- "rule" : r .Rule ,
78
- }).Fatal ("failed to parse domains: " , err )
100
+ log .Errorf ("Failed to parse domains from rule %s: %v" , r .Rule , err )
101
+ continue
79
102
}
103
+
80
104
for _ , domain := range domains {
81
- uniqueDomains [domain ] = struct {}{}
105
+ log .WithFields (log.Fields {
106
+ "domain" : domain ,
107
+ "service" : os .Getenv ("TRAEFIK_SERVICE_ENDPOINT" ),
108
+ "tls" : r .TLS .CertResolver != "" ,
109
+ }).Info ("upserting tunnel" )
110
+
111
+ ingress = append (ingress , cloudflare.UnvalidatedIngressRule {
112
+ Hostname : domain ,
113
+ Service : os .Getenv ("TRAEFIK_SERVICE_ENDPOINT" ),
114
+ OriginRequest : & cloudflare.OriginRequestConfig {
115
+ HTTPHostHeader : & domain ,
116
+ // Add other origin request options if needed
117
+ NoTLSVerify : boolPtr (true ), // Skip TLS verification for internal traffic
118
+ },
119
+ })
82
120
}
83
121
}
84
122
85
- // Build ingress rules from unique domains
86
- ingress := []cloudflare.UnvalidatedIngressRule {}
87
- for domain := range uniqueDomains {
88
- log .WithFields (log.Fields {
89
- "domain" : domain ,
90
- "service" : os .Getenv ("TRAEFIK_SERVICE_ENDPOINT" ),
91
- }).Info ("adding ingress rule" )
92
-
93
- ingress = append (ingress , cloudflare.UnvalidatedIngressRule {
94
- Hostname : domain ,
95
- Service : os .Getenv ("TRAEFIK_SERVICE_ENDPOINT" ),
96
- OriginRequest : & cloudflare.OriginRequestConfig {
97
- HTTPHostHeader : & domain ,
98
- },
99
- })
100
- }
101
-
102
- // Add catch-all rule
123
+ // add catch-all rule
103
124
ingress = append (ingress , cloudflare.UnvalidatedIngressRule {
104
125
Service : "http_status:404" ,
105
126
})
106
127
107
- // Update Cloudflare tunnel configuration
108
128
err = updateTunnels (ctx , cf , ingress )
109
129
if err != nil {
110
130
log .Fatal (err )
111
131
}
112
132
}
113
133
}
114
134
115
- // pollTraefikRouters and updateTunnels remain unchanged but are included for completeness
135
+ // Helper to create a bool pointer
136
+ func boolPtr (b bool ) * bool {
137
+ return & b
138
+ }
139
+
116
140
func pollTraefikRouters (client * resty.Client ) (ch chan PollResponse ) {
117
141
ch = make (chan PollResponse )
118
142
go func () {
119
- defer close (ch )
120
- r := rand .New (rand .NewSource (99 ))
143
+ defer func () {
144
+ close (ch )
145
+ }()
146
+ r := rand .New (rand .NewSource (time .Now ().UnixNano ()))
121
147
c := time .Tick (10 * time .Second )
122
148
123
149
for range c {
124
150
var pollRes PollResponse
125
151
126
- _ , pollRes . Err = client .R ().
152
+ resp , err : = client .R ().
127
153
EnableTrace ().
128
154
SetResult (& pollRes .Routers ).
129
155
Get ("/api/http/routers" )
130
156
131
- if pollRes .Err != nil {
157
+ pollRes .Err = err
158
+ if err != nil {
159
+ log .Errorf ("Error polling Traefik API: %v" , err )
132
160
ch <- pollRes
133
- break
161
+ time .Sleep (5 * time .Second ) // Wait before retrying
162
+ continue
163
+ }
164
+
165
+ if resp .StatusCode () != 200 {
166
+ log .Errorf ("Unexpected status code from Traefik API: %d" , resp .StatusCode ())
167
+ pollRes .Err = fmt .Errorf ("unexpected status code: %d" , resp .StatusCode ())
168
+ ch <- pollRes
169
+ time .Sleep (5 * time .Second ) // Wait before retrying
170
+ continue
134
171
}
135
172
136
173
ch <- pollRes
@@ -143,32 +180,33 @@ func pollTraefikRouters(client *resty.Client) (ch chan PollResponse) {
143
180
}
144
181
145
182
func updateTunnels (ctx context.Context , cf * cloudflare.API , ingress []cloudflare.UnvalidatedIngressRule ) error {
146
- // Get current tunnel config
183
+ // Get Current tunnel config
147
184
aid := cloudflare .AccountIdentifier (os .Getenv ("CLOUDFLARE_ACCOUNT_ID" ))
148
185
cfg , err := cf .GetTunnelConfiguration (ctx , aid , os .Getenv ("CLOUDFLARE_TUNNEL_ID" ))
149
186
if err != nil {
150
- return fmt .Errorf ("unable to pull current tunnel config: %s" , err )
187
+ return fmt .Errorf ("unable to pull current tunnel config, %s" , err . Error () )
151
188
}
152
189
153
190
// Update config with new ingress rules
154
191
cfg .Config .Ingress = ingress
155
- _ , err = cf .UpdateTunnelConfiguration (ctx , aid , cloudflare.TunnelConfigurationParams {
192
+ cfg , err = cf .UpdateTunnelConfiguration (ctx , aid , cloudflare.TunnelConfigurationParams {
156
193
TunnelID : os .Getenv ("CLOUDFLARE_TUNNEL_ID" ),
157
194
Config : cfg .Config ,
158
195
})
159
196
if err != nil {
160
- return fmt .Errorf ("unable to update tunnel config: %s" , err )
197
+ return fmt .Errorf ("unable to update tunnel config, %s" , err . Error () )
161
198
}
162
199
163
200
log .Info ("tunnel config updated" )
164
201
165
- // Update DNS records
202
+ // Update DNS to point to new tunnel
166
203
for _ , i := range ingress {
167
204
if i .Hostname == "" {
168
205
continue
169
206
}
170
207
171
208
var proxied bool = true
209
+
172
210
record := cloudflare.DNSRecord {
173
211
Type : "CNAME" ,
174
212
Name : i .Hostname ,
@@ -180,7 +218,7 @@ func updateTunnels(ctx context.Context, cf *cloudflare.API, ingress []cloudflare
180
218
zid := cloudflare .ZoneIdentifier (os .Getenv ("CLOUDFLARE_ZONE_ID" ))
181
219
r , _ , err := cf .ListDNSRecords (ctx , zid , cloudflare.ListDNSRecordsParams {Name : i .Hostname })
182
220
if err != nil {
183
- return fmt .Errorf ("error checking DNS records: %s" , err )
221
+ return fmt .Errorf ("err checking DNS records, %s" , err . Error () )
184
222
}
185
223
186
224
if len (r ) == 0 {
@@ -192,7 +230,7 @@ func updateTunnels(ctx context.Context, cf *cloudflare.API, ingress []cloudflare
192
230
Proxied : record .Proxied ,
193
231
})
194
232
if err != nil {
195
- return fmt .Errorf ("unable to create DNS record: %s" , err )
233
+ return fmt .Errorf ("unable to create DNS record, %s" , err . Error () )
196
234
}
197
235
log .WithFields (log.Fields {
198
236
"domain" : record .Name ,
@@ -202,15 +240,15 @@ func updateTunnels(ctx context.Context, cf *cloudflare.API, ingress []cloudflare
202
240
203
241
if r [0 ].Content != record .Content {
204
242
_ , err = cf .UpdateDNSRecord (ctx , zid , cloudflare.UpdateDNSRecordParams {
205
- ID : r [0 ].ID , // Use existing record ID
243
+ ID : r [0 ].ID , // Use the actual ID from the retrieved record
206
244
Name : record .Name ,
207
245
Type : record .Type ,
208
246
Content : record .Content ,
209
247
TTL : record .TTL ,
210
248
Proxied : record .Proxied ,
211
249
})
212
250
if err != nil {
213
- return fmt .Errorf ("could not update record for %s: %s" , i .Hostname , err )
251
+ return fmt .Errorf ("could not update record for %s, %s" , i .Hostname , err )
214
252
}
215
253
log .WithFields (log.Fields {
216
254
"domain" : record .Name ,
0 commit comments