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

kubernetes-sigs / external-dns / 17091815621

20 Aug 2025 07:37AM UTC coverage: 77.441% (-0.008%) from 77.449%
17091815621

push

github

web-flow
feat(events): raise k8s events with fake provider (#5659)

* feat(events): publish k8s events

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(events): publish k8s events

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(events): publish k8s events

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(events): publish k8s events

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(events): publish k8s events

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(events): raise k8s events with fake provider

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(events): raise k8s events with fake provider

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(events): raise k8s events with fake provider

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(events): raise k8s events with fake provider

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(events): raise k8s events with fake provider

Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com>

* feat(events): raise k8s events with fake provider

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(events): raise k8s events with fake provide

Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com>

* feat(events): raise k8s events with fake provider

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

* feat(events): raise k8s events with fake provider

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>

---------

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
Co-authored-by: Michel Loiseleur <97035654+mloiseleur@users.noreply.github.com>

222 of 289 new or added lines in 10 files covered. (76.82%)

3 existing lines in 2 files now uncovered.

15324 of 19788 relevant lines covered (77.44%)

732.54 hits per line

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

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

85
        configureLogger(cfg)
×
86

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

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

98
        log.Info(externaldns.Banner())
×
99

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

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

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

110
        domainFilter := createDomainFilter(cfg)
×
111

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

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

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

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

133
                os.Exit(0)
×
134
        }
135

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

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

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

9✔
160
        switch cfg.Provider {
9✔
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":
×
209
                p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.AzureActiveDirectoryAuthorityHost, cfg.AzureZonesCacheDuration, cfg.AzureMaxRetriesCount, cfg.DryRun)
×
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":
2✔
256
                p, err = inmemory.NewInMemoryProvider(inmemory.InMemoryInitZones(cfg.InMemoryZones), inmemory.InMemoryWithDomain(domainFilter), inmemory.InMemoryWithLogging()), nil
2✔
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 {
10✔
339
                p = provider.NewCachedProvider(
1✔
340
                        p,
1✔
341
                        cfg.ProviderCacheTime,
1✔
342
                )
1✔
343
        }
1✔
344
        return p, err
9✔
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,
NEW
353
) (*Controller, error) {
×
354
        policy, ok := plan.Policies[cfg.Policy]
×
355
        if !ok {
×
356
                return nil, fmt.Errorf("unknown policy: %s", cfg.Policy)
×
357
        }
×
358
        reg, err := selectRegistry(cfg, p)
×
359
        if err != nil {
×
360
                return nil, err
×
361
        }
×
NEW
362
        eventsCfg := events.NewConfig(
×
NEW
363
                events.WithKubeConfig(cfg.KubeConfig, cfg.APIServerURL, cfg.RequestTimeout),
×
NEW
364
                events.WithEmitEvents(cfg.EmitEvents),
×
NEW
365
                events.WithDryRun(cfg.DryRun))
×
NEW
366
        var eventsCtrl *events.Controller
×
NEW
367
        if eventsCfg.IsEnabled() {
×
NEW
368
                eventsCtrl, err = events.NewEventController(eventsCfg)
×
NEW
369
                if err != nil {
×
NEW
370
                        log.Fatal(err)
×
NEW
371
                }
×
NEW
372
                eventsCtrl.Run(ctx)
×
373
        }
374

375
        return &Controller{
×
376
                Source:               src,
×
377
                Registry:             reg,
×
378
                Policy:               policy,
×
379
                Interval:             cfg.Interval,
×
380
                DomainFilter:         filter,
×
381
                ManagedRecordTypes:   cfg.ManagedDNSRecordTypes,
×
382
                ExcludeRecordTypes:   cfg.ExcludeDNSRecordTypes,
×
383
                MinEventSyncInterval: cfg.MinEventSyncInterval,
×
NEW
384
                EventController:      eventsCtrl,
×
UNCOV
385
        }, nil
×
386
}
387

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

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

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

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

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

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

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

1✔
494
        http.Handle("/metrics", promhttp.Handler())
1✔
495

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