main.go 9.53 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)
	}
jvassev's avatar
jvassev committed
44
	log.Infof("config: %s", cfg)
Henning Jacobs's avatar
Henning Jacobs committed
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
	}
89

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

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

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

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

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

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

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

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

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

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

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

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