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

kubernetes-sigs / external-dns / 15793548441

21 Jun 2025 07:38AM UTC coverage: 76.619% (-0.05%) from 76.669%
15793548441

push

github

web-flow
chore(domainfilter): use pointer receivers for DomainFilter (#5546)

* refactor(domainfilter): use pointer receivers for DomainFilter

* refactor(domainfilter): complete pointer type consistency across providers

31 of 55 new or added lines in 25 files covered. (56.36%)

1 existing line in 1 file now uncovered.

14350 of 18729 relevant lines covered (76.62%)

758.52 hits per line

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

42.65
/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

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

108
        domainFilter := createDomainFilter(cfg)
×
109

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

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

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

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

131
                os.Exit(0)
×
132
        }
133

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

141
        ctrl.ScheduleRunOnce(time.Now())
×
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) {
×
343
        policy, ok := plan.Policies[cfg.Policy]
×
344
        if !ok {
×
345
                return nil, fmt.Errorf("unknown policy: %s", cfg.Policy)
×
346
        }
×
347
        reg, err := selectRegistry(cfg, p)
×
348
        if err != nil {
×
349
                return nil, err
×
350
        }
×
351
        return &Controller{
×
352
                Source:               src,
×
353
                Registry:             reg,
×
354
                Policy:               policy,
×
355
                Interval:             cfg.Interval,
×
356
                DomainFilter:         filter,
×
357
                ManagedRecordTypes:   cfg.ManagedDNSRecordTypes,
×
358
                ExcludeRecordTypes:   cfg.ExcludeDNSRecordTypes,
×
359
                MinEventSyncInterval: cfg.MinEventSyncInterval,
×
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, sourceCfg.ForceDefaultTargets))
2✔
424
        // Filter targets
2✔
425
        targetFilter := endpoint.NewTargetNetFilterWithExclusions(cfg.TargetNetFilter, cfg.ExcludeTargetNets)
2✔
426
        combinedSource = source.NewNAT64Source(combinedSource, cfg.NAT64Networks)
2✔
427
        combinedSource = source.NewTargetFilterSource(combinedSource, targetFilter)
2✔
428
        return combinedSource, nil
2✔
429
}
430

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

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

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

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

1✔
464
        http.Handle("/metrics", promhttp.Handler())
1✔
465

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