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

kubernetes-sigs / external-dns / 14101750331

27 Mar 2025 08:09AM UTC coverage: 71.619% (+0.3%) from 71.367%
14101750331

Pull #5222

github

ivankatliarchuk
chore(code-cleanup): move logic away from main.go add tests

Signed-off-by: ivan katliarchuk <ivan.katliarchuk@gmail.com>
Pull Request #5222: chore(code-cleanup): move logic away from main.go add tests

59 of 379 new or added lines in 2 files covered. (15.57%)

100 existing lines in 2 files now uncovered.

14366 of 20059 relevant lines covered (71.62%)

702.98 hits per line

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

15.61
/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
        "slices"
26
        "strings"
27
        "syscall"
28
        "time"
29

30
        "github.com/aws/aws-sdk-go-v2/service/dynamodb"
31
        "github.com/aws/aws-sdk-go-v2/service/route53"
32
        sd "github.com/aws/aws-sdk-go-v2/service/servicediscovery"
33
        "github.com/go-logr/logr"
34
        "github.com/prometheus/client_golang/prometheus/promhttp"
35
        log "github.com/sirupsen/logrus"
36
        "k8s.io/apimachinery/pkg/labels"
37
        "k8s.io/klog/v2"
38

39
        "sigs.k8s.io/external-dns/endpoint"
40
        "sigs.k8s.io/external-dns/pkg/apis/externaldns"
41
        "sigs.k8s.io/external-dns/pkg/apis/externaldns/validation"
42
        "sigs.k8s.io/external-dns/pkg/metrics"
43
        "sigs.k8s.io/external-dns/plan"
44
        "sigs.k8s.io/external-dns/provider"
45
        "sigs.k8s.io/external-dns/provider/akamai"
46
        "sigs.k8s.io/external-dns/provider/alibabacloud"
47
        "sigs.k8s.io/external-dns/provider/aws"
48
        "sigs.k8s.io/external-dns/provider/awssd"
49
        "sigs.k8s.io/external-dns/provider/azure"
50
        "sigs.k8s.io/external-dns/provider/civo"
51
        "sigs.k8s.io/external-dns/provider/cloudflare"
52
        "sigs.k8s.io/external-dns/provider/coredns"
53
        "sigs.k8s.io/external-dns/provider/digitalocean"
54
        "sigs.k8s.io/external-dns/provider/dnsimple"
55
        "sigs.k8s.io/external-dns/provider/exoscale"
56
        "sigs.k8s.io/external-dns/provider/gandi"
57
        "sigs.k8s.io/external-dns/provider/godaddy"
58
        "sigs.k8s.io/external-dns/provider/google"
59
        "sigs.k8s.io/external-dns/provider/ibmcloud"
60
        "sigs.k8s.io/external-dns/provider/inmemory"
61
        "sigs.k8s.io/external-dns/provider/linode"
62
        "sigs.k8s.io/external-dns/provider/ns1"
63
        "sigs.k8s.io/external-dns/provider/oci"
64
        "sigs.k8s.io/external-dns/provider/ovh"
65
        "sigs.k8s.io/external-dns/provider/pdns"
66
        "sigs.k8s.io/external-dns/provider/pihole"
67
        "sigs.k8s.io/external-dns/provider/plural"
68
        "sigs.k8s.io/external-dns/provider/rfc2136"
69
        "sigs.k8s.io/external-dns/provider/scaleway"
70
        "sigs.k8s.io/external-dns/provider/tencentcloud"
71
        "sigs.k8s.io/external-dns/provider/transip"
72
        "sigs.k8s.io/external-dns/provider/ultradns"
73
        "sigs.k8s.io/external-dns/provider/webhook"
74
        webhookapi "sigs.k8s.io/external-dns/provider/webhook/api"
75
        "sigs.k8s.io/external-dns/registry"
76
        "sigs.k8s.io/external-dns/source"
77
)
78

