main.go 12.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

30
	_ "k8s.io/client-go/plugin/pkg/client/auth"
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
	"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"
	"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"
	"sigs.k8s.io/external-dns/provider/google"
	"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"
	"sigs.k8s.io/external-dns/provider/transip"
	"sigs.k8s.io/external-dns/provider/vinyldns"
	"sigs.k8s.io/external-dns/provider/vultr"
57

58
	"sigs.k8s.io/external-dns/controller"
59
	"sigs.k8s.io/external-dns/endpoint"
60
61
62
63
64
65
	"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"
	"sigs.k8s.io/external-dns/registry"
	"sigs.k8s.io/external-dns/source"
66
67
)

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

75
	if err := validation.ValidateConfig(cfg); err != nil {
76
		log.Fatalf("config validation failed: %v", err)
77
78
	}

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

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

92
93
	ctx := context.Background()

94
95
	stopChan := make(chan struct{}, 1)

96
	go serveMetrics(cfg.MetricsAddress)
97
98
	go handleSigterm(stopChan)

99
100
	// Create a source.Config from the flags passed by the user.
	sourceCfg := &source.Config{
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
		Namespace:                      cfg.Namespace,
		AnnotationFilter:               cfg.AnnotationFilter,
		FQDNTemplate:                   cfg.FQDNTemplate,
		CombineFQDNAndAnnotation:       cfg.CombineFQDNAndAnnotation,
		IgnoreHostnameAnnotation:       cfg.IgnoreHostnameAnnotation,
		Compatibility:                  cfg.Compatibility,
		PublishInternal:                cfg.PublishInternal,
		PublishHostIP:                  cfg.PublishHostIP,
		AlwaysPublishNotReadyAddresses: cfg.AlwaysPublishNotReadyAddresses,
		ConnectorServer:                cfg.ConnectorSourceServer,
		CRDSourceAPIVersion:            cfg.CRDSourceAPIVersion,
		CRDSourceKind:                  cfg.CRDSourceKind,
		KubeConfig:                     cfg.KubeConfig,
		KubeMaster:                     cfg.Master,
		ServiceTypeFilter:              cfg.ServiceTypeFilter,
		IstioIngressGatewayServices:    cfg.IstioIngressGatewayServices,
		CFAPIEndpoint:                  cfg.CFAPIEndpoint,
		CFUsername:                     cfg.CFUsername,
		CFPassword:                     cfg.CFPassword,
		ContourLoadBalancerService:     cfg.ContourLoadBalancerService,
121
122
		SkipperRouteGroupVersion:       cfg.SkipperRouteGroupVersion,
		RequestTimeout:                 cfg.RequestTimeout,
123
	}
124

125
126
	// Lookup all the selected sources by names and pass them the desired configuration.
	sources, err := source.ByNames(&source.SingletonClientGenerator{
127
128
129
130
131
132
133
134
135
		KubeConfig: cfg.KubeConfig,
		KubeMaster: cfg.Master,
		// If update events are enabled, disable timeout.
		RequestTimeout: func() time.Duration {
			if cfg.UpdateEvents {
				return 0
			}
			return cfg.RequestTimeout
		}(),
136
	}, cfg.Sources, sourceCfg)
137
138
139
140
	if err != nil {
		log.Fatal(err)
	}

141
	// Combine multiple sources into a single, deduplicated source.
142
	endpointsSource := source.NewDedupSource(source.NewMultiSource(sources))
143

144
	domainFilter := endpoint.NewDomainFilterWithExclusions(cfg.DomainFilter, cfg.ExcludeDomains)
145
	zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
146
	zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
Cesar Wong's avatar
Cesar Wong committed
147
	zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter)
148

