main.go 8.6 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
		Namespace:                cfg.Namespace,
		AnnotationFilter:         cfg.AnnotationFilter,
		FQDNTemplate:             cfg.FQDNTemplate,
		CombineFQDNAndAnnotation: cfg.CombineFQDNAndAnnotation,
		Compatibility:            cfg.Compatibility,
		PublishInternal:          cfg.PublishInternal,
76
		PublishHostIP:            cfg.PublishHostIP,
77
		ConnectorServer:          cfg.ConnectorSourceServer,
78 79 80 81
		CRDSourceAPIVersion:      cfg.CRDSourceAPIVersion,
		CRDSourceKind:            cfg.CRDSourceKind,
		KubeConfig:               cfg.KubeConfig,
		KubeMaster:               cfg.Master,
82
		ServiceTypeFilter:        cfg.ServiceTypeFilter,
83
		IstioIngressGateway:      cfg.IstioIngressGateway,
84
	}
85

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

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

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

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

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

Yerken's avatar
Yerken committed
219 220 221 222
	if err != nil {
		log.Fatal(err)
	}

223 224 225 226 227
	policy, exists := plan.Policies[cfg.Policy]
	if !exists {
		log.Fatalf("unknown policy: %s", cfg.Policy)
	}

228
	ctrl := controller.Controller{
229
		Source:   endpointsSource,
Yerken's avatar
Yerken committed
230
		Registry: r,
231
		Policy:   policy,
232
		Interval: cfg.Interval,
233 234
	}

235
	if cfg.Once {
236 237 238 239 240 241
		err := ctrl.RunOnce()
		if err != nil {
			log.Fatal(err)
		}

		os.Exit(0)
242
	}
243
	ctrl.Run(stopChan)
244 245 246 247 248 249
}

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

254 255 256 257 258 259 260 261 262 263
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))
}