sigs.k8s.io/external-dns@v0.14.1/provider/pdns/pdns_test.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 pdns 18 19 import ( 20 "context" 21 "errors" 22 "net/http" 23 "regexp" 24 "strings" 25 "testing" 26 27 pgo "github.com/ffledgling/pdns-go" 28 "github.com/stretchr/testify/assert" 29 "github.com/stretchr/testify/suite" 30 31 "sigs.k8s.io/external-dns/endpoint" 32 ) 33 34 // FIXME: What do we do about labels? 35 36 var ( 37 // Simple RRSets that contain 1 A record and 1 TXT record 38 RRSetSimpleARecord = pgo.RrSet{ 39 Name: "example.com.", 40 Type_: "A", 41 Ttl: 300, 42 Records: []pgo.Record{ 43 {Content: "8.8.8.8", Disabled: false, SetPtr: false}, 44 }, 45 } 46 RRSetSimpleTXTRecord = pgo.RrSet{ 47 Name: "example.com.", 48 Type_: "TXT", 49 Ttl: 300, 50 Records: []pgo.Record{ 51 {Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"", Disabled: false, SetPtr: false}, 52 }, 53 } 54 RRSetLongARecord = pgo.RrSet{ 55 Name: "a.very.long.domainname.example.com.", 56 Type_: "A", 57 Ttl: 300, 58 Records: []pgo.Record{ 59 {Content: "8.8.8.8", Disabled: false, SetPtr: false}, 60 }, 61 } 62 RRSetLongTXTRecord = pgo.RrSet{ 63 Name: "a.very.long.domainname.example.com.", 64 Type_: "TXT", 65 Ttl: 300, 66 Records: []pgo.Record{ 67 {Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"", Disabled: false, SetPtr: false}, 68 }, 69 } 70 // RRSet with one record disabled 71 RRSetDisabledRecord = pgo.RrSet{ 72 Name: "example.com.", 73 Type_: "A", 74 Ttl: 300, 75 Records: []pgo.Record{ 76 {Content: "8.8.8.8", Disabled: false, SetPtr: false}, 77 {Content: "8.8.4.4", Disabled: true, SetPtr: false}, 78 }, 79 } 80 81 RRSetCNAMERecord = pgo.RrSet{ 82 Name: "cname.example.com.", 83 Type_: "CNAME", 84 Ttl: 300, 85 Records: []pgo.Record{ 86 {Content: "example.com.", Disabled: false, SetPtr: false}, 87 }, 88 } 89 90 RRSetALIASRecord = pgo.RrSet{ 91 Name: "alias.example.com.", 92 Type_: "ALIAS", 93 Ttl: 300, 94 Records: []pgo.Record{ 95 {Content: "example.by.any.other.name.com.", Disabled: false, SetPtr: false}, 96 }, 97 } 98 99 RRSetTXTRecord = pgo.RrSet{ 100 Name: "example.com.", 101 Type_: "TXT", 102 Ttl: 300, 103 Records: []pgo.Record{ 104 {Content: "'would smell as sweet'", Disabled: false, SetPtr: false}, 105 }, 106 } 107 108 // Multiple PDNS records in an RRSet of a single type 109 RRSetMultipleRecords = pgo.RrSet{ 110 Name: "example.com.", 111 Type_: "A", 112 Ttl: 300, 113 Records: []pgo.Record{ 114 {Content: "8.8.8.8", Disabled: false, SetPtr: false}, 115 {Content: "8.8.4.4", Disabled: false, SetPtr: false}, 116 {Content: "4.4.4.4", Disabled: false, SetPtr: false}, 117 }, 118 } 119 120 endpointsDisabledRecord = []*endpoint.Endpoint{ 121 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"), 122 } 123 124 endpointsSimpleRecord = []*endpoint.Endpoint{ 125 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"), 126 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), 127 } 128 129 endpointsLongRecord = []*endpoint.Endpoint{ 130 endpoint.NewEndpointWithTTL("a.very.long.domainname.example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"), 131 endpoint.NewEndpointWithTTL("a.very.long.domainname.example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), 132 } 133 134 endpointsNonexistantZone = []*endpoint.Endpoint{ 135 endpoint.NewEndpointWithTTL("does.not.exist.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"), 136 endpoint.NewEndpointWithTTL("does.not.exist.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), 137 } 138 endpointsMultipleRecords = []*endpoint.Endpoint{ 139 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8", "8.8.4.4", "4.4.4.4"), 140 } 141 142 endpointsMixedRecords = []*endpoint.Endpoint{ 143 endpoint.NewEndpointWithTTL("cname.example.com", endpoint.RecordTypeCNAME, endpoint.TTL(300), "example.com"), 144 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "'would smell as sweet'"), 145 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8", "8.8.4.4", "4.4.4.4"), 146 endpoint.NewEndpointWithTTL("alias.example.com", endpoint.RecordTypeCNAME, endpoint.TTL(300), "example.by.any.other.name.com"), 147 } 148 149 endpointsMultipleZones = []*endpoint.Endpoint{ 150 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"), 151 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), 152 endpoint.NewEndpointWithTTL("mock.test", endpoint.RecordTypeA, endpoint.TTL(300), "9.9.9.9"), 153 endpoint.NewEndpointWithTTL("mock.test", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), 154 } 155 156 endpointsMultipleZones2 = []*endpoint.Endpoint{ 157 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"), 158 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), 159 endpoint.NewEndpointWithTTL("abcd.mock.test", endpoint.RecordTypeA, endpoint.TTL(300), "9.9.9.9"), 160 endpoint.NewEndpointWithTTL("abcd.mock.test", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), 161 } 162 163 endpointsMultipleZonesWithNoExist = []*endpoint.Endpoint{ 164 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"), 165 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), 166 endpoint.NewEndpointWithTTL("abcd.mock.noexist", endpoint.RecordTypeA, endpoint.TTL(300), "9.9.9.9"), 167 endpoint.NewEndpointWithTTL("abcd.mock.noexist", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), 168 } 169 endpointsMultipleZonesWithLongRecordNotInDomainFilter = []*endpoint.Endpoint{ 170 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"), 171 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), 172 endpoint.NewEndpointWithTTL("a.very.long.domainname.example.com", endpoint.RecordTypeA, endpoint.TTL(300), "9.9.9.9"), 173 endpoint.NewEndpointWithTTL("a.very.long.domainname.example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), 174 } 175 endpointsMultipleZonesWithSimilarRecordNotInDomainFilter = []*endpoint.Endpoint{ 176 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeA, endpoint.TTL(300), "8.8.8.8"), 177 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), 178 endpoint.NewEndpointWithTTL("test.simexample.com", endpoint.RecordTypeA, endpoint.TTL(300), "9.9.9.9"), 179 endpoint.NewEndpointWithTTL("test.simexample.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), 180 } 181 endpointsApexRecords = []*endpoint.Endpoint{ 182 endpoint.NewEndpointWithTTL("cname.example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), 183 endpoint.NewEndpointWithTTL("cname.example.com", endpoint.RecordTypeCNAME, endpoint.TTL(300), "example.by.any.other.name.com"), 184 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeTXT, endpoint.TTL(300), "\"heritage=external-dns,external-dns/owner=tower-pdns\""), 185 endpoint.NewEndpointWithTTL("example.com", endpoint.RecordTypeCNAME, endpoint.TTL(300), "example.by.any.other.name.com"), 186 } 187 188 ZoneEmpty = pgo.Zone{ 189 // Opaque zone id (string), assigned by the server, should not be interpreted by the application. Guaranteed to be safe for embedding in URLs. 190 Id: "example.com.", 191 // Name of the zone (e.g. “example.com.”) MUST have a trailing dot 192 Name: "example.com.", 193 // Set to “Zone” 194 Type_: "Zone", 195 // API endpoint for this zone 196 Url: "/api/v1/servers/localhost/zones/example.com.", 197 // Zone kind, one of “Native”, “Master”, “Slave” 198 Kind: "Native", 199 // RRSets in this zone 200 Rrsets: []pgo.RrSet{}, 201 } 202 203 ZoneEmptySimilar = pgo.Zone{ 204 Id: "simexample.com.", 205 Name: "simexample.com.", 206 Type_: "Zone", 207 Url: "/api/v1/servers/localhost/zones/simexample.com.", 208 Kind: "Native", 209 Rrsets: []pgo.RrSet{}, 210 } 211 212 ZoneEmptyLong = pgo.Zone{ 213 Id: "long.domainname.example.com.", 214 Name: "long.domainname.example.com.", 215 Type_: "Zone", 216 Url: "/api/v1/servers/localhost/zones/long.domainname.example.com.", 217 Kind: "Native", 218 Rrsets: []pgo.RrSet{}, 219 } 220 221 ZoneEmpty2 = pgo.Zone{ 222 Id: "mock.test.", 223 Name: "mock.test.", 224 Type_: "Zone", 225 Url: "/api/v1/servers/localhost/zones/mock.test.", 226 Kind: "Native", 227 Rrsets: []pgo.RrSet{}, 228 } 229 230 ZoneMixed = pgo.Zone{ 231 Id: "example.com.", 232 Name: "example.com.", 233 Type_: "Zone", 234 Url: "/api/v1/servers/localhost/zones/example.com.", 235 Kind: "Native", 236 Rrsets: []pgo.RrSet{RRSetCNAMERecord, RRSetTXTRecord, RRSetMultipleRecords, RRSetALIASRecord}, 237 } 238 239 ZoneEmptyToSimplePatch = pgo.Zone{ 240 Id: "example.com.", 241 Name: "example.com.", 242 Type_: "Zone", 243 Url: "/api/v1/servers/localhost/zones/example.com.", 244 Kind: "Native", 245 Rrsets: []pgo.RrSet{ 246 { 247 Name: "example.com.", 248 Type_: "A", 249 Ttl: 300, 250 Changetype: "REPLACE", 251 Records: []pgo.Record{ 252 { 253 Content: "8.8.8.8", 254 Disabled: false, 255 SetPtr: false, 256 }, 257 }, 258 Comments: []pgo.Comment(nil), 259 }, 260 { 261 Name: "example.com.", 262 Type_: "TXT", 263 Ttl: 300, 264 Changetype: "REPLACE", 265 Records: []pgo.Record{ 266 { 267 Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"", 268 Disabled: false, 269 SetPtr: false, 270 }, 271 }, 272 Comments: []pgo.Comment(nil), 273 }, 274 }, 275 } 276 277 ZoneEmptyToSimplePatchLongRecordIgnoredInDomainFilter = pgo.Zone{ 278 Id: "example.com.", 279 Name: "example.com.", 280 Type_: "Zone", 281 Url: "/api/v1/servers/localhost/zones/example.com.", 282 Kind: "Native", 283 Rrsets: []pgo.RrSet{ 284 { 285 Name: "a.very.long.domainname.example.com.", 286 Type_: "A", 287 Ttl: 300, 288 Changetype: "REPLACE", 289 Records: []pgo.Record{ 290 { 291 Content: "9.9.9.9", 292 Disabled: false, 293 SetPtr: false, 294 }, 295 }, 296 Comments: []pgo.Comment(nil), 297 }, 298 { 299 Name: "a.very.long.domainname.example.com.", 300 Type_: "TXT", 301 Ttl: 300, 302 Changetype: "REPLACE", 303 Records: []pgo.Record{ 304 { 305 Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"", 306 Disabled: false, 307 SetPtr: false, 308 }, 309 }, 310 Comments: []pgo.Comment(nil), 311 }, 312 { 313 Name: "example.com.", 314 Type_: "A", 315 Ttl: 300, 316 Changetype: "REPLACE", 317 Records: []pgo.Record{ 318 { 319 Content: "8.8.8.8", 320 Disabled: false, 321 SetPtr: false, 322 }, 323 }, 324 Comments: []pgo.Comment(nil), 325 }, 326 { 327 Name: "example.com.", 328 Type_: "TXT", 329 Ttl: 300, 330 Changetype: "REPLACE", 331 Records: []pgo.Record{ 332 { 333 Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"", 334 Disabled: false, 335 SetPtr: false, 336 }, 337 }, 338 Comments: []pgo.Comment(nil), 339 }, 340 }, 341 } 342 343 ZoneEmptyToLongPatch = pgo.Zone{ 344 Id: "long.domainname.example.com.", 345 Name: "long.domainname.example.com.", 346 Type_: "Zone", 347 Url: "/api/v1/servers/localhost/zones/long.domainname.example.com.", 348 Kind: "Native", 349 Rrsets: []pgo.RrSet{ 350 { 351 Name: "a.very.long.domainname.example.com.", 352 Type_: "A", 353 Ttl: 300, 354 Changetype: "REPLACE", 355 Records: []pgo.Record{ 356 { 357 Content: "8.8.8.8", 358 Disabled: false, 359 SetPtr: false, 360 }, 361 }, 362 Comments: []pgo.Comment(nil), 363 }, 364 { 365 Name: "a.very.long.domainname.example.com.", 366 Type_: "TXT", 367 Ttl: 300, 368 Changetype: "REPLACE", 369 Records: []pgo.Record{ 370 { 371 Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"", 372 Disabled: false, 373 SetPtr: false, 374 }, 375 }, 376 Comments: []pgo.Comment(nil), 377 }, 378 }, 379 } 380 381 ZoneEmptyToSimplePatch2 = pgo.Zone{ 382 Id: "mock.test.", 383 Name: "mock.test.", 384 Type_: "Zone", 385 Url: "/api/v1/servers/localhost/zones/mock.test.", 386 Kind: "Native", 387 Rrsets: []pgo.RrSet{ 388 { 389 Name: "mock.test.", 390 Type_: "A", 391 Ttl: 300, 392 Changetype: "REPLACE", 393 Records: []pgo.Record{ 394 { 395 Content: "9.9.9.9", 396 Disabled: false, 397 SetPtr: false, 398 }, 399 }, 400 Comments: []pgo.Comment(nil), 401 }, 402 { 403 Name: "mock.test.", 404 Type_: "TXT", 405 Ttl: 300, 406 Changetype: "REPLACE", 407 Records: []pgo.Record{ 408 { 409 Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"", 410 Disabled: false, 411 SetPtr: false, 412 }, 413 }, 414 Comments: []pgo.Comment(nil), 415 }, 416 }, 417 } 418 419 ZoneEmptyToSimplePatch3 = pgo.Zone{ 420 Id: "mock.test.", 421 Name: "mock.test.", 422 Type_: "Zone", 423 Url: "/api/v1/servers/localhost/zones/mock.test.", 424 Kind: "Native", 425 Rrsets: []pgo.RrSet{ 426 { 427 Name: "abcd.mock.test.", 428 Type_: "A", 429 Ttl: 300, 430 Changetype: "REPLACE", 431 Records: []pgo.Record{ 432 { 433 Content: "9.9.9.9", 434 Disabled: false, 435 SetPtr: false, 436 }, 437 }, 438 Comments: []pgo.Comment(nil), 439 }, 440 { 441 Name: "abcd.mock.test.", 442 Type_: "TXT", 443 Ttl: 300, 444 Changetype: "REPLACE", 445 Records: []pgo.Record{ 446 { 447 Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"", 448 Disabled: false, 449 SetPtr: false, 450 }, 451 }, 452 Comments: []pgo.Comment(nil), 453 }, 454 }, 455 } 456 457 ZoneEmptyToSimpleDelete = pgo.Zone{ 458 Id: "example.com.", 459 Name: "example.com.", 460 Type_: "Zone", 461 Url: "/api/v1/servers/localhost/zones/example.com.", 462 Kind: "Native", 463 Rrsets: []pgo.RrSet{ 464 { 465 Name: "example.com.", 466 Type_: "A", 467 Changetype: "DELETE", 468 Records: []pgo.Record{ 469 { 470 Content: "8.8.8.8", 471 Disabled: false, 472 SetPtr: false, 473 }, 474 }, 475 Comments: []pgo.Comment(nil), 476 }, 477 { 478 Name: "example.com.", 479 Type_: "TXT", 480 Changetype: "DELETE", 481 Records: []pgo.Record{ 482 { 483 Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"", 484 Disabled: false, 485 SetPtr: false, 486 }, 487 }, 488 Comments: []pgo.Comment(nil), 489 }, 490 }, 491 } 492 493 ZoneEmptyToApexPatch = pgo.Zone{ 494 Id: "example.com.", 495 Name: "example.com.", 496 Type_: "Zone", 497 Url: "/api/v1/servers/localhost/zones/example.com.", 498 Kind: "Native", 499 Rrsets: []pgo.RrSet{ 500 { 501 Name: "cname.example.com.", 502 Type_: "CNAME", 503 Ttl: 300, 504 Changetype: "REPLACE", 505 Records: []pgo.Record{ 506 { 507 Content: "example.by.any.other.name.com.", 508 Disabled: false, 509 SetPtr: false, 510 }, 511 }, 512 Comments: []pgo.Comment(nil), 513 }, 514 { 515 Name: "cname.example.com.", 516 Type_: "TXT", 517 Ttl: 300, 518 Changetype: "REPLACE", 519 Records: []pgo.Record{ 520 { 521 Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"", 522 Disabled: false, 523 SetPtr: false, 524 }, 525 }, 526 Comments: []pgo.Comment(nil), 527 }, 528 { 529 Name: "example.com.", 530 Type_: "ALIAS", 531 Ttl: 300, 532 Changetype: "REPLACE", 533 Records: []pgo.Record{ 534 { 535 Content: "example.by.any.other.name.com.", 536 Disabled: false, 537 SetPtr: false, 538 }, 539 }, 540 Comments: []pgo.Comment(nil), 541 }, 542 { 543 Name: "example.com.", 544 Type_: "TXT", 545 Ttl: 300, 546 Changetype: "REPLACE", 547 Records: []pgo.Record{ 548 { 549 Content: "\"heritage=external-dns,external-dns/owner=tower-pdns\"", 550 Disabled: false, 551 SetPtr: false, 552 }, 553 }, 554 Comments: []pgo.Comment(nil), 555 }, 556 }, 557 } 558 559 DomainFilterListSingle = endpoint.DomainFilter{ 560 Filters: []string{ 561 "example.com", 562 }, 563 } 564 565 DomainFilterChildListSingle = endpoint.DomainFilter{ 566 Filters: []string{ 567 "a.example.com", 568 }, 569 } 570 571 DomainFilterListMultiple = endpoint.DomainFilter{ 572 Filters: []string{ 573 "example.com", 574 "mock.com", 575 }, 576 } 577 578 DomainFilterChildListMultiple = endpoint.DomainFilter{ 579 Filters: []string{ 580 "a.example.com", 581 "c.example.com", 582 }, 583 } 584 585 DomainFilterListEmpty = endpoint.DomainFilter{ 586 Filters: []string{}, 587 } 588 589 RegexDomainFilter = endpoint.NewRegexDomainFilter(regexp.MustCompile("example.com"), nil) 590 591 DomainFilterEmptyClient = &PDNSAPIClient{ 592 dryRun: false, 593 authCtx: context.WithValue(context.Background(), pgo.ContextAPIKey, pgo.APIKey{Key: "TEST-API-KEY"}), 594 client: pgo.NewAPIClient(pgo.NewConfiguration()), 595 domainFilter: DomainFilterListEmpty, 596 } 597 598 DomainFilterSingleClient = &PDNSAPIClient{ 599 dryRun: false, 600 authCtx: context.WithValue(context.Background(), pgo.ContextAPIKey, pgo.APIKey{Key: "TEST-API-KEY"}), 601 client: pgo.NewAPIClient(pgo.NewConfiguration()), 602 domainFilter: DomainFilterListSingle, 603 } 604 605 DomainFilterChildSingleClient = &PDNSAPIClient{ 606 dryRun: false, 607 authCtx: context.WithValue(context.Background(), pgo.ContextAPIKey, pgo.APIKey{Key: "TEST-API-KEY"}), 608 client: pgo.NewAPIClient(pgo.NewConfiguration()), 609 domainFilter: DomainFilterChildListSingle, 610 } 611 612 DomainFilterMultipleClient = &PDNSAPIClient{ 613 dryRun: false, 614 authCtx: context.WithValue(context.Background(), pgo.ContextAPIKey, pgo.APIKey{Key: "TEST-API-KEY"}), 615 client: pgo.NewAPIClient(pgo.NewConfiguration()), 616 domainFilter: DomainFilterListMultiple, 617 } 618 619 DomainFilterChildMultipleClient = &PDNSAPIClient{ 620 dryRun: false, 621 authCtx: context.WithValue(context.Background(), pgo.ContextAPIKey, pgo.APIKey{Key: "TEST-API-KEY"}), 622 client: pgo.NewAPIClient(pgo.NewConfiguration()), 623 domainFilter: DomainFilterChildListMultiple, 624 } 625 626 RegexDomainFilterClient = &PDNSAPIClient{ 627 dryRun: false, 628 authCtx: context.WithValue(context.Background(), pgo.ContextAPIKey, pgo.APIKey{Key: "TEST-API-KEY"}), 629 client: pgo.NewAPIClient(pgo.NewConfiguration()), 630 domainFilter: RegexDomainFilter, 631 } 632 ) 633 634 /******************************************************************************/ 635 // API that returns a zone with multiple record types 636 type PDNSAPIClientStub struct{} 637 638 func (c *PDNSAPIClientStub) ListZones() ([]pgo.Zone, *http.Response, error) { 639 return []pgo.Zone{ZoneMixed}, nil, nil 640 } 641 642 func (c *PDNSAPIClientStub) PartitionZones(zones []pgo.Zone) ([]pgo.Zone, []pgo.Zone) { 643 return zones, nil 644 } 645 646 func (c *PDNSAPIClientStub) ListZone(zoneID string) (pgo.Zone, *http.Response, error) { 647 return ZoneMixed, nil, nil 648 } 649 650 func (c *PDNSAPIClientStub) PatchZone(zoneID string, zoneStruct pgo.Zone) (*http.Response, error) { 651 return nil, nil 652 } 653 654 /******************************************************************************/ 655 // API that returns a zones with no records 656 type PDNSAPIClientStubEmptyZones struct { 657 // Keep track of all zones we receive via PatchZone 658 patchedZones []pgo.Zone 659 } 660 661 func (c *PDNSAPIClientStubEmptyZones) ListZones() ([]pgo.Zone, *http.Response, error) { 662 return []pgo.Zone{ZoneEmpty, ZoneEmptyLong, ZoneEmpty2}, nil, nil 663 } 664 665 func (c *PDNSAPIClientStubEmptyZones) PartitionZones(zones []pgo.Zone) ([]pgo.Zone, []pgo.Zone) { 666 return zones, nil 667 } 668 669 func (c *PDNSAPIClientStubEmptyZones) ListZone(zoneID string) (pgo.Zone, *http.Response, error) { 670 if strings.Contains(zoneID, "example.com") { 671 return ZoneEmpty, nil, nil 672 } else if strings.Contains(zoneID, "mock.test") { 673 return ZoneEmpty2, nil, nil 674 } else if strings.Contains(zoneID, "long.domainname.example.com") { 675 return ZoneEmptyLong, nil, nil 676 } 677 return pgo.Zone{}, nil, nil 678 } 679 680 func (c *PDNSAPIClientStubEmptyZones) PatchZone(zoneID string, zoneStruct pgo.Zone) (*http.Response, error) { 681 c.patchedZones = append(c.patchedZones, zoneStruct) 682 return nil, nil 683 } 684 685 /******************************************************************************/ 686 // API that returns error on PatchZone() 687 type PDNSAPIClientStubPatchZoneFailure struct { 688 // Anonymous struct for composition 689 PDNSAPIClientStubEmptyZones 690 } 691 692 // Just overwrite the PatchZone method to introduce a failure 693 func (c *PDNSAPIClientStubPatchZoneFailure) PatchZone(zoneID string, zoneStruct pgo.Zone) (*http.Response, error) { 694 return nil, errors.New("Generic PDNS Error") 695 } 696 697 /******************************************************************************/ 698 // API that returns error on ListZone() 699 type PDNSAPIClientStubListZoneFailure struct { 700 // Anonymous struct for composition 701 PDNSAPIClientStubEmptyZones 702 } 703 704 // Just overwrite the ListZone method to introduce a failure 705 func (c *PDNSAPIClientStubListZoneFailure) ListZone(zoneID string) (pgo.Zone, *http.Response, error) { 706 return pgo.Zone{}, nil, errors.New("Generic PDNS Error") 707 } 708 709 /******************************************************************************/ 710 // API that returns error on ListZones() (Zones - plural) 711 type PDNSAPIClientStubListZonesFailure struct { 712 // Anonymous struct for composition 713 PDNSAPIClientStubEmptyZones 714 } 715 716 // Just overwrite the ListZones method to introduce a failure 717 func (c *PDNSAPIClientStubListZonesFailure) ListZones() ([]pgo.Zone, *http.Response, error) { 718 return []pgo.Zone{}, nil, errors.New("Generic PDNS Error") 719 } 720 721 /******************************************************************************/ 722 // API that returns zone partitions given DomainFilter(s) 723 type PDNSAPIClientStubPartitionZones struct { 724 // Anonymous struct for composition 725 PDNSAPIClientStubEmptyZones 726 } 727 728 func (c *PDNSAPIClientStubPartitionZones) ListZones() ([]pgo.Zone, *http.Response, error) { 729 return []pgo.Zone{ZoneEmpty, ZoneEmptyLong, ZoneEmpty2, ZoneEmptySimilar}, nil, nil 730 } 731 732 func (c *PDNSAPIClientStubPartitionZones) ListZone(zoneID string) (pgo.Zone, *http.Response, error) { 733 if strings.Contains(zoneID, "example.com") { 734 return ZoneEmpty, nil, nil 735 } else if strings.Contains(zoneID, "mock.test") { 736 return ZoneEmpty2, nil, nil 737 } else if strings.Contains(zoneID, "long.domainname.example.com") { 738 return ZoneEmptyLong, nil, nil 739 } else if strings.Contains(zoneID, "simexample.com") { 740 return ZoneEmptySimilar, nil, nil 741 } 742 return pgo.Zone{}, nil, nil 743 } 744 745 // Just overwrite the ListZones method to introduce a failure 746 func (c *PDNSAPIClientStubPartitionZones) PartitionZones(zones []pgo.Zone) ([]pgo.Zone, []pgo.Zone) { 747 return []pgo.Zone{ZoneEmpty}, []pgo.Zone{ZoneEmptyLong, ZoneEmpty2} 748 } 749 750 /******************************************************************************/ 751 752 type NewPDNSProviderTestSuite struct { 753 suite.Suite 754 } 755 756 func (suite *NewPDNSProviderTestSuite) TestPDNSProviderCreate() { 757 _, err := NewPDNSProvider( 758 context.Background(), 759 PDNSConfig{ 760 Server: "http://localhost:8081", 761 DomainFilter: endpoint.NewDomainFilter([]string{""}), 762 }) 763 assert.Error(suite.T(), err, "--pdns-api-key should be specified") 764 765 _, err = NewPDNSProvider( 766 context.Background(), 767 PDNSConfig{ 768 Server: "http://localhost:8081", 769 APIKey: "foo", 770 DomainFilter: endpoint.NewDomainFilter([]string{"example.com", "example.org"}), 771 }) 772 assert.Nil(suite.T(), err, "--domain-filter should raise no error") 773 774 _, err = NewPDNSProvider( 775 context.Background(), 776 PDNSConfig{ 777 Server: "http://localhost:8081", 778 APIKey: "foo", 779 DomainFilter: endpoint.NewDomainFilter([]string{""}), 780 DryRun: true, 781 }) 782 assert.Error(suite.T(), err, "--dry-run should raise an error") 783 784 // This is our "regular" code path, no error should be thrown 785 _, err = NewPDNSProvider( 786 context.Background(), 787 PDNSConfig{ 788 Server: "http://localhost:8081", 789 APIKey: "foo", 790 DomainFilter: endpoint.NewDomainFilter([]string{""}), 791 }) 792 assert.Nil(suite.T(), err, "Regular case should raise no error") 793 } 794 795 func (suite *NewPDNSProviderTestSuite) TestPDNSProviderCreateTLS() { 796 newProvider := func(TLSConfig TLSConfig) error { 797 _, err := NewPDNSProvider( 798 context.Background(), 799 PDNSConfig{APIKey: "foo", TLSConfig: TLSConfig}) 800 return err 801 } 802 803 assert.Nil(suite.T(), newProvider(TLSConfig{SkipTLSVerify: true}), "Disabled TLS Config should raise no error") 804 805 assert.Nil(suite.T(), newProvider(TLSConfig{ 806 SkipTLSVerify: true, 807 CAFilePath: "../../internal/testresources/ca.pem", 808 ClientCertFilePath: "../../internal/testresources/client-cert.pem", 809 ClientCertKeyFilePath: "../../internal/testresources/client-cert-key.pem", 810 }), "Disabled TLS Config with additional flags should raise no error") 811 812 assert.Nil(suite.T(), newProvider(TLSConfig{}), "Enabled TLS Config without --tls-ca should raise no error") 813 814 assert.Nil(suite.T(), newProvider(TLSConfig{ 815 CAFilePath: "../../internal/testresources/ca.pem", 816 }), "Enabled TLS Config with --tls-ca should raise no error") 817 818 assert.Error(suite.T(), newProvider(TLSConfig{ 819 CAFilePath: "../../internal/testresources/ca.pem", 820 ClientCertFilePath: "../../internal/testresources/client-cert.pem", 821 }), "Enabled TLS Config with --tls-client-cert only should raise an error") 822 823 assert.Error(suite.T(), newProvider(TLSConfig{ 824 CAFilePath: "../../internal/testresources/ca.pem", 825 ClientCertKeyFilePath: "../../internal/testresources/client-cert-key.pem", 826 }), "Enabled TLS Config with --tls-client-cert-key only should raise an error") 827 828 assert.Nil(suite.T(), newProvider(TLSConfig{ 829 CAFilePath: "../../internal/testresources/ca.pem", 830 ClientCertFilePath: "../../internal/testresources/client-cert.pem", 831 ClientCertKeyFilePath: "../../internal/testresources/client-cert-key.pem", 832 }), "Enabled TLS Config with all flags should raise no error") 833 } 834 835 func (suite *NewPDNSProviderTestSuite) TestPDNSRRSetToEndpoints() { 836 // Function definition: convertRRSetToEndpoints(rr pgo.RrSet) (endpoints []*endpoint.Endpoint, _ error) 837 838 // Create a new provider to run tests against 839 p := &PDNSProvider{ 840 client: &PDNSAPIClientStub{}, 841 } 842 843 /* given an RRSet with three records, we test: 844 - We correctly create corresponding endpoints 845 */ 846 eps, err := p.convertRRSetToEndpoints(RRSetMultipleRecords) 847 assert.Nil(suite.T(), err) 848 assert.Equal(suite.T(), endpointsMultipleRecords, eps) 849 850 /* Given an RRSet with two records, one of which is disabled, we test: 851 - We can correctly convert the RRSet into a list of valid endpoints 852 - We correctly discard/ignore the disabled record. 853 */ 854 eps, err = p.convertRRSetToEndpoints(RRSetDisabledRecord) 855 assert.Nil(suite.T(), err) 856 assert.Equal(suite.T(), endpointsDisabledRecord, eps) 857 } 858 859 func (suite *NewPDNSProviderTestSuite) TestPDNSRecords() { 860 // Function definition: Records() (endpoints []*endpoint.Endpoint, _ error) 861 862 // Create a new provider to run tests against 863 p := &PDNSProvider{ 864 client: &PDNSAPIClientStub{}, 865 } 866 867 ctx := context.Background() 868 869 /* We test that endpoints are returned correctly for a Zone when Records() is called 870 */ 871 eps, err := p.Records(ctx) 872 assert.Nil(suite.T(), err) 873 assert.Equal(suite.T(), endpointsMixedRecords, eps) 874 875 // Test failures are handled correctly 876 // Create a new provider to run tests against 877 p = &PDNSProvider{ 878 client: &PDNSAPIClientStubListZoneFailure{}, 879 } 880 _, err = p.Records(ctx) 881 assert.NotNil(suite.T(), err) 882 883 p = &PDNSProvider{ 884 client: &PDNSAPIClientStubListZonesFailure{}, 885 } 886 _, err = p.Records(ctx) 887 assert.NotNil(suite.T(), err) 888 } 889 890 func (suite *NewPDNSProviderTestSuite) TestPDNSConvertEndpointsToZones() { 891 // Function definition: ConvertEndpointsToZones(endpoints []*endpoint.Endpoint, changetype pdnsChangeType) (zonelist []pgo.Zone, _ error) 892 893 // Create a new provider to run tests against 894 p := &PDNSProvider{ 895 client: &PDNSAPIClientStubEmptyZones{}, 896 } 897 898 // Check inserting endpoints from a single zone 899 zlist, err := p.ConvertEndpointsToZones(endpointsSimpleRecord, PdnsReplace) 900 assert.Nil(suite.T(), err) 901 assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, zlist) 902 903 // Check deleting endpoints from a single zone 904 zlist, err = p.ConvertEndpointsToZones(endpointsSimpleRecord, PdnsDelete) 905 assert.Nil(suite.T(), err) 906 assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimpleDelete}, zlist) 907 908 // Check endpoints from multiple zones #1 909 zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZones, PdnsReplace) 910 assert.Nil(suite.T(), err) 911 assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch, ZoneEmptyToSimplePatch2}, zlist) 912 913 // Check endpoints from multiple zones #2 914 zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZones2, PdnsReplace) 915 assert.Nil(suite.T(), err) 916 assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch, ZoneEmptyToSimplePatch3}, zlist) 917 918 // Check endpoints from multiple zones where some endpoints which don't exist 919 zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZonesWithNoExist, PdnsReplace) 920 assert.Nil(suite.T(), err) 921 assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, zlist) 922 923 // Check endpoints from a zone that does not exist 924 zlist, err = p.ConvertEndpointsToZones(endpointsNonexistantZone, PdnsReplace) 925 assert.Nil(suite.T(), err) 926 assert.Equal(suite.T(), []pgo.Zone{}, zlist) 927 928 // Check endpoints that match multiple zones (one longer than other), is assigned to the right zone 929 zlist, err = p.ConvertEndpointsToZones(endpointsLongRecord, PdnsReplace) 930 assert.Nil(suite.T(), err) 931 assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToLongPatch}, zlist) 932 933 // Check endpoints of type CNAME always have their target records end with a dot. 934 zlist, err = p.ConvertEndpointsToZones(endpointsMixedRecords, PdnsReplace) 935 assert.Nil(suite.T(), err) 936 937 for _, z := range zlist { 938 for _, rs := range z.Rrsets { 939 if rs.Type_ == "CNAME" { 940 for _, r := range rs.Records { 941 assert.Equal(suite.T(), uint8(0x2e), r.Content[len(r.Content)-1]) 942 } 943 } 944 } 945 } 946 947 // Check endpoints of type CNAME are converted to ALIAS on the domain apex 948 zlist, err = p.ConvertEndpointsToZones(endpointsApexRecords, PdnsReplace) 949 assert.Nil(suite.T(), err) 950 assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToApexPatch}, zlist) 951 } 952 953 func (suite *NewPDNSProviderTestSuite) TestPDNSConvertEndpointsToZonesPartitionZones() { 954 // Test DomainFilters 955 p := &PDNSProvider{ 956 client: &PDNSAPIClientStubPartitionZones{}, 957 } 958 959 // Check inserting endpoints from a single zone which is specified in DomainFilter 960 zlist, err := p.ConvertEndpointsToZones(endpointsSimpleRecord, PdnsReplace) 961 assert.Nil(suite.T(), err) 962 assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, zlist) 963 964 // Check deleting endpoints from a single zone which is specified in DomainFilter 965 zlist, err = p.ConvertEndpointsToZones(endpointsSimpleRecord, PdnsDelete) 966 assert.Nil(suite.T(), err) 967 assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimpleDelete}, zlist) 968 969 // Check endpoints from multiple zones # which one is specified in DomainFilter and one is not 970 zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZones, PdnsReplace) 971 assert.Nil(suite.T(), err) 972 assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, zlist) 973 974 // Check endpoints from multiple zones where some endpoints which don't exist and one that does 975 // and is part of DomainFilter 976 zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZonesWithNoExist, PdnsReplace) 977 assert.Nil(suite.T(), err) 978 assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, zlist) 979 980 // Check endpoints from a zone that does not exist 981 zlist, err = p.ConvertEndpointsToZones(endpointsNonexistantZone, PdnsReplace) 982 assert.Nil(suite.T(), err) 983 assert.Equal(suite.T(), []pgo.Zone{}, zlist) 984 985 // Check endpoints that match multiple zones (one longer than other), is assigned to the right zone when the longer 986 // zone is not part of the DomainFilter 987 zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZonesWithLongRecordNotInDomainFilter, PdnsReplace) 988 assert.Nil(suite.T(), err) 989 assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatchLongRecordIgnoredInDomainFilter}, zlist) 990 991 // Check endpoints that match multiple zones (one longer than other and one is very similar) 992 // is assigned to the right zone when the similar zone is not part of the DomainFilter 993 zlist, err = p.ConvertEndpointsToZones(endpointsMultipleZonesWithSimilarRecordNotInDomainFilter, PdnsReplace) 994 assert.Nil(suite.T(), err) 995 assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, zlist) 996 } 997 998 func (suite *NewPDNSProviderTestSuite) TestPDNSmutateRecords() { 999 // Function definition: mutateRecords(endpoints []*endpoint.Endpoint, changetype pdnsChangeType) error 1000 1001 // Create a new provider to run tests against 1002 c := &PDNSAPIClientStubEmptyZones{} 1003 p := &PDNSProvider{ 1004 client: c, 1005 } 1006 1007 // Check inserting endpoints from a single zone 1008 err := p.mutateRecords(endpointsSimpleRecord, pdnsChangeType("REPLACE")) 1009 assert.Nil(suite.T(), err) 1010 assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimplePatch}, c.patchedZones) 1011 1012 // Reset the "patchedZones" 1013 c.patchedZones = []pgo.Zone{} 1014 1015 // Check deleting endpoints from a single zone 1016 err = p.mutateRecords(endpointsSimpleRecord, pdnsChangeType("DELETE")) 1017 assert.Nil(suite.T(), err) 1018 assert.Equal(suite.T(), []pgo.Zone{ZoneEmptyToSimpleDelete}, c.patchedZones) 1019 1020 // Check we fail correctly when patching fails for whatever reason 1021 p = &PDNSProvider{ 1022 client: &PDNSAPIClientStubPatchZoneFailure{}, 1023 } 1024 // Check inserting endpoints from a single zone 1025 err = p.mutateRecords(endpointsSimpleRecord, pdnsChangeType("REPLACE")) 1026 assert.NotNil(suite.T(), err) 1027 } 1028 1029 func (suite *NewPDNSProviderTestSuite) TestPDNSClientPartitionZones() { 1030 zoneList := []pgo.Zone{ 1031 ZoneEmpty, 1032 ZoneEmpty2, 1033 } 1034 1035 partitionResultFilteredEmptyFilter := []pgo.Zone{ 1036 ZoneEmpty, 1037 ZoneEmpty2, 1038 } 1039 1040 partitionResultResidualEmptyFilter := ([]pgo.Zone)(nil) 1041 1042 partitionResultFilteredSingleFilter := []pgo.Zone{ 1043 ZoneEmpty, 1044 } 1045 1046 partitionResultResidualSingleFilter := []pgo.Zone{ 1047 ZoneEmpty2, 1048 } 1049 1050 partitionResultFilteredMultipleFilter := []pgo.Zone{ 1051 ZoneEmpty, 1052 } 1053 1054 partitionResultResidualMultipleFilter := []pgo.Zone{ 1055 ZoneEmpty2, 1056 } 1057 1058 // Check filtered, residual zones when no domain filter specified 1059 filteredZones, residualZones := DomainFilterEmptyClient.PartitionZones(zoneList) 1060 assert.Equal(suite.T(), partitionResultFilteredEmptyFilter, filteredZones) 1061 assert.Equal(suite.T(), partitionResultResidualEmptyFilter, residualZones) 1062 1063 // Check filtered, residual zones when a single domain filter specified 1064 filteredZones, residualZones = DomainFilterSingleClient.PartitionZones(zoneList) 1065 assert.Equal(suite.T(), partitionResultFilteredSingleFilter, filteredZones) 1066 assert.Equal(suite.T(), partitionResultResidualSingleFilter, residualZones) 1067 1068 // Check filtered, residual zones when a multiple domain filter specified 1069 filteredZones, residualZones = DomainFilterMultipleClient.PartitionZones(zoneList) 1070 assert.Equal(suite.T(), partitionResultFilteredMultipleFilter, filteredZones) 1071 assert.Equal(suite.T(), partitionResultResidualMultipleFilter, residualZones) 1072 1073 filteredZones, residualZones = RegexDomainFilterClient.PartitionZones(zoneList) 1074 assert.Equal(suite.T(), partitionResultFilteredSingleFilter, filteredZones) 1075 assert.Equal(suite.T(), partitionResultResidualSingleFilter, residualZones) 1076 } 1077 1078 func TestNewPDNSProviderTestSuite(t *testing.T) { 1079 suite.Run(t, new(NewPDNSProviderTestSuite)) 1080 }