149
150
	var p provider.Provider
	switch cfg.Provider {
151
	case "akamai":
152
153
		p = akamai.NewAkamaiProvider(
			akamai.AkamaiConfig{
154
155
156
157
158
159
160
161
162
				DomainFilter:          domainFilter,
				ZoneIDFilter:          zoneIDFilter,
				ServiceConsumerDomain: cfg.AkamaiServiceConsumerDomain,
				ClientToken:           cfg.AkamaiClientToken,
				ClientSecret:          cfg.AkamaiClientSecret,
				AccessToken:           cfg.AkamaiAccessToken,
				DryRun:                cfg.DryRun,
			},
		)
Li Yi's avatar
Li Yi committed
163
	case "alibabacloud":
164
		p, err = alibabacloud.NewAlibabaCloudProvider(cfg.AlibabaCloudConfigFile, domainFilter, zoneIDFilter, cfg.AlibabaCloudZoneType, cfg.DryRun)
165
	case "aws":
166
167
		p, err = aws.NewAWSProvider(
			aws.AWSConfig{
168
169
170
				DomainFilter:         domainFilter,
				ZoneIDFilter:         zoneIDFilter,
				ZoneTypeFilter:       zoneTypeFilter,
Cesar Wong's avatar
Cesar Wong committed
171
				ZoneTagFilter:        zoneTagFilter,
172
173
174
175
				BatchChangeSize:      cfg.AWSBatchChangeSize,
				BatchChangeInterval:  cfg.AWSBatchChangeInterval,
				EvaluateTargetHealth: cfg.AWSEvaluateTargetHealth,
				AssumeRole:           cfg.AWSAssumeRole,
Corey O'Brien's avatar
Corey O'Brien committed
176
				APIRetries:           cfg.AWSAPIRetries,
177
				PreferCNAME:          cfg.AWSPreferCNAME,
178
				DryRun:               cfg.DryRun,
179
180
			},
		)
181
182
	case "aws-sd":
		// Check that only compatible Registry is used with AWS-SD
183
		if cfg.Registry != "noop" && cfg.Registry != "aws-sd" {
184
			log.Infof("Registry \"%s\" cannot be used with AWS Cloud Map. Switching to \"aws-sd\".", cfg.Registry)
185
			cfg.Registry = "aws-sd"
186
		}
187
		p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.AWSAssumeRole, cfg.DryRun)
188
	case "azure-dns", "azure":
189
		p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun)
190
	case "azure-private-dns":
191
		p, err = azure.NewAzurePrivateDNSProvider(domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.AzureSubscriptionID, cfg.DryRun)
Dave Grizzanti's avatar
Dave Grizzanti committed
192
	case "vinyldns":
193
		p, err = vinyldns.NewVinylDNSProvider(domainFilter, zoneIDFilter, cfg.DryRun)
David Dymko's avatar
David Dymko committed
194
	case "vultr":
195
		p, err = vultr.NewVultrProvider(domainFilter, cfg.DryRun)
196
	case "cloudflare":
197
		p, err = cloudflare.NewCloudFlareProvider(domainFilter, zoneIDFilter, cfg.CloudflareZonesPerPage, cfg.CloudflareProxied, cfg.DryRun)
Dimitrij Klesev's avatar
Dimitrij Klesev committed
198
	case "rcodezero":
199
		p, err = rcode0.NewRcodeZeroProvider(domainFilter, cfg.DryRun, cfg.RcodezeroTXTEncrypt)
Dimitrij Klesev's avatar
Dimitrij Klesev committed
200
	case "google":
201
		p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.DryRun)
202
	case "digitalocean":
203
		p, err = digitalocean.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun)
Hugome's avatar
Hugome committed
204
	case "ovh":
205
		p, err = ovh.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.DryRun)
cliedeman's avatar
cliedeman committed
206
	case "linode":
207
		p, err = linode.NewLinodeProvider(domainFilter, cfg.DryRun, externaldns.Version)
208
	case "dnsimple":
209
		p, err = dnsimple.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun)
210
	case "infoblox":
211
212
		p, err = infoblox.NewInfobloxProvider(
			infoblox.InfobloxConfig{
213
				DomainFilter: domainFilter,
214
				ZoneIDFilter: zoneIDFilter,
215
216
217
218
219
220
				Host:         cfg.InfobloxGridHost,
				Port:         cfg.InfobloxWapiPort,
				Username:     cfg.InfobloxWapiUsername,
				Password:     cfg.InfobloxWapiPassword,
				Version:      cfg.InfobloxWapiVersion,
				SSLVerify:    cfg.InfobloxSSLVerify,
221
				View:         cfg.InfobloxView,
222
				MaxResults:   cfg.InfobloxMaxResults,
223
224
225
				DryRun:       cfg.DryRun,
			},
		)
Julian Vassev's avatar
Julian Vassev committed
226
	case "dyn":
227
228
		p, err = dyn.NewDynProvider(
			dyn.DynConfig{
229
230
231
232
233
234
235
236
				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
237
238
			},
		)
Stan Lagun's avatar
Stan Lagun committed
239
	case "coredns", "skydns":
240
		p, err = coredns.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.DryRun)
Jason-ZW's avatar
Jason-ZW committed
241
	case "rdns":
242
243
		p, err = rdns.NewRDNSProvider(
			rdns.RDNSConfig{
Jason-ZW's avatar
Jason-ZW committed
244
245
246
247
				DomainFilter: domainFilter,
				DryRun:       cfg.DryRun,
			},
		)
248
	case "exoscale":
