sigs.k8s.io/external-dns@v0.14.1/main.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "context" 21 "fmt" 22 "net/http" 23 "os" 24 "os/signal" 25 "syscall" 26 "time" 27 28 awsSDK "github.com/aws/aws-sdk-go/aws" 29 "github.com/aws/aws-sdk-go/aws/session" 30 "github.com/aws/aws-sdk-go/service/dynamodb" 31 "github.com/aws/aws-sdk-go/service/route53" 32 sd "github.com/aws/aws-sdk-go/service/servicediscovery" 33 "github.com/go-logr/logr" 34 "github.com/prometheus/client_golang/prometheus/promhttp" 35 log "github.com/sirupsen/logrus" 36 "k8s.io/apimachinery/pkg/labels" 37 _ "k8s.io/client-go/plugin/pkg/client/auth" 38 "k8s.io/klog/v2" 39 40 "sigs.k8s.io/external-dns/controller" 41 "sigs.k8s.io/external-dns/endpoint" 42 "sigs.k8s.io/external-dns/pkg/apis/externaldns" 43 "sigs.k8s.io/external-dns/pkg/apis/externaldns/validation" 44 "sigs.k8s.io/external-dns/plan" 45 "sigs.k8s.io/external-dns/provider" 46 "sigs.k8s.io/external-dns/provider/akamai" 47 "sigs.k8s.io/external-dns/provider/alibabacloud" 48 "sigs.k8s.io/external-dns/provider/aws" 49 "sigs.k8s.io/external-dns/provider/awssd" 50 "sigs.k8s.io/external-dns/provider/azure" 51 "sigs.k8s.io/external-dns/provider/bluecat" 52 "sigs.k8s.io/external-dns/provider/civo" 53 "sigs.k8s.io/external-dns/provider/cloudflare" 54 "sigs.k8s.io/external-dns/provider/coredns" 55 "sigs.k8s.io/external-dns/provider/designate" 56 "sigs.k8s.io/external-dns/provider/digitalocean" 57 "sigs.k8s.io/external-dns/provider/dnsimple" 58 "sigs.k8s.io/external-dns/provider/dyn" 59 "sigs.k8s.io/external-dns/provider/exoscale" 60 "sigs.k8s.io/external-dns/provider/gandi" 61 "sigs.k8s.io/external-dns/provider/godaddy" 62 "sigs.k8s.io/external-dns/provider/google" 63 "sigs.k8s.io/external-dns/provider/ibmcloud" 64 "sigs.k8s.io/external-dns/provider/infoblox" 65 "sigs.k8s.io/external-dns/provider/inmemory" 66 "sigs.k8s.io/external-dns/provider/linode" 67 "sigs.k8s.io/external-dns/provider/ns1" 68 "sigs.k8s.io/external-dns/provider/oci" 69 "sigs.k8s.io/external-dns/provider/ovh" 70 "sigs.k8s.io/external-dns/provider/pdns" 71 "sigs.k8s.io/external-dns/provider/pihole" 72 "sigs.k8s.io/external-dns/provider/plural" 73 "sigs.k8s.io/external-dns/provider/rcode0" 74 "sigs.k8s.io/external-dns/provider/rdns" 75 "sigs.k8s.io/external-dns/provider/rfc2136" 76 "sigs.k8s.io/external-dns/provider/safedns" 77 "sigs.k8s.io/external-dns/provider/scaleway" 78 "sigs.k8s.io/external-dns/provider/tencentcloud" 79 "sigs.k8s.io/external-dns/provider/transip" 80 "sigs.k8s.io/external-dns/provider/ultradns" 81 "sigs.k8s.io/external-dns/provider/vinyldns" 82 "sigs.k8s.io/external-dns/provider/vultr" 83 "sigs.k8s.io/external-dns/provider/webhook" 84 webhookapi "sigs.k8s.io/external-dns/provider/webhook/api" 85 "sigs.k8s.io/external-dns/registry" 86 "sigs.k8s.io/external-dns/source" 87 ) 88 89 func main() { 90 cfg := externaldns.NewConfig() 91 if err := cfg.ParseFlags(os.Args[1:]); err != nil { 92 log.Fatalf("flag parsing error: %v", err) 93 } 94 if cfg.LogFormat == "json" { 95 log.SetFormatter(&log.JSONFormatter{}) 96 } 97 log.Infof("config: %s", cfg) 98 99 if err := validation.ValidateConfig(cfg); err != nil { 100 log.Fatalf("config validation failed: %v", err) 101 } 102 103 if cfg.DryRun { 104 log.Info("running in dry-run mode. No changes to DNS records will be made.") 105 } 106 107 ll, err := log.ParseLevel(cfg.LogLevel) 108 if err != nil { 109 log.Fatalf("failed to parse log level: %v", err) 110 } 111 log.SetLevel(ll) 112 113 // Klog V2 is used by k8s.io/apimachinery/pkg/labels and can throw (a lot) of irrelevant logs 114 // See https://github.com/kubernetes-sigs/external-dns/issues/2348 115 defer klog.ClearLogger() 116 klog.SetLogger(logr.Discard()) 117 118 ctx, cancel := context.WithCancel(context.Background()) 119 120 go serveMetrics(cfg.MetricsAddress) 121 go handleSigterm(cancel) 122 123 // error is explicitly ignored because the filter is already validated in validation.ValidateConfig 124 labelSelector, _ := labels.Parse(cfg.LabelFilter) 125 126 // Create a source.Config from the flags passed by the user. 127 sourceCfg := &source.Config{ 128 Namespace: cfg.Namespace, 129 AnnotationFilter: cfg.AnnotationFilter, 130 LabelFilter: labelSelector, 131 IngressClassNames: cfg.IngressClassNames, 132 FQDNTemplate: cfg.FQDNTemplate, 133 CombineFQDNAndAnnotation: cfg.CombineFQDNAndAnnotation, 134 IgnoreHostnameAnnotation: cfg.IgnoreHostnameAnnotation, 135 IgnoreIngressTLSSpec: cfg.IgnoreIngressTLSSpec, 136 IgnoreIngressRulesSpec: cfg.IgnoreIngressRulesSpec, 137 GatewayNamespace: cfg.GatewayNamespace, 138 GatewayLabelFilter: cfg.GatewayLabelFilter, 139 Compatibility: cfg.Compatibility, 140 PublishInternal: cfg.PublishInternal, 141 PublishHostIP: cfg.PublishHostIP, 142 AlwaysPublishNotReadyAddresses: cfg.AlwaysPublishNotReadyAddresses, 143 ConnectorServer: cfg.ConnectorSourceServer, 144 CRDSourceAPIVersion: cfg.CRDSourceAPIVersion, 145 CRDSourceKind: cfg.CRDSourceKind, 146 KubeConfig: cfg.KubeConfig, 147 APIServerURL: cfg.APIServerURL, 148 ServiceTypeFilter: cfg.ServiceTypeFilter, 149 CFAPIEndpoint: cfg.CFAPIEndpoint, 150 CFUsername: cfg.CFUsername, 151 CFPassword: cfg.CFPassword, 152 GlooNamespaces: cfg.GlooNamespaces, 153 SkipperRouteGroupVersion: cfg.SkipperRouteGroupVersion, 154 RequestTimeout: cfg.RequestTimeout, 155 DefaultTargets: cfg.DefaultTargets, 156 OCPRouterName: cfg.OCPRouterName, 157 UpdateEvents: cfg.UpdateEvents, 158 ResolveLoadBalancerHostname: cfg.ResolveServiceLoadBalancerHostname, 159 TraefikDisableLegacy: cfg.TraefikDisableLegacy, 160 TraefikDisableNew: cfg.TraefikDisableNew, 161 } 162 163 // Lookup all the selected sources by names and pass them the desired configuration. 164 sources, err := source.ByNames(ctx, &source.SingletonClientGenerator{ 165 KubeConfig: cfg.KubeConfig, 166 APIServerURL: cfg.APIServerURL, 167 // If update events are enabled, disable timeout. 168 RequestTimeout: func() time.Duration { 169 if cfg.UpdateEvents { 170 return 0 171 } 172 return cfg.RequestTimeout 173 }(), 174 }, cfg.Sources, sourceCfg) 175 if err != nil { 176 log.Fatal(err) 177 } 178 179 // Filter targets 180 targetFilter := endpoint.NewTargetNetFilterWithExclusions(cfg.TargetNetFilter, cfg.ExcludeTargetNets) 181 182 // Combine multiple sources into a single, deduplicated source. 183 endpointsSource := source.NewDedupSource(source.NewMultiSource(sources, sourceCfg.DefaultTargets)) 184 endpointsSource = source.NewTargetFilterSource(endpointsSource, targetFilter) 185 186 // RegexDomainFilter overrides DomainFilter 187 var domainFilter endpoint.DomainFilter 188 if cfg.RegexDomainFilter.String() != "" { 189 domainFilter = endpoint.NewRegexDomainFilter(cfg.RegexDomainFilter, cfg.RegexDomainExclusion) 190 } else { 191 domainFilter = endpoint.NewDomainFilterWithExclusions(cfg.DomainFilter, cfg.ExcludeDomains) 192 } 193 zoneNameFilter := endpoint.NewDomainFilter(cfg.ZoneNameFilter) 194 zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter) 195 zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType) 196 zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter) 197 198 var awsSession *session.Session 199 if cfg.Provider == "aws" || cfg.Provider == "aws-sd" || cfg.Registry == "dynamodb" { 200 awsSession, err = aws.NewSession( 201 aws.AWSSessionConfig{ 202 AssumeRole: cfg.AWSAssumeRole, 203 AssumeRoleExternalID: cfg.AWSAssumeRoleExternalID, 204 APIRetries: cfg.AWSAPIRetries, 205 }, 206 ) 207 if err != nil { 208 log.Fatal(err) 209 } 210 } 211 212 var p provider.Provider 213 switch cfg.Provider { 214 case "akamai": 215 p, err = akamai.NewAkamaiProvider( 216 akamai.AkamaiConfig{ 217 DomainFilter: domainFilter, 218 ZoneIDFilter: zoneIDFilter, 219 ServiceConsumerDomain: cfg.AkamaiServiceConsumerDomain, 220 ClientToken: cfg.AkamaiClientToken, 221 ClientSecret: cfg.AkamaiClientSecret, 222 AccessToken: cfg.AkamaiAccessToken, 223 EdgercPath: cfg.AkamaiEdgercPath, 224 EdgercSection: cfg.AkamaiEdgercSection, 225 DryRun: cfg.DryRun, 226 }, nil) 227 case "alibabacloud": 228 p, err = alibabacloud.NewAlibabaCloudProvider(cfg.AlibabaCloudConfigFile, domainFilter, zoneIDFilter, cfg.AlibabaCloudZoneType, cfg.DryRun) 229 case "aws": 230 p, err = aws.NewAWSProvider( 231 aws.AWSConfig{ 232 DomainFilter: domainFilter, 233 ZoneIDFilter: zoneIDFilter, 234 ZoneTypeFilter: zoneTypeFilter, 235 ZoneTagFilter: zoneTagFilter, 236 ZoneMatchParent: cfg.AWSZoneMatchParent, 237 BatchChangeSize: cfg.AWSBatchChangeSize, 238 BatchChangeSizeBytes: cfg.AWSBatchChangeSizeBytes, 239 BatchChangeSizeValues: cfg.AWSBatchChangeSizeValues, 240 BatchChangeInterval: cfg.AWSBatchChangeInterval, 241 EvaluateTargetHealth: cfg.AWSEvaluateTargetHealth, 242 PreferCNAME: cfg.AWSPreferCNAME, 243 DryRun: cfg.DryRun, 244 ZoneCacheDuration: cfg.AWSZoneCacheDuration, 245 }, 246 route53.New(awsSession), 247 ) 248 case "aws-sd": 249 // Check that only compatible Registry is used with AWS-SD 250 if cfg.Registry != "noop" && cfg.Registry != "aws-sd" { 251 log.Infof("Registry \"%s\" cannot be used with AWS Cloud Map. Switching to \"aws-sd\".", cfg.Registry) 252 cfg.Registry = "aws-sd" 253 } 254 p, err = awssd.NewAWSSDProvider(domainFilter, cfg.AWSZoneType, cfg.DryRun, cfg.AWSSDServiceCleanup, cfg.TXTOwnerID, sd.New(awsSession)) 255 case "azure-dns", "azure": 256 p, err = azure.NewAzureProvider(cfg.AzureConfigFile, domainFilter, zoneNameFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun) 257 case "azure-private-dns": 258 p, err = azure.NewAzurePrivateDNSProvider(cfg.AzureConfigFile, domainFilter, zoneIDFilter, cfg.AzureSubscriptionID, cfg.AzureResourceGroup, cfg.AzureUserAssignedIdentityClientID, cfg.DryRun) 259 case "bluecat": 260 p, err = bluecat.NewBluecatProvider(cfg.BluecatConfigFile, cfg.BluecatDNSConfiguration, cfg.BluecatDNSServerName, cfg.BluecatDNSDeployType, cfg.BluecatDNSView, cfg.BluecatGatewayHost, cfg.BluecatRootZone, cfg.TXTPrefix, cfg.TXTSuffix, domainFilter, zoneIDFilter, cfg.DryRun, cfg.BluecatSkipTLSVerify) 261 case "vinyldns": 262 p, err = vinyldns.NewVinylDNSProvider(domainFilter, zoneIDFilter, cfg.DryRun) 263 case "vultr": 264 p, err = vultr.NewVultrProvider(ctx, domainFilter, cfg.DryRun) 265 case "ultradns": 266 p, err = ultradns.NewUltraDNSProvider(domainFilter, cfg.DryRun) 267 case "civo": 268 p, err = civo.NewCivoProvider(domainFilter, cfg.DryRun) 269 case "cloudflare": 270 p, err = cloudflare.NewCloudFlareProvider(domainFilter, zoneIDFilter, cfg.CloudflareProxied, cfg.DryRun, cfg.CloudflareDNSRecordsPerPage) 271 case "rcodezero": 272 p, err = rcode0.NewRcodeZeroProvider(domainFilter, cfg.DryRun, cfg.RcodezeroTXTEncrypt) 273 case "google": 274 p, err = google.NewGoogleProvider(ctx, cfg.GoogleProject, domainFilter, zoneIDFilter, cfg.GoogleBatchChangeSize, cfg.GoogleBatchChangeInterval, cfg.GoogleZoneVisibility, cfg.DryRun) 275 case "digitalocean": 276 p, err = digitalocean.NewDigitalOceanProvider(ctx, domainFilter, cfg.DryRun, cfg.DigitalOceanAPIPageSize) 277 case "ovh": 278 p, err = ovh.NewOVHProvider(ctx, domainFilter, cfg.OVHEndpoint, cfg.OVHApiRateLimit, cfg.DryRun) 279 case "linode": 280 p, err = linode.NewLinodeProvider(domainFilter, cfg.DryRun, externaldns.Version) 281 case "dnsimple": 282 p, err = dnsimple.NewDnsimpleProvider(domainFilter, zoneIDFilter, cfg.DryRun) 283 case "infoblox": 284 p, err = infoblox.NewInfobloxProvider( 285 infoblox.StartupConfig{ 286 DomainFilter: domainFilter, 287 ZoneIDFilter: zoneIDFilter, 288 Host: cfg.InfobloxGridHost, 289 Port: cfg.InfobloxWapiPort, 290 Username: cfg.InfobloxWapiUsername, 291 Password: cfg.InfobloxWapiPassword, 292 Version: cfg.InfobloxWapiVersion, 293 SSLVerify: cfg.InfobloxSSLVerify, 294 View: cfg.InfobloxView, 295 MaxResults: cfg.InfobloxMaxResults, 296 DryRun: cfg.DryRun, 297 FQDNRegEx: cfg.InfobloxFQDNRegEx, 298 NameRegEx: cfg.InfobloxNameRegEx, 299 CreatePTR: cfg.InfobloxCreatePTR, 300 CacheDuration: cfg.InfobloxCacheDuration, 301 }, 302 ) 303 case "dyn": 304 p, err = dyn.NewDynProvider( 305 dyn.DynConfig{ 306 DomainFilter: domainFilter, 307 ZoneIDFilter: zoneIDFilter, 308 DryRun: cfg.DryRun, 309 CustomerName: cfg.DynCustomerName, 310 Username: cfg.DynUsername, 311 Password: cfg.DynPassword, 312 MinTTLSeconds: cfg.DynMinTTLSeconds, 313 AppVersion: externaldns.Version, 314 }, 315 ) 316 case "coredns", "skydns": 317 p, err = coredns.NewCoreDNSProvider(domainFilter, cfg.CoreDNSPrefix, cfg.DryRun) 318 case "rdns": 319 p, err = rdns.NewRDNSProvider( 320 rdns.RDNSConfig{ 321 DomainFilter: domainFilter, 322 DryRun: cfg.DryRun, 323 }, 324 ) 325 case "exoscale": 326 p, err = exoscale.NewExoscaleProvider( 327 cfg.ExoscaleAPIEnvironment, 328 cfg.ExoscaleAPIZone, 329 cfg.ExoscaleAPIKey, 330 cfg.ExoscaleAPISecret, 331 cfg.DryRun, 332 exoscale.ExoscaleWithDomain(domainFilter), 333 exoscale.ExoscaleWithLogging(), 334 ) 335 case "inmemory": 336 p, err = inmemory.NewInMemoryProvider(inmemory.InMemoryInitZones(cfg.InMemoryZones), inmemory.InMemoryWithDomain(domainFilter), inmemory.InMemoryWithLogging()), nil 337 case "designate": 338 p, err = designate.NewDesignateProvider(domainFilter, cfg.DryRun) 339 case "pdns": 340 p, err = pdns.NewPDNSProvider( 341 ctx, 342 pdns.PDNSConfig{ 343 DomainFilter: domainFilter, 344 DryRun: cfg.DryRun, 345 Server: cfg.PDNSServer, 346 APIKey: cfg.PDNSAPIKey, 347 TLSConfig: pdns.TLSConfig{ 348 SkipTLSVerify: cfg.PDNSSkipTLSVerify, 349 CAFilePath: cfg.TLSCA, 350 ClientCertFilePath: cfg.TLSClientCert, 351 ClientCertKeyFilePath: cfg.TLSClientCertKey, 352 }, 353 }, 354 ) 355 case "oci": 356 var config *oci.OCIConfig 357 // if the instance-principals flag was set, and a compartment OCID was provided, then ignore the 358 // OCI config file, and provide a config that uses instance principal authentication. 359 if cfg.OCIAuthInstancePrincipal { 360 if len(cfg.OCICompartmentOCID) == 0 { 361 err = fmt.Errorf("instance principal authentication requested, but no compartment OCID provided") 362 } else { 363 authConfig := oci.OCIAuthConfig{UseInstancePrincipal: true} 364 config = &oci.OCIConfig{Auth: authConfig, CompartmentID: cfg.OCICompartmentOCID} 365 } 366 } else { 367 config, err = oci.LoadOCIConfig(cfg.OCIConfigFile) 368 } 369 config.ZoneCacheDuration = cfg.OCIZoneCacheDuration 370 if err == nil { 371 p, err = oci.NewOCIProvider(*config, domainFilter, zoneIDFilter, cfg.OCIZoneScope, cfg.DryRun) 372 } 373 case "rfc2136": 374 tlsConfig := rfc2136.TLSConfig{ 375 UseTLS: cfg.RFC2136UseTLS, 376 SkipTLSVerify: cfg.RFC2136SkipTLSVerify, 377 CAFilePath: cfg.TLSCA, 378 ClientCertFilePath: cfg.TLSClientCert, 379 ClientCertKeyFilePath: cfg.TLSClientCertKey, 380 ServerName: "", 381 } 382 p, err = rfc2136.NewRfc2136Provider(cfg.RFC2136Host, cfg.RFC2136Port, cfg.RFC2136Zone, cfg.RFC2136Insecure, cfg.RFC2136TSIGKeyName, cfg.RFC2136TSIGSecret, cfg.RFC2136TSIGSecretAlg, cfg.RFC2136TAXFR, domainFilter, cfg.DryRun, cfg.RFC2136MinTTL, cfg.RFC2136GSSTSIG, cfg.RFC2136KerberosUsername, cfg.RFC2136KerberosPassword, cfg.RFC2136KerberosRealm, cfg.RFC2136BatchChangeSize, tlsConfig, nil) 383 case "ns1": 384 p, err = ns1.NewNS1Provider( 385 ns1.NS1Config{ 386 DomainFilter: domainFilter, 387 ZoneIDFilter: zoneIDFilter, 388 NS1Endpoint: cfg.NS1Endpoint, 389 NS1IgnoreSSL: cfg.NS1IgnoreSSL, 390 DryRun: cfg.DryRun, 391 MinTTLSeconds: cfg.NS1MinTTLSeconds, 392 }, 393 ) 394 case "transip": 395 p, err = transip.NewTransIPProvider(cfg.TransIPAccountName, cfg.TransIPPrivateKeyFile, domainFilter, cfg.DryRun) 396 case "scaleway": 397 p, err = scaleway.NewScalewayProvider(ctx, domainFilter, cfg.DryRun) 398 case "godaddy": 399 p, err = godaddy.NewGoDaddyProvider(ctx, domainFilter, cfg.GoDaddyTTL, cfg.GoDaddyAPIKey, cfg.GoDaddySecretKey, cfg.GoDaddyOTE, cfg.DryRun) 400 case "gandi": 401 p, err = gandi.NewGandiProvider(ctx, domainFilter, cfg.DryRun) 402 case "pihole": 403 p, err = pihole.NewPiholeProvider( 404 pihole.PiholeConfig{ 405 Server: cfg.PiholeServer, 406 Password: cfg.PiholePassword, 407 TLSInsecureSkipVerify: cfg.PiholeTLSInsecureSkipVerify, 408 DomainFilter: domainFilter, 409 DryRun: cfg.DryRun, 410 }, 411 ) 412 case "ibmcloud": 413 p, err = ibmcloud.NewIBMCloudProvider(cfg.IBMCloudConfigFile, domainFilter, zoneIDFilter, endpointsSource, cfg.IBMCloudProxied, cfg.DryRun) 414 case "safedns": 415 p, err = safedns.NewSafeDNSProvider(domainFilter, cfg.DryRun) 416 case "plural": 417 p, err = plural.NewPluralProvider(cfg.PluralCluster, cfg.PluralProvider) 418 case "tencentcloud": 419 p, err = tencentcloud.NewTencentCloudProvider(domainFilter, zoneIDFilter, cfg.TencentCloudConfigFile, cfg.TencentCloudZoneType, cfg.DryRun) 420 case "webhook": 421 p, err = webhook.NewWebhookProvider(cfg.WebhookProviderURL) 422 default: 423 log.Fatalf("unknown dns provider: %s", cfg.Provider) 424 } 425 if err != nil { 426 log.Fatal(err) 427 } 428 429 if cfg.WebhookServer { 430 webhookapi.StartHTTPApi(p, nil, cfg.WebhookProviderReadTimeout, cfg.WebhookProviderWriteTimeout, "127.0.0.1:8888") 431 os.Exit(0) 432 } 433 434 var r registry.Registry 435 switch cfg.Registry { 436 case "dynamodb": 437 config := awsSDK.NewConfig() 438 if cfg.AWSDynamoDBRegion != "" { 439 config = config.WithRegion(cfg.AWSDynamoDBRegion) 440 } 441 r, err = registry.NewDynamoDBRegistry(p, cfg.TXTOwnerID, dynamodb.New(awsSession, config), cfg.AWSDynamoDBTable, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes, cfg.ExcludeDNSRecordTypes, []byte(cfg.TXTEncryptAESKey), cfg.TXTCacheInterval) 442 case "noop": 443 r, err = registry.NewNoopRegistry(p) 444 case "txt": 445 r, err = registry.NewTXTRegistry(p, cfg.TXTPrefix, cfg.TXTSuffix, cfg.TXTOwnerID, cfg.TXTCacheInterval, cfg.TXTWildcardReplacement, cfg.ManagedDNSRecordTypes, cfg.ExcludeDNSRecordTypes, cfg.TXTEncryptEnabled, []byte(cfg.TXTEncryptAESKey)) 446 case "aws-sd": 447 r, err = registry.NewAWSSDRegistry(p.(*awssd.AWSSDProvider), cfg.TXTOwnerID) 448 default: 449 log.Fatalf("unknown registry: %s", cfg.Registry) 450 } 451 452 if err != nil { 453 log.Fatal(err) 454 } 455 456 policy, exists := plan.Policies[cfg.Policy] 457 if !exists { 458 log.Fatalf("unknown policy: %s", cfg.Policy) 459 } 460 461 ctrl := controller.Controller{ 462 Source: endpointsSource, 463 Registry: r, 464 Policy: policy, 465 Interval: cfg.Interval, 466 DomainFilter: domainFilter, 467 ManagedRecordTypes: cfg.ManagedDNSRecordTypes, 468 ExcludeRecordTypes: cfg.ExcludeDNSRecordTypes, 469 MinEventSyncInterval: cfg.MinEventSyncInterval, 470 } 471 472 if cfg.Once { 473 err := ctrl.RunOnce(ctx) 474 if err != nil { 475 log.Fatal(err) 476 } 477 478 os.Exit(0) 479 } 480 481 if cfg.UpdateEvents { 482 // Add RunOnce as the handler function that will be called when ingress/service sources have changed. 483 // Note that k8s Informers will perform an initial list operation, which results in the handler 484 // function initially being called for every Service/Ingress that exists 485 ctrl.Source.AddEventHandler(ctx, func() { ctrl.ScheduleRunOnce(time.Now()) }) 486 } 487 488 ctrl.ScheduleRunOnce(time.Now()) 489 ctrl.Run(ctx) 490 } 491 492 func handleSigterm(cancel func()) { 493 signals := make(chan os.Signal, 1) 494 signal.Notify(signals, syscall.SIGTERM) 495 <-signals 496 log.Info("Received SIGTERM. Terminating...") 497 cancel() 498 } 499 500 func serveMetrics(address string) { 501 http.HandleFunc("/healthz", func(w http.ResponseWriter, _ *http.Request) { 502 w.WriteHeader(http.StatusOK) 503 w.Write([]byte("OK")) 504 }) 505 506 http.Handle("/metrics", promhttp.Handler()) 507 508 log.Fatal(http.ListenAndServe(address, nil)) 509 }