• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubernetes-sigs / external-dns / 14131981358

28 Mar 2025 03:08PM UTC coverage: 71.613% (+0.2%) from 71.367%
14131981358

Pull #5222

github

web-flow
chore(code-cleanup): move logic away from main.go add tests

Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com>
Pull Request #5222: chore(code-cleanup): move logic away from main.go add tests

60 of 382 new or added lines in 4 files covered. (15.71%)

13 existing lines in 1 file now uncovered.

14367 of 20062 relevant lines covered (71.61%)

702.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

17.56
/controller/execute.go
1
/*
2
Copyright 2025 The Kubernetes Authors.
3

4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7

8
    http://www.apache.org/licenses/LICENSE-2.0
9

10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16

17
package controller
18

19
import (
20
        "context"
21
        "fmt"
22
        "net/http"
23
        "os"
24
        "os/signal"
25
        "slices"
26
        "strings"
27
        "syscall"
28
        "time"
29

30
        "github.com/aws/aws-sdk-go-v2/service/dynamodb"
31
        "github.com/aws/aws-sdk-go-v2/service/route53"
32
        sd "github.com/aws/aws-sdk-go-v2/service/servicediscovery"
33
        "github.com/go-logr/logr"
34
        "github.com/prometheus/client_golang/prometheus/promhttp"
35
        log "github.com/sirupsen/logrus"
36
        "k8s.io/klog/v2"
37

38
        "sigs.k8s.io/external-dns/endpoint"
39
        "sigs.k8s.io/external-dns/pkg/apis/externaldns"
40
        "sigs.k8s.io/external-dns/pkg/apis/externaldns/validation"
41
        "sigs.k8s.io/external-dns/pkg/metrics"
42
        "sigs.k8s.io/external-dns/plan"
43
        "sigs.k8s.io/external-dns/provider"
44
        "sigs.k8s.io/external-dns/provider/akamai"
45
        "sigs.k8s.io/external-dns/provider/alibabacloud"
46
        "sigs.k8s.io/external-dns/provider/aws"
47
        "sigs.k8s.io/external-dns/provider/awssd"
48
        "sigs.k8s.io/external-dns/provider/azure"
49
        "sigs.k8s.io/external-dns/provider/civo"
50
        "sigs.k8s.io/external-dns/provider/cloudflare"
51
        "sigs.k8s.io/external-dns/provider/coredns"
52
        "sigs.k8s.io/external-dns/provider/digitalocean"
53
        "sigs.k8s.io/external-dns/provider/dnsimple"
54
        "sigs.k8s.io/external-dns/provider/exoscale"
55
        "sigs.k8s.io/external-dns/provider/gandi"
56
        "sigs.k8s.io/external-dns/provider/godaddy"
57
        "sigs.k8s.io/external-dns/provider/google"
58
        "sigs.k8s.io/external-dns/provider/ibmcloud"
59
        "sigs.k8s.io/external-dns/provider/inmemory"
60
        "sigs.k8s.io/external-dns/provider/linode"
61
        "sigs.k8s.io/external-dns/provider/ns1"
62
        "sigs.k8s.io/external-dns/provider/oci"
63
        "sigs.k8s.io/external-dns/provider/ovh"
64
        "sigs.k8s.io/external-dns/provider/pdns"
65
        "sigs.k8s.io/external-dns/provider/pihole"
66
        "sigs.k8s.io/external-dns/provider/plural"
67
        "sigs.k8s.io/external-dns/provider/rfc2136"
68
        "sigs.k8s.io/external-dns/provider/scaleway"
69
        "sigs.k8s.io/external-dns/provider/tencentcloud"
70
        "sigs.k8s.io/external-dns/provider/transip"
71
        "sigs.k8s.io/external-dns/provider/ultradns"
72
        "sigs.k8s.io/external-dns/provider/webhook"
73
        webhookapi "sigs.k8s.io/external-dns/provider/webhook/api"
74
        "sigs.k8s.io/external-dns/registry"
75
        "sigs.k8s.io/external-dns/source"
76
)
77

NEW
78
func Execute() {
×
NEW
79
        cfg := externaldns.NewConfig()
×
NEW
80
        if err := cfg.ParseFlags(os.Args[1:]); err != nil {
×
NEW
81
                log.Fatalf("flag parsing error: %v", err)
×
NEW
82
        }
×
NEW
83
        log.Infof("config: %s", cfg)
×
NEW
84
        if err := validation.ValidateConfig(cfg); err != nil {
×
NEW
85
                log.Fatalf("config validation failed: %v", err)
×
NEW
86
        }
×
87

NEW
88
        configureLogger(cfg)
×
NEW
89

×
NEW
90
        if cfg.DryRun {
×
NEW
91
                log.Info("running in dry-run mode. No changes to DNS records will be made.")
×
NEW
92
        }
×
93

NEW
94
        if log.GetLevel() < log.DebugLevel {
×
NEW
95
                // Klog V2 is used by k8s.io/apimachinery/pkg/labels and can throw (a lot) of irrelevant logs
×
NEW
96
                // See https://github.com/kubernetes-sigs/external-dns/issues/2348
×
NEW
97
                defer klog.ClearLogger()
×
NEW
98
                klog.SetLogger(logr.Discard())
×
NEW
99
        }
×
100

NEW
101
        log.Info(externaldns.Banner())
×
NEW
102

×
NEW
103
        ctx, cancel := context.WithCancel(context.Background())
×
NEW
104

×
NEW
105
        go serveMetrics(cfg.MetricsAddress)
×
NEW
106
        go handleSigterm(cancel)
×
NEW
107

×
NEW
108
        // Create a source.Config from the flags passed by the user.
×
NEW
109
        sourceCfg := source.NewConfig(cfg)
×
NEW
110

×
NEW
111
        // Lookup all the selected sources by names and pass them the desired configuration.
×
NEW
112
        sources, err := source.ByNames(ctx, &source.SingletonClientGenerator{
×
NEW
113
                KubeConfig:   cfg.KubeConfig,
×
NEW
114
                APIServerURL: cfg.APIServerURL,
×
NEW
115
                // If update events are enabled, disable timeout.
×
NEW
116
                RequestTimeout: func() time.Duration {
×
NEW
117
                        if cfg.UpdateEvents {
×
NEW
118
                                return 0
×
NEW
119
                        }
×
NEW
120
                        return cfg.RequestTimeout
×
121
                }(),
122
        }, cfg.Sources, sourceCfg)
NEW
123
        if err != nil {
×
NEW
124
                log.Fatal(err)
×
NEW
125
        }
×
126

127
        // Filter targets
NEW
128
        targetFilter := endpoint.NewTargetNetFilterWithExclusions(cfg.TargetNetFilter, cfg.ExcludeTargetNets)
×
NEW
129

×
NEW
130
        // Combine multiple sources into a single, deduplicated source.
×
NEW
131
        endpointsSource := source.NewDedupSource(source.NewMultiSource(sources, sourceCfg.DefaultTargets))
×
NEW
132
        endpointsSource = source.NewNAT64Source(endpointsSource, cfg.NAT64Networks)
×
NEW
133
        endpointsSource = source.NewTargetFilterSource(endpointsSource, targetFilter)
×
NEW
134

×
NEW
135
        domainFilter := createDomainFilter(cfg)
×
NEW
136
        zoneNameFilter := endpoint.NewDomainFilter(cfg.ZoneNameFilter)
×
NEW
137
        zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
×
NEW
138
        zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
×
NEW
139
        zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter)
×
NEW
140

×
NEW
141
        var p provider.Provider
×
NEW
142
        switch cfg.Provider {
×
NEW
143
        case "akamai":
×
NEW
144
                p, err = akamai.NewAkamaiProvider(
×
NEW
145
                        akamai.AkamaiConfig{
×
NEW
146
                                DomainFilter:          domainFilter,
×
NEW
147
                                ZoneIDFilter:          zoneIDFilter,
×
NEW
148
                                ServiceConsumerDomain: cfg.AkamaiServiceConsumerDomain,
×
NEW
149
                                ClientToken:           cfg.AkamaiClientToken,
×
NEW
150
                                ClientSecret:          cfg.AkamaiClientSecret,
×
NEW
151
                                AccessToken:           cfg.AkamaiAccessToken,
×
NEW
152
                                EdgercPath:            cfg.AkamaiEdgercPath,
×
NEW
153
                                EdgercSection:         cfg.AkamaiEdgercSection,
×
NEW
154
                                DryRun:                cfg.DryRun,
×
NEW
155
                        }, nil)
×
NEW
156
        case "alibabacloud":
×
NEW
157
                p, err = alibabacloud.NewAlibabaCloudProvider(cfg.AlibabaCloudConfigFile, domainFilter, zoneIDFilter, cfg.AlibabaCloudZoneType, cfg.DryRun)
×
NEW
158
        case "aws":
×
NEW
159
                configs := aws.CreateV2Configs(cfg)
×
NEW
160
                clients := make(map[string]aws.Route53API, len(configs))
×
NEW
161
                for profile, config := range configs {
×
NEW
162
                        clients[profile] = route53.NewFromConfig(config)
×
NEW
163
                }
×
164

NEW
165
                p, err = aws.NewAWSProvider(
×
NEW
166
                        aws.AWSConfig{
×
NEW
167
                                DomainFilter:          domainFilter,
×
NEW
168
                                ZoneIDFilter:          zoneIDFilter,
×
NEW
169
                                ZoneTypeFilter:        zoneTypeFilter,
×
NEW
170
                                ZoneTagFilter:         zoneTagFilter,
×
NEW
171
                                ZoneMatchParent:       cfg.AWSZoneMatchParent,
×
NEW
172
                                BatchChangeSize:       cfg.AWSBatchChangeSize,
×
NEW
173
                                BatchChangeSizeBytes:  cfg.AWSBatchChangeSizeBytes,
×
NEW
174
                                BatchChangeSizeValues: cfg.AWSBatchChangeSizeValues,
×
NEW
175
                                BatchChangeInterval:   cfg.AWSBatchChangeInterval,
×
NEW
176
                                EvaluateTargetHealth:  cfg.AWSEvaluateTargetHealth,
×
NEW
177
                                PreferCNAME:           cfg.AWSPreferCNAME,
×
NEW
178
                                DryRun:                cfg.DryRun,
×
NEW
179
                                ZoneCacheDuration:     cfg.AWSZoneCacheDuration,
×
NEW
180
                        },
×
NEW
181
                        clients,
×
NEW
182
                )
×
NEW
183
        case "aws-sd":
×
NEW
184
                // Check that only compatible Registry is used with AWS-SD
×
NEW
185
                if cfg.Registry != "noop" && cfg.Registry != "aws-sd" {
×
NEW
186
                        log.Infof("Registry \"%s\" cannot be used with AWS Cloud Map. Switching to \"aws-sd\".", cfg.Registry)
×
NEW
187
                        cfg.Registry = "aws-sd"
×
NEW
188
                }
×
NEW
189
                p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.DryRun, cfg.AWSSDServiceCleanup, cfg.TXTOwnerID, cfg.AWSSDCreateTag, sd.NewFromConfig(aws.CreateDefaultV2Config(cfg)))
×
NEW
190
        case "azure-dns", "azure":
×
NEW
191
                p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.AzureZonesCacheDuration, cfg.DryRun)
×
NEW
192
        case "azure-private-dns":
×
NEW
193
                p, err = azure.NewAzurePrivateDNSProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.AzureZonesCacheDuration, cfg.DryRun)
×
NEW
194
        case "ultradns":
×
NEW
195
                p, err = ultradns.NewUltraDNSProvider(domainFilter, cfg.DryRun)
×
NEW
196
        case "civo":
×
NEW
197
                p, err = civo.NewCivoProvider(domainFilter, cfg.DryRun)
×
NEW
198
        case "cloudflare":
×
NEW
199
                p, err = cloudflare.NewCloudFlareProvider(
×
NEW
200
                        domainFilter,
×
NEW
201
                        zoneIDFilter,
×
NEW
202
                        cfg.CloudflareProxied,
×
NEW
203
                        cfg.DryRun,
×
NEW
204
                        cfg.CloudflareDNSRecordsPerPage,
×
NEW
205
                        cfg.CloudflareRegionKey,
×
NEW
206
                        cloudflare.CustomHostnamesConfig{
×
NEW
207
                                Enabled:              cfg.CloudflareCustomHostnames,
×
NEW
208
                                MinTLSVersion:        cfg.CloudflareCustomHostnamesMinTLSVersion,
×
NEW
209
                                CertificateAuthority: cfg.CloudflareCustomHostnamesCertificateAuthority,
×
NEW
210
                        })
×
NEW
211
        case "google":
×
NEW
212
                p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.GoogleZoneVisibility, cfg.DryRun)
×
NEW
213
        case "digitalocean":
×
NEW
214
                p, err = digitalocean.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun, cfg.DigitalOceanAPIPageSize)
×
NEW
215
        case "ovh":
×
NEW
216
                p, err = ovh.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.OVHApiRateLimit, cfg.OVHEnableCNAMERelative, cfg.DryRun)
×
NEW
217
        case "linode":
×
NEW
218
                p, err = linode.NewLinodeProvider(domainFilter, cfg.DryRun)
×
NEW
219
        case "dnsimple":
×
NEW
220
                p, err = dnsimple.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun)
×
NEW
221
        case "coredns", "skydns":
×
NEW
222
                p, err = coredns.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.DryRun)
×
NEW
223
        case "exoscale":
×
NEW
224
                p, err = exoscale.NewExoscaleProvider(
×
NEW
225
                        cfg.ExoscaleAPIEnvironment,
×
NEW
226
                        cfg.ExoscaleAPIZone,
×
NEW
227
                        cfg.ExoscaleAPIKey,
×
NEW
228
                        cfg.ExoscaleAPISecret,
×
NEW
229
                        cfg.DryRun,
×
NEW
230
                        exoscale.ExoscaleWithDomain(domainFilter),
×
NEW
231
                        exoscale.ExoscaleWithLogging(),
×
NEW
232
                )
×
NEW
233
        case "inmemory":
×
NEW
234
                p, err = inmemory.NewInMemoryProvider(inmemory.InMemoryInitZones(cfg.InMemoryZones), inmemory.InMemoryWithDomain(domainFilter), inmemory.InMemoryWithLogging()), nil
×
NEW
235
        case "pdns":
×
NEW
236
                p, err = pdns.NewPDNSProvider(
×
NEW
237
                        ctx,
×
NEW
238
                        pdns.PDNSConfig{
×
NEW
239
                                DomainFilter: domainFilter,
×
NEW
240
                                DryRun:       cfg.DryRun,
×
NEW
241
                                Server:       cfg.PDNSServer,
×
NEW
242
                                ServerID:     cfg.PDNSServerID,
×
NEW
243
                                APIKey:       cfg.PDNSAPIKey,
×
NEW
244
                                TLSConfig: pdns.TLSConfig{
×
NEW
245
                                        SkipTLSVerify:         cfg.PDNSSkipTLSVerify,
×
NEW
246
                                        CAFilePath:            cfg.TLSCA,
×
NEW
247
                                        ClientCertFilePath:    cfg.TLSClientCert,
×
NEW
248
                                        ClientCertKeyFilePath: cfg.TLSClientCertKey,
×
NEW
249
                                },
×
NEW
250
                        },
×
NEW
251
                )
×
NEW
252
        case "oci":
×
NEW
253
                var config *oci.OCIConfig
×
NEW
254
                // if the instance-principals flag was set, and a compartment OCID was provided, then ignore the
×
NEW
255
                // OCI config file, and provide a config that uses instance principal authentication.
×
NEW
256
                if cfg.OCIAuthInstancePrincipal {
×
NEW
257
                        if len(cfg.OCICompartmentOCID) == 0 {
×
NEW
258
                                err = fmt.Errorf("instance principal authentication requested, but no compartment OCID provided")
×
NEW
259
                        } else {
×
NEW
260
                                authConfig := oci.OCIAuthConfig{UseInstancePrincipal: true}
×
NEW
261
                                config = &oci.OCIConfig{Auth: authConfig, CompartmentID: cfg.OCICompartmentOCID}
×
NEW
262
                        }
×
NEW
263
                } else {
×
NEW
264
                        config, err = oci.LoadOCIConfig(cfg.OCIConfigFile)
×
NEW
265
                }
×
NEW
266
                config.ZoneCacheDuration = cfg.OCIZoneCacheDuration
×
NEW
267
                if err == nil {
×
NEW
268
                        p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.OCIZoneScope, cfg.DryRun)
×
NEW
269
                }
×
NEW
270
        case "rfc2136":
×
NEW
271
                tlsConfig := rfc2136.TLSConfig{
×
NEW
272
                        UseTLS:                cfg.RFC2136UseTLS,
×
NEW
273
                        SkipTLSVerify:         cfg.RFC2136SkipTLSVerify,
×
NEW
274
                        CAFilePath:            cfg.TLSCA,
×
NEW
275
                        ClientCertFilePath:    cfg.TLSClientCert,
×
NEW
276
                        ClientCertKeyFilePath: cfg.TLSClientCertKey,
×
NEW
277
                }
×
NEW
278
                p, err = rfc2136.NewRfc2136Provider(cfg.RFC2136Host, cfg.RFC2136Port, cfg.RFC2136Zone, cfg.RFC2136Insecure, cfg.RFC2136TSIGKeyName, cfg.RFC2136TSIGSecret, cfg.RFC2136TSIGSecretAlg, cfg.RFC2136TAXFR, domainFilter, cfg.DryRun, cfg.RFC2136MinTTL, cfg.RFC2136CreatePTR, cfg.RFC2136GSSTSIG, cfg.RFC2136KerberosUsername, cfg.RFC2136KerberosPassword, cfg.RFC2136KerberosRealm, cfg.RFC2136BatchChangeSize, tlsConfig, cfg.RFC2136LoadBalancingStrategy, nil)
×
NEW
279
        case "ns1":
×
NEW
280
                p, err = ns1.NewNS1Provider(
×
NEW
281
                        ns1.NS1Config{
×
NEW
282
                                DomainFilter:  domainFilter,
×
NEW
283
                                ZoneIDFilter:  zoneIDFilter,
×
NEW
284
                                NS1Endpoint:   cfg.NS1Endpoint,
×
NEW
285
                                NS1IgnoreSSL:  cfg.NS1IgnoreSSL,
×
NEW
286
                                DryRun:        cfg.DryRun,
×
NEW
287
                                MinTTLSeconds: cfg.NS1MinTTLSeconds,
×
NEW
288
                        },
×
NEW
289
                )
×
NEW
290
        case "transip":
×
NEW
291
                p, err = transip.NewTransIPProvider(cfg.TransIPAccountName, cfg.TransIPPrivateKeyFile, domainFilter, cfg.DryRun)
×
NEW
292
        case "scaleway":
×
NEW
293
                p, err = scaleway.NewScalewayProvider(ctx, domainFilter, cfg.DryRun)
×
NEW
294
        case "godaddy":
×
NEW
295
                p, err = godaddy.NewGoDaddyProvider(ctx, domainFilter, cfg.GoDaddyTTL, cfg.GoDaddyAPIKey, cfg.GoDaddySecretKey, cfg.GoDaddyOTE, cfg.DryRun)
×
NEW
296
        case "gandi":
×
NEW
297
                p, err = gandi.NewGandiProvider(ctx, domainFilter, cfg.DryRun)
×
NEW
298
        case "pihole":
×
NEW
299
                p, err = pihole.NewPiholeProvider(
×
NEW
300
                        pihole.PiholeConfig{
×
NEW
301
                                Server:                cfg.PiholeServer,
×
NEW
302
                                Password:              cfg.PiholePassword,
×
NEW
303
                                TLSInsecureSkipVerify: cfg.PiholeTLSInsecureSkipVerify,
×
NEW
304
                                DomainFilter:          domainFilter,
×
NEW
305
                                DryRun:                cfg.DryRun,
×
NEW
306
                        },
×
NEW
307
                )
×
NEW
308
        case "ibmcloud":
×
NEW
309
                p, err = ibmcloud.NewIBMCloudProvider(cfg.IBMCloudConfigFile, domainFilter, zoneIDFilter, endpointsSource, cfg.IBMCloudProxied, cfg.DryRun)
×
NEW
310
        case "plural":
×
NEW
311
                p, err = plural.NewPluralProvider(cfg.PluralCluster, cfg.PluralProvider)
×
NEW
312
        case "tencentcloud":
×
NEW
313
                p, err = tencentcloud.NewTencentCloudProvider(domainFilter, zoneIDFilter, cfg.TencentCloudConfigFile, cfg.TencentCloudZoneType, cfg.DryRun)
×
NEW
314
        case "webhook":
×
NEW
315
                p, err = webhook.NewWebhookProvider(cfg.WebhookProviderURL)
×
NEW
316
        default:
×
NEW
317
                log.Fatalf("unknown dns provider: %s", cfg.Provider)
×
318
        }
NEW
319
        if err != nil {
×
NEW
320
                log.Fatal(err)
×
NEW
321
        }
×
322

NEW
323
        if cfg.WebhookServer {
×
NEW
324
                webhookapi.StartHTTPApi(p, nil, cfg.WebhookProviderReadTimeout, cfg.WebhookProviderWriteTimeout, "127.0.0.1:8888")
×
NEW
325
                os.Exit(0)
×
NEW
326
        }
×
327

NEW
328
        if cfg.ProviderCacheTime > 0 {
×
NEW
329
                p = provider.NewCachedProvider(
×
NEW
330
                        p,
×
NEW
331
                        cfg.ProviderCacheTime,
×
NEW
332
                )
×
NEW
333
        }
×
334

NEW
335
        reg, err := selectRegistry(cfg, p)
×
NEW
336
        if err != nil {
×
NEW
337
                log.Fatal(err)
×
NEW
338
        }
×
339

NEW
340
        policy, exists := plan.Policies[cfg.Policy]
×
NEW
341
        if !exists {
×
NEW
342
                log.Fatalf("unknown policy: %s", cfg.Policy)
×
NEW
343
        }
×
344

NEW
345
        ctrl := Controller{
×
NEW
346
                Source:               endpointsSource,
×
NEW
347
                Registry:             reg,
×
NEW
348
                Policy:               policy,
×
NEW
349
                Interval:             cfg.Interval,
×
NEW
350
                DomainFilter:         domainFilter,
×
NEW
351
                ManagedRecordTypes:   cfg.ManagedDNSRecordTypes,
×
NEW
352
                ExcludeRecordTypes:   cfg.ExcludeDNSRecordTypes,
×
NEW
353
                MinEventSyncInterval: cfg.MinEventSyncInterval,
×
NEW
354
        }
×
NEW
355

×
NEW
356
        if cfg.Once {
×
NEW
357
                err := ctrl.RunOnce(ctx)
×
NEW
358
                if err != nil {
×
NEW
359
                        log.Fatal(err)
×
NEW
360
                }
×
361

NEW
362
                os.Exit(0)
×
363
        }
364

NEW
365
        if cfg.UpdateEvents {
×
NEW
366
                // Add RunOnce as the handler function that will be called when ingress/service sources have changed.
×
NEW
367
                // Note that k8s Informers will perform an initial list operation, which results in the handler
×
NEW
368
                // function initially being called for every Service/Ingress that exists
×
NEW
369
                ctrl.Source.AddEventHandler(ctx, func() { ctrl.ScheduleRunOnce(time.Now()) })
×
370
        }
371

NEW
372
        ctrl.ScheduleRunOnce(time.Now())
×
NEW
373
        ctrl.Run(ctx)
×
374
}
375

376
// This function configures the logger format and level based on the provided configuration.
377
func configureLogger(cfg *externaldns.Config) {
4✔
378
        logFormats := []string{"text", "json"}
4✔
379
        if !slices.Contains(logFormats, cfg.LogFormat) {
5✔
380
                log.Fatalf("unknown log format: '%s'. known formats: '%s'", cfg.LogFormat, strings.Join(logFormats, ","))
1✔
381
        }
1✔
382
        if cfg.LogFormat == "json" {
5✔
383
                log.SetFormatter(&log.JSONFormatter{})
1✔
384
        }
1✔
385
        ll, err := log.ParseLevel(cfg.LogLevel)
4✔
386
        if err != nil {
5✔
387
                log.Fatalf("failed to parse log level: %v", err)
1✔
388
        }
1✔
389
        log.SetLevel(ll)
4✔
390
}
391

392
// selectRegistry selects the appropriate registry implementation based on the configuration in cfg.
393
// It initializes and returns a registry along with any error encountered during setup.
394
// Supported registry types include: dynamodb, noop, txt, and aws-sd.
395
func selectRegistry(cfg *externaldns.Config, p provider.Provider) (registry.Registry, error) {
5✔
396
        var r registry.Registry
5✔
397
        var err error
5✔
398
        switch cfg.Registry {
5✔
399
        case "dynamodb":
1✔
400
                var dynamodbOpts []func(*dynamodb.Options)
1✔
401
                if cfg.AWSDynamoDBRegion != "" {
2✔
402
                        dynamodbOpts = []func(*dynamodb.Options){
1✔
403
                                func(opts *dynamodb.Options) {
2✔
404
                                        opts.Region = cfg.AWSDynamoDBRegion
1✔
405
                                },
1✔
406
                        }
407
                }
408
                r, err = registry.NewDynamoDBRegistry(p, cfg.TXTOwnerID, dynamodb.NewFromConfig(aws.CreateDefaultV2Config(cfg), dynamodbOpts...), cfg.AWSDynamoDBTable, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes, cfg.ExcludeDNSRecordTypes, []byte(cfg.TXTEncryptAESKey), cfg.TXTCacheInterval)
1✔
409
        case "noop":
1✔
410
                r, err = registry.NewNoopRegistry(p)
1✔
411
        case "txt":
1✔
412
                r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes, cfg.ExcludeDNSRecordTypes, cfg.TXTEncryptEnabled, []byte(cfg.TXTEncryptAESKey), cfg.TXTNewFormatOnly)
1✔
413
        case "aws-sd":
1✔
414
                r, err = registry.NewAWSSDRegistry(p, cfg.TXTOwnerID)
1✔
415
        default:
1✔
416
                log.Fatalf("unknown registry: %s", cfg.Registry)
1✔
417
        }
418
        return r, err
5✔
419
}
420

421
// RegexDomainFilter overrides DomainFilter
422
func createDomainFilter(cfg *externaldns.Config) endpoint.DomainFilter {
5✔
423
        if cfg.RegexDomainFilter != nil && cfg.RegexDomainFilter.String() != "" {
7✔
424
                return endpoint.NewRegexDomainFilter(cfg.RegexDomainFilter, cfg.RegexDomainExclusion)
2✔
425
        } else {
5✔
426
                return endpoint.NewDomainFilterWithExclusions(cfg.DomainFilter, cfg.ExcludeDomains)
3✔
427
        }
3✔
428
}
429

430
// handleSigterm listens for a SIGTERM signal and triggers the provided cancel function
431
// to gracefully terminate the application. It logs a message when the signal is received.
432
func handleSigterm(cancel func()) {
1✔
433
        signals := make(chan os.Signal, 1)
1✔
434
        signal.Notify(signals, syscall.SIGTERM)
1✔
435
        <-signals
1✔
436
        log.Info("Received SIGTERM. Terminating...")
1✔
437
        cancel()
1✔
438
}
1✔
439

440
// serveMetrics starts an HTTP server that serves health and metrics endpoints.
441
// The /healthz endpoint returns a 200 OK status to indicate the service is healthy.
442
// The /metrics endpoint serves Prometheus metrics.
443
// The server listens on the specified address and logs debug information about the endpoints.
444
func serveMetrics(address string) {
1✔
445
        http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
2✔
446
                w.WriteHeader(http.StatusOK)
1✔
447
                _, _ = w.Write([]byte("OK"))
1✔
448
        })
1✔
449

450
        log.Debugf("serving 'healthz' on 'localhost:%s/healthz'", address)
1✔
451
        log.Debugf("serving 'metrics' on 'localhost:%s/metrics'", address)
1✔
452
        log.Debugf("registered '%d' metrics", len(metrics.RegisterMetric.Metrics))
1✔
453

1✔
454
        http.Handle("/metrics", promhttp.Handler())
1✔
455

1✔
456
        log.Fatal(http.ListenAndServe(address, nil))
1✔
457
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc