main.go 9.58 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
20
21
22
23
24
package main

import (
	"net/http"
	"os"
	"os/signal"
	"syscall"

25
	"github.com/prometheus/client_golang/prometheus/promhttp"
26
	log "github.com/sirupsen/logrus"
27

28
	_ "k8s.io/client-go/plugin/pkg/client/auth"
29

30
	"github.com/kubernetes-incubator/external-dns/controller"
31
32
	"github.com/kubernetes-incubator/external-dns/pkg/apis/externaldns"
	"github.com/kubernetes-incubator/external-dns/pkg/apis/externaldns/validation"
33
	"github.com/kubernetes-incubator/external-dns/plan"
34
	"github.com/kubernetes-incubator/external-dns/provider"
Yerken's avatar
Yerken committed
35
	"github.com/kubernetes-incubator/external-dns/registry"
36
	"github.com/kubernetes-incubator/external-dns/source"
37
38
)

39
func main() {
40
	cfg := externaldns.NewConfig()
41
	if err := cfg.ParseFlags(os.Args[1:]); err != nil {
ideahitme's avatar
ideahitme committed
42
43
		log.Fatalf("flag parsing error: %v", err)
	}
44
	log.Infof("config: %s", cfg)
45

46
	if err := validation.ValidateConfig(cfg); err != nil {
47
		log.Fatalf("config validation failed: %v", err)
48
49
	}

ideahitme's avatar
ideahitme committed
50
	if cfg.LogFormat == "json" {
51
52
		log.SetFormatter(&log.JSONFormatter{})
	}
53
	if cfg.DryRun {
ideahitme's avatar
ideahitme committed
54
		log.Info("running in dry-run mode. No changes to DNS records will be made.")
55
	}
56
57
58
59

	ll, err := log.ParseLevel(cfg.LogLevel)
	if err != nil {
		log.Fatalf("failed to parse log level: %v", err)
60
	}
61
	log.SetLevel(ll)
62
63
64

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

65
	go serveMetrics(cfg.MetricsAddress)
66
67
	go handleSigterm(stopChan)

68
69
	// Create a source.Config from the flags passed by the user.
	sourceCfg := &source.Config{
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
		Namespace:                   cfg.Namespace,
		AnnotationFilter:            cfg.AnnotationFilter,
		FQDNTemplate:                cfg.FQDNTemplate,
		CombineFQDNAndAnnotation:    cfg.CombineFQDNAndAnnotation,
		IgnoreHostnameAnnotation:    cfg.IgnoreHostnameAnnotation,
		Compatibility:               cfg.Compatibility,
		PublishInternal:             cfg.PublishInternal,
		PublishHostIP:               cfg.PublishHostIP,
		ConnectorServer:             cfg.ConnectorSourceServer,
		CRDSourceAPIVersion:         cfg.CRDSourceAPIVersion,
		CRDSourceKind:               cfg.CRDSourceKind,
		KubeConfig:                  cfg.KubeConfig,
		KubeMaster:                  cfg.Master,
		ServiceTypeFilter:           cfg.ServiceTypeFilter,
		IstioIngressGatewayServices: cfg.IstioIngressGatewayServices,
Dave Grizzanti's avatar
Dave Grizzanti committed
85
86
87
		CFAPIEndpoint:               cfg.CFAPIEndpoint,
		CFUsername:                  cfg.CFUsername,
		CFPassword:                  cfg.CFPassword,
88
		ContourLoadBalancerService:  cfg.ContourLoadBalancerService,
89
	}
90

91
92
	// Lookup all the selected sources by names and pass them the desired configuration.
	sources, err := source.ByNames(&source.SingletonClientGenerator{
93
94
95
		KubeConfig:     cfg.KubeConfig,
		KubeMaster:     cfg.Master,
		RequestTimeout: cfg.RequestTimeout,
96
	}, cfg.Sources, sourceCfg)
97
98
99
100
	if err != nil {
		log.Fatal(err)
	}

101
	// Combine multiple sources into a single, deduplicated source.
102
	endpointsSource := source.NewDedupSource(source.NewMultiSource(sources))
103

104
	domainFilter := provider.NewDomainFilterWithExclusions(cfg.DomainFilter, cfg.ExcludeDomains)
105
	zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
106
	zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
Cesar Wong's avatar
Cesar Wong committed
107
	zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter)
108

109
110
	var p provider.Provider
	switch cfg.Provider {
Li Yi's avatar
Li Yi committed
111
112
	case "alibabacloud":
		p, err = provider.NewAlibabaCloudProvider(cfg.AlibabaCloudConfigFile, domainFilter, zoneIDFilter, cfg.AlibabaCloudZoneType, cfg.DryRun)
113
	case "aws":
114
115
		p, err = provider.NewAWSProvider(
			provider.AWSConfig{
116
117
118
				DomainFilter:         domainFilter,
				ZoneIDFilter:         zoneIDFilter,
				ZoneTypeFilter:       zoneTypeFilter,
Cesar Wong's avatar
Cesar Wong committed
119
				ZoneTagFilter:        zoneTagFilter,
120
121
122
123
124
				BatchChangeSize:      cfg.AWSBatchChangeSize,
				BatchChangeInterval:  cfg.AWSBatchChangeInterval,
				EvaluateTargetHealth: cfg.AWSEvaluateTargetHealth,
				AssumeRole:           cfg.AWSAssumeRole,
				DryRun:               cfg.DryRun,
125
126
			},
		)
127
128
	case "aws-sd":
		// Check that only compatible Registry is used with AWS-SD
129
130
131
		if cfg.Registry != "noop" && cfg.Registry != "aws-sd" {
			log.Infof("Registry \"%s\" cannot be used with AWS ServiceDiscovery. Switching to \"aws-sd\".", cfg.Registry)
			cfg.Registry = "aws-sd"
132
		}
133
		p, err = provider.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.AWSAssumeRole, cfg.DryRun)
134
	case "azure":
135
		p, err = provider.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureResourceGroup, cfg.DryRun)
Dave Grizzanti's avatar
Dave Grizzanti committed
136
	case "vinyldns":
Dave Grizzanti's avatar
Dave Grizzanti committed
137
		p, err = provider.NewVinylDNSProvider(domainFilter, zoneIDFilter, cfg.DryRun)
138
	case "cloudflare":
njuettner's avatar
njuettner committed
139
		p, err = provider.NewCloudFlareProvider(domainFilter, zoneIDFilter, cfg.CloudflareZonesPerPage, cfg.CloudflareProxied, cfg.DryRun)
Dimitrij Klesev's avatar
Dimitrij Klesev committed
140
141
142
	case "rcodezero":
		p, err = provider.NewRcodeZeroProvider(domainFilter, cfg.DryRun, cfg.RcodezeroTXTEncrypt)
	case "google":
143
		p, err = provider.NewGoogleProvider(cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.DryRun)
144
	case "digitalocean":
145
		p, err = provider.NewDigitalOceanProvider(domainFilter, cfg.DryRun)
cliedeman's avatar
cliedeman committed
146
	case "linode":
147
		p, err = provider.NewLinodeProvider(domainFilter, cfg.DryRun, externaldns.Version)
148
	case "dnsimple":
149
		p, err = provider.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun)
150
151
152
153
	case "infoblox":
		p, err = provider.NewInfobloxProvider(
			provider.InfobloxConfig{
				DomainFilter: domainFilter,
154
				ZoneIDFilter: zoneIDFilter,
155
156
157
158
159
160
				Host:         cfg.InfobloxGridHost,
				Port:         cfg.InfobloxWapiPort,
				Username:     cfg.InfobloxWapiUsername,
				Password:     cfg.InfobloxWapiPassword,
				Version:      cfg.InfobloxWapiVersion,
				SSLVerify:    cfg.InfobloxSSLVerify,
161
				View:         cfg.InfobloxView,
162
				MaxResults:   cfg.InfobloxMaxResults,
163
164
165
				DryRun:       cfg.DryRun,
			},
		)
Julian Vassev's avatar
Julian Vassev committed
166
167
168
	case "dyn":
		p, err = provider.NewDynProvider(
			provider.DynConfig{
169
170
171
172
173
174
175
176
				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
177
178
			},
		)
Stan Lagun's avatar
Stan Lagun committed
179
	case "coredns", "skydns":
180
		p, err = provider.NewCoreDNSProvider(domainFilter, cfg.DryRun)
181
	case "exoscale":
Christopher Schmidt's avatar
Christopher Schmidt committed
182
		p, err = provider.NewExoscaleProvider(cfg.ExoscaleEndpoint, cfg.ExoscaleAPIKey, cfg.ExoscaleAPISecret, cfg.DryRun, provider.ExoscaleWithDomain(domainFilter), provider.ExoscaleWithLogging()), nil
183
	case "inmemory":
Anhad Jai Singh's avatar
Anhad Jai Singh committed
184
		p, err = provider.NewInMemoryProvider(provider.InMemoryInitZones(cfg.InMemoryZones), provider.InMemoryWithDomain(domainFilter), provider.InMemoryWithLogging()), nil
