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

kubernetes-sigs / external-dns / 15634311847

13 Jun 2025 12:15PM UTC coverage: 76.613% (+0.4%) from 76.253%
15634311847

Pull #5523

github

ivankatliarchuk
chore(codebase): reduce complexity and improve code coverage for controller/execute.go

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
Pull Request #5523: test(controller): reduce complexity and improve code coverage

29 of 67 new or added lines in 1 file covered. (43.28%)

55 existing lines in 4 files now uncovered.

14299 of 18664 relevant lines covered (76.61%)

760.69 hits per line

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

42.98
/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)
×
NEW
104
        if err != nil {
×
NEW
105
                log.Fatal(err)
×
NEW
106
        }
×
107

NEW
108
        domainFilter := createDomainFilter(cfg)
×
NEW
109

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

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

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

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

NEW
131
                os.Exit(0)
×
132
        }
133

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

NEW
141
        ctrl.ScheduleRunOnce(time.Now())
×
NEW
142
        ctrl.Run(ctx)
×
143
}
144

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

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

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

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

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

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

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

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

2✔
427
        // Combine multiple sources into a single, deduplicated source.
2✔
428
        combinedSource = source.NewNAT64Source(combinedSource, cfg.NAT64Networks)
2✔
429
        combinedSource = source.NewTargetFilterSource(combinedSource, targetFilter)
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