sigs.k8s.io/external-dns@v0.14.1/provider/infoblox/infoblox.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 infoblox 18 19 import ( 20 "context" 21 "fmt" 22 "net" 23 "net/http" 24 "os" 25 "sort" 26 "strconv" 27 "strings" 28 29 ibclient "github.com/infobloxopen/infoblox-go-client/v2" 30 "github.com/sirupsen/logrus" 31 32 "sigs.k8s.io/external-dns/endpoint" 33 "sigs.k8s.io/external-dns/pkg/rfc2317" 34 "sigs.k8s.io/external-dns/plan" 35 "sigs.k8s.io/external-dns/provider" 36 ) 37 38 const ( 39 // provider specific key to track if PTR record was already created or not for A records 40 providerSpecificInfobloxPtrRecord = "infoblox-ptr-record-exists" 41 ) 42 43 func isNotFoundError(err error) bool { 44 _, ok := err.(*ibclient.NotFoundError) 45 return ok 46 } 47 48 // StartupConfig clarifies the method signature 49 type StartupConfig struct { 50 DomainFilter endpoint.DomainFilter 51 ZoneIDFilter provider.ZoneIDFilter 52 Host string 53 Port int 54 Username string 55 Password string 56 Version string 57 SSLVerify bool 58 DryRun bool 59 View string 60 MaxResults int 61 FQDNRegEx string 62 NameRegEx string 63 CreatePTR bool 64 CacheDuration int 65 } 66 67 // ProviderConfig implements the DNS provider for Infoblox. 68 type ProviderConfig struct { 69 provider.BaseProvider 70 client ibclient.IBConnector 71 domainFilter endpoint.DomainFilter 72 zoneIDFilter provider.ZoneIDFilter 73 view string 74 dryRun bool 75 fqdnRegEx string 76 createPTR bool 77 cacheDuration int 78 } 79 80 type infobloxRecordSet struct { 81 obj ibclient.IBObject 82 res interface{} 83 } 84 85 // ExtendedRequestBuilder implements a HttpRequestBuilder which sets 86 // additional query parameter on all get requests 87 type ExtendedRequestBuilder struct { 88 fqdnRegEx string 89 nameRegEx string 90 maxResults int 91 ibclient.WapiRequestBuilder 92 } 93 94 // NewExtendedRequestBuilder returns a ExtendedRequestBuilder which adds 95 // _max_results query parameter to all GET requests 96 func NewExtendedRequestBuilder(maxResults int, fqdnRegEx string, nameRegEx string) *ExtendedRequestBuilder { 97 return &ExtendedRequestBuilder{ 98 fqdnRegEx: fqdnRegEx, 99 nameRegEx: nameRegEx, 100 maxResults: maxResults, 101 } 102 } 103 104 // BuildRequest prepares the api request. it uses BuildRequest of 105 // WapiRequestBuilder and then add the _max_requests parameter 106 func (mrb *ExtendedRequestBuilder) BuildRequest(t ibclient.RequestType, obj ibclient.IBObject, ref string, queryParams *ibclient.QueryParams) (req *http.Request, err error) { 107 req, err = mrb.WapiRequestBuilder.BuildRequest(t, obj, ref, queryParams) 108 if req.Method == "GET" { 109 query := req.URL.Query() 110 if mrb.maxResults > 0 { 111 query.Set("_max_results", strconv.Itoa(mrb.maxResults)) 112 } 113 _, zoneAuthQuery := obj.(*ibclient.ZoneAuth) 114 if zoneAuthQuery && t == ibclient.GET && mrb.fqdnRegEx != "" { 115 query.Set("fqdn~", mrb.fqdnRegEx) 116 } 117 118 // if we are not doing a ZoneAuth query, support the name filter 119 if !zoneAuthQuery && mrb.nameRegEx != "" { 120 query.Set("name~", mrb.nameRegEx) 121 } 122 123 req.URL.RawQuery = query.Encode() 124 } 125 return 126 } 127 128 // NewInfobloxProvider creates a new Infoblox provider. 129 func NewInfobloxProvider(ibStartupCfg StartupConfig) (*ProviderConfig, error) { 130 hostCfg := ibclient.HostConfig{ 131 Host: ibStartupCfg.Host, 132 Port: strconv.Itoa(ibStartupCfg.Port), 133 Version: ibStartupCfg.Version, 134 } 135 136 authCfg := ibclient.AuthConfig{ 137 Username: ibStartupCfg.Username, 138 Password: ibStartupCfg.Password, 139 } 140 141 httpPoolConnections := lookupEnvAtoi("EXTERNAL_DNS_INFOBLOX_HTTP_POOL_CONNECTIONS", 10) 142 httpRequestTimeout := lookupEnvAtoi("EXTERNAL_DNS_INFOBLOX_HTTP_REQUEST_TIMEOUT", 60) 143 144 transportConfig := ibclient.NewTransportConfig( 145 strconv.FormatBool(ibStartupCfg.SSLVerify), 146 httpRequestTimeout, 147 httpPoolConnections, 148 ) 149 150 var ( 151 requestBuilder ibclient.HttpRequestBuilder 152 err error 153 ) 154 if ibStartupCfg.MaxResults != 0 || ibStartupCfg.FQDNRegEx != "" || ibStartupCfg.NameRegEx != "" { 155 // use our own HttpRequestBuilder which sets _max_results parameter on GET requests 156 requestBuilder = NewExtendedRequestBuilder(ibStartupCfg.MaxResults, ibStartupCfg.FQDNRegEx, ibStartupCfg.NameRegEx) 157 } else { 158 // use the default HttpRequestBuilder of the infoblox client 159 requestBuilder, err = ibclient.NewWapiRequestBuilder(hostCfg, authCfg) 160 if err != nil { 161 return nil, err 162 } 163 } 164 165 requestor := &ibclient.WapiHttpRequestor{} 166 167 client, err := ibclient.NewConnector(hostCfg, authCfg, transportConfig, requestBuilder, requestor) 168 if err != nil { 169 return nil, err 170 } 171 172 providerCfg := &ProviderConfig{ 173 client: client, 174 domainFilter: ibStartupCfg.DomainFilter, 175 zoneIDFilter: ibStartupCfg.ZoneIDFilter, 176 dryRun: ibStartupCfg.DryRun, 177 view: ibStartupCfg.View, 178 fqdnRegEx: ibStartupCfg.FQDNRegEx, 179 createPTR: ibStartupCfg.CreatePTR, 180 cacheDuration: ibStartupCfg.CacheDuration, 181 } 182 183 return providerCfg, nil 184 } 185 186 func recordQueryParams(zone string, view string) *ibclient.QueryParams { 187 searchFields := map[string]string{} 188 if zone != "" { 189 searchFields["zone"] = zone 190 } 191 192 if view != "" { 193 searchFields["view"] = view 194 } 195 return ibclient.NewQueryParams(false, searchFields) 196 } 197 198 // Records gets the current records. 199 func (p *ProviderConfig) Records(ctx context.Context) (endpoints []*endpoint.Endpoint, err error) { 200 zones, err := p.zones() 201 if err != nil { 202 return nil, fmt.Errorf("could not fetch zones: %w", err) 203 } 204 205 for _, zone := range zones { 206 logrus.Debugf("fetch records from zone '%s'", zone.Fqdn) 207 searchParams := recordQueryParams(zone.Fqdn, p.view) 208 var resA []ibclient.RecordA 209 objA := ibclient.NewEmptyRecordA() 210 err = p.client.GetObject(objA, "", searchParams, &resA) 211 if err != nil && !isNotFoundError(err) { 212 return nil, fmt.Errorf("could not fetch A records from zone '%s': %w", zone.Fqdn, err) 213 } 214 for _, res := range resA { 215 // Check if endpoint already exists and add to existing endpoint if it does 216 foundExisting := false 217 for _, ep := range endpoints { 218 if ep.DNSName == *res.Name && ep.RecordType == endpoint.RecordTypeA { 219 foundExisting = true 220 duplicateTarget := false 221 222 for _, t := range ep.Targets { 223 if t == *res.Ipv4Addr { 224 duplicateTarget = true 225 break 226 } 227 } 228 229 if duplicateTarget { 230 logrus.Debugf("A duplicate target '%s' found for existing A record '%s'", *res.Ipv4Addr, ep.DNSName) 231 } else { 232 logrus.Debugf("Adding target '%s' to existing A record '%s'", *res.Ipv4Addr, *res.Name) 233 ep.Targets = append(ep.Targets, *res.Ipv4Addr) 234 } 235 break 236 } 237 } 238 if !foundExisting { 239 newEndpoint := endpoint.NewEndpoint(*res.Name, endpoint.RecordTypeA, *res.Ipv4Addr) 240 if p.createPTR { 241 newEndpoint.WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true") 242 } 243 endpoints = append(endpoints, newEndpoint) 244 } 245 } 246 // sort targets so that they are always in same order, as infoblox might return them in different order 247 for _, ep := range endpoints { 248 sort.Sort(ep.Targets) 249 } 250 251 // Include Host records since they should be treated synonymously with A records 252 var resH []ibclient.HostRecord 253 objH := ibclient.NewEmptyHostRecord() 254 err = p.client.GetObject(objH, "", searchParams, &resH) 255 if err != nil && !isNotFoundError(err) { 256 return nil, fmt.Errorf("could not fetch host records from zone '%s': %w", zone.Fqdn, err) 257 } 258 for _, res := range resH { 259 for _, ip := range res.Ipv4Addrs { 260 logrus.Debugf("Record='%s' A(H):'%s'", *res.Name, *ip.Ipv4Addr) 261 262 // host record is an abstraction in infoblox that combines A and PTR records 263 // for any host record we already should have a PTR record in infoblox, so mark it as created 264 newEndpoint := endpoint.NewEndpoint(*res.Name, endpoint.RecordTypeA, *ip.Ipv4Addr) 265 if p.createPTR { 266 newEndpoint.WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true") 267 } 268 endpoints = append(endpoints, newEndpoint) 269 } 270 } 271 272 var resC []ibclient.RecordCNAME 273 objC := ibclient.NewEmptyRecordCNAME() 274 err = p.client.GetObject(objC, "", searchParams, &resC) 275 if err != nil && !isNotFoundError(err) { 276 return nil, fmt.Errorf("could not fetch CNAME records from zone '%s': %w", zone.Fqdn, err) 277 } 278 for _, res := range resC { 279 logrus.Debugf("Record='%s' CNAME:'%s'", *res.Name, *res.Canonical) 280 endpoints = append(endpoints, endpoint.NewEndpoint(*res.Name, endpoint.RecordTypeCNAME, *res.Canonical)) 281 } 282 283 if p.createPTR { 284 // infoblox doesn't accept reverse zone's fqdn, and instead expects .in-addr.arpa zone 285 // so convert our zone fqdn (if it is a correct cidr block) into in-addr.arpa address and pass that into infoblox 286 // example: 10.196.38.0/24 becomes 38.196.10.in-addr.arpa 287 arpaZone, err := rfc2317.CidrToInAddr(zone.Fqdn) 288 if err == nil { 289 var resP []ibclient.RecordPTR 290 objP := ibclient.NewEmptyRecordPTR() 291 err = p.client.GetObject(objP, "", recordQueryParams(arpaZone, p.view), &resP) 292 if err != nil && !isNotFoundError(err) { 293 return nil, fmt.Errorf("could not fetch PTR records from zone '%s': %w", zone.Fqdn, err) 294 } 295 for _, res := range resP { 296 endpoints = append(endpoints, endpoint.NewEndpoint(*res.PtrdName, endpoint.RecordTypePTR, *res.Ipv4Addr)) 297 } 298 } 299 } 300 301 var resT []ibclient.RecordTXT 302 objT := ibclient.NewEmptyRecordTXT() 303 err = p.client.GetObject(objT, "", searchParams, &resT) 304 if err != nil && !isNotFoundError(err) { 305 return nil, fmt.Errorf("could not fetch TXT records from zone '%s': %w", zone.Fqdn, err) 306 } 307 for _, res := range resT { 308 // The Infoblox API strips enclosing double quotes from TXT records lacking whitespace. 309 // Unhandled, the missing double quotes would break the extractOwnerID method of the registry package. 310 if _, err := strconv.Unquote(*res.Text); err != nil { 311 quoted := strconv.Quote(*res.Text) 312 res.Text = "ed 313 } 314 315 foundExisting := false 316 317 for _, ep := range endpoints { 318 if ep.DNSName == *res.Name && ep.RecordType == endpoint.RecordTypeTXT { 319 foundExisting = true 320 duplicateTarget := false 321 322 for _, t := range ep.Targets { 323 if t == *res.Text { 324 duplicateTarget = true 325 break 326 } 327 } 328 329 if duplicateTarget { 330 logrus.Debugf("A duplicate target '%s' found for existing TXT record '%s'", *res.Text, ep.DNSName) 331 } else { 332 logrus.Debugf("Adding target '%s' to existing TXT record '%s'", *res.Text, *res.Name) 333 ep.Targets = append(ep.Targets, *res.Text) 334 } 335 break 336 } 337 } 338 if !foundExisting { 339 logrus.Debugf("Record='%s' TXT:'%s'", *res.Name, *res.Text) 340 newEndpoint := endpoint.NewEndpoint(*res.Name, endpoint.RecordTypeTXT, *res.Text) 341 endpoints = append(endpoints, newEndpoint) 342 } 343 } 344 } 345 346 // update A records that have PTR record created for them already 347 if p.createPTR { 348 // save all ptr records into map for a quick look up 349 ptrRecordsMap := make(map[string]bool) 350 for _, ptrRecord := range endpoints { 351 if ptrRecord.RecordType != endpoint.RecordTypePTR { 352 continue 353 } 354 ptrRecordsMap[ptrRecord.DNSName] = true 355 } 356 357 for i := range endpoints { 358 if endpoints[i].RecordType != endpoint.RecordTypeA { 359 continue 360 } 361 // if PTR record already exists for A record, then mark it as such 362 if ptrRecordsMap[endpoints[i].DNSName] { 363 found := false 364 for j := range endpoints[i].ProviderSpecific { 365 if endpoints[i].ProviderSpecific[j].Name == providerSpecificInfobloxPtrRecord { 366 endpoints[i].ProviderSpecific[j].Value = "true" 367 found = true 368 } 369 } 370 if !found { 371 endpoints[i].WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true") 372 } 373 } 374 } 375 } 376 logrus.Debugf("fetched %d records from infoblox", len(endpoints)) 377 return endpoints, nil 378 } 379 380 func (p *ProviderConfig) AdjustEndpoints(endpoints []*endpoint.Endpoint) ([]*endpoint.Endpoint, error) { 381 // Update user specified TTL (0 == disabled) 382 for i := range endpoints { 383 endpoints[i].RecordTTL = endpoint.TTL(p.cacheDuration) 384 } 385 386 if !p.createPTR { 387 return endpoints, nil 388 } 389 390 // for all A records, we want to create PTR records 391 // so add provider specific property to track if the record was created or not 392 for i := range endpoints { 393 if endpoints[i].RecordType == endpoint.RecordTypeA { 394 found := false 395 for j := range endpoints[i].ProviderSpecific { 396 if endpoints[i].ProviderSpecific[j].Name == providerSpecificInfobloxPtrRecord { 397 endpoints[i].ProviderSpecific[j].Value = "true" 398 found = true 399 } 400 } 401 if !found { 402 endpoints[i].WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true") 403 } 404 } 405 } 406 407 return endpoints, nil 408 } 409 410 // ApplyChanges applies the given changes. 411 func (p *ProviderConfig) ApplyChanges(ctx context.Context, changes *plan.Changes) error { 412 zones, err := p.zones() 413 if err != nil { 414 return err 415 } 416 417 created, deleted := p.mapChanges(zones, changes) 418 p.deleteRecords(deleted) 419 p.createRecords(created) 420 return nil 421 } 422 423 func (p *ProviderConfig) zones() ([]ibclient.ZoneAuth, error) { 424 var res, result []ibclient.ZoneAuth 425 obj := ibclient.NewZoneAuth(ibclient.ZoneAuth{}) 426 queryParams := recordQueryParams("", p.view) 427 err := p.client.GetObject(obj, "", queryParams, &res) 428 if err != nil && !isNotFoundError(err) { 429 return nil, err 430 } 431 432 for _, zone := range res { 433 if !p.domainFilter.Match(zone.Fqdn) { 434 continue 435 } 436 437 if !p.zoneIDFilter.Match(zone.Ref) { 438 continue 439 } 440 441 result = append(result, zone) 442 } 443 444 return result, nil 445 } 446 447 type infobloxChangeMap map[string][]*endpoint.Endpoint 448 449 func (p *ProviderConfig) mapChanges(zones []ibclient.ZoneAuth, changes *plan.Changes) (infobloxChangeMap, infobloxChangeMap) { 450 created := infobloxChangeMap{} 451 deleted := infobloxChangeMap{} 452 453 mapChange := func(changeMap infobloxChangeMap, change *endpoint.Endpoint) { 454 zone := p.findZone(zones, change.DNSName) 455 if zone == nil { 456 logrus.Debugf("Ignoring changes to '%s' because a suitable Infoblox DNS zone was not found.", change.DNSName) 457 return 458 } 459 // Ensure the record type is suitable 460 changeMap[zone.Fqdn] = append(changeMap[zone.Fqdn], change) 461 462 if p.createPTR && change.RecordType == endpoint.RecordTypeA { 463 reverseZone := p.findReverseZone(zones, change.Targets[0]) 464 if reverseZone == nil { 465 logrus.Debugf("Ignoring changes to '%s' because a suitable Infoblox DNS reverse zone was not found.", change.Targets[0]) 466 return 467 } 468 changecopy := *change 469 changecopy.RecordType = endpoint.RecordTypePTR 470 changeMap[reverseZone.Fqdn] = append(changeMap[reverseZone.Fqdn], &changecopy) 471 } 472 } 473 474 for _, change := range changes.Delete { 475 mapChange(deleted, change) 476 } 477 for _, change := range changes.UpdateOld { 478 mapChange(deleted, change) 479 } 480 for _, change := range changes.Create { 481 mapChange(created, change) 482 } 483 for _, change := range changes.UpdateNew { 484 mapChange(created, change) 485 } 486 487 return created, deleted 488 } 489 490 func (p *ProviderConfig) findZone(zones []ibclient.ZoneAuth, name string) *ibclient.ZoneAuth { 491 var result *ibclient.ZoneAuth 492 493 // Go through every zone looking for the longest name (i.e. most specific) as a matching suffix 494 for idx := range zones { 495 zone := &zones[idx] 496 if strings.HasSuffix(name, "."+zone.Fqdn) { 497 if result == nil || len(zone.Fqdn) > len(result.Fqdn) { 498 result = zone 499 } 500 } else if strings.EqualFold(name, zone.Fqdn) { 501 if result == nil || len(zone.Fqdn) > len(result.Fqdn) { 502 result = zone 503 } 504 } 505 } 506 return result 507 } 508 509 func (p *ProviderConfig) findReverseZone(zones []ibclient.ZoneAuth, name string) *ibclient.ZoneAuth { 510 ip := net.ParseIP(name) 511 networks := map[int]*ibclient.ZoneAuth{} 512 maxMask := 0 513 514 for i, zone := range zones { 515 _, rZoneNet, err := net.ParseCIDR(zone.Fqdn) 516 if err != nil { 517 logrus.WithError(err).Debugf("fqdn %s is no cidr", zone.Fqdn) 518 } else { 519 if rZoneNet.Contains(ip) { 520 _, mask := rZoneNet.Mask.Size() 521 networks[mask] = &zones[i] 522 if mask > maxMask { 523 maxMask = mask 524 } 525 } 526 } 527 } 528 return networks[maxMask] 529 } 530 531 func (p *ProviderConfig) recordSet(ep *endpoint.Endpoint, getObject bool, targetIndex int) (recordSet infobloxRecordSet, err error) { 532 switch ep.RecordType { 533 case endpoint.RecordTypeA: 534 var res []ibclient.RecordA 535 obj := ibclient.NewEmptyRecordA() 536 obj.Name = &ep.DNSName 537 obj.Ipv4Addr = &ep.Targets[targetIndex] 538 obj.View = p.view 539 if getObject { 540 queryParams := ibclient.NewQueryParams(false, map[string]string{"name": *obj.Name}) 541 err = p.client.GetObject(obj, "", queryParams, &res) 542 if err != nil && !isNotFoundError(err) { 543 return 544 } 545 } 546 recordSet = infobloxRecordSet{ 547 obj: obj, 548 res: &res, 549 } 550 case endpoint.RecordTypePTR: 551 var res []ibclient.RecordPTR 552 obj := ibclient.NewEmptyRecordPTR() 553 obj.PtrdName = &ep.DNSName 554 obj.Ipv4Addr = &ep.Targets[targetIndex] 555 obj.View = p.view 556 if getObject { 557 queryParams := ibclient.NewQueryParams(false, map[string]string{"name": *obj.PtrdName}) 558 err = p.client.GetObject(obj, "", queryParams, &res) 559 if err != nil && !isNotFoundError(err) { 560 return 561 } 562 } 563 recordSet = infobloxRecordSet{ 564 obj: obj, 565 res: &res, 566 } 567 case endpoint.RecordTypeCNAME: 568 var res []ibclient.RecordCNAME 569 obj := ibclient.NewEmptyRecordCNAME() 570 obj.Name = &ep.DNSName 571 obj.Canonical = &ep.Targets[0] 572 obj.View = &p.view 573 if getObject { 574 queryParams := ibclient.NewQueryParams(false, map[string]string{"name": *obj.Name}) 575 err = p.client.GetObject(obj, "", queryParams, &res) 576 if err != nil && !isNotFoundError(err) { 577 return 578 } 579 } 580 recordSet = infobloxRecordSet{ 581 obj: obj, 582 res: &res, 583 } 584 case endpoint.RecordTypeTXT: 585 var res []ibclient.RecordTXT 586 // The Infoblox API strips enclosing double quotes from TXT records lacking whitespace. 587 // Here we reconcile that fact by making this state match that reality. 588 if target, err2 := strconv.Unquote(ep.Targets[0]); err2 == nil && !strings.Contains(ep.Targets[0], " ") { 589 ep.Targets = endpoint.Targets{target} 590 } 591 obj := ibclient.NewEmptyRecordTXT() 592 obj.Name = &ep.DNSName 593 obj.Text = &ep.Targets[0] 594 obj.View = &p.view 595 if getObject { 596 queryParams := ibclient.NewQueryParams(false, map[string]string{"name": *obj.Name}) 597 err = p.client.GetObject(obj, "", queryParams, &res) 598 if err != nil && !isNotFoundError(err) { 599 return 600 } 601 } 602 recordSet = infobloxRecordSet{ 603 obj: obj, 604 res: &res, 605 } 606 } 607 return 608 } 609 610 func (p *ProviderConfig) createRecords(created infobloxChangeMap) { 611 for zone, endpoints := range created { 612 for _, ep := range endpoints { 613 for targetIndex := range ep.Targets { 614 if p.dryRun { 615 logrus.Infof( 616 617 "Would create %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", 618 ep.RecordType, 619 ep.DNSName, 620 ep.Targets[targetIndex], 621 zone, 622 ) 623 continue 624 } 625 626 logrus.Infof( 627 "Creating %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", 628 ep.RecordType, 629 ep.DNSName, 630 ep.Targets[targetIndex], 631 zone, 632 ) 633 634 recordSet, err := p.recordSet(ep, false, targetIndex) 635 if err != nil && !isNotFoundError(err) { 636 logrus.Errorf( 637 "Failed to retrieve %s record named '%s' to '%s' for DNS zone '%s': %v", 638 ep.RecordType, 639 ep.DNSName, 640 ep.Targets[targetIndex], 641 zone, 642 err, 643 ) 644 continue 645 } 646 _, err = p.client.CreateObject(recordSet.obj) 647 if err != nil { 648 logrus.Errorf( 649 "Failed to create %s record named '%s' to '%s' for DNS zone '%s': %v", 650 ep.RecordType, 651 ep.DNSName, 652 ep.Targets[targetIndex], 653 zone, 654 err, 655 ) 656 } 657 } 658 } 659 } 660 } 661 662 func (p *ProviderConfig) deleteRecords(deleted infobloxChangeMap) { 663 // Delete records first 664 for zone, endpoints := range deleted { 665 for _, ep := range endpoints { 666 for targetIndex := range ep.Targets { 667 recordSet, err := p.recordSet(ep, true, targetIndex) 668 if err != nil && !isNotFoundError(err) { 669 logrus.Errorf( 670 "Failed to retrieve %s record named '%s' to '%s' for DNS zone '%s': %v", 671 ep.RecordType, 672 ep.DNSName, 673 ep.Targets[targetIndex], 674 zone, 675 err, 676 ) 677 continue 678 } 679 switch ep.RecordType { 680 case endpoint.RecordTypeA: 681 for _, record := range *recordSet.res.(*[]ibclient.RecordA) { 682 if p.dryRun { 683 logrus.Infof("Would delete %s record named '%p' to '%p' for Infoblox DNS zone '%s'.", "A", record.Name, record.Ipv4Addr, record.Zone) 684 } else { 685 logrus.Infof("Deleting %s record named '%p' to '%p' for Infoblox DNS zone '%s'.", "A", record.Name, record.Ipv4Addr, record.Zone) 686 _, err = p.client.DeleteObject(record.Ref) 687 } 688 } 689 case endpoint.RecordTypePTR: 690 for _, record := range *recordSet.res.(*[]ibclient.RecordPTR) { 691 if p.dryRun { 692 logrus.Infof("Would delete %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "PTR", *record.PtrdName, *record.Ipv4Addr, record.Zone) 693 } else { 694 logrus.Infof("Deleting %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "PTR", *record.PtrdName, *record.Ipv4Addr, record.Zone) 695 _, err = p.client.DeleteObject(record.Ref) 696 } 697 } 698 case endpoint.RecordTypeCNAME: 699 for _, record := range *recordSet.res.(*[]ibclient.RecordCNAME) { 700 if p.dryRun { 701 logrus.Infof("Would delete %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "CNAME", *record.Name, *record.Canonical, record.Zone) 702 } else { 703 logrus.Infof("Deleting %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "CNAME", *record.Name, *record.Canonical, record.Zone) 704 _, err = p.client.DeleteObject(record.Ref) 705 } 706 } 707 case endpoint.RecordTypeTXT: 708 for _, record := range *recordSet.res.(*[]ibclient.RecordTXT) { 709 if p.dryRun { 710 logrus.Infof("Would delete %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "TXT", *record.Name, *record.Text, record.Zone) 711 } else { 712 logrus.Infof("Deleting %s record named '%s' to '%s' for Infoblox DNS zone '%s'.", "TXT", *record.Name, *record.Text, record.Zone) 713 _, err = p.client.DeleteObject(record.Ref) 714 } 715 } 716 } 717 if err != nil && !isNotFoundError(err) { 718 logrus.Errorf( 719 "Failed to delete %s record named '%s' to '%s' for Infoblox DNS zone '%s': %v", 720 ep.RecordType, 721 ep.DNSName, 722 ep.Targets[targetIndex], 723 zone, 724 err, 725 ) 726 } 727 } 728 } 729 } 730 } 731 732 func lookupEnvAtoi(key string, fallback int) (i int) { 733 val, ok := os.LookupEnv(key) 734 if !ok { 735 i = fallback 736 return 737 } 738 i, err := strconv.Atoi(val) 739 if err != nil { 740 i = fallback 741 return 742 } 743 return 744 }