NEW
79
func Execute() {
×
NEW
80
        cfg := externaldns.NewConfig()
×
NEW
81
        if err := cfg.ParseFlags(os.Args[1:]); err != nil {
×
NEW
82
                log.Fatalf("flag parsing error: %v", err)
×
NEW
83
        }
×
NEW
84
        log.Infof("config: %s", cfg)
×
NEW
85
        if err := validation.ValidateConfig(cfg); err != nil {
×
NEW
86
                log.Fatalf("config validation failed: %v", err)
×
NEW
87
        }
×
88

NEW
89
        configureLogger(cfg)
×
NEW
90

×
NEW
91
        if cfg.DryRun {
×
NEW
92
                log.Info("running in dry-run mode. No changes to DNS records will be made.")
×
NEW
93
        }
×
94

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

NEW
102
        log.Info(externaldns.Banner())
×
NEW
103

×
NEW
104
        ctx, cancel := context.WithCancel(context.Background())
×
NEW
105

×
NEW
106
        go serveMetrics(cfg.MetricsAddress)
×
NEW
107
        go handleSigterm(cancel)
×
NEW
108

×
NEW
109
        // error is explicitly ignored because the filter is already validated in validation.ValidateConfig
×
NEW
110
        labelSelector, _ := labels.Parse(cfg.LabelFilter)
×
NEW
111

×
NEW
112
        // Create a source.Config from the flags passed by the user.
×
NEW
113
        sourceCfg := &source.Config{
×
NEW
114
                Namespace:                      cfg.Namespace,
×
NEW
115
                AnnotationFilter:               cfg.AnnotationFilter,
×
NEW
116
                LabelFilter:                    labelSelector,
×
NEW
117
                IngressClassNames:              cfg.IngressClassNames,
×
NEW
118
                FQDNTemplate:                   cfg.FQDNTemplate,
×
NEW
119
                CombineFQDNAndAnnotation:       cfg.CombineFQDNAndAnnotation,
×
NEW
120
                IgnoreHostnameAnnotation:       cfg.IgnoreHostnameAnnotation,
×
NEW
121
                IgnoreNonHostNetworkPods:       cfg.IgnoreNonHostNetworkPods,
×
NEW
122
                IgnoreIngressTLSSpec:           cfg.IgnoreIngressTLSSpec,
×
NEW
123
                IgnoreIngressRulesSpec:         cfg.IgnoreIngressRulesSpec,
×
NEW
124
                ListenEndpointEvents:           cfg.ListenEndpointEvents,
×
NEW
125
                GatewayName:                    cfg.GatewayName,
×
NEW
126
                GatewayNamespace:               cfg.GatewayNamespace,
×
NEW
127
                GatewayLabelFilter:             cfg.GatewayLabelFilter,
×
NEW
128
                Compatibility:                  cfg.Compatibility,
×
NEW
129
                PodSourceDomain:                cfg.PodSourceDomain,
×
NEW
130
                PublishInternal:                cfg.PublishInternal,
×
NEW
131
                PublishHostIP:                  cfg.PublishHostIP,
×
NEW
132
                AlwaysPublishNotReadyAddresses: cfg.AlwaysPublishNotReadyAddresses,
×
NEW
133
                ConnectorServer:                cfg.ConnectorSourceServer,
×
NEW
134
                CRDSourceAPIVersion:            cfg.CRDSourceAPIVersion,
×
NEW
135
                CRDSourceKind:                  cfg.CRDSourceKind,
×
NEW
136
                KubeConfig:                     cfg.KubeConfig,
×
NEW
137
                APIServerURL:                   cfg.APIServerURL,
×
NEW
138
                ServiceTypeFilter:              cfg.ServiceTypeFilter,
×
NEW
139
                CFAPIEndpoint:                  cfg.CFAPIEndpoint,
×
NEW
140
                CFUsername:                     cfg.CFUsername,
×
NEW
141
                CFPassword:                     cfg.CFPassword,
×
NEW
142
                GlooNamespaces:                 cfg.GlooNamespaces,
×
NEW
143
                SkipperRouteGroupVersion:       cfg.SkipperRouteGroupVersion,
×
NEW
144
                RequestTimeout:                 cfg.RequestTimeout,
×
NEW
145
                DefaultTargets:                 cfg.DefaultTargets,
×
NEW
146
                OCPRouterName:                  cfg.OCPRouterName,
×
NEW
147
                UpdateEvents:                   cfg.UpdateEvents,
×
NEW
148
                ResolveLoadBalancerHostname:    cfg.ResolveServiceLoadBalancerHostname,
×
NEW
149
                TraefikDisableLegacy:           cfg.TraefikDisableLegacy,
×
NEW
150
                TraefikDisableNew:              cfg.TraefikDisableNew,
×
NEW
151
                ExposeInternalIPv6:             cfg.ExposeInternalIPV6,
×
NEW
152
        }
×
NEW
153

×
NEW
154
        // Lookup all the selected sources by names and pass them the desired configuration.
×
NEW
155
        sources, err := source.ByNames(ctx, &source.SingletonClientGenerator{
×
NEW
156
                KubeConfig:   cfg.KubeConfig,
×
NEW
157
                APIServerURL: cfg.APIServerURL,
×
NEW
158
                // If update events are enabled, disable timeout.
×
NEW
159
                RequestTimeout: func() time.Duration {
×
NEW
160
                        if cfg.UpdateEvents {
×
NEW
161
                                return 0
×
NEW
162
                        }
×
NEW
163
                        return cfg.RequestTimeout
×
164
                }(),
165
        }, cfg.Sources, sourceCfg)
NEW
166
        if err != nil {
×
NEW
167
                log.Fatal(err)
×
NEW
168
        }
×
169

170
        // Filter targets
NEW
171
        targetFilter := endpoint.NewTargetNetFilterWithExclusions(cfg.TargetNetFilter, cfg.ExcludeTargetNets)
×
NEW
172

×
NEW
173
        // Combine multiple sources into a single, deduplicated source.
×
NEW
174
        endpointsSource := source.NewDedupSource(source.NewMultiSource(sources, sourceCfg.DefaultTargets))
×
NEW
175
        endpointsSource = source.NewNAT64Source(endpointsSource, cfg.NAT64Networks)
×
NEW
176
        endpointsSource = source.NewTargetFilterSource(endpointsSource, targetFilter)
×
NEW
177

×
NEW
178
        domainFilter := createDomainFilter(cfg)
×
NEW
179
        zoneNameFilter := endpoint.NewDomainFilter(cfg.ZoneNameFilter)
×
NEW
180
        zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
×
NEW
181
        zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
×
NEW
182
        zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter)
