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

kubernetes-sigs / external-dns / 15633747409

13 Jun 2025 11:43AM UTC coverage: 76.585% (+0.3%) from 76.253%
15633747409

Pull #5523

github

web-flow
apply suggestions from code review

Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com>
Pull Request #5523: test(controller): reduce complexity and improve code coverage

24 of 61 new or added lines in 1 file covered. (39.34%)

55 existing lines in 4 files now uncovered.

14293 of 18663 relevant lines covered (76.58%)

760.74 hits per line

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

41.35
/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/metrics"
40
        "sigs.k8s.io/external-dns/plan"
41
        "sigs.k8s.io/external-dns/provider"
42
        "sigs.k8s.io/external-dns/provider/akamai"
43
        "sigs.k8s.io/external-dns/provider/alibabacloud"
44
        "sigs.k8s.io/external-dns/provider/aws"
45
        "sigs.k8s.io/external-dns/provider/awssd"
46
        "sigs.k8s.io/external-dns/provider/azure"
47
        "sigs.k8s.io/external-dns/provider/civo"
48
        "sigs.k8s.io/external-dns/provider/cloudflare"
49
        "sigs.k8s.io/external-dns/provider/coredns"
50
        "sigs.k8s.io/external-dns/provider/digitalocean"
51
        "sigs.k8s.io/external-dns/provider/dnsimple"
52
        "sigs.k8s.io/external-dns/provider/exoscale"
53
        "sigs.k8s.io/external-dns/provider/gandi"
54
        "sigs.k8s.io/external-dns/provider/godaddy"
55
        "sigs.k8s.io/external-dns/provider/google"
56
        "sigs.k8s.io/external-dns/provider/inmemory"
57
        "sigs.k8s.io/external-dns/provider/linode"
58
        "sigs.k8s.io/external-dns/provider/ns1"
59
        "sigs.k8s.io/external-dns/provider/oci"
60
        "sigs.k8s.io/external-dns/provider/ovh"
61
        "sigs.k8s.io/external-dns/provider/pdns"
62
        "sigs.k8s.io/external-dns/provider/pihole"
63
        "sigs.k8s.io/external-dns/provider/plural"
64
        "sigs.k8s.io/external-dns/provider/rfc2136"
65
        "sigs.k8s.io/external-dns/provider/scaleway"
66
        "sigs.k8s.io/external-dns/provider/transip"
67
        "sigs.k8s.io/external-dns/provider/webhook"
68
        webhookapi "sigs.k8s.io/external-dns/provider/webhook/api"
69
        "sigs.k8s.io/external-dns/registry"
70
        "sigs.k8s.io/external-dns/source"
71
)
72

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

83
        configureLogger(cfg)
×
84

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

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

96
        log.Info(externaldns.Banner())
×
97

×
98
        ctx, cancel := context.WithCancel(context.Background())
×
99

×
100
        go serveMetrics(cfg.MetricsAddress)
×
101
        go handleSigterm(cancel)
×
102

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

108
        // Filter targets
109
        targetFilter := endpoint.NewTargetNetFilterWithExclusions(cfg.TargetNetFilter, cfg.ExcludeTargetNets)
×
110

×
111
        // Combine multiple sources into a single, deduplicated source.
×
112
        endpointsSource = source.NewNAT64Source(endpointsSource, cfg.NAT64Networks)
×
113
        endpointsSource = source.NewTargetFilterSource(endpointsSource, targetFilter)
×
114
        domainFilter := createDomainFilter(cfg)
×
NEW
115

×
NEW
116
        prvdr, err := buildProvider(ctx, cfg, domainFilter)
×
NEW
117
        if err != nil {
×
NEW
118
                log.Fatal(err)
×
NEW
119
        }
×
120

NEW
121
        if cfg.WebhookServer {
×
NEW
122
                webhookapi.StartHTTPApi(prvdr, nil, cfg.WebhookProviderReadTimeout, cfg.WebhookProviderWriteTimeout, "127.0.0.1:8888")
×
NEW
123
                os.Exit(0)
×
NEW
124
        }
×
125

NEW
126
        ctrl, err := buildController(cfg, endpointsSource, prvdr, domainFilter)
×
NEW
127
        if err != nil {
×
NEW
128
                log.Fatal(err)
×
NEW
129
        }
×
130

NEW
131
        if cfg.Once {
×
NEW
132
                err := ctrl.RunOnce(ctx)
×
NEW
133
                if err != nil {
×
NEW
134
                        log.Fatal(err)
×
NEW
135
                }
×
136

NEW
137
                os.Exit(0)
×
138
        }
139

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

NEW
147
        ctrl.ScheduleRunOnce(time.Now())
×
NEW
148
        ctrl.Run(ctx)
×
149
}
150

151
func buildProvider(
152
        ctx context.Context,
153
        cfg *externaldns.Config,
154
        domainFilter endpoint.DomainFilter,
155
) (provider.Provider, error) {
9✔
156
        var p provider.Provider
9✔
157
        var err error
9✔
158

9✔
159
        zoneNameFilter := endpoint.NewDomainFilter(cfg.ZoneNameFilter)
9✔
160
        zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
9✔
161
        zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
9✔
162
        zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter)
9✔
163

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

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

