sigs.k8s.io/external-dns@v0.14.1/provider/infoblox/infoblox_test.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 "bytes" 21 "context" 22 "encoding/base64" 23 "fmt" 24 "net/http" 25 "net/url" 26 "regexp" 27 "strings" 28 "testing" 29 30 ibclient "github.com/infobloxopen/infoblox-go-client/v2" 31 "github.com/miekg/dns" 32 "github.com/stretchr/testify/assert" 33 34 "sigs.k8s.io/external-dns/endpoint" 35 "sigs.k8s.io/external-dns/internal/testutils" 36 "sigs.k8s.io/external-dns/plan" 37 "sigs.k8s.io/external-dns/provider" 38 ) 39 40 type mockIBConnector struct { 41 mockInfobloxZones *[]ibclient.ZoneAuth 42 mockInfobloxObjects *[]ibclient.IBObject 43 createdEndpoints []*endpoint.Endpoint 44 deletedEndpoints []*endpoint.Endpoint 45 updatedEndpoints []*endpoint.Endpoint 46 getObjectRequests []*getObjectRequest 47 requestBuilder ExtendedRequestBuilder 48 } 49 50 type getObjectRequest struct { 51 obj string 52 ref string 53 queryParams string 54 url url.URL 55 verified bool 56 } 57 58 func (req *getObjectRequest) ExpectRequestURLQueryParam(t *testing.T, name string, value string) *getObjectRequest { 59 if req.url.Query().Get(name) != value { 60 t.Errorf("Expected GetObject Request URL to contain query parameter %s=%s, Got: %v", name, value, req.url.Query()) 61 } 62 63 return req 64 } 65 66 func (req *getObjectRequest) ExpectNotRequestURLQueryParam(t *testing.T, name string) *getObjectRequest { 67 if req.url.Query().Has(name) { 68 t.Errorf("Expected GetObject Request URL not to contain query parameter %s, Got: %v", name, req.url.Query()) 69 } 70 71 return req 72 } 73 74 func (client *mockIBConnector) verifyGetObjectRequest(t *testing.T, obj string, ref string, query *map[string]string) *getObjectRequest { 75 qp := "" 76 if query != nil { 77 qp = fmt.Sprint(ibclient.NewQueryParams(false, *query)) 78 } 79 80 for _, req := range client.getObjectRequests { 81 if !req.verified && req.obj == obj && req.ref == ref && req.queryParams == qp { 82 req.verified = true 83 return req 84 } 85 } 86 87 t.Errorf("Expected GetObject obj=%s, query=%s, ref=%s", obj, qp, ref) 88 return &getObjectRequest{} 89 } 90 91 // verifyNoMoreGetObjectRequests will assert that all "GetObject" calls have been verified. 92 func (client *mockIBConnector) verifyNoMoreGetObjectRequests(t *testing.T) { 93 unverified := []getObjectRequest{} 94 for _, req := range client.getObjectRequests { 95 if !req.verified { 96 unverified = append(unverified, *req) 97 } 98 } 99 100 if len(unverified) > 0 { 101 b := new(bytes.Buffer) 102 for _, req := range unverified { 103 fmt.Fprintf(b, "obj=%s, ref=%s, params=%s (url=%s)\n", req.obj, req.ref, req.queryParams, req.url.String()) 104 } 105 106 t.Errorf("Unverified GetObject Requests: %v", unverified) 107 } 108 } 109 110 func (client *mockIBConnector) CreateObject(obj ibclient.IBObject) (ref string, err error) { 111 switch obj.ObjectType() { 112 case "record:a": 113 client.createdEndpoints = append( 114 client.createdEndpoints, 115 endpoint.NewEndpoint( 116 *obj.(*ibclient.RecordA).Name, 117 endpoint.RecordTypeA, 118 *obj.(*ibclient.RecordA).Ipv4Addr, 119 ), 120 ) 121 ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(*obj.(*ibclient.RecordA).Name)), *obj.(*ibclient.RecordA).Name) 122 obj.(*ibclient.RecordA).Ref = ref 123 case "record:cname": 124 client.createdEndpoints = append( 125 client.createdEndpoints, 126 endpoint.NewEndpoint( 127 *obj.(*ibclient.RecordCNAME).Name, 128 endpoint.RecordTypeCNAME, 129 *obj.(*ibclient.RecordCNAME).Canonical, 130 ), 131 ) 132 ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(*obj.(*ibclient.RecordCNAME).Name)), *obj.(*ibclient.RecordCNAME).Name) 133 obj.(*ibclient.RecordCNAME).Ref = ref 134 case "record:host": 135 for _, i := range obj.(*ibclient.HostRecord).Ipv4Addrs { 136 client.createdEndpoints = append( 137 client.createdEndpoints, 138 endpoint.NewEndpoint( 139 *obj.(*ibclient.HostRecord).Name, 140 endpoint.RecordTypeA, 141 *i.Ipv4Addr, 142 ), 143 ) 144 } 145 ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(*obj.(*ibclient.HostRecord).Name)), *obj.(*ibclient.HostRecord).Name) 146 obj.(*ibclient.HostRecord).Ref = ref 147 case "record:txt": 148 client.createdEndpoints = append( 149 client.createdEndpoints, 150 endpoint.NewEndpoint( 151 *obj.(*ibclient.RecordTXT).Name, 152 endpoint.RecordTypeTXT, 153 *obj.(*ibclient.RecordTXT).Text, 154 ), 155 ) 156 obj.(*ibclient.RecordTXT).Ref = ref 157 ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(*obj.(*ibclient.RecordTXT).Name)), *obj.(*ibclient.RecordTXT).Name) 158 case "record:ptr": 159 client.createdEndpoints = append( 160 client.createdEndpoints, 161 endpoint.NewEndpoint( 162 *obj.(*ibclient.RecordPTR).PtrdName, 163 endpoint.RecordTypePTR, 164 *obj.(*ibclient.RecordPTR).Ipv4Addr, 165 ), 166 ) 167 obj.(*ibclient.RecordPTR).Ref = ref 168 reverseAddr, err := dns.ReverseAddr(*obj.(*ibclient.RecordPTR).Ipv4Addr) 169 if err != nil { 170 return ref, fmt.Errorf("unable to create reverse addr from %s", *obj.(*ibclient.RecordPTR).Ipv4Addr) 171 } 172 ref = fmt.Sprintf("%s/%s:%s/default", obj.ObjectType(), base64.StdEncoding.EncodeToString([]byte(*obj.(*ibclient.RecordPTR).PtrdName)), reverseAddr) 173 } 174 *client.mockInfobloxObjects = append( 175 *client.mockInfobloxObjects, 176 obj, 177 ) 178 return ref, nil 179 } 180 181 func (client *mockIBConnector) GetObject(obj ibclient.IBObject, ref string, queryParams *ibclient.QueryParams, res interface{}) (err error) { 182 req := getObjectRequest{ 183 obj: obj.ObjectType(), 184 ref: ref, 185 } 186 if queryParams != nil { 187 req.queryParams = fmt.Sprint(queryParams) 188 } 189 r, _ := client.requestBuilder.BuildRequest(ibclient.GET, obj, ref, queryParams) 190 if r != nil { 191 req.url = *r.URL 192 } 193 client.getObjectRequests = append(client.getObjectRequests, &req) 194 switch obj.ObjectType() { 195 case "record:a": 196 var result []ibclient.RecordA 197 for _, object := range *client.mockInfobloxObjects { 198 if object.ObjectType() == "record:a" { 199 if ref != "" && 200 ref != object.(*ibclient.RecordA).Ref { 201 continue 202 } 203 if obj.(*ibclient.RecordA).Name != nil && 204 *obj.(*ibclient.RecordA).Name != *object.(*ibclient.RecordA).Name { 205 continue 206 } 207 result = append(result, *object.(*ibclient.RecordA)) 208 } 209 } 210 *res.(*[]ibclient.RecordA) = result 211 case "record:cname": 212 var result []ibclient.RecordCNAME 213 for _, object := range *client.mockInfobloxObjects { 214 if object.ObjectType() == "record:cname" { 215 if ref != "" && 216 ref != object.(*ibclient.RecordCNAME).Ref { 217 continue 218 } 219 if obj.(*ibclient.RecordCNAME).Name != nil && 220 *obj.(*ibclient.RecordCNAME).Name != *object.(*ibclient.RecordCNAME).Name { 221 continue 222 } 223 result = append(result, *object.(*ibclient.RecordCNAME)) 224 } 225 } 226 *res.(*[]ibclient.RecordCNAME) = result 227 case "record:host": 228 var result []ibclient.HostRecord 229 for _, object := range *client.mockInfobloxObjects { 230 if object.ObjectType() == "record:host" { 231 if ref != "" && 232 ref != object.(*ibclient.HostRecord).Ref { 233 continue 234 } 235 if obj.(*ibclient.HostRecord).Name != nil && 236 *obj.(*ibclient.HostRecord).Name != *object.(*ibclient.HostRecord).Name { 237 continue 238 } 239 result = append(result, *object.(*ibclient.HostRecord)) 240 } 241 } 242 *res.(*[]ibclient.HostRecord) = result 243 case "record:txt": 244 var result []ibclient.RecordTXT 245 for _, object := range *client.mockInfobloxObjects { 246 if object.ObjectType() == "record:txt" { 247 if ref != "" && 248 ref != object.(*ibclient.RecordTXT).Ref { 249 continue 250 } 251 if obj.(*ibclient.RecordTXT).Name != nil && 252 *obj.(*ibclient.RecordTXT).Name != *object.(*ibclient.RecordTXT).Name { 253 continue 254 } 255 result = append(result, *object.(*ibclient.RecordTXT)) 256 } 257 } 258 *res.(*[]ibclient.RecordTXT) = result 259 case "record:ptr": 260 var result []ibclient.RecordPTR 261 for _, object := range *client.mockInfobloxObjects { 262 if object.ObjectType() == "record:ptr" { 263 if ref != "" && 264 ref != object.(*ibclient.RecordPTR).Ref { 265 continue 266 } 267 if obj.(*ibclient.RecordPTR).PtrdName != nil && 268 *obj.(*ibclient.RecordPTR).PtrdName != *object.(*ibclient.RecordPTR).PtrdName { 269 continue 270 } 271 result = append(result, *object.(*ibclient.RecordPTR)) 272 } 273 } 274 *res.(*[]ibclient.RecordPTR) = result 275 case "zone_auth": 276 *res.(*[]ibclient.ZoneAuth) = *client.mockInfobloxZones 277 } 278 return 279 } 280 281 func (client *mockIBConnector) DeleteObject(ref string) (refRes string, err error) { 282 re := regexp.MustCompile(`([^/]+)/[^:]+:([^/]+)/default`) 283 result := re.FindStringSubmatch(ref) 284 285 switch result[1] { 286 case "record:a": 287 var records []ibclient.RecordA 288 obj := ibclient.NewEmptyRecordA() 289 obj.Name = &result[2] 290 client.GetObject(obj, ref, nil, &records) 291 for _, record := range records { 292 client.deletedEndpoints = append( 293 client.deletedEndpoints, 294 endpoint.NewEndpoint( 295 *record.Name, 296 endpoint.RecordTypeA, 297 "", 298 ), 299 ) 300 } 301 case "record:cname": 302 var records []ibclient.RecordCNAME 303 obj := ibclient.NewEmptyRecordCNAME() 304 obj.Name = &result[2] 305 client.GetObject(obj, ref, nil, &records) 306 for _, record := range records { 307 client.deletedEndpoints = append( 308 client.deletedEndpoints, 309 endpoint.NewEndpoint( 310 *record.Name, 311 endpoint.RecordTypeCNAME, 312 "", 313 ), 314 ) 315 } 316 case "record:host": 317 var records []ibclient.HostRecord 318 obj := ibclient.NewEmptyHostRecord() 319 obj.Name = &result[2] 320 client.GetObject(obj, ref, nil, &records) 321 for _, record := range records { 322 client.deletedEndpoints = append( 323 client.deletedEndpoints, 324 endpoint.NewEndpoint( 325 *record.Name, 326 endpoint.RecordTypeA, 327 "", 328 ), 329 ) 330 } 331 case "record:txt": 332 var records []ibclient.RecordTXT 333 obj := ibclient.NewEmptyRecordTXT() 334 obj.Name = &result[2] 335 client.GetObject(obj, ref, nil, &records) 336 for _, record := range records { 337 client.deletedEndpoints = append( 338 client.deletedEndpoints, 339 endpoint.NewEndpoint( 340 *record.Name, 341 endpoint.RecordTypeTXT, 342 "", 343 ), 344 ) 345 } 346 case "record:ptr": 347 var records []ibclient.RecordPTR 348 obj := ibclient.NewEmptyRecordPTR() 349 obj.Name = &result[2] 350 client.GetObject(obj, ref, nil, &records) 351 for _, record := range records { 352 client.deletedEndpoints = append( 353 client.deletedEndpoints, 354 endpoint.NewEndpoint( 355 *record.PtrdName, 356 endpoint.RecordTypePTR, 357 "", 358 ), 359 ) 360 } 361 } 362 return "", nil 363 } 364 365 func (client *mockIBConnector) UpdateObject(obj ibclient.IBObject, ref string) (refRes string, err error) { 366 switch obj.ObjectType() { 367 case "record:a": 368 client.updatedEndpoints = append( 369 client.updatedEndpoints, 370 endpoint.NewEndpoint( 371 *obj.(*ibclient.RecordA).Name, 372 *obj.(*ibclient.RecordA).Ipv4Addr, 373 endpoint.RecordTypeA, 374 ), 375 ) 376 case "record:cname": 377 client.updatedEndpoints = append( 378 client.updatedEndpoints, 379 endpoint.NewEndpoint( 380 *obj.(*ibclient.RecordCNAME).Name, 381 *obj.(*ibclient.RecordCNAME).Canonical, 382 endpoint.RecordTypeCNAME, 383 ), 384 ) 385 case "record:host": 386 for _, i := range obj.(*ibclient.HostRecord).Ipv4Addrs { 387 client.updatedEndpoints = append( 388 client.updatedEndpoints, 389 endpoint.NewEndpoint( 390 *obj.(*ibclient.HostRecord).Name, 391 *i.Ipv4Addr, 392 endpoint.RecordTypeA, 393 ), 394 ) 395 } 396 case "record:txt": 397 client.updatedEndpoints = append( 398 client.updatedEndpoints, 399 endpoint.NewEndpoint( 400 *obj.(*ibclient.RecordTXT).Name, 401 *obj.(*ibclient.RecordTXT).Text, 402 endpoint.RecordTypeTXT, 403 ), 404 ) 405 } 406 return "", nil 407 } 408 409 func createMockInfobloxZone(fqdn string) ibclient.ZoneAuth { 410 return ibclient.ZoneAuth{ 411 Fqdn: fqdn, 412 } 413 } 414 415 func createMockInfobloxObject(name, recordType, value string) ibclient.IBObject { 416 ref := fmt.Sprintf("record:%s/%s:%s/default", strings.ToLower(recordType), base64.StdEncoding.EncodeToString([]byte(name)), name) 417 switch recordType { 418 case endpoint.RecordTypeA: 419 obj := ibclient.NewEmptyRecordA() 420 obj.Name = &name 421 obj.Ref = ref 422 obj.Ipv4Addr = &value 423 return obj 424 case endpoint.RecordTypeCNAME: 425 obj := ibclient.NewEmptyRecordCNAME() 426 obj.Name = &name 427 obj.Ref = ref 428 obj.Canonical = &value 429 return obj 430 case endpoint.RecordTypeTXT: 431 obj := ibclient.NewEmptyRecordTXT() 432 obj.Name = &name 433 obj.Ref = ref 434 obj.Text = &value 435 return obj 436 case "HOST": 437 obj := ibclient.NewEmptyHostRecord() 438 obj.Name = &name 439 obj.Ref = ref 440 obj.Ipv4Addrs = []ibclient.HostRecordIpv4Addr{ 441 { 442 Ipv4Addr: &value, 443 }, 444 } 445 return obj 446 case endpoint.RecordTypePTR: 447 obj := ibclient.NewEmptyRecordPTR() 448 obj.PtrdName = &name 449 obj.Ref = ref 450 obj.Ipv4Addr = &value 451 return obj 452 } 453 454 return nil 455 } 456 457 func newInfobloxProvider(domainFilter endpoint.DomainFilter, zoneIDFilter provider.ZoneIDFilter, view string, dryRun bool, createPTR bool, client ibclient.IBConnector) *ProviderConfig { 458 return &ProviderConfig{ 459 client: client, 460 domainFilter: domainFilter, 461 zoneIDFilter: zoneIDFilter, 462 dryRun: dryRun, 463 createPTR: createPTR, 464 view: view, 465 } 466 } 467 468 func TestInfobloxRecords(t *testing.T) { 469 client := mockIBConnector{ 470 mockInfobloxZones: &[]ibclient.ZoneAuth{ 471 createMockInfobloxZone("example.com"), 472 createMockInfobloxZone("other.com"), 473 }, 474 mockInfobloxObjects: &[]ibclient.IBObject{ 475 createMockInfobloxObject("example.com", endpoint.RecordTypeA, "123.123.123.122"), 476 createMockInfobloxObject("example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"), 477 createMockInfobloxObject("nginx.example.com", endpoint.RecordTypeA, "123.123.123.123"), 478 createMockInfobloxObject("nginx.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"), 479 createMockInfobloxObject("whitespace.example.com", endpoint.RecordTypeA, "123.123.123.124"), 480 createMockInfobloxObject("whitespace.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=white space"), 481 createMockInfobloxObject("hack.example.com", endpoint.RecordTypeCNAME, "cerberus.infoblox.com"), 482 createMockInfobloxObject("multiple.example.com", endpoint.RecordTypeA, "123.123.123.122"), 483 createMockInfobloxObject("multiple.example.com", endpoint.RecordTypeA, "123.123.123.121"), 484 createMockInfobloxObject("multiple.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"), 485 createMockInfobloxObject("existing.example.com", endpoint.RecordTypeA, "124.1.1.1"), 486 createMockInfobloxObject("existing.example.com", endpoint.RecordTypeA, "124.1.1.2"), 487 createMockInfobloxObject("existing.example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=existing"), 488 createMockInfobloxObject("host.example.com", "HOST", "125.1.1.1"), 489 }, 490 } 491 492 providerCfg := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), "", true, false, &client) 493 actual, err := providerCfg.Records(context.Background()) 494 if err != nil { 495 t.Fatal(err) 496 } 497 expected := []*endpoint.Endpoint{ 498 endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "123.123.123.122"), 499 endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=default\""), 500 endpoint.NewEndpoint("nginx.example.com", endpoint.RecordTypeA, "123.123.123.123"), 501 endpoint.NewEndpoint("nginx.example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=default\""), 502 endpoint.NewEndpoint("whitespace.example.com", endpoint.RecordTypeA, "123.123.123.124"), 503 endpoint.NewEndpoint("whitespace.example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=white space\""), 504 endpoint.NewEndpoint("hack.example.com", endpoint.RecordTypeCNAME, "cerberus.infoblox.com"), 505 endpoint.NewEndpoint("multiple.example.com", endpoint.RecordTypeA, "123.123.123.122", "123.123.123.121"), 506 endpoint.NewEndpoint("multiple.example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=default\""), 507 endpoint.NewEndpoint("existing.example.com", endpoint.RecordTypeA, "124.1.1.1", "124.1.1.2"), 508 endpoint.NewEndpoint("existing.example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=existing\""), 509 endpoint.NewEndpoint("host.example.com", endpoint.RecordTypeA, "125.1.1.1"), 510 } 511 validateEndpoints(t, actual, expected) 512 client.verifyGetObjectRequest(t, "zone_auth", "", &map[string]string{}). 513 ExpectNotRequestURLQueryParam(t, "view"). 514 ExpectNotRequestURLQueryParam(t, "zone") 515 client.verifyGetObjectRequest(t, "record:a", "", &map[string]string{"zone": "example.com"}). 516 ExpectRequestURLQueryParam(t, "zone", "example.com") 517 client.verifyGetObjectRequest(t, "record:host", "", &map[string]string{"zone": "example.com"}). 518 ExpectRequestURLQueryParam(t, "zone", "example.com") 519 client.verifyGetObjectRequest(t, "record:cname", "", &map[string]string{"zone": "example.com"}). 520 ExpectRequestURLQueryParam(t, "zone", "example.com") 521 client.verifyGetObjectRequest(t, "record:txt", "", &map[string]string{"zone": "example.com"}). 522 ExpectRequestURLQueryParam(t, "zone", "example.com") 523 client.verifyNoMoreGetObjectRequests(t) 524 } 525 526 func TestInfobloxRecordsWithView(t *testing.T) { 527 client := mockIBConnector{ 528 mockInfobloxZones: &[]ibclient.ZoneAuth{ 529 createMockInfobloxZone("foo.example.com"), 530 createMockInfobloxZone("bar.example.com"), 531 }, 532 mockInfobloxObjects: &[]ibclient.IBObject{ 533 createMockInfobloxObject("cat.foo.example.com", endpoint.RecordTypeA, "123.123.123.122"), 534 createMockInfobloxObject("dog.bar.example.com", endpoint.RecordTypeA, "123.123.123.123"), 535 }, 536 } 537 538 providerCfg := newInfobloxProvider(endpoint.NewDomainFilter([]string{"foo.example.com", "bar.example.com"}), provider.NewZoneIDFilter([]string{""}), "Inside", true, false, &client) 539 actual, err := providerCfg.Records(context.Background()) 540 if err != nil { 541 t.Fatal(err) 542 } 543 expected := []*endpoint.Endpoint{ 544 endpoint.NewEndpoint("cat.foo.example.com", endpoint.RecordTypeA, "123.123.123.122"), 545 endpoint.NewEndpoint("dog.bar.example.com", endpoint.RecordTypeA, "123.123.123.123"), 546 } 547 validateEndpoints(t, actual, expected) 548 client.verifyGetObjectRequest(t, "zone_auth", "", &map[string]string{"view": "Inside"}). 549 ExpectRequestURLQueryParam(t, "view", "Inside"). 550 ExpectNotRequestURLQueryParam(t, "zone") 551 client.verifyGetObjectRequest(t, "record:a", "", &map[string]string{"zone": "foo.example.com", "view": "Inside"}). 552 ExpectRequestURLQueryParam(t, "zone", "foo.example.com"). 553 ExpectRequestURLQueryParam(t, "view", "Inside") 554 client.verifyGetObjectRequest(t, "record:host", "", &map[string]string{"zone": "foo.example.com", "view": "Inside"}). 555 ExpectRequestURLQueryParam(t, "zone", "foo.example.com"). 556 ExpectRequestURLQueryParam(t, "view", "Inside") 557 client.verifyGetObjectRequest(t, "record:cname", "", &map[string]string{"zone": "foo.example.com", "view": "Inside"}). 558 ExpectRequestURLQueryParam(t, "zone", "foo.example.com"). 559 ExpectRequestURLQueryParam(t, "view", "Inside") 560 client.verifyGetObjectRequest(t, "record:txt", "", &map[string]string{"zone": "foo.example.com", "view": "Inside"}). 561 ExpectRequestURLQueryParam(t, "zone", "foo.example.com"). 562 ExpectRequestURLQueryParam(t, "view", "Inside") 563 client.verifyGetObjectRequest(t, "record:a", "", &map[string]string{"zone": "bar.example.com", "view": "Inside"}). 564 ExpectRequestURLQueryParam(t, "zone", "bar.example.com"). 565 ExpectRequestURLQueryParam(t, "view", "Inside") 566 client.verifyGetObjectRequest(t, "record:host", "", &map[string]string{"zone": "bar.example.com", "view": "Inside"}). 567 ExpectRequestURLQueryParam(t, "zone", "bar.example.com"). 568 ExpectRequestURLQueryParam(t, "view", "Inside") 569 client.verifyGetObjectRequest(t, "record:cname", "", &map[string]string{"zone": "bar.example.com", "view": "Inside"}). 570 ExpectRequestURLQueryParam(t, "zone", "bar.example.com"). 571 ExpectRequestURLQueryParam(t, "view", "Inside") 572 client.verifyGetObjectRequest(t, "record:txt", "", &map[string]string{"zone": "bar.example.com", "view": "Inside"}). 573 ExpectRequestURLQueryParam(t, "zone", "bar.example.com"). 574 ExpectRequestURLQueryParam(t, "view", "Inside") 575 client.verifyNoMoreGetObjectRequests(t) 576 } 577 578 func TestInfobloxAdjustEndpoints(t *testing.T) { 579 client := mockIBConnector{ 580 mockInfobloxZones: &[]ibclient.ZoneAuth{ 581 createMockInfobloxZone("example.com"), 582 createMockInfobloxZone("other.com"), 583 }, 584 mockInfobloxObjects: &[]ibclient.IBObject{ 585 createMockInfobloxObject("example.com", endpoint.RecordTypeA, "123.123.123.122"), 586 createMockInfobloxObject("example.com", endpoint.RecordTypeTXT, "heritage=external-dns,external-dns/owner=default"), 587 createMockInfobloxObject("hack.example.com", endpoint.RecordTypeCNAME, "cerberus.infoblox.com"), 588 createMockInfobloxObject("host.example.com", "HOST", "125.1.1.1"), 589 }, 590 } 591 592 providerCfg := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com"}), provider.NewZoneIDFilter([]string{""}), "", true, true, &client) 593 actual, err := providerCfg.Records(context.Background()) 594 if err != nil { 595 t.Fatal(err) 596 } 597 providerCfg.AdjustEndpoints(actual) 598 599 expected := []*endpoint.Endpoint{ 600 endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "123.123.123.122").WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true"), 601 endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "\"heritage=external-dns,external-dns/owner=default\""), 602 endpoint.NewEndpoint("hack.example.com", endpoint.RecordTypeCNAME, "cerberus.infoblox.com"), 603 endpoint.NewEndpoint("host.example.com", endpoint.RecordTypeA, "125.1.1.1").WithProviderSpecific(providerSpecificInfobloxPtrRecord, "true"), 604 } 605 validateEndpoints(t, actual, expected) 606 } 607 608 func TestInfobloxRecordsReverse(t *testing.T) { 609 client := mockIBConnector{ 610 mockInfobloxZones: &[]ibclient.ZoneAuth{ 611 createMockInfobloxZone("10.0.0.0/24"), 612 createMockInfobloxZone("10.0.1.0/24"), 613 }, 614 mockInfobloxObjects: &[]ibclient.IBObject{ 615 createMockInfobloxObject("example.com", endpoint.RecordTypePTR, "10.0.0.1"), 616 createMockInfobloxObject("example2.com", endpoint.RecordTypePTR, "10.0.0.2"), 617 }, 618 } 619 620 providerCfg := newInfobloxProvider(endpoint.NewDomainFilter([]string{"10.0.0.0/24"}), provider.NewZoneIDFilter([]string{""}), "", true, true, &client) 621 actual, err := providerCfg.Records(context.Background()) 622 if err != nil { 623 t.Fatal(err) 624 } 625 expected := []*endpoint.Endpoint{ 626 endpoint.NewEndpoint("example.com", endpoint.RecordTypePTR, "10.0.0.1"), 627 endpoint.NewEndpoint("example2.com", endpoint.RecordTypePTR, "10.0.0.2"), 628 } 629 validateEndpoints(t, actual, expected) 630 } 631 632 func TestInfobloxApplyChanges(t *testing.T) { 633 client := mockIBConnector{} 634 635 testInfobloxApplyChangesInternal(t, false, false, &client) 636 637 validateEndpoints(t, client.createdEndpoints, []*endpoint.Endpoint{ 638 endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "1.2.3.4"), 639 endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "tag"), 640 endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "1.2.3.4"), 641 endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeTXT, "tag"), 642 endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeCNAME, "other.com"), 643 endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeTXT, "tag"), 644 endpoint.NewEndpoint("other.com", endpoint.RecordTypeA, "5.6.7.8"), 645 endpoint.NewEndpoint("other.com", endpoint.RecordTypeTXT, "tag"), 646 endpoint.NewEndpoint("new.example.com", endpoint.RecordTypeA, "111.222.111.222"), 647 endpoint.NewEndpoint("newcname.example.com", endpoint.RecordTypeCNAME, "other.com"), 648 endpoint.NewEndpoint("multiple.example.com", endpoint.RecordTypeA, "1.2.3.4,3.4.5.6,8.9.10.11"), 649 endpoint.NewEndpoint("multiple.example.com", endpoint.RecordTypeTXT, "tag-multiple-A-records"), 650 }) 651 652 validateEndpoints(t, client.deletedEndpoints, []*endpoint.Endpoint{ 653 endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, ""), 654 endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, ""), 655 endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, ""), 656 endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, ""), 657 }) 658 659 validateEndpoints(t, client.updatedEndpoints, []*endpoint.Endpoint{}) 660 } 661 662 func TestInfobloxApplyChangesReverse(t *testing.T) { 663 client := mockIBConnector{} 664 665 testInfobloxApplyChangesInternal(t, false, true, &client) 666 667 validateEndpoints(t, client.createdEndpoints, []*endpoint.Endpoint{ 668 endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "1.2.3.4"), 669 endpoint.NewEndpoint("example.com", endpoint.RecordTypePTR, "1.2.3.4"), 670 endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "tag"), 671 endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "1.2.3.4"), 672 endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypePTR, "1.2.3.4"), 673 endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeTXT, "tag"), 674 endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeCNAME, "other.com"), 675 endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeTXT, "tag"), 676 endpoint.NewEndpoint("other.com", endpoint.RecordTypeA, "5.6.7.8"), 677 endpoint.NewEndpoint("other.com", endpoint.RecordTypeTXT, "tag"), 678 endpoint.NewEndpoint("new.example.com", endpoint.RecordTypeA, "111.222.111.222"), 679 endpoint.NewEndpoint("newcname.example.com", endpoint.RecordTypeCNAME, "other.com"), 680 endpoint.NewEndpoint("multiple.example.com", endpoint.RecordTypeA, "1.2.3.4,3.4.5.6,8.9.10.11"), 681 endpoint.NewEndpoint("multiple.example.com", endpoint.RecordTypeTXT, "tag-multiple-A-records"), 682 }) 683 684 validateEndpoints(t, client.deletedEndpoints, []*endpoint.Endpoint{ 685 endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, ""), 686 endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, ""), 687 endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, ""), 688 endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypePTR, ""), 689 endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, ""), 690 }) 691 692 validateEndpoints(t, client.updatedEndpoints, []*endpoint.Endpoint{}) 693 } 694 695 func TestInfobloxApplyChangesDryRun(t *testing.T) { 696 client := mockIBConnector{ 697 mockInfobloxObjects: &[]ibclient.IBObject{}, 698 } 699 700 testInfobloxApplyChangesInternal(t, true, false, &client) 701 702 validateEndpoints(t, client.createdEndpoints, []*endpoint.Endpoint{}) 703 704 validateEndpoints(t, client.deletedEndpoints, []*endpoint.Endpoint{}) 705 706 validateEndpoints(t, client.updatedEndpoints, []*endpoint.Endpoint{}) 707 } 708 709 func testInfobloxApplyChangesInternal(t *testing.T, dryRun, createPTR bool, client ibclient.IBConnector) { 710 client.(*mockIBConnector).mockInfobloxZones = &[]ibclient.ZoneAuth{ 711 createMockInfobloxZone("example.com"), 712 createMockInfobloxZone("other.com"), 713 createMockInfobloxZone("1.2.3.0/24"), 714 } 715 client.(*mockIBConnector).mockInfobloxObjects = &[]ibclient.IBObject{ 716 createMockInfobloxObject("deleted.example.com", endpoint.RecordTypeA, "121.212.121.212"), 717 createMockInfobloxObject("deleted.example.com", endpoint.RecordTypeTXT, "test-deleting-txt"), 718 createMockInfobloxObject("deleted.example.com", endpoint.RecordTypePTR, "121.212.121.212"), 719 createMockInfobloxObject("deletedcname.example.com", endpoint.RecordTypeCNAME, "other.com"), 720 createMockInfobloxObject("old.example.com", endpoint.RecordTypeA, "121.212.121.212"), 721 createMockInfobloxObject("oldcname.example.com", endpoint.RecordTypeCNAME, "other.com"), 722 } 723 724 providerCfg := newInfobloxProvider( 725 endpoint.NewDomainFilter([]string{""}), 726 provider.NewZoneIDFilter([]string{""}), 727 "", 728 dryRun, 729 createPTR, 730 client, 731 ) 732 733 createRecords := []*endpoint.Endpoint{ 734 endpoint.NewEndpoint("example.com", endpoint.RecordTypeA, "1.2.3.4"), 735 endpoint.NewEndpoint("example.com", endpoint.RecordTypeTXT, "tag"), 736 endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeA, "1.2.3.4"), 737 endpoint.NewEndpoint("foo.example.com", endpoint.RecordTypeTXT, "tag"), 738 endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeCNAME, "other.com"), 739 endpoint.NewEndpoint("bar.example.com", endpoint.RecordTypeTXT, "tag"), 740 endpoint.NewEndpoint("other.com", endpoint.RecordTypeA, "5.6.7.8"), 741 endpoint.NewEndpoint("other.com", endpoint.RecordTypeTXT, "tag"), 742 endpoint.NewEndpoint("nope.com", endpoint.RecordTypeA, "4.4.4.4"), 743 endpoint.NewEndpoint("nope.com", endpoint.RecordTypeTXT, "tag"), 744 endpoint.NewEndpoint("multiple.example.com", endpoint.RecordTypeA, "1.2.3.4,3.4.5.6,8.9.10.11"), 745 endpoint.NewEndpoint("multiple.example.com", endpoint.RecordTypeTXT, "tag-multiple-A-records"), 746 } 747 748 updateOldRecords := []*endpoint.Endpoint{ 749 endpoint.NewEndpoint("old.example.com", endpoint.RecordTypeA, "121.212.121.212"), 750 endpoint.NewEndpoint("oldcname.example.com", endpoint.RecordTypeCNAME, "other.com"), 751 endpoint.NewEndpoint("old.nope.com", endpoint.RecordTypeA, "121.212.121.212"), 752 } 753 754 updateNewRecords := []*endpoint.Endpoint{ 755 endpoint.NewEndpoint("new.example.com", endpoint.RecordTypeA, "111.222.111.222"), 756 endpoint.NewEndpoint("newcname.example.com", endpoint.RecordTypeCNAME, "other.com"), 757 endpoint.NewEndpoint("new.nope.com", endpoint.RecordTypeA, "222.111.222.111"), 758 } 759 760 deleteRecords := []*endpoint.Endpoint{ 761 endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypeA, "121.212.121.212"), 762 endpoint.NewEndpoint("deletedcname.example.com", endpoint.RecordTypeCNAME, "other.com"), 763 endpoint.NewEndpoint("deleted.nope.com", endpoint.RecordTypeA, "222.111.222.111"), 764 } 765 766 if createPTR { 767 deleteRecords = append(deleteRecords, endpoint.NewEndpoint("deleted.example.com", endpoint.RecordTypePTR, "121.212.121.212")) 768 } 769 770 changes := &plan.Changes{ 771 Create: createRecords, 772 UpdateNew: updateNewRecords, 773 UpdateOld: updateOldRecords, 774 Delete: deleteRecords, 775 } 776 777 if err := providerCfg.ApplyChanges(context.Background(), changes); err != nil { 778 t.Fatal(err) 779 } 780 } 781 782 func TestInfobloxZones(t *testing.T) { 783 client := mockIBConnector{ 784 mockInfobloxZones: &[]ibclient.ZoneAuth{ 785 createMockInfobloxZone("example.com"), 786 createMockInfobloxZone("lvl1-1.example.com"), 787 createMockInfobloxZone("lvl2-1.lvl1-1.example.com"), 788 createMockInfobloxZone("1.2.3.0/24"), 789 }, 790 mockInfobloxObjects: &[]ibclient.IBObject{}, 791 } 792 793 providerCfg := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com", "1.2.3.0/24"}), provider.NewZoneIDFilter([]string{""}), "", true, false, &client) 794 zones, _ := providerCfg.zones() 795 var emptyZoneAuth *ibclient.ZoneAuth 796 assert.Equal(t, providerCfg.findZone(zones, "example.com").Fqdn, "example.com") 797 assert.Equal(t, providerCfg.findZone(zones, "nomatch-example.com"), emptyZoneAuth) 798 assert.Equal(t, providerCfg.findZone(zones, "nginx.example.com").Fqdn, "example.com") 799 assert.Equal(t, providerCfg.findZone(zones, "lvl1-1.example.com").Fqdn, "lvl1-1.example.com") 800 assert.Equal(t, providerCfg.findZone(zones, "lvl1-2.example.com").Fqdn, "example.com") 801 assert.Equal(t, providerCfg.findZone(zones, "lvl2-1.lvl1-1.example.com").Fqdn, "lvl2-1.lvl1-1.example.com") 802 assert.Equal(t, providerCfg.findZone(zones, "lvl2-2.lvl1-1.example.com").Fqdn, "lvl1-1.example.com") 803 assert.Equal(t, providerCfg.findZone(zones, "lvl2-2.lvl1-2.example.com").Fqdn, "example.com") 804 assert.Equal(t, providerCfg.findZone(zones, "1.2.3.0/24").Fqdn, "1.2.3.0/24") 805 } 806 807 func TestInfobloxReverseZones(t *testing.T) { 808 client := mockIBConnector{ 809 mockInfobloxZones: &[]ibclient.ZoneAuth{ 810 createMockInfobloxZone("example.com"), 811 createMockInfobloxZone("1.2.3.0/24"), 812 createMockInfobloxZone("10.0.0.0/8"), 813 }, 814 mockInfobloxObjects: &[]ibclient.IBObject{}, 815 } 816 817 providerCfg := newInfobloxProvider(endpoint.NewDomainFilter([]string{"example.com", "1.2.3.0/24", "10.0.0.0/8"}), provider.NewZoneIDFilter([]string{""}), "", true, false, &client) 818 zones, _ := providerCfg.zones() 819 var emptyZoneAuth *ibclient.ZoneAuth 820 assert.Equal(t, providerCfg.findReverseZone(zones, "nomatch-example.com"), emptyZoneAuth) 821 assert.Equal(t, providerCfg.findReverseZone(zones, "192.168.0.1"), emptyZoneAuth) 822 assert.Equal(t, providerCfg.findReverseZone(zones, "1.2.3.4").Fqdn, "1.2.3.0/24") 823 assert.Equal(t, providerCfg.findReverseZone(zones, "10.28.29.30").Fqdn, "10.0.0.0/8") 824 } 825 826 func TestExtendedRequestFDQDRegExBuilder(t *testing.T) { 827 hostCfg := ibclient.HostConfig{ 828 Host: "localhost", 829 Port: "8080", 830 Version: "2.3.1", 831 } 832 833 authCfg := ibclient.AuthConfig{ 834 Username: "user", 835 Password: "abcd", 836 } 837 838 requestBuilder := NewExtendedRequestBuilder(0, "^staging.*test.com$", "") 839 requestBuilder.Init(hostCfg, authCfg) 840 841 obj := ibclient.NewZoneAuth(ibclient.ZoneAuth{}) 842 843 req, _ := requestBuilder.BuildRequest(ibclient.GET, obj, "", &ibclient.QueryParams{}) 844 845 assert.True(t, req.URL.Query().Get("fqdn~") == "^staging.*test.com$") 846 847 req, _ = requestBuilder.BuildRequest(ibclient.CREATE, obj, "", &ibclient.QueryParams{}) 848 849 assert.True(t, req.URL.Query().Get("fqdn~") == "") 850 } 851 852 func TestExtendedRequestNameRegExBuilder(t *testing.T) { 853 hostCfg := ibclient.HostConfig{ 854 Host: "localhost", 855 Port: "8080", 856 Version: "2.3.1", 857 } 858 859 authCfg := ibclient.AuthConfig{ 860 Username: "user", 861 Password: "abcd", 862 } 863 864 requestBuilder := NewExtendedRequestBuilder(0, "", "^staging.*test.com$") 865 requestBuilder.Init(hostCfg, authCfg) 866 867 obj := ibclient.NewEmptyRecordCNAME() 868 869 req, _ := requestBuilder.BuildRequest(ibclient.GET, obj, "", &ibclient.QueryParams{}) 870 871 assert.True(t, req.URL.Query().Get("name~") == "^staging.*test.com$") 872 873 req, _ = requestBuilder.BuildRequest(ibclient.CREATE, obj, "", &ibclient.QueryParams{}) 874 875 assert.True(t, req.URL.Query().Get("name~") == "") 876 } 877 878 func TestExtendedRequestMaxResultsBuilder(t *testing.T) { 879 hostCfg := ibclient.HostConfig{ 880 Host: "localhost", 881 Port: "8080", 882 Version: "2.3.1", 883 } 884 885 authCfg := ibclient.AuthConfig{ 886 Username: "user", 887 Password: "abcd", 888 } 889 890 requestBuilder := NewExtendedRequestBuilder(54321, "", "") 891 requestBuilder.Init(hostCfg, authCfg) 892 893 obj := ibclient.NewEmptyRecordCNAME() 894 obj.Zone = "foo.bar.com" 895 896 req, _ := requestBuilder.BuildRequest(ibclient.GET, obj, "", &ibclient.QueryParams{}) 897 898 assert.True(t, req.URL.Query().Get("_max_results") == "54321") 899 900 req, _ = requestBuilder.BuildRequest(ibclient.CREATE, obj, "", &ibclient.QueryParams{}) 901 902 assert.True(t, req.URL.Query().Get("_max_results") == "") 903 } 904 905 func TestGetObject(t *testing.T) { 906 hostCfg := ibclient.HostConfig{} 907 authCfg := ibclient.AuthConfig{} 908 transportConfig := ibclient.TransportConfig{} 909 requestBuilder := NewExtendedRequestBuilder(1000, "mysite.com", "") 910 requestor := mockRequestor{} 911 client, _ := ibclient.NewConnector(hostCfg, authCfg, transportConfig, requestBuilder, &requestor) 912 913 providerConfig := newInfobloxProvider(endpoint.NewDomainFilter([]string{"mysite.com"}), provider.NewZoneIDFilter([]string{""}), "", true, true, client) 914 915 providerConfig.deleteRecords(infobloxChangeMap{ 916 "myzone.com": []*endpoint.Endpoint{ 917 endpoint.NewEndpoint("deletethisrecord.com", endpoint.RecordTypeA, "1.2.3.4"), 918 }, 919 }) 920 921 requestQuery := requestor.request.URL.Query() 922 assert.True(t, requestQuery.Has("name"), "Expected the request to filter objects by name") 923 } 924 925 // Mock requestor that doesn't send request 926 type mockRequestor struct { 927 request *http.Request 928 } 929 930 func (r *mockRequestor) Init(ibclient.AuthConfig, ibclient.TransportConfig) {} 931 func (r *mockRequestor) SendRequest(req *http.Request) (res []byte, err error) { 932 res = []byte("[{}]") 933 r.request = req 934 return 935 } 936 937 func validateEndpoints(t *testing.T, endpoints []*endpoint.Endpoint, expected []*endpoint.Endpoint) { 938 assert.True(t, testutils.SameEndpoints(endpoints, expected), "actual and expected endpoints don't match. %s:%s", endpoints, expected) 939 }