sigs.k8s.io/external-dns@v0.14.1/provider/gandi/gandi_test.go (about) 1 /* 2 Copyright 2021 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 gandi 15 16 import ( 17 "context" 18 "fmt" 19 "os" 20 "testing" 21 22 "github.com/go-gandi/go-gandi/domain" 23 "github.com/go-gandi/go-gandi/livedns" 24 "github.com/maxatome/go-testdeep/td" 25 "github.com/stretchr/testify/assert" 26 27 "sigs.k8s.io/external-dns/endpoint" 28 "sigs.k8s.io/external-dns/internal/testutils" 29 "sigs.k8s.io/external-dns/plan" 30 ) 31 32 type MockAction struct { 33 Name string 34 FQDN string 35 Record livedns.DomainRecord 36 } 37 38 type mockGandiClient struct { 39 Actions []MockAction 40 FunctionToFail string `default:""` 41 RecordsToReturn []livedns.DomainRecord 42 } 43 44 const ( 45 domainUriPrefix = "https://api.gandi.net/v5/domain/domains/" 46 exampleDotComUri = domainUriPrefix + "example.com" 47 exampleDotNetUri = domainUriPrefix + "example.net" 48 ) 49 50 // Mock all methods 51 52 func (m *mockGandiClient) GetDomainRecords(fqdn string) (records []livedns.DomainRecord, err error) { 53 m.Actions = append(m.Actions, MockAction{ 54 Name: "GetDomainRecords", 55 FQDN: fqdn, 56 }) 57 58 if m.FunctionToFail == "GetDomainRecords" { 59 return nil, fmt.Errorf("injected error") 60 } 61 62 return m.RecordsToReturn, nil 63 } 64 65 func (m *mockGandiClient) CreateDomainRecord(fqdn, name, recordtype string, ttl int, values []string) (response standardResponse, err error) { 66 m.Actions = append(m.Actions, MockAction{ 67 Name: "CreateDomainRecord", 68 FQDN: fqdn, 69 Record: livedns.DomainRecord{ 70 RrsetType: recordtype, 71 RrsetTTL: ttl, 72 RrsetName: name, 73 RrsetValues: values, 74 }, 75 }) 76 77 if m.FunctionToFail == "CreateDomainRecord" { 78 return standardResponse{}, fmt.Errorf("injected error") 79 } 80 81 return standardResponse{}, nil 82 } 83 84 func (m *mockGandiClient) DeleteDomainRecord(fqdn, name, recordtype string) (err error) { 85 m.Actions = append(m.Actions, MockAction{ 86 Name: "DeleteDomainRecord", 87 FQDN: fqdn, 88 Record: livedns.DomainRecord{ 89 RrsetType: recordtype, 90 RrsetName: name, 91 }, 92 }) 93 94 if m.FunctionToFail == "DeleteDomainRecord" { 95 return fmt.Errorf("injected error") 96 } 97 98 return nil 99 } 100 101 func (m *mockGandiClient) UpdateDomainRecordByNameAndType(fqdn, name, recordtype string, ttl int, values []string) (response standardResponse, err error) { 102 m.Actions = append(m.Actions, MockAction{ 103 Name: "UpdateDomainRecordByNameAndType", 104 FQDN: fqdn, 105 Record: livedns.DomainRecord{ 106 RrsetType: recordtype, 107 RrsetTTL: ttl, 108 RrsetName: name, 109 RrsetValues: values, 110 }, 111 }) 112 113 if m.FunctionToFail == "UpdateDomainRecordByNameAndType" { 114 return standardResponse{}, fmt.Errorf("injected error") 115 } 116 117 return standardResponse{}, nil 118 } 119 120 func (m *mockGandiClient) ListDomains() (domains []domain.ListResponse, err error) { 121 m.Actions = append(m.Actions, MockAction{ 122 Name: "ListDomains", 123 }) 124 125 if m.FunctionToFail == "ListDomains" { 126 return []domain.ListResponse{}, fmt.Errorf("injected error") 127 } 128 129 return []domain.ListResponse{ 130 // Tests are using example.com 131 { 132 FQDN: "example.com", 133 FQDNUnicode: "example.com", 134 Href: exampleDotComUri, 135 ID: "b3e9c271-1c29-4441-97d9-bc021a7ac7c3", 136 NameServer: &domain.NameServerConfig{ 137 Current: gandiLiveDNSProvider, 138 }, 139 TLD: "com", 140 }, 141 // example.net returns "other" as NameServer, so it is ignored 142 { 143 FQDN: "example.net", 144 FQDNUnicode: "example.net", 145 Href: exampleDotNetUri, 146 ID: "dc78c1d8-6143-4edb-93bc-3a20d8bc3570", 147 NameServer: &domain.NameServerConfig{ 148 Current: "other", 149 }, 150 TLD: "net", 151 }, 152 }, nil 153 } 154 155 // Tests 156 157 func TestNewGandiProvider(t *testing.T) { 158 _ = os.Setenv("GANDI_KEY", "myGandiKey") 159 provider, err := NewGandiProvider(context.Background(), endpoint.NewDomainFilter([]string{"example.com"}), true) 160 if err != nil { 161 t.Errorf("failed : %s", err) 162 } 163 assert.Equal(t, true, provider.DryRun) 164 165 _ = os.Setenv("GANDI_PAT", "myGandiPAT") 166 provider, err = NewGandiProvider(context.Background(), endpoint.NewDomainFilter([]string{"example.com"}), true) 167 if err != nil { 168 t.Errorf("failed : %s", err) 169 } 170 assert.Equal(t, true, provider.DryRun) 171 172 _ = os.Unsetenv("GANDI_KEY") 173 provider, err = NewGandiProvider(context.Background(), endpoint.NewDomainFilter([]string{"example.com"}), true) 174 if err != nil { 175 t.Errorf("failed : %s", err) 176 } 177 assert.Equal(t, true, provider.DryRun) 178 179 _ = os.Setenv("GANDI_SHARING_ID", "aSharingId") 180 provider, err = NewGandiProvider(context.Background(), endpoint.NewDomainFilter([]string{"example.com"}), false) 181 if err != nil { 182 t.Errorf("failed : %s", err) 183 } 184 assert.Equal(t, false, provider.DryRun) 185 186 _ = os.Unsetenv("GANDI_PAT") 187 _, err = NewGandiProvider(context.Background(), endpoint.NewDomainFilter([]string{"example.com"}), true) 188 if err == nil { 189 t.Errorf("expected to fail") 190 } 191 } 192 193 func TestGandiProvider_RecordsReturnsCorrectEndpoints(t *testing.T) { 194 mockedClient := &mockGandiClient{ 195 RecordsToReturn: []livedns.DomainRecord{ 196 { 197 RrsetType: endpoint.RecordTypeCNAME, 198 RrsetTTL: 600, 199 RrsetName: "@", 200 RrsetHref: exampleDotComUri + "/records/%40/A", 201 RrsetValues: []string{"192.168.0.1"}, 202 }, 203 { 204 RrsetType: endpoint.RecordTypeCNAME, 205 RrsetTTL: 600, 206 RrsetName: "www", 207 RrsetHref: exampleDotComUri + "/records/www/CNAME", 208 RrsetValues: []string{"lb.example.com"}, 209 }, 210 { 211 RrsetType: endpoint.RecordTypeA, 212 RrsetTTL: 600, 213 RrsetName: "test", 214 RrsetHref: exampleDotComUri + "/records/test/A", 215 RrsetValues: []string{"192.168.0.2"}, 216 }, 217 }, 218 } 219 220 mockedProvider := &GandiProvider{ 221 DomainClient: mockedClient, 222 LiveDNSClient: mockedClient, 223 } 224 225 actualEndpoints, err := mockedProvider.Records(context.Background()) 226 if err != nil { 227 t.Errorf("should not fail, %s", err) 228 } 229 230 expectedEndpoints := []*endpoint.Endpoint{ 231 { 232 RecordType: endpoint.RecordTypeCNAME, 233 DNSName: "example.com", 234 Targets: endpoint.Targets{"192.168.0.1"}, 235 RecordTTL: 600, 236 }, 237 { 238 RecordType: endpoint.RecordTypeCNAME, 239 DNSName: "www.example.com", 240 Targets: endpoint.Targets{"lb.example.com"}, 241 RecordTTL: 600, 242 }, 243 { 244 RecordType: endpoint.RecordTypeA, 245 DNSName: "test.example.com", 246 Targets: endpoint.Targets{"192.168.0.2"}, 247 RecordTTL: 600, 248 }, 249 } 250 251 assert.Equal(t, len(expectedEndpoints), len(actualEndpoints)) 252 // we could use testutils.SameEndpoints (plural), but this makes it easier to identify which case is failing 253 for i := range actualEndpoints { 254 if !testutils.SameEndpoint(expectedEndpoints[i], actualEndpoints[i]) { 255 t.Errorf("should be equal, expected:%v <> actual:%v", expectedEndpoints[i], actualEndpoints[i]) 256 257 } 258 } 259 } 260 261 func TestGandiProvider_RecordsOnFilteredDomainsShouldYieldNoEndpoints(t *testing.T) { 262 mockedClient := &mockGandiClient{ 263 RecordsToReturn: []livedns.DomainRecord{ 264 { 265 RrsetType: endpoint.RecordTypeCNAME, 266 RrsetTTL: 600, 267 RrsetName: "@", 268 RrsetHref: exampleDotComUri + "/records/test/MX", 269 RrsetValues: []string{"192.168.0.1"}, 270 }, 271 }, 272 } 273 274 mockedProvider := &GandiProvider{ 275 DomainClient: mockedClient, 276 LiveDNSClient: mockedClient, 277 domainFilter: endpoint.NewDomainFilterWithExclusions([]string{}, []string{"example.com"}), 278 } 279 280 endpoints, _ := mockedProvider.Records(context.Background()) 281 assert.Empty(t, endpoints) 282 } 283 284 func TestGandiProvider_RecordsWithUnsupportedTypesAreNotReturned(t *testing.T) { 285 mockedClient := &mockGandiClient{ 286 RecordsToReturn: []livedns.DomainRecord{ 287 { 288 RrsetType: "MX", 289 RrsetTTL: 360, 290 RrsetName: "@", 291 RrsetHref: exampleDotComUri + "/records/%40/A", 292 RrsetValues: []string{"smtp.example.com"}, 293 }, 294 }, 295 } 296 297 mockedProvider := &GandiProvider{ 298 DomainClient: mockedClient, 299 LiveDNSClient: mockedClient, 300 } 301 302 endpoints, _ := mockedProvider.Records(context.Background()) 303 assert.Empty(t, endpoints) 304 } 305 306 func TestGandiProvider_ApplyChangesMakesExpectedAPICalls(t *testing.T) { 307 changes := &plan.Changes{} 308 mockedClient := &mockGandiClient{} 309 mockedProvider := &GandiProvider{ 310 DomainClient: mockedClient, 311 LiveDNSClient: mockedClient, 312 } 313 314 changes.Create = []*endpoint.Endpoint{ 315 { 316 DNSName: "test2.example.com", 317 Targets: endpoint.Targets{"192.168.0.1"}, 318 RecordType: "A", 319 RecordTTL: 666, 320 }, 321 } 322 changes.UpdateNew = []*endpoint.Endpoint{ 323 { 324 DNSName: "test3.example.com", 325 Targets: endpoint.Targets{"192.168.0.2"}, 326 RecordType: "A", 327 RecordTTL: 777, 328 }, 329 { 330 DNSName: "example.com.example.com", 331 Targets: endpoint.Targets{"lb-2.example.net"}, 332 RecordType: "CNAME", 333 RecordTTL: 777, 334 }, 335 } 336 changes.Delete = []*endpoint.Endpoint{ 337 { 338 DNSName: "test4.example.com", 339 Targets: endpoint.Targets{"192.168.0.3"}, 340 RecordType: "A", 341 }, 342 } 343 344 err := mockedProvider.ApplyChanges(context.Background(), changes) 345 if err != nil { 346 t.Errorf("should not fail, %s", err) 347 } 348 349 td.Cmp(t, mockedClient.Actions, []MockAction{ 350 { 351 Name: "ListDomains", 352 }, 353 { 354 Name: "CreateDomainRecord", 355 FQDN: "example.com", 356 Record: livedns.DomainRecord{ 357 RrsetType: endpoint.RecordTypeA, 358 RrsetName: "test2", 359 RrsetValues: []string{"192.168.0.1"}, 360 RrsetTTL: 666, 361 }, 362 }, 363 { 364 Name: "UpdateDomainRecordByNameAndType", 365 FQDN: "example.com", 366 Record: livedns.DomainRecord{ 367 RrsetType: endpoint.RecordTypeA, 368 RrsetName: "test3", 369 RrsetValues: []string{"192.168.0.2"}, 370 RrsetTTL: 777, 371 }, 372 }, 373 { 374 Name: "UpdateDomainRecordByNameAndType", 375 FQDN: "example.com", 376 Record: livedns.DomainRecord{ 377 RrsetType: endpoint.RecordTypeCNAME, 378 RrsetName: "example.com", 379 RrsetValues: []string{"lb-2.example.net."}, 380 RrsetTTL: 777, 381 }, 382 }, 383 { 384 Name: "DeleteDomainRecord", 385 FQDN: "example.com", 386 Record: livedns.DomainRecord{ 387 RrsetType: endpoint.RecordTypeA, 388 RrsetName: "test4", 389 }, 390 }, 391 }) 392 } 393 394 func TestGandiProvider_ApplyChangesRespectsDryRun(t *testing.T) { 395 changes := &plan.Changes{} 396 mockedClient := &mockGandiClient{} 397 mockedProvider := &GandiProvider{ 398 DryRun: true, 399 DomainClient: mockedClient, 400 LiveDNSClient: mockedClient, 401 } 402 403 changes.Create = []*endpoint.Endpoint{{DNSName: "test2.example.com", Targets: endpoint.Targets{"192.168.0.1"}, RecordType: "A", RecordTTL: 666}} 404 changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "test3.example.com", Targets: endpoint.Targets{"192.168.0.2"}, RecordType: "A", RecordTTL: 777}} 405 changes.Delete = []*endpoint.Endpoint{{DNSName: "test4.example.com", Targets: endpoint.Targets{"192.168.0.3"}, RecordType: "A"}} 406 407 mockedProvider.ApplyChanges(context.Background(), changes) 408 409 td.Cmp(t, mockedClient.Actions, []MockAction{ 410 { 411 Name: "ListDomains", 412 }, 413 }) 414 } 415 416 func TestGandiProvider_ApplyChangesWithEmptyResultDoesNothing(t *testing.T) { 417 changes := &plan.Changes{} 418 mockedClient := &mockGandiClient{} 419 mockedProvider := &GandiProvider{ 420 DomainClient: mockedClient, 421 LiveDNSClient: mockedClient, 422 } 423 424 mockedProvider.ApplyChanges(context.Background(), changes) 425 426 assert.Empty(t, mockedClient.Actions) 427 } 428 429 func TestGandiProvider_ApplyChangesWithUnknownDomainDoesNoUpdate(t *testing.T) { 430 changes := &plan.Changes{} 431 mockedClient := &mockGandiClient{} 432 mockedProvider := &GandiProvider{ 433 DomainClient: mockedClient, 434 LiveDNSClient: mockedClient, 435 } 436 437 changes.Create = []*endpoint.Endpoint{ 438 { 439 DNSName: "test.example.net", 440 Targets: endpoint.Targets{"192.168.0.1"}, 441 RecordType: "A", 442 RecordTTL: 666, 443 }, 444 } 445 446 mockedProvider.ApplyChanges(context.Background(), changes) 447 448 td.Cmp(t, mockedClient.Actions, []MockAction{ 449 { 450 Name: "ListDomains", 451 }, 452 }) 453 } 454 455 func TestGandiProvider_FailingCases(t *testing.T) { 456 changes := &plan.Changes{} 457 changes.Create = []*endpoint.Endpoint{{DNSName: "test2.example.com", Targets: endpoint.Targets{"192.168.0.1"}, RecordType: "A", RecordTTL: 666}} 458 changes.UpdateNew = []*endpoint.Endpoint{{DNSName: "test3.example.com", Targets: endpoint.Targets{"192.168.0.2"}, RecordType: "A", RecordTTL: 777}} 459 changes.Delete = []*endpoint.Endpoint{{DNSName: "test4.example.com", Targets: endpoint.Targets{"192.168.0.3"}, RecordType: "A"}} 460 461 // Failing ListDomains API call creates an error when calling Records 462 mockedClient := &mockGandiClient{ 463 FunctionToFail: "ListDomains", 464 } 465 mockedProvider := &GandiProvider{ 466 DomainClient: mockedClient, 467 LiveDNSClient: mockedClient, 468 } 469 470 _, err := mockedProvider.Records(context.Background()) 471 if err == nil { 472 t.Error("should have failed") 473 } 474 475 // Failing GetDomainRecords API call creates an error when calling Records 476 mockedClient = &mockGandiClient{ 477 FunctionToFail: "GetDomainRecords", 478 } 479 mockedProvider = &GandiProvider{ 480 DomainClient: mockedClient, 481 LiveDNSClient: mockedClient, 482 } 483 484 _, err = mockedProvider.Records(context.Background()) 485 if err == nil { 486 t.Error("should have failed") 487 } 488 489 // Failing ListDomains API call creates an error when calling ApplyChanges 490 mockedClient = &mockGandiClient{ 491 FunctionToFail: "ListDomains", 492 } 493 mockedProvider = &GandiProvider{ 494 DomainClient: mockedClient, 495 LiveDNSClient: mockedClient, 496 } 497 498 err = mockedProvider.ApplyChanges(context.Background(), changes) 499 if err == nil { 500 t.Error("should have failed") 501 } 502 503 // Failing CreateDomainRecord API call creates an error when calling ApplyChanges 504 mockedClient = &mockGandiClient{ 505 FunctionToFail: "CreateDomainRecord", 506 } 507 mockedProvider = &GandiProvider{ 508 DomainClient: mockedClient, 509 LiveDNSClient: mockedClient, 510 } 511 512 err = mockedProvider.ApplyChanges(context.Background(), changes) 513 if err == nil { 514 t.Error("should have failed") 515 } 516 517 // Failing DeleteDomainRecord API call creates an error when calling ApplyChanges 518 mockedClient = &mockGandiClient{ 519 FunctionToFail: "DeleteDomainRecord", 520 } 521 mockedProvider = &GandiProvider{ 522 DomainClient: mockedClient, 523 LiveDNSClient: mockedClient, 524 } 525 526 err = mockedProvider.ApplyChanges(context.Background(), changes) 527 if err == nil { 528 t.Error("should have failed") 529 } 530 531 // Failing UpdateDomainRecordByNameAndType API call creates an error when calling ApplyChanges 532 mockedClient = &mockGandiClient{ 533 FunctionToFail: "UpdateDomainRecordByNameAndType", 534 } 535 mockedProvider = &GandiProvider{ 536 DomainClient: mockedClient, 537 LiveDNSClient: mockedClient, 538 } 539 540 err = mockedProvider.ApplyChanges(context.Background(), changes) 541 if err == nil { 542 t.Error("should have failed") 543 } 544 }