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

kubernetes-sigs / external-dns / 17438302085

03 Sep 2025 03:27PM UTC coverage: 77.449% (+0.008%) from 77.441%
17438302085

Pull #4823

github

troll-os
Fix label overriding in TXT record generation when migration is enabled
Pull Request #4823: feat: add new flags to allow migration of OwnerID

22 of 23 new or added lines in 5 files covered. (95.65%)

2 existing lines in 1 file now uncovered.

15369 of 19844 relevant lines covered (77.45%)

731.91 hits per line

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

41.44
/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
        "syscall"
26
        "time"
27

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

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

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

85
        configureLogger(cfg)
×
86

×
87
        if cfg.DryRun {
×
88
                log.Info("running in dry-run mode. No changes to DNS records will be made.")
×
89
        }
×
90

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

98
        log.Info(externaldns.Banner())
×
99

×
100
        ctx, cancel := context.WithCancel(context.Background())
×
101

×
102
        go serveMetrics(cfg.MetricsAddress)
×
103
        go handleSigterm(cancel)
×
104

×
105
        endpointsSource, err := buildSource(ctx, cfg)
×
106
        if err != nil {
×
107
                log.Fatal(err)
×
108
        }
×
109

110
        domainFilter := createDomainFilter(cfg)
×
111

×
112
        prvdr, err := buildProvider(ctx, cfg, domainFilter)
×
113
        if err != nil {
×
114
                log.Fatal(err)
×
115
        }
×
116

117
        if cfg.WebhookServer {
×
118
                webhookapi.StartHTTPApi(prvdr, nil, cfg.WebhookProviderReadTimeout, cfg.WebhookProviderWriteTimeout, "127.0.0.1:8888")
×
119
                os.Exit(0)
×
120
        }
×
121

122
        ctrl, err := buildController(ctx, cfg, endpointsSource, prvdr, domainFilter)
×
123
        if err != nil {
×
124
                log.Fatal(err)
×
125
        }
×
126

127
        if cfg.Once {
×
128
                err := ctrl.RunOnce(ctx)
×
129
                if err != nil {
×
130
                        log.Fatal(err)
×
131
                }
×
132

133
                os.Exit(0)
×
134
        }
135

136
        if cfg.UpdateEvents {
×
137
                // Add RunOnce as the handler function that will be called when ingress/service sources have changed.
×
138
                // Note that k8s Informers will perform an initial list operation, which results in the handler
×
139
                // function initially being called for every Service/Ingress that exists
×
140
                ctrl.Source.AddEventHandler(ctx, func() { ctrl.ScheduleRunOnce(time.Now()) })
×
141
        }
142

143
        ctrl.ScheduleRunOnce(time.Now())
×
144
        ctrl.Run(ctx)
×
145
}
146

147
func buildProvider(
148
        ctx context.Context,
149
        cfg *externaldns.Config,
150
        domainFilter *endpoint.DomainFilter,
151
) (provider.Provider, error) {
9✔
152
        var p provider.Provider
9✔
153
        var err error
9✔
154

9✔
155
        zoneNameFilter := endpoint.NewDomainFilter(cfg.ZoneNameFilter)
9✔
156
        zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
9✔
157
        zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
9✔
158
        zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter)
9✔
159

