sigs.k8s.io/external-dns@v0.14.1/provider/awssd/aws_sd.go (about) 1 /* 2 Copyright 2018 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 awssd 18 19 import ( 20 "context" 21 "crypto/sha256" 22 "encoding/hex" 23 "fmt" 24 "regexp" 25 "strings" 26 27 "github.com/aws/aws-sdk-go/aws" 28 "github.com/aws/aws-sdk-go/aws/request" 29 sd "github.com/aws/aws-sdk-go/service/servicediscovery" 30 log "github.com/sirupsen/logrus" 31 32 "sigs.k8s.io/external-dns/endpoint" 33 "sigs.k8s.io/external-dns/plan" 34 "sigs.k8s.io/external-dns/provider" 35 ) 36 37 const ( 38 sdDefaultRecordTTL = 300 39 40 sdNamespaceTypePublic = "public" 41 sdNamespaceTypePrivate = "private" 42 43 sdInstanceAttrIPV4 = "AWS_INSTANCE_IPV4" 44 sdInstanceAttrCname = "AWS_INSTANCE_CNAME" 45 sdInstanceAttrAlias = "AWS_ALIAS_DNS_NAME" 46 ) 47 48 var ( 49 // matches ELB with hostname format load-balancer.us-east-1.elb.amazonaws.com 50 sdElbHostnameRegex = regexp.MustCompile(`.+\.[^.]+\.elb\.amazonaws\.com$`) 51 52 // matches NLB with hostname format load-balancer.elb.us-east-1.amazonaws.com 53 sdNlbHostnameRegex = regexp.MustCompile(`.+\.elb\.[^.]+\.amazonaws\.com$`) 54 ) 55 56 // AWSSDClient is the subset of the AWS Cloud Map API that we actually use. Add methods as required. 57 // Signatures must match exactly. Taken from https://github.com/aws/aws-sdk-go/blob/HEAD/service/servicediscovery/api.go 58 type AWSSDClient interface { 59 CreateService(input *sd.CreateServiceInput) (*sd.CreateServiceOutput, error) 60 DeregisterInstance(input *sd.DeregisterInstanceInput) (*sd.DeregisterInstanceOutput, error) 61 DiscoverInstancesWithContext(ctx aws.Context, input *sd.DiscoverInstancesInput, opts ...request.Option) (*sd.DiscoverInstancesOutput, error) 62 ListNamespacesPages(input *sd.ListNamespacesInput, fn func(*sd.ListNamespacesOutput, bool) bool) error 63 ListServicesPages(input *sd.ListServicesInput, fn func(*sd.ListServicesOutput, bool) bool) error 64 RegisterInstance(input *sd.RegisterInstanceInput) (*sd.RegisterInstanceOutput, error) 65 UpdateService(input *sd.UpdateServiceInput) (*sd.UpdateServiceOutput, error) 66 DeleteService(input *sd.DeleteServiceInput) (*sd.DeleteServiceOutput, error) 67 } 68 69 // AWSSDProvider is an implementation of Provider for AWS Cloud Map. 70 type AWSSDProvider struct { 71 provider.BaseProvider 72 client AWSSDClient 73 dryRun bool 74 // only consider namespaces ending in this suffix 75 namespaceFilter endpoint.DomainFilter 76 // filter namespace by type (private or public) 77 namespaceTypeFilter *sd.NamespaceFilter 78 // enables service without instances cleanup 79 cleanEmptyService bool 80 // filter services for removal 81 ownerID string 82 } 83 84 // NewAWSSDProvider initializes a new AWS Cloud Map based Provider. 85 func NewAWSSDProvider(domainFilter endpoint.DomainFilter, namespaceType string, dryRun, cleanEmptyService bool, ownerID string, client AWSSDClient) (*AWSSDProvider, error) { 86 provider := &AWSSDProvider{ 87 client: client, 88 dryRun: dryRun, 89 namespaceFilter: domainFilter, 90 namespaceTypeFilter: newSdNamespaceFilter(namespaceType), 91 cleanEmptyService: cleanEmptyService, 92 ownerID: ownerID, 93 } 94 95 return provider, nil 96 } 97 98 // newSdNamespaceFilter initialized AWS SD Namespace Filter based on given string config 99 func newSdNamespaceFilter(namespaceTypeConfig string) *sd.NamespaceFilter { 100 switch namespaceTypeConfig { 101 case sdNamespaceTypePublic: 102 return &sd.NamespaceFilter{ 103 Name: aws.String(sd.NamespaceFilterNameType), 104 Values: []*string{aws.String(sd.NamespaceTypeDnsPublic)}, 105 } 106 case sdNamespaceTypePrivate: 107 return &sd.NamespaceFilter{ 108 Name: aws.String(sd.NamespaceFilterNameType), 109 Values: []*string{aws.String(sd.NamespaceTypeDnsPrivate)}, 110 } 111 default: 112 return nil 113 } 114 } 115 116 // Records returns list of all endpoints. 117 func (p *AWSSDProvider) Records(ctx context.Context) (endpoints []*endpoint.Endpoint, err error) { 118 namespaces, err := p.ListNamespaces() 119 if err != nil { 120 return nil, err 121 } 122 123 for _, ns := range namespaces { 124 services, err := p.ListServicesByNamespaceID(ns.Id) 125 if err != nil { 126 return nil, err 127 } 128 129 for _, srv := range services { 130 resp, err := p.client.DiscoverInstancesWithContext(ctx, &sd.DiscoverInstancesInput{ 131 NamespaceName: ns.Name, 132 ServiceName: srv.Name, 133 }) 134 if err != nil { 135 return nil, err 136 } 137 138 if len(resp.Instances) == 0 { 139 if err := p.DeleteService(srv); err != nil { 140 log.Errorf("Failed to delete service %q, error: %s", aws.StringValue(srv.Name), err) 141 } 142 continue 143 } 144 145 endpoints = append(endpoints, p.instancesToEndpoint(ns, srv, resp.Instances)) 146 } 147 } 148 149 return endpoints, nil 150 } 151 152 func (p *AWSSDProvider) instancesToEndpoint(ns *sd.NamespaceSummary, srv *sd.Service, instances []*sd.HttpInstanceSummary) *endpoint.Endpoint { 153 // DNS name of the record is a concatenation of service and namespace 154 recordName := *srv.Name + "." + *ns.Name 155 156 labels := endpoint.NewLabels() 157 labels[endpoint.AWSSDDescriptionLabel] = aws.StringValue(srv.Description) 158 159 newEndpoint := &endpoint.Endpoint{ 160 DNSName: recordName, 161 RecordTTL: endpoint.TTL(aws.Int64Value(srv.DnsConfig.DnsRecords[0].TTL)), 162 Targets: make(endpoint.Targets, 0, len(instances)), 163 Labels: labels, 164 } 165 166 for _, inst := range instances { 167 // CNAME 168 if inst.Attributes[sdInstanceAttrCname] != nil && aws.StringValue(srv.DnsConfig.DnsRecords[0].Type) == sd.RecordTypeCname { 169 newEndpoint.RecordType = endpoint.RecordTypeCNAME 170 newEndpoint.Targets = append(newEndpoint.Targets, aws.StringValue(inst.Attributes[sdInstanceAttrCname])) 171 172 // ALIAS 173 } else if inst.Attributes[sdInstanceAttrAlias] != nil { 174 newEndpoint.RecordType = endpoint.RecordTypeCNAME 175 newEndpoint.Targets = append(newEndpoint.Targets, aws.StringValue(inst.Attributes[sdInstanceAttrAlias])) 176 177 // IP-based target 178 } else if inst.Attributes[sdInstanceAttrIPV4] != nil { 179 newEndpoint.RecordType = endpoint.RecordTypeA 180 newEndpoint.Targets = append(newEndpoint.Targets, aws.StringValue(inst.Attributes[sdInstanceAttrIPV4])) 181 } else { 182 log.Warnf("Invalid instance \"%v\" found in service \"%v\"", inst, srv.Name) 183 } 184 } 185 186 return newEndpoint 187 } 188 189 // ApplyChanges applies Kubernetes changes in endpoints to AWS API 190 func (p *AWSSDProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { 191 // return early if there is nothing to change 192 if len(changes.Create) == 0 && len(changes.Delete) == 0 && len(changes.UpdateNew) == 0 { 193 log.Info("All records are already up to date") 194 return nil 195 } 196 197 // convert updates to delete and create operation if applicable (updates not supported) 198 creates, deletes := p.updatesToCreates(changes) 199 changes.Delete = append(changes.Delete, deletes...) 200 changes.Create = append(changes.Create, creates...) 201 202 namespaces, err := p.ListNamespaces() 203 if err != nil { 204 return err 205 } 206 207 // Deletes must be executed first to support update case. 208 // When just list of targets is updated `[1.2.3.4] -> [1.2.3.4, 1.2.3.5]` it is translated to: 209 // ``` 210 // deletes = [1.2.3.4] 211 // creates = [1.2.3.4, 1.2.3.5] 212 // ``` 213 // then when deletes are executed after creates it will miss the `1.2.3.4` instance. 214 err = p.submitDeletes(namespaces, changes.Delete) 215 if err != nil { 216 return err 217 } 218 219 err = p.submitCreates(namespaces, changes.Create) 220 if err != nil { 221 return err 222 } 223 224 return nil 225 } 226 227 func (p *AWSSDProvider) updatesToCreates(changes *plan.Changes) (creates []*endpoint.Endpoint, deletes []*endpoint.Endpoint) { 228 updateNewMap := map[string]*endpoint.Endpoint{} 229 for _, e := range changes.UpdateNew { 230 updateNewMap[e.DNSName] = e 231 } 232 233 for _, old := range changes.UpdateOld { 234 current := updateNewMap[old.DNSName] 235 236 if !old.Targets.Same(current.Targets) { 237 // when targets differ the old instances need to be de-registered first 238 deletes = append(deletes, old) 239 } 240 241 // always register (or re-register) instance with the current data 242 creates = append(creates, current) 243 } 244 245 return creates, deletes 246 } 247 248 func (p *AWSSDProvider) submitCreates(namespaces []*sd.NamespaceSummary, changes []*endpoint.Endpoint) error { 249 changesByNamespaceID := p.changesByNamespaceID(namespaces, changes) 250 251 for nsID, changeList := range changesByNamespaceID { 252 services, err := p.ListServicesByNamespaceID(aws.String(nsID)) 253 if err != nil { 254 return err 255 } 256 257 for _, ch := range changeList { 258 _, srvName := p.parseHostname(ch.DNSName) 259 260 srv := services[srvName] 261 if srv == nil { 262 // when service is missing create a new one 263 srv, err = p.CreateService(&nsID, &srvName, ch) 264 if err != nil { 265 return err 266 } 267 // update local list of services 268 services[*srv.Name] = srv 269 } else if ch.RecordTTL.IsConfigured() && *srv.DnsConfig.DnsRecords[0].TTL != int64(ch.RecordTTL) { 270 // update service when TTL differ 271 err = p.UpdateService(srv, ch) 272 if err != nil { 273 return err 274 } 275 } 276 277 err = p.RegisterInstance(srv, ch) 278 if err != nil { 279 return err 280 } 281 } 282 } 283 284 return nil 285 } 286 287 func (p *AWSSDProvider) submitDeletes(namespaces []*sd.NamespaceSummary, changes []*endpoint.Endpoint) error { 288 changesByNamespaceID := p.changesByNamespaceID(namespaces, changes) 289 290 for nsID, changeList := range changesByNamespaceID { 291 services, err := p.ListServicesByNamespaceID(aws.String(nsID)) 292 if err != nil { 293 return err 294 } 295 296 for _, ch := range changeList { 297 hostname := ch.DNSName 298 _, srvName := p.parseHostname(hostname) 299 300 srv := services[srvName] 301 if srv == nil { 302 return fmt.Errorf("service \"%s\" is missing when trying to delete \"%v\"", srvName, hostname) 303 } 304 305 err := p.DeregisterInstance(srv, ch) 306 if err != nil { 307 return err 308 } 309 } 310 } 311 312 return nil 313 } 314 315 // ListNamespaces returns all namespaces matching defined namespace filter 316 func (p *AWSSDProvider) ListNamespaces() ([]*sd.NamespaceSummary, error) { 317 namespaces := make([]*sd.NamespaceSummary, 0) 318 319 f := func(resp *sd.ListNamespacesOutput, lastPage bool) bool { 320 for _, ns := range resp.Namespaces { 321 if !p.namespaceFilter.Match(aws.StringValue(ns.Name)) { 322 continue 323 } 324 namespaces = append(namespaces, ns) 325 } 326 327 return true 328 } 329 330 err := p.client.ListNamespacesPages(&sd.ListNamespacesInput{ 331 Filters: []*sd.NamespaceFilter{p.namespaceTypeFilter}, 332 }, f) 333 if err != nil { 334 return nil, err 335 } 336 337 return namespaces, nil 338 } 339 340 // ListServicesByNamespaceID returns list of services in given namespace. Returns map[srv_name]*sd.Service 341 func (p *AWSSDProvider) ListServicesByNamespaceID(namespaceID *string) (map[string]*sd.Service, error) { 342 services := make([]*sd.ServiceSummary, 0) 343 344 f := func(resp *sd.ListServicesOutput, lastPage bool) bool { 345 services = append(services, resp.Services...) 346 return true 347 } 348 349 err := p.client.ListServicesPages(&sd.ListServicesInput{ 350 Filters: []*sd.ServiceFilter{{ 351 Name: aws.String(sd.ServiceFilterNameNamespaceId), 352 Values: []*string{namespaceID}, 353 }}, 354 MaxResults: aws.Int64(100), 355 }, f) 356 if err != nil { 357 return nil, err 358 } 359 360 servicesMap := make(map[string]*sd.Service) 361 for _, serviceSummary := range services { 362 service := &sd.Service{ 363 Arn: serviceSummary.Arn, 364 CreateDate: serviceSummary.CreateDate, 365 Description: serviceSummary.Description, 366 DnsConfig: serviceSummary.DnsConfig, 367 HealthCheckConfig: serviceSummary.HealthCheckConfig, 368 HealthCheckCustomConfig: serviceSummary.HealthCheckCustomConfig, 369 Id: serviceSummary.Id, 370 InstanceCount: serviceSummary.InstanceCount, 371 Name: serviceSummary.Name, 372 NamespaceId: namespaceID, 373 Type: serviceSummary.Type, 374 } 375 376 servicesMap[aws.StringValue(service.Name)] = service 377 } 378 return servicesMap, nil 379 } 380 381 // CreateService creates a new service in AWS API. Returns the created service. 382 func (p *AWSSDProvider) CreateService(namespaceID *string, srvName *string, ep *endpoint.Endpoint) (*sd.Service, error) { 383 log.Infof("Creating a new service \"%s\" in \"%s\" namespace", *srvName, *namespaceID) 384 385 srvType := p.serviceTypeFromEndpoint(ep) 386 routingPolicy := p.routingPolicyFromEndpoint(ep) 387 388 ttl := int64(sdDefaultRecordTTL) 389 if ep.RecordTTL.IsConfigured() { 390 ttl = int64(ep.RecordTTL) 391 } 392 393 if !p.dryRun { 394 out, err := p.client.CreateService(&sd.CreateServiceInput{ 395 Name: srvName, 396 Description: aws.String(ep.Labels[endpoint.AWSSDDescriptionLabel]), 397 DnsConfig: &sd.DnsConfig{ 398 RoutingPolicy: aws.String(routingPolicy), 399 DnsRecords: []*sd.DnsRecord{{ 400 Type: aws.String(srvType), 401 TTL: aws.Int64(ttl), 402 }}, 403 }, 404 NamespaceId: namespaceID, 405 }) 406 if err != nil { 407 return nil, err 408 } 409 410 return out.Service, nil 411 } 412 413 // return mock service summary in case of dry run 414 return &sd.Service{Id: aws.String("dry-run-service"), Name: aws.String("dry-run-service")}, nil 415 } 416 417 // UpdateService updates the specified service with information from provided endpoint. 418 func (p *AWSSDProvider) UpdateService(service *sd.Service, ep *endpoint.Endpoint) error { 419 log.Infof("Updating service \"%s\"", *service.Name) 420 421 srvType := p.serviceTypeFromEndpoint(ep) 422 423 ttl := int64(sdDefaultRecordTTL) 424 if ep.RecordTTL.IsConfigured() { 425 ttl = int64(ep.RecordTTL) 426 } 427 428 if !p.dryRun { 429 _, err := p.client.UpdateService(&sd.UpdateServiceInput{ 430 Id: service.Id, 431 Service: &sd.ServiceChange{ 432 Description: aws.String(ep.Labels[endpoint.AWSSDDescriptionLabel]), 433 DnsConfig: &sd.DnsConfigChange{ 434 DnsRecords: []*sd.DnsRecord{{ 435 Type: aws.String(srvType), 436 TTL: aws.Int64(ttl), 437 }}, 438 }, 439 }, 440 }) 441 if err != nil { 442 return err 443 } 444 } 445 446 return nil 447 } 448 449 // DeleteService deletes empty Service from AWS API if its owner id match 450 func (p *AWSSDProvider) DeleteService(service *sd.Service) error { 451 log.Debugf("Check if service \"%s\" owner id match and it can be deleted", *service.Name) 452 if !p.dryRun && p.cleanEmptyService { 453 // convert ownerID string to service description format 454 label := endpoint.NewLabels() 455 label[endpoint.OwnerLabelKey] = p.ownerID 456 label[endpoint.AWSSDDescriptionLabel] = label.SerializePlain(false) 457 458 if strings.HasPrefix(aws.StringValue(service.Description), label[endpoint.AWSSDDescriptionLabel]) { 459 log.Infof("Deleting service \"%s\"", *service.Name) 460 _, err := p.client.DeleteService(&sd.DeleteServiceInput{ 461 Id: aws.String(*service.Id), 462 }) 463 return err 464 } 465 log.Debugf("Skipping service removal %s because owner id does not match, found: \"%s\", required: \"%s\"", aws.StringValue(service.Name), aws.StringValue(service.Description), label[endpoint.AWSSDDescriptionLabel]) 466 } 467 return nil 468 } 469 470 // RegisterInstance creates a new instance in given service. 471 func (p *AWSSDProvider) RegisterInstance(service *sd.Service, ep *endpoint.Endpoint) error { 472 for _, target := range ep.Targets { 473 log.Infof("Registering a new instance \"%s\" for service \"%s\" (%s)", target, *service.Name, *service.Id) 474 475 attr := make(map[string]*string) 476 477 if ep.RecordType == endpoint.RecordTypeCNAME { 478 if p.isAWSLoadBalancer(target) { 479 attr[sdInstanceAttrAlias] = aws.String(target) 480 } else { 481 attr[sdInstanceAttrCname] = aws.String(target) 482 } 483 } else if ep.RecordType == endpoint.RecordTypeA { 484 attr[sdInstanceAttrIPV4] = aws.String(target) 485 } else { 486 return fmt.Errorf("invalid endpoint type (%v)", ep) 487 } 488 489 if !p.dryRun { 490 _, err := p.client.RegisterInstance(&sd.RegisterInstanceInput{ 491 ServiceId: service.Id, 492 Attributes: attr, 493 InstanceId: aws.String(p.targetToInstanceID(target)), 494 }) 495 if err != nil { 496 return err 497 } 498 } 499 } 500 501 return nil 502 } 503 504 // DeregisterInstance removes an instance from given service. 505 func (p *AWSSDProvider) DeregisterInstance(service *sd.Service, ep *endpoint.Endpoint) error { 506 for _, target := range ep.Targets { 507 log.Infof("De-registering an instance \"%s\" for service \"%s\" (%s)", target, *service.Name, *service.Id) 508 509 if !p.dryRun { 510 _, err := p.client.DeregisterInstance(&sd.DeregisterInstanceInput{ 511 InstanceId: aws.String(p.targetToInstanceID(target)), 512 ServiceId: service.Id, 513 }) 514 if err != nil { 515 return err 516 } 517 } 518 } 519 520 return nil 521 } 522 523 // Instance ID length is limited by AWS API to 64 characters. For longer strings SHA-256 hash will be used instead of 524 // the verbatim target to limit the length. 525 func (p *AWSSDProvider) targetToInstanceID(target string) string { 526 if len(target) > 64 { 527 hash := sha256.Sum256([]byte(strings.ToLower(target))) 528 return hex.EncodeToString(hash[:]) 529 } 530 531 return strings.ToLower(target) 532 } 533 534 // nolint: deadcode 535 // used from unit test 536 func namespaceToNamespaceSummary(namespace *sd.Namespace) *sd.NamespaceSummary { 537 if namespace == nil { 538 return nil 539 } 540 541 return &sd.NamespaceSummary{ 542 Id: namespace.Id, 543 Type: namespace.Type, 544 Name: namespace.Name, 545 Arn: namespace.Arn, 546 } 547 } 548 549 // nolint: deadcode 550 // used from unit test 551 func serviceToServiceSummary(service *sd.Service) *sd.ServiceSummary { 552 if service == nil { 553 return nil 554 } 555 556 return &sd.ServiceSummary{ 557 Arn: service.Arn, 558 CreateDate: service.CreateDate, 559 Description: service.Description, 560 DnsConfig: service.DnsConfig, 561 HealthCheckConfig: service.HealthCheckConfig, 562 HealthCheckCustomConfig: service.HealthCheckCustomConfig, 563 Id: service.Id, 564 InstanceCount: service.InstanceCount, 565 Name: service.Name, 566 Type: service.Type, 567 } 568 } 569 570 func (p *AWSSDProvider) changesByNamespaceID(namespaces []*sd.NamespaceSummary, changes []*endpoint.Endpoint) map[string][]*endpoint.Endpoint { 571 changesByNsID := make(map[string][]*endpoint.Endpoint) 572 573 for _, ns := range namespaces { 574 changesByNsID[*ns.Id] = []*endpoint.Endpoint{} 575 } 576 577 for _, c := range changes { 578 // trim the trailing dot from hostname if any 579 hostname := strings.TrimSuffix(c.DNSName, ".") 580 nsName, _ := p.parseHostname(hostname) 581 582 matchingNamespaces := matchingNamespaces(nsName, namespaces) 583 if len(matchingNamespaces) == 0 { 584 log.Warnf("Skipping record %s because no namespace matching record DNS Name was detected ", c.String()) 585 continue 586 } 587 for _, ns := range matchingNamespaces { 588 changesByNsID[*ns.Id] = append(changesByNsID[*ns.Id], c) 589 } 590 } 591 592 // separating a change could lead to empty sub changes, remove them here. 593 for zone, change := range changesByNsID { 594 if len(change) == 0 { 595 delete(changesByNsID, zone) 596 } 597 } 598 599 return changesByNsID 600 } 601 602 // returns list of all namespaces matching given hostname 603 func matchingNamespaces(hostname string, namespaces []*sd.NamespaceSummary) []*sd.NamespaceSummary { 604 matchingNamespaces := make([]*sd.NamespaceSummary, 0) 605 606 for _, ns := range namespaces { 607 if *ns.Name == hostname { 608 matchingNamespaces = append(matchingNamespaces, ns) 609 } 610 } 611 612 return matchingNamespaces 613 } 614 615 // parse hostname to namespace (domain) and service 616 func (p *AWSSDProvider) parseHostname(hostname string) (namespace string, service string) { 617 parts := strings.Split(hostname, ".") 618 service = parts[0] 619 namespace = strings.Join(parts[1:], ".") 620 return 621 } 622 623 // determine service routing policy based on endpoint type 624 func (p *AWSSDProvider) routingPolicyFromEndpoint(ep *endpoint.Endpoint) string { 625 if ep.RecordType == endpoint.RecordTypeA { 626 return sd.RoutingPolicyMultivalue 627 } 628 629 return sd.RoutingPolicyWeighted 630 } 631 632 // determine service type (A, CNAME) from given endpoint 633 func (p *AWSSDProvider) serviceTypeFromEndpoint(ep *endpoint.Endpoint) string { 634 if ep.RecordType == endpoint.RecordTypeCNAME { 635 // FIXME service type is derived from the first target only. Theoretically this may be problem. 636 // But I don't see a scenario where one endpoint contains targets of different types. 637 if p.isAWSLoadBalancer(ep.Targets[0]) { 638 // ALIAS target uses DNS record type of A 639 return sd.RecordTypeA 640 } 641 return sd.RecordTypeCname 642 } 643 return sd.RecordTypeA 644 } 645 646 // determine if a given hostname belongs to an AWS load balancer 647 func (p *AWSSDProvider) isAWSLoadBalancer(hostname string) bool { 648 matchElb := sdElbHostnameRegex.MatchString(hostname) 649 matchNlb := sdNlbHostnameRegex.MatchString(hostname) 650 651 return matchElb || matchNlb 652 }