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

kubernetes-sigs / external-dns / 14077752915

26 Mar 2025 07:28AM UTC coverage: 71.615% (+0.3%) from 71.364%
14077752915

Pull #5222

github

ivankatliarchuk
chore(code-cleanup): move logic 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 378 new or added lines in 2 files covered. (15.61%)

100 existing lines in 2 files now uncovered.

14361 of 20053 relevant lines covered (71.62%)

703.15 hits per line

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

15.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
        "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
        }
×
NEW
152

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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