main.go 11.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
	"sigs.k8s.io/external-dns/controller"
33
	"sigs.k8s.io/external-dns/endpoint"
34 35 36 37 38 39
	"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"
40 41
)

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

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

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

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

66 67
	ctx := context.Background()

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

70
	go serveMetrics(cfg.MetricsAddress)
71 72
	go handleSigterm(stopChan)

73 74
	// Create a source.Config from the flags passed by the user.
	sourceCfg := &source.Config{
75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
		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,
95 96
		SkipperRouteGroupVersion:       cfg.SkipperRouteGroupVersion,
		RequestTimeout:                 cfg.RequestTimeout,
97
	}
98

99 100
	// Lookup all the selected sources by names and pass them the desired configuration.
	sources, err := source.ByNames(&source.SingletonClientGenerator{
101 102 103 104 105 106 107 108 109
		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
		}(),
110
	}, cfg.Sources, sourceCfg)
111 112 113 114
	if err != nil {
		log.Fatal(err)
	}

115
	// Combine multiple sources into a single, deduplicated source.
116
	endpointsSource := source.NewDedupSource(source.NewMultiSource(sources))
117

118
	domainFilter := endpoint.NewDomainFilterWithExclusions(cfg.DomainFilter, cfg.ExcludeDomains)
119
	zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
120
	zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
Cesar Wong's avatar
Cesar Wong committed
121
	zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter)
122

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

Yerken's avatar
Yerken committed
271 272 273 274 275
	var r registry.Registry
	switch cfg.Registry {
	case "noop":
		r, err = registry.NewNoopRegistry(p)
	case "txt":
276
		r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTOwnerID, cfg.TXTCacheInterval)
277
	case "aws-sd":
278
		r, err = registry.NewAWSSDRegistry(p.(*provider.AWSSDProvider), cfg.TXTOwnerID)
Yerken's avatar
Yerken committed
279 280 281 282
	default:
		log.Fatalf("unknown registry: %s", cfg.Registry)
	}

Yerken's avatar
Yerken committed
283 284 285 286
	if err != nil {
		log.Fatal(err)
	}

287 288 289 290 291
	policy, exists := plan.Policies[cfg.Policy]
	if !exists {
		log.Fatalf("unknown policy: %s", cfg.Policy)
	}

292
	ctrl := controller.Controller{
293 294 295 296 297
		Source:       endpointsSource,
		Registry:     r,
		Policy:       policy,
		Interval:     cfg.Interval,
		DomainFilter: domainFilter,
298 299
	}

300 301 302 303 304 305 306
	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)
	}

307
	if cfg.Once {
308
		err := ctrl.RunOnce(ctx)
309 310 311 312 313
		if err != nil {
			log.Fatal(err)
		}

		os.Exit(0)
314
	}
315
	ctrl.Run(ctx, stopChan)
316 317 318 319 320 321
}

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

326 327 328 329 330 331 332 333 334 335
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))
}