main.go 14.5 KB
Newer Older
ideahitme's avatar
ideahitme committed
1
2
/*
Copyright 2017 The Kubernetes Authors.
3

ideahitme's avatar
ideahitme committed
4
5
6
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
7

ideahitme's avatar
ideahitme committed
8
    http://www.apache.org/licenses/LICENSE-2.0
9

ideahitme's avatar
ideahitme committed
10
11
12
13
14
15
16
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

17
18
19
package main

import (
20
	"context"
21
22
23
24
	"net/http"
	"os"
	"os/signal"
	"syscall"
25
	"time"
26

27
	"github.com/prometheus/client_golang/prometheus/promhttp"
28
	log "github.com/sirupsen/logrus"
29
	_ "k8s.io/client-go/plugin/pkg/client/auth"
30

31
	"sigs.k8s.io/external-dns/controller"
32
	"sigs.k8s.io/external-dns/endpoint"
33
34
35
36
	"sigs.k8s.io/external-dns/pkg/apis/externaldns"
	"sigs.k8s.io/external-dns/pkg/apis/externaldns/validation"
	"sigs.k8s.io/external-dns/plan"
	"sigs.k8s.io/external-dns/provider"
37
38
39
40
41
	"sigs.k8s.io/external-dns/provider/akamai"
	"sigs.k8s.io/external-dns/provider/alibabacloud"
	"sigs.k8s.io/external-dns/provider/aws"
	"sigs.k8s.io/external-dns/provider/awssd"
	"sigs.k8s.io/external-dns/provider/azure"
42
	"sigs.k8s.io/external-dns/provider/bluecat"
43
44
45
46
47
48
49
	"sigs.k8s.io/external-dns/provider/cloudflare"
	"sigs.k8s.io/external-dns/provider/coredns"
	"sigs.k8s.io/external-dns/provider/designate"
	"sigs.k8s.io/external-dns/provider/digitalocean"
	"sigs.k8s.io/external-dns/provider/dnsimple"
	"sigs.k8s.io/external-dns/provider/dyn"
	"sigs.k8s.io/external-dns/provider/exoscale"
Patrick Stählin's avatar
Patrick Stählin committed
50
	"sigs.k8s.io/external-dns/provider/gandi"
fboltz's avatar
fboltz committed
51
	"sigs.k8s.io/external-dns/provider/godaddy"
52
	"sigs.k8s.io/external-dns/provider/google"
Vladimir Smagin's avatar
Vladimir Smagin committed
53
	"sigs.k8s.io/external-dns/provider/hetzner"
54
55
56
57
58
59
60
61
62
63
	"sigs.k8s.io/external-dns/provider/infoblox"
	"sigs.k8s.io/external-dns/provider/inmemory"
	"sigs.k8s.io/external-dns/provider/linode"
	"sigs.k8s.io/external-dns/provider/ns1"
	"sigs.k8s.io/external-dns/provider/oci"
	"sigs.k8s.io/external-dns/provider/ovh"
	"sigs.k8s.io/external-dns/provider/pdns"
	"sigs.k8s.io/external-dns/provider/rcode0"
	"sigs.k8s.io/external-dns/provider/rdns"
	"sigs.k8s.io/external-dns/provider/rfc2136"
64
	"sigs.k8s.io/external-dns/provider/scaleway"
65
	"sigs.k8s.io/external-dns/provider/transip"
kbhandari's avatar
kbhandari committed
66
	"sigs.k8s.io/external-dns/provider/ultradns"
67
68
	"sigs.k8s.io/external-dns/provider/vinyldns"
	"sigs.k8s.io/external-dns/provider/vultr"
69
70
	"sigs.k8s.io/external-dns/registry"
	"sigs.k8s.io/external-dns/source"
71
72
)

73
func main() {
74
	cfg := externaldns.NewConfig()
75
	if err := cfg.ParseFlags(os.Args[1:]); err != nil {
ideahitme's avatar
ideahitme committed
76
77
		log.Fatalf("flag parsing error: %v", err)
	}
78
	log.Infof("config: %s", cfg)
79

80
	if err := validation.ValidateConfig(cfg); err != nil {
81
		log.Fatalf("config validation failed: %v", err)
82
83
	}

ideahitme's avatar
ideahitme committed
84
	if cfg.LogFormat == "json" {
85
86
		log.SetFormatter(&log.JSONFormatter{})
	}
87
	if cfg.DryRun {
ideahitme's avatar
ideahitme committed
88
		log.Info("running in dry-run mode. No changes to DNS records will be made.")
89
	}
90
91
92
93

	ll, err := log.ParseLevel(cfg.LogLevel)
	if err != nil {
		log.Fatalf("failed to parse log level: %v", err)
94
	}
95
	log.SetLevel(ll)
96

97
	ctx, cancel := context.WithCancel(context.Background())
98

99
	go serveMetrics(cfg.MetricsAddress)
100
	go handleSigterm(cancel)
101

102
103
	// Create a source.Config from the flags passed by the user.
	sourceCfg := &source.Config{
104
105
		Namespace:                      cfg.Namespace,
		AnnotationFilter:               cfg.AnnotationFilter,
106
		LabelFilter:                    cfg.LabelFilter,
107
108
109
		FQDNTemplate:                   cfg.FQDNTemplate,
		CombineFQDNAndAnnotation:       cfg.CombineFQDNAndAnnotation,
		IgnoreHostnameAnnotation:       cfg.IgnoreHostnameAnnotation,
110
		IgnoreIngressTLSSpec:           cfg.IgnoreIngressTLSSpec,
111
112
113
114
115
116
117
118
		Compatibility:                  cfg.Compatibility,
		PublishInternal:                cfg.PublishInternal,
		PublishHostIP:                  cfg.PublishHostIP,
		AlwaysPublishNotReadyAddresses: cfg.AlwaysPublishNotReadyAddresses,
		ConnectorServer:                cfg.ConnectorSourceServer,
		CRDSourceAPIVersion:            cfg.CRDSourceAPIVersion,
		CRDSourceKind:                  cfg.CRDSourceKind,
		KubeConfig:                     cfg.KubeConfig,
119
		APIServerURL:                   cfg.APIServerURL,
120
121
122
123
124
		ServiceTypeFilter:              cfg.ServiceTypeFilter,
		CFAPIEndpoint:                  cfg.CFAPIEndpoint,
		CFUsername:                     cfg.CFUsername,
		CFPassword:                     cfg.CFPassword,
		ContourLoadBalancerService:     cfg.ContourLoadBalancerService,
Hugome's avatar
Hugome committed
125
		GlooNamespace:                  cfg.GlooNamespace,
126
127
		SkipperRouteGroupVersion:       cfg.SkipperRouteGroupVersion,
		RequestTimeout:                 cfg.RequestTimeout,
Bogdan's avatar
Bogdan committed
128
		DefaultTargets:                 cfg.DefaultTargets,
129
	}
130

131
132
	// Lookup all the selected sources by names and pass them the desired configuration.
	sources, err := source.ByNames(&source.SingletonClientGenerator{
133
134
		KubeConfig:   cfg.KubeConfig,
		APIServerURL: cfg.APIServerURL,
135
136
137
138
139
140
141
		// If update events are enabled, disable timeout.
		RequestTimeout: func() time.Duration {
			if cfg.UpdateEvents {
				return 0
			}
			return cfg.RequestTimeout
		}(),
142
	}, cfg.Sources, sourceCfg)
143
144
145
146
	if err != nil {
		log.Fatal(err)
	}

147
	// Combine multiple sources into a single, deduplicated source.
Bogdan's avatar
Bogdan committed
148
	endpointsSource := source.NewDedupSource(source.NewMultiSource(sources, sourceCfg.DefaultTargets))
149

150
151
	// RegexDomainFilter overrides DomainFilter
	var domainFilter endpoint.DomainFilter
152
	if cfg.RegexDomainFilter.String() != "" {
153
154
155
156
		domainFilter = endpoint.NewRegexDomainFilter(cfg.RegexDomainFilter, cfg.RegexDomainExclusion)
	} else {
		domainFilter = endpoint.NewDomainFilterWithExclusions(cfg.DomainFilter, cfg.ExcludeDomains)
	}
157
	zoneNameFilter := endpoint.NewDomainFilter(cfg.ZoneNameFilter)
158
	zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
159
	zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
Cesar Wong's avatar
Cesar Wong committed
160
	zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter)
161

162
163
	var p provider.Provider
	switch cfg.Provider {
164
	case "akamai":
165
		p, err = akamai.NewAkamaiProvider(
166
			akamai.AkamaiConfig{
167
168
169
170
171
172
				DomainFilter:          domainFilter,
				ZoneIDFilter:          zoneIDFilter,
				ServiceConsumerDomain: cfg.AkamaiServiceConsumerDomain,
				ClientToken:           cfg.AkamaiClientToken,
				ClientSecret:          cfg.AkamaiClientSecret,
				AccessToken:           cfg.AkamaiAccessToken,
173
174
				EdgercPath:            cfg.AkamaiEdgercPath,
				EdgercSection:         cfg.AkamaiEdgercSection,
175
				DryRun:                cfg.DryRun,
176
			}, nil)
Li Yi's avatar
Li Yi committed
177
	case "alibabacloud":
178
		p, err = alibabacloud.NewAlibabaCloudProvider(cfg.AlibabaCloudConfigFile, domainFilter, zoneIDFilter, cfg.AlibabaCloudZoneType, cfg.DryRun)
179
	case "aws":
180
181
		p, err = aws.NewAWSProvider(
			aws.AWSConfig{
182
183
184
				DomainFilter:         domainFilter,
				ZoneIDFilter:         zoneIDFilter,
				ZoneTypeFilter:       zoneTypeFilter,
Cesar Wong's avatar
Cesar Wong committed
185
				ZoneTagFilter:        zoneTagFilter,
186
187
188
189
				BatchChangeSize:      cfg.AWSBatchChangeSize,
				BatchChangeInterval:  cfg.AWSBatchChangeInterval,
				EvaluateTargetHealth: cfg.AWSEvaluateTargetHealth,
				AssumeRole:           cfg.AWSAssumeRole,
Corey O'Brien's avatar
Corey O'Brien committed
190
				APIRetries:           cfg.AWSAPIRetries,
191
				PreferCNAME:          cfg.AWSPreferCNAME,
192
				DryRun:               cfg.DryRun,
Benjamin Pineau's avatar
Benjamin Pineau committed
193
				ZoneCacheDuration:    cfg.AWSZoneCacheDuration,
194
195
			},
		)
196
197
	case "aws-sd":
		// Check that only compatible Registry is used with AWS-SD
198
		if cfg.Registry != "noop" && cfg.Registry != "aws-sd" {
199
			log.Infof("Registry \"%s\" cannot be used with AWS Cloud Map. Switching to \"aws-sd\".", cfg.Registry)
200
			cfg.Registry = "aws-sd"
201
		}
202
		p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.AWSAssumeRole, cfg.DryRun)
203
	case "azure-dns", "azure":
204
		p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun)
205
	case "azure-private-dns":
206
		p, err = azure.NewAzurePrivateDNSProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun)
207
208
	case "bluecat":
		p, err = bluecat.NewBluecatProvider(cfg.BluecatConfigFile, domainFilter, zoneIDFilter, cfg.DryRun)
Dave Grizzanti's avatar
Dave Grizzanti committed
209
	case "vinyldns":
210
		p, err = vinyldns.NewVinylDNSProvider(domainFilter, zoneIDFilter, cfg.DryRun)
David Dymko's avatar
David Dymko committed
211
	case "vultr":
David Dymko's avatar
David Dymko committed
212
		p, err = vultr.NewVultrProvider(ctx, domainFilter, cfg.DryRun)
213
	case "ultradns":
kbhandari's avatar
kbhandari committed
214
		p, err = ultradns.NewUltraDNSProvider(domainFilter, cfg.DryRun)
215
	case "cloudflare":
216
		p, err = cloudflare.NewCloudFlareProvider(domainFilter, zoneIDFilter, cfg.CloudflareZonesPerPage, cfg.CloudflareProxied, cfg.DryRun)
Dimitrij Klesev's avatar
Dimitrij Klesev committed
217
	case "rcodezero":
218
		p, err = rcode0.NewRcodeZeroProvider(domainFilter, cfg.DryRun, cfg.RcodezeroTXTEncrypt)
Dimitrij Klesev's avatar
Dimitrij Klesev committed
219
	case "google":
220
		p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.DryRun)
221
	case "digitalocean":
222
		p, err = digitalocean.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun, cfg.DigitalOceanAPIPageSize)
Vladimir Smagin's avatar
Vladimir Smagin committed
223
224
	case "hetzner":
		p, err = hetzner.NewHetznerProvider(ctx, domainFilter, cfg.DryRun)
Hugome's avatar
Hugome committed
225
	case "ovh":
Hugome's avatar
Hugome committed
226
		p, err = ovh.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.OVHApiRateLimit, cfg.DryRun)
cliedeman's avatar
cliedeman committed
227
	case "linode":
228
		p, err = linode.NewLinodeProvider(domainFilter, cfg.DryRun, externaldns.Version)
229
	case "dnsimple":
230
		p, err = dnsimple.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun)
231
	case "infoblox":
232
233
		p, err = infoblox.NewInfobloxProvider(
			infoblox.InfobloxConfig{
234
				DomainFilter: domainFilter,
235
				ZoneIDFilter: zoneIDFilter,
236
237
238
239
240
241
				Host:         cfg.InfobloxGridHost,
				Port:         cfg.InfobloxWapiPort,
				Username:     cfg.InfobloxWapiUsername,
				Password:     cfg.InfobloxWapiPassword,
				Version:      cfg.InfobloxWapiVersion,
				SSLVerify:    cfg.InfobloxSSLVerify,
242
				View:         cfg.InfobloxView,
243
				MaxResults:   cfg.InfobloxMaxResults,
244
				DryRun:       cfg.DryRun,
245
				FQDNRexEx:    cfg.InfobloxFQDNRegEx,
246
247
			},
		)
Julian Vassev's avatar
Julian Vassev committed
248
	case "dyn":
249
250
		p, err = dyn.NewDynProvider(
			dyn.DynConfig{
251
252
253
254
255
256
257
258
				DomainFilter:  domainFilter,
				ZoneIDFilter:  zoneIDFilter,
				DryRun:        cfg.DryRun,
				CustomerName:  cfg.DynCustomerName,
				Username:      cfg.DynUsername,
				Password:      cfg.DynPassword,
				MinTTLSeconds: cfg.DynMinTTLSeconds,
				AppVersion:    externaldns.Version,
Julian Vassev's avatar
Julian Vassev committed
259
260
			},
		)
Stan Lagun's avatar
Stan Lagun committed
261
	case "coredns", "skydns":
262
		p, err = coredns.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.DryRun)
Jason-ZW's avatar
Jason-ZW committed
263
	case "rdns":
264
265
		p, err = rdns.NewRDNSProvider(
			rdns.RDNSConfig{
Jason-ZW's avatar
Jason-ZW committed
266
267
268
269
				DomainFilter: domainFilter,
				DryRun:       cfg.DryRun,
			},
		)
270
	case "exoscale":
271
		p, err = exoscale.NewExoscaleProvider(cfg.ExoscaleEndpoint, cfg.ExoscaleAPIKey, cfg.ExoscaleAPISecret, cfg.DryRun, exoscale.ExoscaleWithDomain(domainFilter), exoscale.ExoscaleWithLogging()), nil
272
	case "inmemory":
273
		p, err = inmemory.NewInMemoryProvider(inmemory.InMemoryInitZones(cfg.InMemoryZones), inmemory.InMemoryWithDomain(domainFilter), inmemory.InMemoryWithLogging()), nil
Stan Lagun's avatar
Stan Lagun committed
274
	case "designate":
275
		p, err = designate.NewDesignateProvider(domainFilter, cfg.DryRun)
Anhad Jai Singh's avatar
Anhad Jai Singh committed
276
	case "pdns":
277
		p, err = pdns.NewPDNSProvider(
278
			ctx,
279
			pdns.PDNSConfig{
280
				DomainFilter: domainFilter,
Jason Hoch's avatar
gofmt    
Jason Hoch committed
281
282
283
				DryRun:       cfg.DryRun,
				Server:       cfg.PDNSServer,
				APIKey:       cfg.PDNSAPIKey,
284
				TLSConfig: pdns.TLSConfig{
Jason Hoch's avatar
gofmt    
Jason Hoch committed
285
286
287
					TLSEnabled:            cfg.PDNSTLSEnabled,
					CAFilePath:            cfg.TLSCA,
					ClientCertFilePath:    cfg.TLSClientCert,
288
289
290
291
					ClientCertKeyFilePath: cfg.TLSClientCertKey,
				},
			},
		)
292
	case "oci":
293
294
		var config *oci.OCIConfig
		config, err = oci.LoadOCIConfig(cfg.OCIConfigFile)
Andrew Pryde's avatar
Andrew Pryde committed
295
		if err == nil {
296
			p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.DryRun)
Andrew Pryde's avatar
Andrew Pryde committed
297
		}
Vladislav Troinich's avatar
Vladislav Troinich committed
298
	case "rfc2136":
Brock Alberry's avatar
Brock Alberry committed
299
		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.RFC2136GSSTSIG, cfg.RFC2136KerberosUsername, cfg.RFC2136KerberosPassword, cfg.RFC2136KerberosRealm, nil)
300
	case "ns1":
301
302
		p, err = ns1.NewNS1Provider(
			ns1.NS1Config{
Matt Dennison's avatar
Matt Dennison committed
303
304
305
306
307
308
				DomainFilter:  domainFilter,
				ZoneIDFilter:  zoneIDFilter,
				NS1Endpoint:   cfg.NS1Endpoint,
				NS1IgnoreSSL:  cfg.NS1IgnoreSSL,
				DryRun:        cfg.DryRun,
				MinTTLSeconds: cfg.NS1MinTTLSeconds,
309
310
			},
		)
Reinier Schoof's avatar
Reinier Schoof committed
311
	case "transip":
312
		p, err = transip.NewTransIPProvider(cfg.TransIPAccountName, cfg.TransIPPrivateKeyFile, domainFilter, cfg.DryRun)
313
314
	case "scaleway":
		p, err = scaleway.NewScalewayProvider(ctx, domainFilter, cfg.DryRun)
fboltz's avatar
fboltz committed
315
	case "godaddy":
316
		p, err = godaddy.NewGoDaddyProvider(ctx, domainFilter, cfg.GoDaddyTTL, cfg.GoDaddyAPIKey, cfg.GoDaddySecretKey, cfg.GoDaddyOTE, cfg.DryRun)
Patrick Stählin's avatar
Patrick Stählin committed
317
318
	case "gandi":
		p, err = gandi.NewGandiProvider(ctx, domainFilter, cfg.DryRun)
319
	default:
320
		log.Fatalf("unknown dns provider: %s", cfg.Provider)
321
	}
322
323
324
325
	if err != nil {
		log.Fatal(err)
	}

Yerken's avatar
Yerken committed
326
327
328
329
330
	var r registry.Registry
	switch cfg.Registry {
	case "noop":
		r, err = registry.NewNoopRegistry(p)
	case "txt":
331
		r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval, cfg.TXTWildcardReplacement)
332
	case "aws-sd":
333
		r, err = registry.NewAWSSDRegistry(p.(*awssd.AWSSDProvider), cfg.TXTOwnerID)
Yerken's avatar
Yerken committed
334
335
336
337
	default:
		log.Fatalf("unknown registry: %s", cfg.Registry)
	}

Yerken's avatar
Yerken committed
338
339
340
341
	if err != nil {
		log.Fatal(err)
	}

342
343
344
345
346
	policy, exists := plan.Policies[cfg.Policy]
	if !exists {
		log.Fatalf("unknown policy: %s", cfg.Policy)
	}

347
	ctrl := controller.Controller{
348
349
350
351
352
353
354
		Source:               endpointsSource,
		Registry:             r,
		Policy:               policy,
		Interval:             cfg.Interval,
		DomainFilter:         domainFilter,
		ManagedRecordTypes:   cfg.ManagedDNSRecordTypes,
		MinEventSyncInterval: cfg.MinEventSyncInterval,
355
356
	}

357
	if cfg.Once {
358
		err := ctrl.RunOnce(ctx)
359
360
361
362
363
		if err != nil {
			log.Fatal(err)
		}

		os.Exit(0)
364
	}
365
366
367
368
369
370
371
372
373
374

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

	ctrl.ScheduleRunOnce(time.Now())
	ctrl.Run(ctx)
375
376
}

377
func handleSigterm(cancel func()) {
378
379
380
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGTERM)
	<-signals
381
	log.Info("Received SIGTERM. Terminating...")
382
	cancel()
383
}
384

385
386
387
388
389
390
391
392
393
394
func serveMetrics(address string) {
	http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		w.Write([]byte("OK"))
	})

	http.Handle("/metrics", promhttp.Handler())

	log.Fatal(http.ListenAndServe(address, nil))
}