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

kubernetes-sigs / external-dns / 17533233775

07 Sep 2025 07:56PM UTC coverage: 78.07% (+0.4%) from 77.666%
17533233775

Pull #5816

github

TobyTheHutt
 test(controller): patch go-lint results

Signed-off-by: Tobias Harnickell <tobias.harnickell@bedag.ch>
Pull Request #5816: test(controller): improve coverage on controller

15468 of 19813 relevant lines covered (78.07%)

738.9 hits per line

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

61.5
/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() {
9✔
76
        cfg := externaldns.NewConfig()
9✔
77
        if err := cfg.ParseFlags(os.Args[1:]); err != nil {
12✔
78
                log.Fatalf("flag parsing error: %v", err)
3✔
79
        }
3✔
80
        log.Infof("config: %s", cfg)
6✔
81
        if err := validation.ValidateConfig(cfg); err != nil {
6✔
82
                log.Fatalf("config validation failed: %v", err)
×
83
        }
×
84

85
        configureLogger(cfg)
6✔
86

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

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

98
        log.Info(externaldns.Banner())
6✔
99

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

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

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

110
        domainFilter := createDomainFilter(cfg)
5✔
111

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

117
        if cfg.WebhookServer {
4✔
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)
4✔
123
        if err != nil {
5✔
124
                log.Fatal(err)
1✔
125
        }
1✔
126

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

133
                os.Exit(0)
1✔
134
        }
135

136
        if cfg.UpdateEvents {
1✔
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())
1✔
144
        ctrl.Run(ctx)
1✔
145
}
146

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

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

15✔
160
        switch cfg.Provider {
15✔
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":
1✔
209
                p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.AzureZonesCacheDuration, cfg.AzureMaxRetriesCount, cfg.DryRun)
1✔
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":
7✔
256
                p, err = inmemory.NewInMemoryProvider(inmemory.InMemoryInitZones(cfg.InMemoryZones), inmemory.InMemoryWithDomain(domainFilter), inmemory.InMemoryWithLogging()), nil
7✔
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 {
16✔
339
                p = provider.NewCachedProvider(
1✔
340
                        p,
1✔
341
                        cfg.ProviderCacheTime,
1✔
342
                )
1✔
343
        }
1✔
344
        return p, err
15✔
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) {
5✔
354
        policy, ok := plan.Policies[cfg.Policy]
5✔
355
        if !ok {
5✔
356
                return nil, fmt.Errorf("unknown policy: %s", cfg.Policy)
×
357
        }
×
358
        reg, err := selectRegistry(cfg, p)
5✔
359
        if err != nil {
6✔
360
                return nil, err
1✔
361
        }
1✔
362
        eventsCfg := events.NewConfig(
4✔
363
                events.WithKubeConfig(cfg.KubeConfig, cfg.APIServerURL, cfg.RequestTimeout),
4✔
364
                events.WithEmitEvents(cfg.EmitEvents),
4✔
365
                events.WithDryRun(cfg.DryRun))
4✔
366
        var eventEmitter events.EventEmitter
4✔
367
        if eventsCfg.IsEnabled() {
4✔
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{
4✔
377
                Source:               src,
4✔
378
                Registry:             reg,
4✔
379
                Policy:               policy,
4✔
380
                Interval:             cfg.Interval,
4✔
381
                DomainFilter:         filter,
4✔
382
                ManagedRecordTypes:   cfg.ManagedDNSRecordTypes,
4✔
383
                ExcludeRecordTypes:   cfg.ExcludeDNSRecordTypes,
4✔
384
                MinEventSyncInterval: cfg.MinEventSyncInterval,
4✔
385
                EventEmitter:         eventEmitter,
4✔
386
        }, nil
4✔
387
}
388

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

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

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

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

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

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

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

2✔
495
        http.Handle("/metrics", promhttp.Handler())
2✔
496

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

© 2025 Coveralls, Inc