9✔
160
        switch cfg.Provider {
9✔
161
        case "akamai":
×
162
                p, err = akamai.NewAkamaiProvider(
×
163
                        akamai.AkamaiConfig{
×
164
                                DomainFilter:          domainFilter,
×
165
                                ZoneIDFilter:          zoneIDFilter,
×
166
                                ServiceConsumerDomain: cfg.AkamaiServiceConsumerDomain,
×
167
                                ClientToken:           cfg.AkamaiClientToken,
×
168
                                ClientSecret:          cfg.AkamaiClientSecret,
×
169
                                AccessToken:           cfg.AkamaiAccessToken,
×
170
                                EdgercPath:            cfg.AkamaiEdgercPath,
×
171
                                EdgercSection:         cfg.AkamaiEdgercSection,
×
172
                                DryRun:                cfg.DryRun,
×
173
                        }, nil)
×
174
        case "alibabacloud":
×
175
                p, err = alibabacloud.NewAlibabaCloudProvider(cfg.AlibabaCloudConfigFile, domainFilter, zoneIDFilter, cfg.AlibabaCloudZoneType, cfg.DryRun)
×
176
        case "aws":
1✔
177
                configs := aws.CreateV2Configs(cfg)
1✔
178
                clients := make(map[string]aws.Route53API, len(configs))
1✔
179
                for profile, config := range configs {
2✔
180
                        clients[profile] = route53.NewFromConfig(config)
1✔
181
                }
1✔
182

183
                p, err = aws.NewAWSProvider(
1✔
184
                        aws.AWSConfig{
1✔
185
                                DomainFilter:          domainFilter,
1✔
186
                                ZoneIDFilter:          zoneIDFilter,
1✔
187
                                ZoneTypeFilter:        zoneTypeFilter,
1✔
188
                                ZoneTagFilter:         zoneTagFilter,
1✔
189
                                ZoneMatchParent:       cfg.AWSZoneMatchParent,
1✔
190
                                BatchChangeSize:       cfg.AWSBatchChangeSize,
1✔
191
                                BatchChangeSizeBytes:  cfg.AWSBatchChangeSizeBytes,
1✔
192
                                BatchChangeSizeValues: cfg.AWSBatchChangeSizeValues,
1✔
193
                                BatchChangeInterval:   cfg.AWSBatchChangeInterval,
1✔
194
                                EvaluateTargetHealth:  cfg.AWSEvaluateTargetHealth,
1✔
195
                                PreferCNAME:           cfg.AWSPreferCNAME,
1✔
196
                                DryRun:                cfg.DryRun,
1✔
197
                                ZoneCacheDuration:     cfg.AWSZoneCacheDuration,
1✔
198
                        },
1✔
199
                        clients,
1✔
200
                )
1✔
201
        case "aws-sd":
×
202
                // Check that only compatible Registry is used with AWS-SD
×
203
                if cfg.Registry != "noop" && cfg.Registry != "aws-sd" {
×
204
                        log.Infof("Registry \"%s\" cannot be used with AWS Cloud Map. Switching to \"aws-sd\".", cfg.Registry)
×
205
                        cfg.Registry = "aws-sd"
×
206
                }
×
207
                p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.DryRun, cfg.AWSSDServiceCleanup, cfg.TXTOwnerID, cfg.AWSSDCreateTag, sd.NewFromConfig(aws.CreateDefaultV2Config(cfg)))
×
208
        case "azure-dns", "azure":
×
209
                p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.AzureZonesCacheDuration, cfg.AzureMaxRetriesCount, cfg.DryRun)
×
210
        case "azure-private-dns":
×
211
                p, err = azure.NewAzurePrivateDNSProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.AzureZonesCacheDuration, cfg.AzureMaxRetriesCount, cfg.DryRun)
×
212
        case "civo":
×
213
                p, err = civo.NewCivoProvider(domainFilter, cfg.DryRun)
×
214
        case "cloudflare":
×
215
                p, err = cloudflare.NewCloudFlareProvider(
×
216
                        domainFilter,
×
217
                        zoneIDFilter,
×
218
                        cfg.CloudflareProxied,
×
219
                        cfg.DryRun,
×
220
                        cloudflare.RegionalServicesConfig{
×
221
                                Enabled:   cfg.CloudflareRegionalServices,
×
222
                                RegionKey: cfg.CloudflareRegionKey,
×
223
                        },
×
224
                        cloudflare.CustomHostnamesConfig{
×
225
                                Enabled:              cfg.CloudflareCustomHostnames,
×
226
                                MinTLSVersion:        cfg.CloudflareCustomHostnamesMinTLSVersion,
×
227
                                CertificateAuthority: cfg.CloudflareCustomHostnamesCertificateAuthority,
×
228
                        },
×
229
                        cloudflare.DNSRecordsConfig{
×
230
                                PerPage: cfg.CloudflareDNSRecordsPerPage,
×
231
                                Comment: cfg.CloudflareDNSRecordsComment,
×
232
                        })
×
233
        case "google":
×
234
                p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.GoogleZoneVisibility, cfg.DryRun)
×
235
        case "digitalocean":
×
236
                p, err = digitalocean.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun, cfg.DigitalOceanAPIPageSize)
×
237
        case "ovh":
×
238
                p, err = ovh.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.OVHApiRateLimit, cfg.OVHEnableCNAMERelative, cfg.DryRun)
×
239
        case "linode":
×
240
                p, err = linode.NewLinodeProvider(domainFilter, cfg.DryRun)
×
241
        case "dnsimple":
1✔
242
                p, err = dnsimple.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun)
1✔
243
        case "coredns", "skydns":
1✔
244
                p, err = coredns.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.DryRun)
1✔
245
        case "exoscale":
×
246
                p, err = exoscale.NewExoscaleProvider(
×
247
                        cfg.ExoscaleAPIEnvironment,
×
248
                        cfg.ExoscaleAPIZone,
×
249
                        cfg.ExoscaleAPIKey,
×
250
                        cfg.ExoscaleAPISecret,
×
251
                        cfg.DryRun,
×
252
                        exoscale.ExoscaleWithDomain(domainFilter),
×
253
                        exoscale.ExoscaleWithLogging(),
×
254
                )