249
		p, err = exoscale.NewExoscaleProvider(cfg.ExoscaleEndpoint, cfg.ExoscaleAPIKey, cfg.ExoscaleAPISecret, cfg.DryRun, exoscale.ExoscaleWithDomain(domainFilter), exoscale.ExoscaleWithLogging()), nil
250
	case "inmemory":
251
		p, err = inmemory.NewInMemoryProvider(inmemory.InMemoryInitZones(cfg.InMemoryZones), inmemory.InMemoryWithDomain(domainFilter), inmemory.InMemoryWithLogging()), nil
Stan Lagun's avatar
Stan Lagun committed
252
	case "designate":
253
		p, err = designate.NewDesignateProvider(domainFilter, cfg.DryRun)
Anhad Jai Singh's avatar
Anhad Jai Singh committed
254
	case "pdns":
255
		p, err = pdns.NewPDNSProvider(
256
			ctx,
257
			pdns.PDNSConfig{
258
				DomainFilter: domainFilter,
Jason Hoch's avatar
gofmt    
Jason Hoch committed
259
260
261
				DryRun:       cfg.DryRun,
				Server:       cfg.PDNSServer,
				APIKey:       cfg.PDNSAPIKey,
262
				TLSConfig: pdns.TLSConfig{
Jason Hoch's avatar
gofmt    
Jason Hoch committed
263
264
265
					TLSEnabled:            cfg.PDNSTLSEnabled,
					CAFilePath:            cfg.TLSCA,
					ClientCertFilePath:    cfg.TLSClientCert,
266
267
268
269
					ClientCertKeyFilePath: cfg.TLSClientCertKey,
				},
			},
		)
270
	case "oci":
271
272
		var config *oci.OCIConfig
		config, err = oci.LoadOCIConfig(cfg.OCIConfigFile)
Andrew Pryde's avatar
Andrew Pryde committed
273
		if err == nil {
274
			p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.DryRun)
Andrew Pryde's avatar
Andrew Pryde committed
275
		}
Vladislav Troinich's avatar
Vladislav Troinich committed
276
	case "rfc2136":
277
		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, nil)
278
	case "ns1":
279
280
		p, err = ns1.NewNS1Provider(
			ns1.NS1Config{
281
282
				DomainFilter: domainFilter,
				ZoneIDFilter: zoneIDFilter,
283
284
				NS1Endpoint:  cfg.NS1Endpoint,
				NS1IgnoreSSL: cfg.NS1IgnoreSSL,
285
				DryRun:       cfg.DryRun,
286
287
			},
		)
Reinier Schoof's avatar
Reinier Schoof committed
288
	case "transip":
289
		p, err = transip.NewTransIPProvider(cfg.TransIPAccountName, cfg.TransIPPrivateKeyFile, domainFilter, cfg.DryRun)
290
	default:
291
		log.Fatalf("unknown dns provider: %s", cfg.Provider)
292
	}
293
294
295
296
	if err != nil {
		log.Fatal(err)
	}

Yerken's avatar
Yerken committed
297
298
299
300
301
	var r registry.Registry
	switch cfg.Registry {
	case "noop":
		r, err = registry.NewNoopRegistry(p)
	case "txt":
302
		r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTOwnerID, cfg.TXTCacheInterval)
303
	case "aws-sd":
304
		r, err = registry.NewAWSSDRegistry(p.(*awssd.AWSSDProvider), cfg.TXTOwnerID)
Yerken's avatar
Yerken committed
305
306
307
308
	default:
		log.Fatalf("unknown registry: %s", cfg.Registry)
	}

Yerken's avatar
Yerken committed
309
310
311
312
	if err != nil {
		log.Fatal(err)
	}

313
314
315
316
317
	policy, exists := plan.Policies[cfg.Policy]
	if !exists {
		log.Fatalf("unknown policy: %s", cfg.Policy)
	}

318
	ctrl := controller.Controller{
319
320
321
322
323
		Source:       endpointsSource,
		Registry:     r,
		Policy:       policy,
		Interval:     cfg.Interval,
		DomainFilter: domainFilter,
324
325
	}

326
327
328
329
330
331
332
	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 limted by minInterval.
		ctrl.Source.AddEventHandler(func() error { return ctrl.RunOnce(ctx) }, stopChan, 1*time.Minute)
	}

333
	if cfg.Once {
334
		err := ctrl.RunOnce(ctx)
335
336
337
338
339
		if err != nil {
			log.Fatal(err)
		}

		os.Exit(0)
340
	}
341
	ctrl.Run(ctx, stopChan)
342
343
344
345
346
347
}

func handleSigterm(stopChan chan struct{}) {
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGTERM)
	<-signals
348
	log.Info("Received SIGTERM. Terminating...")
349
350
	close(stopChan)
}
351

352
353
354
355
356
357
358
359
360
361
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))
}