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

kubernetes-sigs / external-dns / 16017762842

02 Jul 2025 06:27AM UTC coverage: 76.817% (-0.01%) from 76.829%
16017762842

Pull #5598

github

ivankatliarchuk
chore(source): reorganise sources and wrappers

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
Pull Request #5598: chore(source): reorganise sources and wrappers

14 of 17 new or added lines in 6 files covered. (82.35%)

18 existing lines in 1 file now uncovered.

14480 of 18850 relevant lines covered (76.82%)

753.2 hits per line

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

42.27
/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
        "sigs.k8s.io/external-dns/source/wrappers"
36

37
        "sigs.k8s.io/external-dns/endpoint"
38
        "sigs.k8s.io/external-dns/pkg/apis/externaldns"
39
        "sigs.k8s.io/external-dns/pkg/apis/externaldns/validation"
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
)
73

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

UNCOV
84
        configureLogger(cfg)
×
85

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

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

UNCOV
97
        log.Info(externaldns.Banner())
×
98

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

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

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

UNCOV
109
        domainFilter := createDomainFilter(cfg)
×
110

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

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

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

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

UNCOV
132
                os.Exit(0)
×
133
        }
134

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

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

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

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

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

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

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

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

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

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

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

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

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

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

1✔
468
        http.Handle("/metrics", promhttp.Handler())
1✔
469

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