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  }