sigs.k8s.io/external-dns@v0.14.1/provider/ultradns/ultradns.go (about) 1 /* 2 Copyright 2020 The Kubernetes Authors. 3 Licensed under the Apache License, Version 2.0 (the "License"); 4 you may not use this file except in compliance with the License. 5 You may obtain a copy of the License at 6 http://www.apache.org/licenses/LICENSE-2.0 7 Unless required by applicable law or agreed to in writing, software 8 distributed under the License is distributed on an "AS IS" BASIS, 9 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10 See the License for the specific language governing permissions and 11 limitations under the License. 12 */ 13 14 package ultradns 15 16 import ( 17 "context" 18 "encoding/base64" 19 "fmt" 20 "os" 21 "strconv" 22 "strings" 23 "time" 24 25 log "github.com/sirupsen/logrus" 26 udnssdk "github.com/ultradns/ultradns-sdk-go" 27 28 "sigs.k8s.io/external-dns/endpoint" 29 "sigs.k8s.io/external-dns/plan" 30 "sigs.k8s.io/external-dns/provider" 31 ) 32 33 const ( 34 ultradnsCreate = "CREATE" 35 ultradnsDelete = "DELETE" 36 ultradnsUpdate = "UPDATE" 37 sbPoolPriority = 1 38 sbPoolOrder = "ROUND_ROBIN" 39 rdPoolOrder = "ROUND_ROBIN" 40 ) 41 42 // global variables 43 var sbPoolRunProbes = true 44 45 var ( 46 sbPoolActOnProbes = true 47 ultradnsPoolType = "rdpool" 48 accountName string 49 ) 50 51 // Setting custom headers for ultradns api calls 52 var customHeader = []udnssdk.CustomHeader{ 53 { 54 Key: "UltraClient", 55 Value: "kube-client", 56 }, 57 } 58 59 // UltraDNSProvider struct 60 type UltraDNSProvider struct { 61 provider.BaseProvider 62 client udnssdk.Client 63 domainFilter endpoint.DomainFilter 64 dryRun bool 65 } 66 67 // UltraDNSChanges struct 68 type UltraDNSChanges struct { 69 Action string 70 ResourceRecordSetUltraDNS udnssdk.RRSet 71 } 72 73 // NewUltraDNSProvider initializes a new UltraDNS DNS based provider 74 func NewUltraDNSProvider(domainFilter endpoint.DomainFilter, dryRun bool) (*UltraDNSProvider, error) { 75 username, ok := os.LookupEnv("ULTRADNS_USERNAME") 76 udnssdk.SetCustomHeader = customHeader 77 if !ok { 78 return nil, fmt.Errorf("no username found") 79 } 80 81 base64password, ok := os.LookupEnv("ULTRADNS_PASSWORD") 82 if !ok { 83 return nil, fmt.Errorf("no password found") 84 } 85 86 // Base64 Standard Decoding 87 password, err := base64.StdEncoding.DecodeString(base64password) 88 if err != nil { 89 fmt.Printf("Error decoding string: %s ", err.Error()) 90 return nil, err 91 } 92 93 baseURL, ok := os.LookupEnv("ULTRADNS_BASEURL") 94 if !ok { 95 return nil, fmt.Errorf("no baseurl found") 96 } 97 accountName, ok = os.LookupEnv("ULTRADNS_ACCOUNTNAME") 98 if !ok { 99 accountName = "" 100 } 101 102 probeValue, ok := os.LookupEnv("ULTRADNS_ENABLE_PROBING") 103 if ok { 104 if (probeValue != "true") && (probeValue != "false") { 105 return nil, fmt.Errorf("please set proper probe value, the values can be either true or false") 106 } 107 sbPoolRunProbes, _ = strconv.ParseBool(probeValue) 108 } 109 110 actOnProbeValue, ok := os.LookupEnv("ULTRADNS_ENABLE_ACTONPROBE") 111 if ok { 112 if (actOnProbeValue != "true") && (actOnProbeValue != "false") { 113 return nil, fmt.Errorf("please set proper act on probe value, the values can be either true or false") 114 } 115 sbPoolActOnProbes, _ = strconv.ParseBool(actOnProbeValue) 116 } 117 118 poolValue, ok := os.LookupEnv("ULTRADNS_POOL_TYPE") 119 if ok { 120 if (poolValue != "sbpool") && (poolValue != "rdpool") { 121 return nil, fmt.Errorf(" please set proper ULTRADNS_POOL_TYPE, supported types are sbpool or rdpool") 122 } 123 ultradnsPoolType = poolValue 124 } 125 126 client, err := udnssdk.NewClient(username, string(password), baseURL) 127 if err != nil { 128 return nil, fmt.Errorf("connection cannot be established") 129 } 130 131 provider := &UltraDNSProvider{ 132 client: *client, 133 domainFilter: domainFilter, 134 dryRun: dryRun, 135 } 136 137 return provider, nil 138 } 139 140 // Zones returns list of hosted zones 141 func (p *UltraDNSProvider) Zones(ctx context.Context) ([]udnssdk.Zone, error) { 142 zoneKey := &udnssdk.ZoneKey{} 143 var err error 144 145 if p.domainFilter.IsConfigured() { 146 zonesAppender := []udnssdk.Zone{} 147 for _, zone := range p.domainFilter.Filters { 148 zoneKey.Zone = zone 149 zoneKey.AccountName = accountName 150 zones, err := p.fetchZones(ctx, zoneKey) 151 if err != nil { 152 return nil, err 153 } 154 155 zonesAppender = append(zonesAppender, zones...) 156 } 157 return zonesAppender, nil 158 } 159 zoneKey.AccountName = accountName 160 zones, err := p.fetchZones(ctx, zoneKey) 161 if err != nil { 162 return nil, err 163 } 164 165 return zones, nil 166 } 167 168 func (p *UltraDNSProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, error) { 169 var endpoints []*endpoint.Endpoint 170 171 zones, err := p.Zones(ctx) 172 if err != nil { 173 return nil, err 174 } 175 176 for _, zone := range zones { 177 log.Infof("zones : %v", zone) 178 var rrsetType string 179 var ownerName string 180 rrsetKey := udnssdk.RRSetKey{ 181 Zone: zone.Properties.Name, 182 Type: rrsetType, 183 Name: ownerName, 184 } 185 186 if zone.Properties.ResourceRecordCount != 0 { 187 records, err := p.fetchRecords(ctx, rrsetKey) 188 if err != nil { 189 return nil, err 190 } 191 192 for _, r := range records { 193 recordTypeArray := strings.Fields(r.RRType) 194 if provider.SupportedRecordType(recordTypeArray[0]) { 195 log.Infof("owner name %s", r.OwnerName) 196 name := r.OwnerName 197 198 // root name is identified by the empty string and should be 199 // translated to zone name for the endpoint entry. 200 if r.OwnerName == "" { 201 name = zone.Properties.Name 202 } 203 204 endPointTTL := endpoint.NewEndpointWithTTL(name, recordTypeArray[0], endpoint.TTL(r.TTL), r.RData...) 205 endpoints = append(endpoints, endPointTTL) 206 } 207 } 208 } 209 } 210 log.Infof("endpoints %v", endpoints) 211 return endpoints, nil 212 } 213 214 func (p *UltraDNSProvider) fetchRecords(ctx context.Context, k udnssdk.RRSetKey) ([]udnssdk.RRSet, error) { 215 // Logic to paginate through all available results 216 maxerrs := 5 217 waittime := 5 * time.Second 218 219 rrsets := []udnssdk.RRSet{} 220 errcnt := 0 221 offset := 0 222 limit := 1000 223 224 for { 225 reqRrsets, ri, res, err := p.client.RRSets.SelectWithOffsetWithLimit(k, offset, limit) 226 if err != nil { 227 if res != nil && res.StatusCode >= 500 { 228 errcnt = errcnt + 1 229 if errcnt < maxerrs { 230 time.Sleep(waittime) 231 continue 232 } 233 } 234 return rrsets, err 235 } 236 rrsets = append(rrsets, reqRrsets...) 237 238 if ri.ReturnedCount+ri.Offset >= ri.TotalCount { 239 return rrsets, nil 240 } 241 offset = ri.ReturnedCount + ri.Offset 242 continue 243 } 244 } 245 246 func (p *UltraDNSProvider) fetchZones(ctx context.Context, zoneKey *udnssdk.ZoneKey) ([]udnssdk.Zone, error) { 247 // Logic to paginate through all available results 248 offset := 0 249 limit := 1000 250 maxerrs := 5 251 waittime := 5 * time.Second 252 253 zones := []udnssdk.Zone{} 254 255 errcnt := 0 256 257 for { 258 reqZones, ri, res, err := p.client.Zone.SelectWithOffsetWithLimit(zoneKey, offset, limit) 259 if err != nil { 260 if res != nil && res.StatusCode >= 500 { 261 errcnt = errcnt + 1 262 if errcnt < maxerrs { 263 time.Sleep(waittime) 264 continue 265 } 266 } 267 return zones, err 268 } 269 270 zones = append(zones, reqZones...) 271 if ri.ReturnedCount+ri.Offset >= ri.TotalCount { 272 return zones, nil 273 } 274 offset = ri.ReturnedCount + ri.Offset 275 continue 276 } 277 } 278 279 func (p *UltraDNSProvider) submitChanges(ctx context.Context, changes []*UltraDNSChanges) error { 280 cnameownerName := "cname" 281 txtownerName := "txt" 282 if len(changes) == 0 { 283 log.Infof("All records are already up to date") 284 return nil 285 } 286 287 zones, err := p.Zones(ctx) 288 if err != nil { 289 return err 290 } 291 zoneChanges := seperateChangeByZone(zones, changes) 292 293 for zoneName, changes := range zoneChanges { 294 for _, change := range changes { 295 if change.ResourceRecordSetUltraDNS.RRType == "CNAME" { 296 cnameownerName = change.ResourceRecordSetUltraDNS.OwnerName 297 } else if change.ResourceRecordSetUltraDNS.RRType == "TXT" { 298 txtownerName = change.ResourceRecordSetUltraDNS.OwnerName 299 } 300 301 if cnameownerName == txtownerName { 302 rrsetKey := udnssdk.RRSetKey{ 303 Zone: zoneName, 304 Type: endpoint.RecordTypeCNAME, 305 Name: change.ResourceRecordSetUltraDNS.OwnerName, 306 } 307 err := p.getSpecificRecord(ctx, rrsetKey) 308 if err != nil { 309 return err 310 } 311 if !p.dryRun { 312 _, err = p.client.RRSets.Delete(rrsetKey) 313 if err != nil { 314 return err 315 } 316 } 317 return fmt.Errorf("the 'cname' and 'txt' record name cannot be same please recreate external-dns with - --txt-prefix=") 318 } 319 rrsetKey := udnssdk.RRSetKey{ 320 Zone: zoneName, 321 Type: change.ResourceRecordSetUltraDNS.RRType, 322 Name: change.ResourceRecordSetUltraDNS.OwnerName, 323 } 324 record := udnssdk.RRSet{} 325 if (change.ResourceRecordSetUltraDNS.RRType == "A" || change.ResourceRecordSetUltraDNS.RRType == "AAAA") && (len(change.ResourceRecordSetUltraDNS.RData) >= 2) { 326 if ultradnsPoolType == "sbpool" && change.ResourceRecordSetUltraDNS.RRType == "A" { 327 sbPoolObject, _ := p.newSBPoolObjectCreation(ctx, change) 328 record = udnssdk.RRSet{ 329 RRType: change.ResourceRecordSetUltraDNS.RRType, 330 OwnerName: change.ResourceRecordSetUltraDNS.OwnerName, 331 RData: change.ResourceRecordSetUltraDNS.RData, 332 TTL: change.ResourceRecordSetUltraDNS.TTL, 333 Profile: sbPoolObject.RawProfile(), 334 } 335 } else if ultradnsPoolType == "rdpool" { 336 rdPoolObject, _ := p.newRDPoolObjectCreation(ctx, change) 337 record = udnssdk.RRSet{ 338 RRType: change.ResourceRecordSetUltraDNS.RRType, 339 OwnerName: change.ResourceRecordSetUltraDNS.OwnerName, 340 RData: change.ResourceRecordSetUltraDNS.RData, 341 TTL: change.ResourceRecordSetUltraDNS.TTL, 342 Profile: rdPoolObject.RawProfile(), 343 } 344 } else { 345 return fmt.Errorf("we do not support Multiple target 'aaaa' records in sb pool please contact to neustar for further details") 346 } 347 } else { 348 record = udnssdk.RRSet{ 349 RRType: change.ResourceRecordSetUltraDNS.RRType, 350 OwnerName: change.ResourceRecordSetUltraDNS.OwnerName, 351 RData: change.ResourceRecordSetUltraDNS.RData, 352 TTL: change.ResourceRecordSetUltraDNS.TTL, 353 } 354 } 355 356 log.WithFields(log.Fields{ 357 "record": record.OwnerName, 358 "type": record.RRType, 359 "ttl": record.TTL, 360 "action": change.Action, 361 "zone": zoneName, 362 "profile": record.Profile, 363 }).Info("Changing record.") 364 365 switch change.Action { 366 case ultradnsCreate: 367 if !p.dryRun { 368 res, err := p.client.RRSets.Create(rrsetKey, record) 369 _ = res 370 if err != nil { 371 return err 372 } 373 } 374 375 case ultradnsDelete: 376 err := p.getSpecificRecord(ctx, rrsetKey) 377 if err != nil { 378 return err 379 } 380 381 if !p.dryRun { 382 _, err = p.client.RRSets.Delete(rrsetKey) 383 if err != nil { 384 return err 385 } 386 } 387 case ultradnsUpdate: 388 err := p.getSpecificRecord(ctx, rrsetKey) 389 if err != nil { 390 return err 391 } 392 393 if !p.dryRun { 394 _, err = p.client.RRSets.Update(rrsetKey, record) 395 if err != nil { 396 return err 397 } 398 } 399 } 400 } 401 } 402 403 return nil 404 } 405 406 func (p *UltraDNSProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { 407 combinedChanges := make([]*UltraDNSChanges, 0, len(changes.Create)+len(changes.UpdateNew)+len(changes.Delete)) 408 log.Infof("value of changes %v,%v,%v", changes.Create, changes.UpdateNew, changes.Delete) 409 combinedChanges = append(combinedChanges, newUltraDNSChanges(ultradnsCreate, changes.Create)...) 410 combinedChanges = append(combinedChanges, newUltraDNSChanges(ultradnsUpdate, changes.UpdateNew)...) 411 combinedChanges = append(combinedChanges, newUltraDNSChanges(ultradnsDelete, changes.Delete)...) 412 413 return p.submitChanges(ctx, combinedChanges) 414 } 415 416 func newUltraDNSChanges(action string, endpoints []*endpoint.Endpoint) []*UltraDNSChanges { 417 changes := make([]*UltraDNSChanges, 0, len(endpoints)) 418 var ttl int 419 for _, e := range endpoints { 420 if e.RecordTTL.IsConfigured() { 421 ttl = int(e.RecordTTL) 422 } 423 424 // Adding suffix dot to the record name 425 recordName := fmt.Sprintf("%s.", e.DNSName) 426 change := &UltraDNSChanges{ 427 Action: action, 428 ResourceRecordSetUltraDNS: udnssdk.RRSet{ 429 RRType: e.RecordType, 430 OwnerName: recordName, 431 RData: e.Targets, 432 TTL: ttl, 433 }, 434 } 435 changes = append(changes, change) 436 } 437 return changes 438 } 439 440 func seperateChangeByZone(zones []udnssdk.Zone, changes []*UltraDNSChanges) map[string][]*UltraDNSChanges { 441 change := make(map[string][]*UltraDNSChanges) 442 zoneNameID := provider.ZoneIDName{} 443 for _, z := range zones { 444 zoneNameID.Add(z.Properties.Name, z.Properties.Name) 445 change[z.Properties.Name] = []*UltraDNSChanges{} 446 } 447 448 for _, c := range changes { 449 zone, _ := zoneNameID.FindZone(c.ResourceRecordSetUltraDNS.OwnerName) 450 if zone == "" { 451 log.Infof("Skipping record %s because no hosted zone matching record DNS Name was detected", c.ResourceRecordSetUltraDNS.OwnerName) 452 continue 453 } 454 change[zone] = append(change[zone], c) 455 } 456 return change 457 } 458 459 func (p *UltraDNSProvider) getSpecificRecord(ctx context.Context, rrsetKey udnssdk.RRSetKey) (err error) { 460 _, err = p.client.RRSets.Select(rrsetKey) 461 if err != nil { 462 return fmt.Errorf("no record was found for %v", rrsetKey) 463 } 464 465 return nil 466 } 467 468 // Creation of SBPoolObject 469 func (p *UltraDNSProvider) newSBPoolObjectCreation(ctx context.Context, change *UltraDNSChanges) (sbPool udnssdk.SBPoolProfile, err error) { 470 sbpoolRDataList := []udnssdk.SBRDataInfo{} 471 for range change.ResourceRecordSetUltraDNS.RData { 472 rrdataInfo := udnssdk.SBRDataInfo{ 473 RunProbes: sbPoolRunProbes, 474 Priority: sbPoolPriority, 475 State: "NORMAL", 476 Threshold: 1, 477 Weight: nil, 478 } 479 sbpoolRDataList = append(sbpoolRDataList, rrdataInfo) 480 } 481 sbPoolObject := udnssdk.SBPoolProfile{ 482 Context: udnssdk.SBPoolSchema, 483 Order: sbPoolOrder, 484 Description: change.ResourceRecordSetUltraDNS.OwnerName, 485 MaxActive: len(change.ResourceRecordSetUltraDNS.RData), 486 MaxServed: len(change.ResourceRecordSetUltraDNS.RData), 487 RDataInfo: sbpoolRDataList, 488 RunProbes: sbPoolRunProbes, 489 ActOnProbes: sbPoolActOnProbes, 490 } 491 return sbPoolObject, nil 492 } 493 494 // Creation of RDPoolObject 495 func (p *UltraDNSProvider) newRDPoolObjectCreation(ctx context.Context, change *UltraDNSChanges) (rdPool udnssdk.RDPoolProfile, err error) { 496 rdPoolObject := udnssdk.RDPoolProfile{ 497 Context: udnssdk.RDPoolSchema, 498 Order: rdPoolOrder, 499 Description: change.ResourceRecordSetUltraDNS.OwnerName, 500 } 501 return rdPoolObject, nil 502 }