×
255
        case "inmemory":
2✔
256
                p, err = inmemory.NewInMemoryProvider(inmemory.InMemoryInitZones(cfg.InMemoryZones), inmemory.InMemoryWithDomain(domainFilter), inmemory.InMemoryWithLogging()), nil
2✔
257
        case "pdns":
×
258
                p, err = pdns.NewPDNSProvider(
×
259
                        ctx,
×
260
                        pdns.PDNSConfig{
×
261
                                DomainFilter: domainFilter,
×
262
                                DryRun:       cfg.DryRun,
×
263
                                Server:       cfg.PDNSServer,
×
264
                                ServerID:     cfg.PDNSServerID,
×
265
                                APIKey:       cfg.PDNSAPIKey,
×
266
                                TLSConfig: pdns.TLSConfig{
×
267
                                        SkipTLSVerify:         cfg.PDNSSkipTLSVerify,
×
268
                                        CAFilePath:            cfg.TLSCA,
×
269
                                        ClientCertFilePath:    cfg.TLSClientCert,
×
270
                                        ClientCertKeyFilePath: cfg.TLSClientCertKey,
×
271
                                },
×
272
                        },
×
273
                )
×
274
        case "oci":
×
275
                var config *oci.OCIConfig
×
276
                // if the instance-principals flag was set, and a compartment OCID was provided, then ignore the
×
277
                // OCI config file, and provide a config that uses instance principal authentication.
×
278
                if cfg.OCIAuthInstancePrincipal {
×
279
                        if len(cfg.OCICompartmentOCID) == 0 {
×
280
                                err = fmt.Errorf("instance principal authentication requested, but no compartment OCID provided")
×
281
                        } else {
×
282
                                authConfig := oci.OCIAuthConfig{UseInstancePrincipal: true}
×
283
                                config = &oci.OCIConfig{Auth: authConfig, CompartmentID: cfg.OCICompartmentOCID}
×
284
                        }
×
285
                } else {
×
286
                        config, err = oci.LoadOCIConfig(cfg.OCIConfigFile)
×
287
                }
×
288
                config.ZoneCacheDuration = cfg.OCIZoneCacheDuration
×
289
                if err == nil {
×
290
                        p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.OCIZoneScope, cfg.DryRun)
×
291
                }
×
292
        case "rfc2136":
1✔
293
                tlsConfig := rfc2136.TLSConfig{
1✔
294
                        UseTLS:                cfg.RFC2136UseTLS,
1✔
295
                        SkipTLSVerify:         cfg.RFC2136SkipTLSVerify,
1✔
296
                        CAFilePath:            cfg.TLSCA,
1✔
297
                        ClientCertFilePath:    cfg.TLSClientCert,
1✔
298
                        ClientCertKeyFilePath: cfg.TLSClientCertKey,
1✔
299
                }
1✔
300
                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)
1✔
301
        case "ns1":
×
302
                p, err = ns1.NewNS1Provider(
×
303
                        ns1.NS1Config{
×
304
                                DomainFilter:  domainFilter,
×
305
                                ZoneIDFilter:  zoneIDFilter,
×
306
                                NS1Endpoint:   cfg.NS1Endpoint,
×
307
                                NS1IgnoreSSL:  cfg.NS1IgnoreSSL,
×
308
                                DryRun:        cfg.DryRun,
×
309
                                MinTTLSeconds: cfg.NS1MinTTLSeconds,
×
310
                        },
×
311
                )
×
312
        case "transip":
×
313
                p, err = transip.NewTransIPProvider(cfg.TransIPAccountName, cfg.TransIPPrivateKeyFile, domainFilter, cfg.DryRun)
×
314
        case "scaleway":
×
315
                p, err = scaleway.NewScalewayProvider(ctx, domainFilter, cfg.DryRun)
×
316
        case "godaddy":
×
317
                p, err = godaddy.NewGoDaddyProvider(ctx, domainFilter, cfg.GoDaddyTTL, cfg.GoDaddyAPIKey, cfg.GoDaddySecretKey, cfg.GoDaddyOTE, cfg.DryRun)
×
318
        case "gandi":
1✔
319
                p, err = gandi.NewGandiProvider(ctx, domainFilter, cfg.DryRun)
1✔
320
        case "pihole":