×
NEW
183

×
NEW
184
        var p provider.Provider
×
NEW
185
        switch cfg.Provider {
×
NEW
186
        case "akamai":
×
NEW
187
                p, err = akamai.NewAkamaiProvider(
×
NEW
188
                        akamai.AkamaiConfig{
×
NEW
189
                                DomainFilter:          domainFilter,
×
NEW
190
                                ZoneIDFilter:          zoneIDFilter,
×
NEW
191
                                ServiceConsumerDomain: cfg.AkamaiServiceConsumerDomain,
×
NEW
192
                                ClientToken:           cfg.AkamaiClientToken,
×
NEW
193
                                ClientSecret:          cfg.AkamaiClientSecret,
×
NEW
194
                                AccessToken:           cfg.AkamaiAccessToken,
×
NEW
195
                                EdgercPath:            cfg.AkamaiEdgercPath,
×
NEW
196
                                EdgercSection:         cfg.AkamaiEdgercSection,
×
NEW
197
                                DryRun:                cfg.DryRun,
×
NEW
198
                        }, nil)
×
NEW
199
        case "alibabacloud":
×
NEW
200
                p, err = alibabacloud.NewAlibabaCloudProvider(cfg.AlibabaCloudConfigFile, domainFilter, zoneIDFilter, cfg.AlibabaCloudZoneType, cfg.DryRun)
×
NEW
201
        case "aws":
×
NEW
202
                configs := aws.CreateV2Configs(cfg)
×
NEW
203
                clients := make(map[string]aws.Route53API, len(configs))
×
NEW
204
                for profile, config := range configs {
×
NEW
205
                        clients[profile] = route53.NewFromConfig(config)
×
NEW
206
                }
×
207

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

NEW
366
        if cfg.WebhookServer {
×
NEW
367
                webhookapi.StartHTTPApi(p, nil, cfg.WebhookProviderReadTimeout, cfg.WebhookProviderWriteTimeout, "127.0.0.1:8888")
×
NEW
368
                os.Exit(0)
×
NEW
369
        }
×
370

NEW
371
        if cfg.ProviderCacheTime > 0 {
×
NEW
372
                p = provider.NewCachedProvider(
×
NEW
373
                        p,
×
NEW
374
                        cfg.ProviderCacheTime,
×
NEW
375
                )
×
NEW
376
        }
×
377

NEW
378
        reg, err := selectRegistry(cfg, p)
×
NEW
379
        if err != nil {
×
NEW
380
                log.Fatal(err)
×
NEW
381
        }
×
382

NEW
383
        policy, exists := plan.Policies[cfg.Policy]
×
NEW
384
        if !exists {
×
NEW
385
                log.Fatalf("unknown policy: %s", cfg.Policy)
×
NEW
386
        }
×
387

NEW
388
        ctrl := Controller{
×
NEW
389
                Source:               endpointsSource,
×
NEW
390
                Registry:             reg,
×
NEW
391
                Policy:               policy,
×
NEW
392
                Interval:             cfg.Interval,
×
NEW
393
                DomainFilter:         domainFilter,
×
NEW
394
                ManagedRecordTypes:   cfg.ManagedDNSRecordTypes,
×
NEW
395
                ExcludeRecordTypes:   cfg.ExcludeDNSRecordTypes,
×
NEW
396
                MinEventSyncInterval: cfg.MinEventSyncInterval,
×
NEW
397
        }
×
NEW
398

×
NEW
399
        if cfg.Once {
×
NEW
400
                err := ctrl.RunOnce(ctx)
×
NEW
401
                if err != nil {
×
NEW
402
                        log.Fatal(err)
×
NEW
403
                }
×
404

NEW
405
                os.Exit(0)
×
406
        }
407

NEW
408
        if cfg.UpdateEvents {
×
NEW
409
                // Add RunOnce as the handler function that will be called when ingress/service sources have changed.
×
NEW
410
                // Note that k8s Informers will perform an initial list operation, which results in the handler
×
NEW
411
                // function initially being called for every Service/Ingress that exists
×
NEW
412
                ctrl.Source.AddEventHandler(ctx, func() { ctrl.ScheduleRunOnce(time.Now()) })
×
413
        }
414

NEW
415
        ctrl.ScheduleRunOnce(time.Now())
×
NEW
416
        ctrl.Run(ctx)
×
417
}
418