NEW
348
func buildController(cfg *externaldns.Config, src source.Source, p provider.Provider, filter endpoint.DomainFilter) (*Controller, error) {
×
NEW
349
        policy, ok := plan.Policies[cfg.Policy]
×
NEW
350
        if !ok {
×
NEW
351
                return nil, fmt.Errorf("unknown policy: %s", cfg.Policy)
×
NEW
352
        }
×
353
        reg, err := selectRegistry(cfg, p)
×
354
        if err != nil {
×
NEW
355
                return nil, err
×
356
        }
×
NEW
357
        return &Controller{
×
NEW
358
                Source:               src,
×
359
                Registry:             reg,
×
360
                Policy:               policy,
×
361
                Interval:             cfg.Interval,
×
NEW
362
                DomainFilter:         filter,
×
363
                ManagedRecordTypes:   cfg.ManagedDNSRecordTypes,
×
364
                ExcludeRecordTypes:   cfg.ExcludeDNSRecordTypes,
×
365
                MinEventSyncInterval: cfg.MinEventSyncInterval,
×
NEW
366
        }, nil
×
367
}
368

369
// This function configures the logger format and level based on the provided configuration.
370
func configureLogger(cfg *externaldns.Config) {
3✔
371
        if cfg.LogFormat == "json" {
4✔
372
                log.SetFormatter(&log.JSONFormatter{})
1✔
373
        }
1✔
374
        ll, err := log.ParseLevel(cfg.LogLevel)
3✔
375
        if err != nil {
4✔
376
                log.Fatalf("failed to parse log level: %v", err)
1✔
377
        }
1✔
378
        log.SetLevel(ll)
3✔
379
}
380

381
// selectRegistry selects the appropriate registry implementation based on the configuration in cfg.
382
// It initializes and returns a registry along with any error encountered during setup.
383
// Supported registry types include: dynamodb, noop, txt, and aws-sd.
384
func selectRegistry(cfg *externaldns.Config, p provider.Provider) (registry.Registry, error) {
5✔
385
        var r registry.Registry
5✔
386
        var err error
5✔
387
        switch cfg.Registry {
5✔
388
        case "dynamodb":
1✔
389
                var dynamodbOpts []func(*dynamodb.Options)
1✔
390
                if cfg.AWSDynamoDBRegion != "" {
2✔
391
                        dynamodbOpts = []func(*dynamodb.Options){
1✔
392
                                func(opts *dynamodb.Options) {
2✔
393
                                        opts.Region = cfg.AWSDynamoDBRegion
1✔
394
                                },
1✔
395
                        }
396
                }
397
                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✔
398
        case "noop":
1✔
399
                r, err = registry.NewNoopRegistry(p)
1✔
400
        case "txt":
1✔
401
                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✔
402
        case "aws-sd":
1✔
403
                r, err = registry.NewAWSSDRegistry(p, cfg.TXTOwnerID)
1✔
404
        default:
1✔
405
                log.Fatalf("unknown registry: %s", cfg.Registry)
1✔
406
        }
407
        return r, err
5✔
408
}
409

410
// buildSource creates and configures the source(s) for endpoint discovery based on the provided configuration.
411
// It initializes the source configuration, generates the required sources, and combines them into a single,
412
// deduplicated source. Returns the combined source or an error if source creation fails.
413
func buildSource(ctx context.Context, cfg *externaldns.Config) (source.Source, error) {
3✔
414
        sourceCfg := source.NewSourceConfig(cfg)
3✔
415
        sources, err := source.ByNames(ctx, &source.SingletonClientGenerator{
3✔
416
                KubeConfig:   cfg.KubeConfig,
3✔
417
                APIServerURL: cfg.APIServerURL,
3✔
418
                RequestTimeout: func() time.Duration {
6✔
419
                        if cfg.UpdateEvents {
4✔
420
                                return 0
1✔
421
                        }
1✔
422
                        return cfg.RequestTimeout
2✔
423
                }(),
424
        }, cfg.Sources, sourceCfg)
425
        if err != nil {
4✔
426
                return nil, err
1✔
427
        }
1✔
428
        // Combine multiple sources into a single, deduplicated source.
429
        combinedSource := source.NewDedupSource(source.NewMultiSource(sources, sourceCfg.DefaultTargets))
2✔
430
        return combinedSource, nil
2✔
431
}
432

433
// RegexDomainFilter overrides DomainFilter
434
func createDomainFilter(cfg *externaldns.Config) endpoint.DomainFilter {
5✔
435
        if cfg.RegexDomainFilter != nil && cfg.RegexDomainFilter.String() != "" {
7✔
436
                return endpoint.NewRegexDomainFilter(cfg.RegexDomainFilter, cfg.RegexDomainExclusion)
2✔
437
        } else {
5✔
438
                return endpoint.NewDomainFilterWithExclusions(cfg.DomainFilter, cfg.ExcludeDomains)
3✔
439
        }
3✔
440
}
441

442
// handleSigterm listens for a SIGTERM signal and triggers the provided cancel function
443
// to gracefully terminate the application. It logs a message when the signal is received.
444
func handleSigterm(cancel func()) {
1✔
445
        signals := make(chan os.Signal, 1)
1✔
446
        signal.Notify(signals, syscall.SIGTERM)
1✔
447
        <-signals
1✔
448
        log.Info("Received SIGTERM. Terminating...")
1✔
449
        cancel()
1✔
450
}
1✔
451

452
// serveMetrics starts an HTTP server that serves health and metrics endpoints.
453
// The /healthz endpoint returns a 200 OK status to indicate the service is healthy.
454
// The /metrics endpoint serves Prometheus metrics.
455
// The server listens on the specified address and logs debug information about the endpoints.
456
func serveMetrics(address string) {
1✔
457
        http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
2✔
458
                w.WriteHeader(http.StatusOK)
1✔
459
                _, _ = w.Write([]byte("OK"))
1✔
460
        })
1✔
461

462
        log.Debugf("serving 'healthz' on '%s/healthz'", address)
1✔
463
        log.Debugf("serving 'metrics' on '%s/metrics'", address)
1✔
464
        log.Debugf("registered '%d' metrics", len(metrics.RegisterMetric.Metrics))
1✔
465

1✔
466
        http.Handle("/metrics", promhttp.Handler())
1✔
467

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