1✔
321
                p, err = pihole.NewPiholeProvider(
1✔
322
                        pihole.PiholeConfig{
1✔
323
                                Server:                cfg.PiholeServer,
1✔
324
                                Password:              cfg.PiholePassword,
1✔
325
                                TLSInsecureSkipVerify: cfg.PiholeTLSInsecureSkipVerify,
1✔
326
                                DomainFilter:          domainFilter,
1✔
327
                                DryRun:                cfg.DryRun,
1✔
328
                                APIVersion:            cfg.PiholeApiVersion,
1✔
329
                        },
1✔
330
                )
1✔
331
        case "plural":
×
332
                p, err = plural.NewPluralProvider(cfg.PluralCluster, cfg.PluralProvider)
×
333
        case "webhook":
×
334
                p, err = webhook.NewWebhookProvider(cfg.WebhookProviderURL)
×
335
        default:
1✔
336
                err = fmt.Errorf("unknown dns provider: %s", cfg.Provider)
1✔
337
        }
338
        if p != nil && cfg.ProviderCacheTime > 0 {
10✔
339
                p = provider.NewCachedProvider(
1✔
340
                        p,
1✔
341
                        cfg.ProviderCacheTime,
1✔
342
                )
1✔
343
        }
1✔
344
        return p, err
9✔
345
}
346

347
func buildController(
348
        ctx context.Context,
349
        cfg *externaldns.Config,
350
        src source.Source,
351
        p provider.Provider,
352
        filter *endpoint.DomainFilter,
353
) (*Controller, error) {
×
354
        policy, ok := plan.Policies[cfg.Policy]
×
355
        if !ok {
×
356
                return nil, fmt.Errorf("unknown policy: %s", cfg.Policy)
×
357
        }
×
358
        reg, err := selectRegistry(cfg, p)
×
359
        if err != nil {
×
360
                return nil, err
×
361
        }
×
362
        eventsCfg := events.NewConfig(
×
363
                events.WithKubeConfig(cfg.KubeConfig, cfg.APIServerURL, cfg.RequestTimeout),
×
364
                events.WithEmitEvents(cfg.EmitEvents),
×
365
                events.WithDryRun(cfg.DryRun))
×
366
        var eventEmitter events.EventEmitter
×
367
        if eventsCfg.IsEnabled() {
×
368
                eventCtrl, err := events.NewEventController(eventsCfg)
×
369
                if err != nil {
×
370
                        log.Fatal(err)
×
371
                }
×
372
                eventCtrl.Run(ctx)
×
373
                eventEmitter = eventCtrl
×
374
        }
375

376
        return &Controller{
×
377
                Source:               src,
×
378
                Registry:             reg,
×
379
                Policy:               policy,
×
380
                Interval:             cfg.Interval,
×
381
                DomainFilter:         filter,
×
382
                ManagedRecordTypes:   cfg.ManagedDNSRecordTypes,
×
383
                ExcludeRecordTypes:   cfg.ExcludeDNSRecordTypes,
×
384
                MinEventSyncInterval: cfg.MinEventSyncInterval,
×
NEW
385
                TXTOwnerOld:          cfg.TXTOwnerOld,
×
386
                EventEmitter:         eventEmitter,
×
387
        }, nil
×
388
}
389

390
// This function configures the logger format and level based on the provided configuration.
391
func configureLogger(cfg *externaldns.Config) {
3✔
392
        if cfg.LogFormat == "json" {
4✔
393
                log.SetFormatter(&log.JSONFormatter{})
1✔
394
        }
1✔
395
        ll, err := log.ParseLevel(cfg.LogLevel)
3✔
396
        if err != nil {
4✔
397
                log.Fatalf("failed to parse log level: %v", err)
1✔
398
        }
1✔
399
        log.SetLevel(ll)
3✔
400
}
401

402
// selectRegistry selects the appropriate registry implementation based on the configuration in cfg.
403
// It initializes and returns a registry along with any error encountered during setup.
404
// Supported registry types include: dynamodb, noop, txt, and aws-sd.
405
func selectRegistry(cfg *externaldns.Config, p provider.Provider) (registry.Registry, error) {
5✔
406
        var r registry.Registry
5✔
407
        var err error
5✔
408
        switch cfg.Registry {
5✔
409
        case "dynamodb":
1✔
410
                var dynamodbOpts []func(*dynamodb.Options)
1✔
411
                if cfg.AWSDynamoDBRegion != "" {
2✔
412
                        dynamodbOpts = []func(*dynamodb.Options){
1✔
413
                                func(opts *dynamodb.Options) {
2✔
414
                                        opts.Region = cfg.AWSDynamoDBRegion
1✔
415
                                },
1✔
416
                        }
417
                }
418
                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✔
419
        case "noop":
1✔
420
                r, err = registry.NewNoopRegistry(p)
1✔
421
        case "txt":
1✔
422
                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.TXTOwnerMigrate, cfg.TXTOwnerOld)
1✔
423
        case "aws-sd":
1✔
424
                r, err = registry.NewAWSSDRegistry(p, cfg.TXTOwnerID)
1✔
425
        default:
1✔
426
                log.Fatalf("unknown registry: %s", cfg.Registry)
1✔
427
        }
