sigs.k8s.io/external-dns@v0.14.1/provider/scaleway/scaleway_test.go (about)

     1  /*
     2  Copyright 2020 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 scaleway
    18  
    19  import (
    20  	"context"
    21  	"os"
    22  	"reflect"
    23  	"testing"
    24  
    25  	domain "github.com/scaleway/scaleway-sdk-go/api/domain/v2beta1"
    26  	"github.com/scaleway/scaleway-sdk-go/scw"
    27  	"github.com/stretchr/testify/assert"
    28  	"github.com/stretchr/testify/require"
    29  	"sigs.k8s.io/external-dns/endpoint"
    30  	"sigs.k8s.io/external-dns/plan"
    31  )
    32  
    33  type mockScalewayDomain struct {
    34  	*domain.API
    35  }
    36  
    37  func (m *mockScalewayDomain) ListDNSZones(req *domain.ListDNSZonesRequest, opts ...scw.RequestOption) (*domain.ListDNSZonesResponse, error) {
    38  	return &domain.ListDNSZonesResponse{
    39  		DNSZones: []*domain.DNSZone{
    40  			{
    41  				Domain:    "example.com",
    42  				Subdomain: "",
    43  			},
    44  			{
    45  				Domain:    "example.com",
    46  				Subdomain: "test",
    47  			},
    48  			{
    49  				Domain:    "dummy.me",
    50  				Subdomain: "",
    51  			},
    52  			{
    53  				Domain:    "dummy.me",
    54  				Subdomain: "test",
    55  			},
    56  		},
    57  	}, nil
    58  }
    59  
    60  func (m *mockScalewayDomain) ListDNSZoneRecords(req *domain.ListDNSZoneRecordsRequest, opts ...scw.RequestOption) (*domain.ListDNSZoneRecordsResponse, error) {
    61  	records := []*domain.Record{}
    62  	if req.DNSZone == "example.com" {
    63  		records = []*domain.Record{
    64  			{
    65  				Data:     "1.1.1.1",
    66  				Name:     "one",
    67  				TTL:      300,
    68  				Priority: 0,
    69  				Type:     domain.RecordTypeA,
    70  			},
    71  			{
    72  				Data:     "1.1.1.2",
    73  				Name:     "two",
    74  				TTL:      300,
    75  				Priority: 0,
    76  				Type:     domain.RecordTypeA,
    77  			},
    78  			{
    79  				Data:     "1.1.1.3",
    80  				Name:     "two",
    81  				TTL:      300,
    82  				Priority: 0,
    83  				Type:     domain.RecordTypeA,
    84  			},
    85  		}
    86  	} else if req.DNSZone == "test.example.com" {
    87  		records = []*domain.Record{
    88  			{
    89  				Data:     "1.1.1.1",
    90  				Name:     "",
    91  				TTL:      300,
    92  				Priority: 0,
    93  				Type:     domain.RecordTypeA,
    94  			},
    95  			{
    96  				Data:     "test.example.com.",
    97  				Name:     "two",
    98  				TTL:      600,
    99  				Priority: 30,
   100  				Type:     domain.RecordTypeCNAME,
   101  			},
   102  		}
   103  	}
   104  	return &domain.ListDNSZoneRecordsResponse{
   105  		Records: records,
   106  	}, nil
   107  }
   108  
   109  func (m *mockScalewayDomain) UpdateDNSZoneRecords(req *domain.UpdateDNSZoneRecordsRequest, opts ...scw.RequestOption) (*domain.UpdateDNSZoneRecordsResponse, error) {
   110  	return nil, nil
   111  }
   112  
   113  func TestScalewayProvider_NewScalewayProvider(t *testing.T) {
   114  	profile := `profiles:
   115    foo:
   116      access_key: SCWXXXXXXXXXXXXXXXXX
   117      secret_key: 11111111-1111-1111-1111-111111111111
   118  `
   119  	tmpDir := t.TempDir()
   120  	err := os.WriteFile(tmpDir+"/config.yaml", []byte(profile), 0600)
   121  	if err != nil {
   122  		t.Errorf("failed : %s", err)
   123  	}
   124  	_ = os.Setenv(scw.ScwActiveProfileEnv, "foo")
   125  	_ = os.Setenv(scw.ScwConfigPathEnv, tmpDir+"/config.yaml")
   126  	_, err = NewScalewayProvider(context.TODO(), endpoint.NewDomainFilter([]string{"example.com"}), true)
   127  	if err != nil {
   128  		t.Errorf("failed : %s", err)
   129  	}
   130  
   131  	_ = os.Setenv(scw.ScwAccessKeyEnv, "SCWXXXXXXXXXXXXXXXXX")
   132  	_ = os.Setenv(scw.ScwSecretKeyEnv, "11111111-1111-1111-1111-111111111111")
   133  	_, err = NewScalewayProvider(context.TODO(), endpoint.NewDomainFilter([]string{"example.com"}), true)
   134  	if err != nil {
   135  		t.Errorf("failed : %s", err)
   136  	}
   137  
   138  	_ = os.Unsetenv(scw.ScwSecretKeyEnv)
   139  	_, err = NewScalewayProvider(context.TODO(), endpoint.NewDomainFilter([]string{"example.com"}), true)
   140  	if err == nil {
   141  		t.Errorf("expected to fail")
   142  	}
   143  
   144  	_ = os.Setenv(scw.ScwSecretKeyEnv, "dummy")
   145  	_, err = NewScalewayProvider(context.TODO(), endpoint.NewDomainFilter([]string{"example.com"}), true)
   146  	if err == nil {
   147  		t.Errorf("expected to fail")
   148  	}
   149  
   150  	_ = os.Unsetenv(scw.ScwAccessKeyEnv)
   151  	_ = os.Setenv(scw.ScwSecretKeyEnv, "11111111-1111-1111-1111-111111111111")
   152  	_, err = NewScalewayProvider(context.TODO(), endpoint.NewDomainFilter([]string{"example.com"}), true)
   153  	if err == nil {
   154  		t.Errorf("expected to fail")
   155  	}
   156  
   157  	_ = os.Setenv(scw.ScwAccessKeyEnv, "dummy")
   158  	_, err = NewScalewayProvider(context.TODO(), endpoint.NewDomainFilter([]string{"example.com"}), true)
   159  	if err == nil {
   160  		t.Errorf("expected to fail")
   161  	}
   162  }
   163  
   164  func TestScalewayProvider_OptionnalConfigFile(t *testing.T) {
   165  	_ = os.Setenv(scw.ScwAccessKeyEnv, "SCWXXXXXXXXXXXXXXXXX")
   166  	_ = os.Setenv(scw.ScwSecretKeyEnv, "11111111-1111-1111-1111-111111111111")
   167  
   168  	_, err := NewScalewayProvider(context.TODO(), endpoint.NewDomainFilter([]string{"example.com"}), true)
   169  	assert.NoError(t, err)
   170  }
   171  
   172  func TestScalewayProvider_AdjustEndpoints(t *testing.T) {
   173  	provider := &ScalewayProvider{}
   174  
   175  	before := []*endpoint.Endpoint{
   176  		{
   177  			DNSName:    "one.example.com",
   178  			RecordTTL:  300,
   179  			RecordType: "A",
   180  			Targets:    []string{"1.1.1.1"},
   181  			ProviderSpecific: endpoint.ProviderSpecific{
   182  				{
   183  					Name:  scalewayPriorityKey,
   184  					Value: "0",
   185  				},
   186  			},
   187  		},
   188  		{
   189  			DNSName:    "two.example.com",
   190  			RecordTTL:  0,
   191  			RecordType: "A",
   192  			Targets:    []string{"1.1.1.1"},
   193  			ProviderSpecific: endpoint.ProviderSpecific{
   194  				{
   195  					Name:  scalewayPriorityKey,
   196  					Value: "10",
   197  				},
   198  			},
   199  		},
   200  		{
   201  			DNSName:          "three.example.com",
   202  			RecordTTL:        600,
   203  			RecordType:       "A",
   204  			Targets:          []string{"1.1.1.1"},
   205  			ProviderSpecific: endpoint.ProviderSpecific{},
   206  		},
   207  	}
   208  
   209  	expected := []*endpoint.Endpoint{
   210  		{
   211  			DNSName:    "one.example.com",
   212  			RecordTTL:  300,
   213  			RecordType: "A",
   214  			Targets:    []string{"1.1.1.1"},
   215  			ProviderSpecific: endpoint.ProviderSpecific{
   216  				{
   217  					Name:  scalewayPriorityKey,
   218  					Value: "0",
   219  				},
   220  			},
   221  		},
   222  		{
   223  			DNSName:    "two.example.com",
   224  			RecordTTL:  300,
   225  			RecordType: "A",
   226  			Targets:    []string{"1.1.1.1"},
   227  			ProviderSpecific: endpoint.ProviderSpecific{
   228  				{
   229  					Name:  scalewayPriorityKey,
   230  					Value: "10",
   231  				},
   232  			},
   233  		},
   234  		{
   235  			DNSName:    "three.example.com",
   236  			RecordTTL:  600,
   237  			RecordType: "A",
   238  			Targets:    []string{"1.1.1.1"},
   239  			ProviderSpecific: endpoint.ProviderSpecific{
   240  				{
   241  					Name:  scalewayPriorityKey,
   242  					Value: "0",
   243  				},
   244  			},
   245  		},
   246  	}
   247  
   248  	after, err := provider.AdjustEndpoints(before)
   249  	assert.NoError(t, err)
   250  	for i := range after {
   251  		if !checkRecordEquality(after[i], expected[i]) {
   252  			t.Errorf("got record %s instead of %s", after[i], expected[i])
   253  		}
   254  	}
   255  }
   256  
   257  func TestScalewayProvider_Zones(t *testing.T) {
   258  	mocked := mockScalewayDomain{nil}
   259  	provider := &ScalewayProvider{
   260  		domainAPI:    &mocked,
   261  		domainFilter: endpoint.NewDomainFilter([]string{"example.com"}),
   262  	}
   263  
   264  	expected := []*domain.DNSZone{
   265  		{
   266  			Domain:    "example.com",
   267  			Subdomain: "",
   268  		},
   269  		{
   270  			Domain:    "example.com",
   271  			Subdomain: "test",
   272  		},
   273  	}
   274  	zones, err := provider.Zones(context.Background())
   275  	if err != nil {
   276  		t.Fatal(err)
   277  	}
   278  	require.Len(t, zones, len(expected))
   279  	for i, zone := range zones {
   280  		assert.Equal(t, expected[i], zone)
   281  	}
   282  }
   283  
   284  func TestScalewayProvider_Records(t *testing.T) {
   285  	mocked := mockScalewayDomain{nil}
   286  	provider := &ScalewayProvider{
   287  		domainAPI:    &mocked,
   288  		domainFilter: endpoint.NewDomainFilter([]string{"example.com"}),
   289  	}
   290  
   291  	expected := []*endpoint.Endpoint{
   292  		{
   293  			DNSName:    "one.example.com",
   294  			RecordTTL:  300,
   295  			RecordType: "A",
   296  			Targets:    []string{"1.1.1.1"},
   297  			ProviderSpecific: endpoint.ProviderSpecific{
   298  				{
   299  					Name:  scalewayPriorityKey,
   300  					Value: "0",
   301  				},
   302  			},
   303  		},
   304  		{
   305  			DNSName:    "two.example.com",
   306  			RecordTTL:  300,
   307  			RecordType: "A",
   308  			Targets:    []string{"1.1.1.2", "1.1.1.3"},
   309  			ProviderSpecific: endpoint.ProviderSpecific{
   310  				{
   311  					Name:  scalewayPriorityKey,
   312  					Value: "0",
   313  				},
   314  			},
   315  		},
   316  		{
   317  			DNSName:    "test.example.com",
   318  			RecordTTL:  300,
   319  			RecordType: "A",
   320  			Targets:    []string{"1.1.1.1"},
   321  			ProviderSpecific: endpoint.ProviderSpecific{
   322  				{
   323  					Name:  scalewayPriorityKey,
   324  					Value: "0",
   325  				},
   326  			},
   327  		},
   328  		{
   329  			DNSName:    "two.test.example.com",
   330  			RecordTTL:  600,
   331  			RecordType: "CNAME",
   332  			Targets:    []string{"test.example.com"},
   333  			ProviderSpecific: endpoint.ProviderSpecific{
   334  				{
   335  					Name:  scalewayPriorityKey,
   336  					Value: "30",
   337  				},
   338  			},
   339  		},
   340  	}
   341  
   342  	records, err := provider.Records(context.TODO())
   343  	if err != nil {
   344  		t.Fatal(err)
   345  	}
   346  
   347  	require.Len(t, records, len(expected))
   348  	for _, record := range records {
   349  		found := false
   350  		for _, expectedRecord := range expected {
   351  			if checkRecordEquality(record, expectedRecord) {
   352  				found = true
   353  			}
   354  		}
   355  		assert.Equal(t, true, found)
   356  	}
   357  }
   358  
   359  // this test is really ugly since we are working on maps, so array are randomly sorted
   360  // feel free to modify if you have a better idea
   361  func TestScalewayProvider_generateApplyRequests(t *testing.T) {
   362  	mocked := mockScalewayDomain{nil}
   363  	provider := &ScalewayProvider{
   364  		domainAPI:    &mocked,
   365  		domainFilter: endpoint.NewDomainFilter([]string{"example.com"}),
   366  	}
   367  
   368  	expected := []*domain.UpdateDNSZoneRecordsRequest{
   369  		{
   370  			DNSZone: "example.com",
   371  			Changes: []*domain.RecordChange{
   372  				{
   373  					Add: &domain.RecordChangeAdd{
   374  						Records: []*domain.Record{
   375  							{
   376  								Data:     "1.1.1.1",
   377  								Name:     "",
   378  								TTL:      300,
   379  								Type:     domain.RecordTypeA,
   380  								Priority: 0,
   381  							},
   382  							{
   383  								Data:     "1.1.1.2",
   384  								Name:     "",
   385  								TTL:      300,
   386  								Type:     domain.RecordTypeA,
   387  								Priority: 0,
   388  							},
   389  							{
   390  								Data:     "2.2.2.2",
   391  								Name:     "me",
   392  								TTL:      600,
   393  								Type:     domain.RecordTypeA,
   394  								Priority: 30,
   395  							},
   396  						},
   397  					},
   398  				},
   399  				{
   400  					Delete: &domain.RecordChangeDelete{
   401  						IDFields: &domain.RecordIdentifier{
   402  							Data: scw.StringPtr("3.3.3.3"),
   403  							Name: "me",
   404  							Type: domain.RecordTypeA,
   405  						},
   406  					},
   407  				},
   408  				{
   409  					Delete: &domain.RecordChangeDelete{
   410  						IDFields: &domain.RecordIdentifier{
   411  							Data: scw.StringPtr("1.1.1.1"),
   412  							Name: "here",
   413  							Type: domain.RecordTypeA,
   414  						},
   415  					},
   416  				},
   417  				{
   418  					Delete: &domain.RecordChangeDelete{
   419  						IDFields: &domain.RecordIdentifier{
   420  							Data: scw.StringPtr("1.1.1.2"),
   421  							Name: "here",
   422  							Type: domain.RecordTypeA,
   423  						},
   424  					},
   425  				},
   426  			},
   427  		},
   428  		{
   429  			DNSZone: "test.example.com",
   430  			Changes: []*domain.RecordChange{
   431  				{
   432  					Add: &domain.RecordChangeAdd{
   433  						Records: []*domain.Record{
   434  							{
   435  								Data:     "example.com.",
   436  								Name:     "",
   437  								TTL:      600,
   438  								Type:     domain.RecordTypeCNAME,
   439  								Priority: 20,
   440  							},
   441  							{
   442  								Data:     "1.2.3.4",
   443  								Name:     "my",
   444  								TTL:      300,
   445  								Type:     domain.RecordTypeA,
   446  								Priority: 0,
   447  							},
   448  							{
   449  								Data:     "5.6.7.8",
   450  								Name:     "my",
   451  								TTL:      300,
   452  								Type:     domain.RecordTypeA,
   453  								Priority: 0,
   454  							},
   455  						},
   456  					},
   457  				},
   458  				{
   459  					Delete: &domain.RecordChangeDelete{
   460  						IDFields: &domain.RecordIdentifier{
   461  							Data: scw.StringPtr("1.1.1.1"),
   462  							Name: "here.is.my",
   463  							Type: domain.RecordTypeA,
   464  						},
   465  					},
   466  				},
   467  				{
   468  					Delete: &domain.RecordChangeDelete{
   469  						IDFields: &domain.RecordIdentifier{
   470  							Data: scw.StringPtr("4.4.4.4"),
   471  							Name: "my",
   472  							Type: domain.RecordTypeA,
   473  						},
   474  					},
   475  				},
   476  				{
   477  					Delete: &domain.RecordChangeDelete{
   478  						IDFields: &domain.RecordIdentifier{
   479  							Data: scw.StringPtr("5.5.5.5"),
   480  							Name: "my",
   481  							Type: domain.RecordTypeA,
   482  						},
   483  					},
   484  				},
   485  			},
   486  		},
   487  	}
   488  
   489  	changes := &plan.Changes{
   490  		Create: []*endpoint.Endpoint{
   491  			{
   492  				DNSName:    "example.com",
   493  				RecordType: "A",
   494  				Targets:    []string{"1.1.1.1", "1.1.1.2"},
   495  			},
   496  			{
   497  				DNSName:    "test.example.com",
   498  				RecordType: "CNAME",
   499  				ProviderSpecific: endpoint.ProviderSpecific{
   500  					{
   501  						Name:  scalewayPriorityKey,
   502  						Value: "20",
   503  					},
   504  				},
   505  				RecordTTL: 600,
   506  				Targets:   []string{"example.com"},
   507  			},
   508  		},
   509  		Delete: []*endpoint.Endpoint{
   510  			{
   511  				DNSName:    "here.example.com",
   512  				RecordType: "A",
   513  				Targets:    []string{"1.1.1.1", "1.1.1.2"},
   514  			},
   515  			{
   516  				DNSName:    "here.is.my.test.example.com",
   517  				RecordType: "A",
   518  				Targets:    []string{"1.1.1.1"},
   519  			},
   520  		},
   521  		UpdateNew: []*endpoint.Endpoint{
   522  			{
   523  				DNSName: "me.example.com",
   524  				ProviderSpecific: endpoint.ProviderSpecific{
   525  					{
   526  						Name:  scalewayPriorityKey,
   527  						Value: "30",
   528  					},
   529  				},
   530  				RecordType: "A",
   531  				RecordTTL:  600,
   532  				Targets:    []string{"2.2.2.2"},
   533  			},
   534  			{
   535  				DNSName:    "my.test.example.com",
   536  				RecordType: "A",
   537  				Targets:    []string{"1.2.3.4", "5.6.7.8"},
   538  			},
   539  		},
   540  		UpdateOld: []*endpoint.Endpoint{
   541  			{
   542  				DNSName: "me.example.com",
   543  				ProviderSpecific: endpoint.ProviderSpecific{
   544  					{
   545  						Name:  scalewayPriorityKey,
   546  						Value: "1234",
   547  					},
   548  				},
   549  				RecordType: "A",
   550  				Targets:    []string{"3.3.3.3"},
   551  			},
   552  			{
   553  				DNSName:    "my.test.example.com",
   554  				RecordType: "A",
   555  				Targets:    []string{"4.4.4.4", "5.5.5.5"},
   556  			},
   557  		},
   558  	}
   559  
   560  	requests, err := provider.generateApplyRequests(context.TODO(), changes)
   561  	if err != nil {
   562  		t.Fatal(err)
   563  	}
   564  
   565  	require.Len(t, requests, len(expected))
   566  	total := int(len(expected))
   567  	for _, req := range requests {
   568  		for _, exp := range expected {
   569  			if checkScalewayReqChanges(req, exp) {
   570  				total--
   571  			}
   572  		}
   573  	}
   574  	assert.Equal(t, 0, total)
   575  }
   576  
   577  func checkRecordEquality(record1, record2 *endpoint.Endpoint) bool {
   578  	return record1.Targets.Same(record2.Targets) &&
   579  		record1.DNSName == record2.DNSName &&
   580  		record1.RecordTTL == record2.RecordTTL &&
   581  		record1.RecordType == record2.RecordType &&
   582  		reflect.DeepEqual(record1.ProviderSpecific, record2.ProviderSpecific)
   583  }
   584  
   585  func checkScalewayReqChanges(r1, r2 *domain.UpdateDNSZoneRecordsRequest) bool {
   586  	if r1.DNSZone != r2.DNSZone {
   587  		return false
   588  	}
   589  	if len(r1.Changes) != len(r2.Changes) {
   590  		return false
   591  	}
   592  	total := int(len(r1.Changes))
   593  	for _, c1 := range r1.Changes {
   594  		for _, c2 := range r2.Changes {
   595  			// we only have 1 add per request
   596  			if c1.Add != nil && c2.Add != nil && checkScalewayRecords(c1.Add.Records, c2.Add.Records) {
   597  				total--
   598  			} else if c1.Delete != nil && c2.Delete != nil {
   599  				if *c1.Delete.IDFields.Data == *c2.Delete.IDFields.Data && c1.Delete.IDFields.Name == c2.Delete.IDFields.Name && c1.Delete.IDFields.Type == c2.Delete.IDFields.Type {
   600  					total--
   601  				}
   602  			}
   603  		}
   604  	}
   605  	return total == 0
   606  }
   607  
   608  func checkScalewayRecords(rs1, rs2 []*domain.Record) bool {
   609  	if len(rs1) != len(rs2) {
   610  		return false
   611  	}
   612  	total := int(len(rs1))
   613  	for _, r1 := range rs1 {
   614  		for _, r2 := range rs2 {
   615  			if r1.Data == r2.Data &&
   616  				r1.Name == r2.Name &&
   617  				r1.Priority == r2.Priority &&
   618  				r1.TTL == r2.TTL &&
   619  				r1.Type == r2.Type {
   620  				total--
   621  			}
   622  		}
   623  	}
   624  	return total == 0
   625  }