419
// This function configures the logger format and level based on the provided configuration.
420
func configureLogger(cfg *externaldns.Config) {
4✔
421
        logFormats := []string{"text", "json"}
4✔
422
        if !slices.Contains(logFormats, cfg.LogFormat) {
5✔
423
                log.Fatalf("unknown log format: '%s'. known formats: '%s'", cfg.LogFormat, strings.Join(logFormats, ","))
1✔
424
        }
1✔
425
        if cfg.LogFormat == "json" {
5✔
426
                log.SetFormatter(&log.JSONFormatter{})
1✔
427
        }
1✔
428
        ll, err := log.ParseLevel(cfg.LogLevel)
4✔
429
        if err != nil {
5✔
430
                log.Fatalf("failed to parse log level: %v", err)
1✔
431
        }
1✔
432
        log.SetLevel(ll)
4✔
433
}
434

435
// selectRegistry selects the appropriate registry implementation based on the configuration in cfg.
436
// It initializes and returns a registry along with any error encountered during setup.
437
// Supported registry types include: dynamodb, noop, txt, and aws-sd.
438
func selectRegistry(cfg *externaldns.Config, p provider.Provider) (registry.Registry, error) {
5✔
439
        var r registry.Registry
5✔
440
        var err error
5✔
441
        switch cfg.Registry {
5✔
442
        case "dynamodb":
1✔
443
                var dynamodbOpts []func(*dynamodb.Options)
1✔
444
                if cfg.AWSDynamoDBRegion != "" {
2✔
445
                        dynamodbOpts = []func(*dynamodb.Options){
1✔
446
                                func(opts *dynamodb.Options) {
2✔
447
                                        opts.Region = cfg.AWSDynamoDBRegion
1✔
448
                                },
1✔
449
                        }
450
                }
451
                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✔
452
        case "noop":
1✔
453
                r, err = registry.NewNoopRegistry(p)
1✔
454
        case "txt":
1✔
455
                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✔
456
        case "aws-sd":
1✔
457
                r, err = registry.NewAWSSDRegistry(p, cfg.TXTOwnerID)
1✔
458
        default:
1✔
459
                log.Fatalf("unknown registry: %s", cfg.Registry)
1✔
460
        }
461
        return r, err
5✔
462
}
463

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

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

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

493
        log.Debugf("serving 'healthz' on 'localhost:%s/healthz'", address)
1✔
494
        log.Debugf("serving 'metrics' on 'localhost:%s/metrics'", address)
1✔
495
        log.Debugf("registered '%d' metrics", len(metrics.RegisterMetric.Metrics))
1✔
496

1✔
497
        http.Handle("/metrics", promhttp.Handler())
1✔
498

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