428
        return r, err
5✔
429
}
430

431
// buildSource creates and configures the source(s) for endpoint discovery based on the provided configuration.
432
// It initializes the source configuration, generates the required sources, and combines them into a single,
433
// deduplicated source. Returns the combined source or an error if source creation fails.
434
func buildSource(ctx context.Context, cfg *externaldns.Config) (source.Source, error) {
5✔
435
        sourceCfg := source.NewSourceConfig(cfg)
5✔
436
        sources, err := source.ByNames(ctx, &source.SingletonClientGenerator{
5✔
437
                KubeConfig:   cfg.KubeConfig,
5✔
438
                APIServerURL: cfg.APIServerURL,
5✔
439
                RequestTimeout: func() time.Duration {
10✔
440
                        if cfg.UpdateEvents {
6✔
441
                                return 0
1✔
442
                        }
1✔
443
                        return cfg.RequestTimeout
4✔
444
                }(),
445
        }, cfg.Sources, sourceCfg)
446
        if err != nil {
6✔
447
                return nil, err
1✔
448
        }
1✔
449
        // Combine multiple sources into a single, deduplicated source.
450
        combinedSource := wrappers.NewDedupSource(wrappers.NewMultiSource(sources, sourceCfg.DefaultTargets, sourceCfg.ForceDefaultTargets))
4✔
451
        cfg.AddSourceWrapper("dedup")
4✔
452
        combinedSource = wrappers.NewNAT64Source(combinedSource, cfg.NAT64Networks)
4✔
453
        cfg.AddSourceWrapper("nat64")
4✔
454
        // Filter targets
4✔
455
        targetFilter := endpoint.NewTargetNetFilterWithExclusions(cfg.TargetNetFilter, cfg.ExcludeTargetNets)
4✔
456
        if targetFilter.IsEnabled() {
5✔
457
                combinedSource = wrappers.NewTargetFilterSource(combinedSource, targetFilter)
1✔
458
                cfg.AddSourceWrapper("target-filter")
1✔
459
        }
1✔
460
        return combinedSource, nil
4✔
461
}
462

463
// RegexDomainFilter overrides DomainFilter
464
func createDomainFilter(cfg *externaldns.Config) *endpoint.DomainFilter {
5✔
465
        if cfg.RegexDomainFilter != nil && cfg.RegexDomainFilter.String() != "" {
7✔
466
                return endpoint.NewRegexDomainFilter(cfg.RegexDomainFilter, cfg.RegexDomainExclusion)
2✔
467
        } else {
5✔
468
                return endpoint.NewDomainFilterWithExclusions(cfg.DomainFilter, cfg.ExcludeDomains)
3✔
469
        }
3✔
470
}
471

472
// handleSigterm listens for a SIGTERM signal and triggers the provided cancel function
473
// to gracefully terminate the application. It logs a message when the signal is received.
474
func handleSigterm(cancel func()) {
1✔
475
        signals := make(chan os.Signal, 1)
1✔
476
        signal.Notify(signals, syscall.SIGTERM)
1✔
477
        <-signals
1✔
478
        log.Info("Received SIGTERM. Terminating...")
1✔
479
        cancel()
1✔
480
}
1✔
481

482
// serveMetrics starts an HTTP server that serves health and metrics endpoints.
483
// The /healthz endpoint returns a 200 OK status to indicate the service is healthy.
484
// The /metrics endpoint serves Prometheus metrics.
485
// The server listens on the specified address and logs debug information about the endpoints.
486
func serveMetrics(address string) {
1✔
487
        http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
2✔
488
                w.WriteHeader(http.StatusOK)
1✔
489
                _, _ = w.Write([]byte("OK"))
1✔
490
        })
1✔
491

492
        log.Debugf("serving 'healthz' on '%s/healthz'", address)
1✔
493
        log.Debugf("serving 'metrics' on '%s/metrics'", address)
1✔
494
        log.Debugf("registered '%d' metrics", len(metrics.RegisterMetric.Metrics))
1✔
495

1✔
496
        http.Handle("/metrics", promhttp.Handler())
1✔
497

1✔
498
        log.Fatal(http.ListenAndServe(address, nil))
1✔
499
}
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