main.go 8.88 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,
85
	}
86

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

97
	// Combine multiple sources into a single, deduplicated source.
98
	endpointsSource := source.NewDedupSource(source.NewMultiSource(sources))
99

100
	domainFilter := provider.NewDomainFilter(cfg.DomainFilter)
101
	zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
102
	zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
Cesar Wong's avatar
Cesar Wong committed
103
	zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter)
104

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

Yerken's avatar
Yerken committed
211 212 213 214 215
	var r registry.Registry
	switch cfg.Registry {
	case "noop":
		r, err = registry.NewNoopRegistry(p)
	case "txt":
216
		r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTOwnerID, cfg.TXTCacheInterval)
217
	case "aws-sd":
218
		r, err = registry.NewAWSSDRegistry(p.(*provider.AWSSDProvider), cfg.TXTOwnerID)
Yerken's avatar
Yerken committed
219 220 221 222
	default:
		log.Fatalf("unknown registry: %s", cfg.Registry)
	}

Yerken's avatar
Yerken committed
223 224 225 226
	if err != nil {
		log.Fatal(err)
	}

227 228 229 230 231
	policy, exists := plan.Policies[cfg.Policy]
	if !exists {
		log.Fatalf("unknown policy: %s", cfg.Policy)
	}

232
	ctrl := controller.Controller{
233
		Source:   endpointsSource,
Yerken's avatar
Yerken committed
234
		Registry: r,
235
		Policy:   policy,
236
		Interval: cfg.Interval,
237 238
	}

239
	if cfg.Once {
240 241 242 243 244 245
		err := ctrl.RunOnce()
		if err != nil {
			log.Fatal(err)
		}

		os.Exit(0)
246
	}
247
	ctrl.Run(stopChan)
248 249 250 251 252 253
}

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

258 259 260 261 262 263 264 265 266 267
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))
}