Stan Lagun's avatar
Stan Lagun committed
185
186
	case "designate":
		p, err = provider.NewDesignateProvider(domainFilter, cfg.DryRun)
Anhad Jai Singh's avatar
Anhad Jai Singh committed
187
	case "pdns":
188
189
190
		p, err = provider.NewPDNSProvider(
			provider.PDNSConfig{
				DomainFilter: domainFilter,
Jason Hoch's avatar
gofmt    
Jason Hoch committed
191
192
193
				DryRun:       cfg.DryRun,
				Server:       cfg.PDNSServer,
				APIKey:       cfg.PDNSAPIKey,
194
				TLSConfig: provider.TLSConfig{
Jason Hoch's avatar
gofmt    
Jason Hoch committed
195
196
197
					TLSEnabled:            cfg.PDNSTLSEnabled,
					CAFilePath:            cfg.TLSCA,
					ClientCertFilePath:    cfg.TLSClientCert,
198
199
200
201
					ClientCertKeyFilePath: cfg.TLSClientCertKey,
				},
			},
		)
202
	case "oci":
Andrew Pryde's avatar
Andrew Pryde committed
203
204
205
206
207
		var config *provider.OCIConfig
		config, err = provider.LoadOCIConfig(cfg.OCIConfigFile)
		if err == nil {
			p, err = provider.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.DryRun)
		}
Vladislav Troinich's avatar
Vladislav Troinich committed
208
	case "rfc2136":
209
		p, err = provider.NewRfc2136Provider(cfg.RFC2136Host, cfg.RFC2136Port, cfg.RFC2136Zone, cfg.RFC2136Insecure, cfg.RFC2136TSIGKeyName, cfg.RFC2136TSIGSecret, cfg.RFC2136TSIGSecretAlg, cfg.RFC2136TAXFR, domainFilter, cfg.DryRun, nil)
210
211
212
	case "ns1":
		p, err = provider.NewNS1Provider(
			provider.NS1Config{
213
214
				DomainFilter: domainFilter,
				ZoneIDFilter: zoneIDFilter,
215
216
				NS1Endpoint:  cfg.NS1Endpoint,
				NS1IgnoreSSL: cfg.NS1IgnoreSSL,
217
				DryRun:       cfg.DryRun,
218
219
			},
		)
Reinier Schoof's avatar
Reinier Schoof committed
220
221
	case "transip":
		p, err = provider.NewTransIPProvider(cfg.TransIPAccountName, cfg.TransIPPrivateKeyFile, domainFilter, cfg.DryRun)
222
	default:
223
		log.Fatalf("unknown dns provider: %s", cfg.Provider)
224
	}
225
226
227
228
	if err != nil {
		log.Fatal(err)
	}

Yerken's avatar
Yerken committed
229
230
231
232
233
	var r registry.Registry
	switch cfg.Registry {
	case "noop":
		r, err = registry.NewNoopRegistry(p)
	case "txt":
234
		r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTOwnerID, cfg.TXTCacheInterval)
235
	case "aws-sd":
236
		r, err = registry.NewAWSSDRegistry(p.(*provider.AWSSDProvider), cfg.TXTOwnerID)
Yerken's avatar
Yerken committed
237
238
239
240
	default:
		log.Fatalf("unknown registry: %s", cfg.Registry)
	}

Yerken's avatar
Yerken committed
241
242
243
244
	if err != nil {
		log.Fatal(err)
	}

245
246
247
248
249
	policy, exists := plan.Policies[cfg.Policy]
	if !exists {
		log.Fatalf("unknown policy: %s", cfg.Policy)
	}

250
	ctrl := controller.Controller{
251
		Source:   endpointsSource,
Yerken's avatar
Yerken committed
252
		Registry: r,
253
		Policy:   policy,
254
		Interval: cfg.Interval,
255
256
	}

257
	if cfg.Once {
258
259
260
261
262
263
		err := ctrl.RunOnce()
		if err != nil {
			log.Fatal(err)
		}

		os.Exit(0)
264
	}
265
	ctrl.Run(stopChan)
266
267
268
269
270
271
}

func handleSigterm(stopChan chan struct{}) {
	signals := make(chan os.Signal, 1)
	signal.Notify(signals, syscall.SIGTERM)
	<-signals
272
	log.Info("Received SIGTERM. Terminating...")
273
274
	close(stopChan)
}
275

276
277
278
279
280
281
282
283
284
285
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))
}