Skip to content

Commit

Permalink
feat(provider): adapt webhook to latest provider interface changes [D…
Browse files Browse the repository at this point in the history
…NS-970] (#31)
  • Loading branch information
fyousfi-ionos authored Sep 7, 2023
1 parent 814aed1 commit 60fea84
Show file tree
Hide file tree
Showing 12 changed files with 569 additions and 237 deletions.
15 changes: 8 additions & 7 deletions cmd/webhook/init/dnsprovider/dnsprovider.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const (
webtokenIonosISSValue = "ionoscloud"
)

type IONOSProviderFactory func(domainFilter endpoint.DomainFilter, ionosConfig *ionos.Configuration) provider.Provider
type IONOSProviderFactory func(baseProvider *provider.BaseProvider, ionosConfig *ionos.Configuration) provider.Provider

func setDefaults(apiEndpointURL, authHeader string, ionosConfig *ionos.Configuration) {
if ionosConfig.APIEndpointURL == "" {
Expand All @@ -34,14 +34,14 @@ func setDefaults(apiEndpointURL, authHeader string, ionosConfig *ionos.Configura
}
}

var IonosCoreProviderFactory = func(domainFilter endpoint.DomainFilter, ionosConfig *ionos.Configuration) provider.Provider {
var IonosCoreProviderFactory = func(baseProvider *provider.BaseProvider, ionosConfig *ionos.Configuration) provider.Provider {
setDefaults("https://api.hosting.ionos.com/dns", "X-API-Key", ionosConfig)
return ionoscore.NewProvider(domainFilter, ionosConfig)
return ionoscore.NewProvider(baseProvider, ionosConfig)
}

var IonosCloudProviderFactory = func(domainFilter endpoint.DomainFilter, ionosConfig *ionos.Configuration) provider.Provider {
var IonosCloudProviderFactory = func(baseProvider *provider.BaseProvider, ionosConfig *ionos.Configuration) provider.Provider {
setDefaults("https://dns.de-fra.ionos.com", "Bearer", ionosConfig)
return ionoscloud.NewProvider(domainFilter, ionosConfig)
return ionoscloud.NewProvider(baseProvider, ionosConfig)
}

func Init(config configuration.Config) (provider.Provider, error) {
Expand Down Expand Up @@ -77,8 +77,9 @@ func Init(config configuration.Config) (provider.Provider, error) {
return nil, fmt.Errorf("reading ionos ionosConfig failed: %v", err)
}
createProvider := detectProvider(&ionosConfig)
provider := createProvider(domainFilter, &ionosConfig)
return provider, nil
baseProvider := provider.NewBaseProvider(domainFilter)
ionosProvider := createProvider(baseProvider, &ionosConfig)
return ionosProvider, nil
}

func detectProvider(ionosConfig *ionos.Configuration) IONOSProviderFactory {
Expand Down
7 changes: 6 additions & 1 deletion cmd/webhook/init/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,17 @@ import (
)

// Init server initialization function
// The server will respond to the following endpoints:
// - / (GET): initialization, negotiates headers and returns the domain filter
// - /records (GET): returns the current records
// - /records (POST): applies the changes
// - /adjustendpoints (POST): executes the AdjustEndpoints method
func Init(config configuration.Config, p *webhook.Webhook) *http.Server {
r := chi.NewRouter()
r.Use(webhook.Health)
r.Get("/", p.Negotiate)
r.Get("/records", p.Records)
r.Post("/records", p.ApplyChanges)
r.Post("/propertyvaluesequals", p.PropertyValuesEquals)
r.Post("/adjustendpoints", p.AdjustEndpoints)

srv := createHTTPServer(fmt.Sprintf("%s:%d", config.ServerHost, config.ServerPort), r)
Expand Down
135 changes: 33 additions & 102 deletions cmd/webhook/init/server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,21 @@ import (
)

type testCase struct {
name string
returnRecords []*endpoint.Endpoint
returnAdjustedEndpoints []*endpoint.Endpoint
hasError error
method string
path string
headers map[string]string
body string
expectedStatusCode int
expectedResponseHeaders map[string]string
expectedBody string
expectedChanges *plan.Changes
expectedEndpointsToAdjust []*endpoint.Endpoint
expectedPropertyValuesEqualName string
expectedPropertyValuesEqualPrevious string
expectedPropertyValuesEqualCurrent string
name string
returnRecords []*endpoint.Endpoint
returnAdjustedEndpoints []*endpoint.Endpoint
returnDomainFilter endpoint.DomainFilter
hasError error
method string
path string
headers map[string]string
body string
expectedStatusCode int
expectedResponseHeaders map[string]string
expectedBody string
expectedChanges *plan.Changes
expectedEndpointsToAdjust []*endpoint.Endpoint
log.Ext1FieldLogger
}

var mockProvider *MockProvider
Expand Down Expand Up @@ -354,67 +353,26 @@ func TestAdjustEndpoints(t *testing.T) {
executeTestCases(t, testCases)
}

func TestPropertyValuesEqual(t *testing.T) {
func TestNegotiate(t *testing.T) {
testCases := []testCase{
{
name: "happy case",
method: http.MethodPost,
headers: map[string]string{
"Content-Type": "application/external.dns.webhook+json;version=1",
"Accept": "application/external.dns.webhook+json;version=1",
},
path: "/propertyvaluesequals",
body: `
{
"name": "propertyname",
"previous": "previousvalue",
"current": "currentvalue"
}`,
name: "happy case",
returnDomainFilter: endpoint.NewDomainFilter([]string{"a.de"}),
method: http.MethodGet,
headers: map[string]string{"Accept": "application/external.dns.webhook+json;version=1"},
path: "/",
body: "",
expectedStatusCode: http.StatusOK,
expectedResponseHeaders: map[string]string{
"Content-Type": "application/external.dns.webhook+json;version=1",
},
expectedBody: "{\"equals\":false}",
expectedPropertyValuesEqualName: "propertyname",
expectedPropertyValuesEqualCurrent: "currentvalue",
expectedPropertyValuesEqualPrevious: "previousvalue",
expectedBody: `{"include":["a.de"]}`,
},
{
name: "no content type header",
method: http.MethodPost,
headers: map[string]string{
"Accept": "application/external.dns.webhook+json;version=1",
},
path: "/propertyvaluesequals",
body: "",
expectedStatusCode: http.StatusNotAcceptable,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
},
expectedBody: "client must provide a content type",
},
{
name: "wrong content type header",
method: http.MethodPost,
headers: map[string]string{
"Content-Type": "invalid",
"Accept": "application/external.dns.webhook+json;version=1",
},
path: "/propertyvaluesequals",
body: "",
expectedStatusCode: http.StatusUnsupportedMediaType,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
},
expectedBody: "client must provide a valid versioned media type in the content type: unsupported media type version: 'invalid'. Supported media types are: 'application/external.dns.webhook+json;version=1'",
},
{
name: "no accept header",
method: http.MethodPost,
headers: map[string]string{
"Content-Type": "application/external.dns.webhook+json;version=1",
},
path: "/propertyvaluesequals",
name: "no accept header",
method: http.MethodGet,
headers: map[string]string{},
path: "/",
body: "",
expectedStatusCode: http.StatusNotAcceptable,
expectedResponseHeaders: map[string]string{
Expand All @@ -423,35 +381,17 @@ func TestPropertyValuesEqual(t *testing.T) {
expectedBody: "client must provide an accept header",
},
{
name: "wrong accept header",
method: http.MethodPost,
headers: map[string]string{
"Content-Type": "application/external.dns.webhook+json;version=1",
"Accept": "invalid",
},
path: "/propertyvaluesequals",
name: "wrong accept header",
method: http.MethodGet,
headers: map[string]string{"Accept": "invalid"},
path: "/",
body: "",
expectedStatusCode: http.StatusUnsupportedMediaType,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
},
expectedBody: "client must provide a valid versioned media type in the accept header: unsupported media type version: 'invalid'. Supported media types are: 'application/external.dns.webhook+json;version=1'",
},
{
name: "invalid json",
method: http.MethodPost,
headers: map[string]string{
"Content-Type": "application/external.dns.webhook+json;version=1",
"Accept": "application/external.dns.webhook+json;version=1",
},
path: "/propertyvaluesequals",
body: "invalid",
expectedStatusCode: http.StatusBadRequest,
expectedResponseHeaders: map[string]string{
"Content-Type": "text/plain",
},
expectedBody: "failed to decode request body: invalid character 'i' looking for beginning of value",
},
}
executeTestCases(t, testCases)
}
Expand Down Expand Up @@ -528,15 +468,6 @@ func (d *MockProvider) AdjustEndpoints(endpoints []*endpoint.Endpoint) []*endpoi
return d.testCase.returnAdjustedEndpoints
}

func (d *MockProvider) PropertyValuesEqual(name string, previous string, current string) bool {
if name != d.testCase.expectedPropertyValuesEqualName {
d.t.Errorf("expected name '%s', got '%s'", d.testCase.expectedPropertyValuesEqualName, name)
}
if previous != d.testCase.expectedPropertyValuesEqualPrevious {
d.t.Errorf("expected previous '%s', got '%s'", d.testCase.expectedPropertyValuesEqualPrevious, previous)
}
if current != d.testCase.expectedPropertyValuesEqualCurrent {
d.t.Errorf("expected current '%s', got '%s'", d.testCase.expectedPropertyValuesEqualCurrent, current)
}
return previous == current
func (d *MockProvider) GetDomainFilter() endpoint.DomainFilter {
return d.testCase.returnDomainFilter
}
15 changes: 7 additions & 8 deletions internal/ionoscloud/ionoscloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,16 +138,15 @@ func (c *DNSClient) DeleteRecord(ctx context.Context, zoneId string, recordId st
// Provider extends base provider to work with paas dns rest API
type Provider struct {
provider.BaseProvider
client DNSService
domainFilter endpoint.DomainFilter
client DNSService
}

// NewProvider returns an instance of new provider
func NewProvider(domainFilter endpoint.DomainFilter, configuration *ionos.Configuration) *Provider {
func NewProvider(baseProvider *provider.BaseProvider, configuration *ionos.Configuration) *Provider {
client := createClient(configuration)
prov := &Provider{
BaseProvider: *baseProvider,
client: &DNSClient{client: client, dryRun: configuration.DryRun},
domainFilter: domainFilter,
}
return prov
}
Expand Down Expand Up @@ -200,15 +199,15 @@ func (p *Provider) readAllRecords(ctx context.Context) ([]sdk.RecordRead, error)
}
}

if p.domainFilter.IsConfigured() {
if p.BaseProvider.GetDomainFilter().IsConfigured() {
filteredResult := make([]sdk.RecordRead, 0)
for _, record := range result {
fqdn := *record.GetMetadata().GetFqdn()
if p.domainFilter.Match(fqdn) {
if p.BaseProvider.GetDomainFilter().Match(fqdn) {
filteredResult = append(filteredResult, record)
}
}
logger := log.WithField(logFieldDomainFilter, p.domainFilter)
logger := log.WithField(logFieldDomainFilter, p.BaseProvider.GetDomainFilter())
logger.Debugf("found %d records after applying domainFilter", len(filteredResult))
return filteredResult, nil
} else {
Expand Down Expand Up @@ -363,7 +362,7 @@ func (p *Provider) createZoneTree(ctx context.Context) (*ionos.ZoneTree[sdk.Zone
}
for _, zoneRead := range allZones {
zoneName := *zoneRead.GetProperties().GetZoneName()
if !p.domainFilter.IsConfigured() || p.domainFilter.Match(zoneName) {
if !p.BaseProvider.GetDomainFilter().IsConfigured() || p.BaseProvider.GetDomainFilter().Match(zoneName) {
zt.AddZone(zoneRead, zoneName)
}
}
Expand Down
30 changes: 13 additions & 17 deletions internal/ionoscloud/ionoscloud_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"math/rand"
"testing"

"github.com/ionos-cloud/external-dns-ionos-webhook/pkg/provider"

"github.com/ionos-cloud/external-dns-ionos-webhook/internal/ionos"
"github.com/ionos-cloud/external-dns-ionos-webhook/pkg/endpoint"
"github.com/ionos-cloud/external-dns-ionos-webhook/pkg/plan"
Expand All @@ -20,14 +22,15 @@ var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func TestNewProvider(t *testing.T) {
log.SetLevel(log.DebugLevel)
t.Setenv("IONOS_API_KEY", "1")

p := NewProvider(endpoint.NewDomainFilter([]string{"a.de."}), &ionos.Configuration{})
require.Equal(t, true, p.domainFilter.IsConfigured())
require.Equal(t, false, p.domainFilter.Match("b.de."))

p = NewProvider(endpoint.DomainFilter{}, &ionos.Configuration{})
require.Equal(t, false, p.domainFilter.IsConfigured())
require.Equal(t, true, p.domainFilter.Match("a.de."))
domainFilter := endpoint.NewDomainFilter([]string{"a.de."})
baseProvider := provider.NewBaseProvider(domainFilter)
p := NewProvider(baseProvider, &ionos.Configuration{})
require.Equal(t, true, p.BaseProvider.GetDomainFilter().IsConfigured())
require.Equal(t, false, p.BaseProvider.GetDomainFilter().Match("b.de."))

p = NewProvider(provider.NewBaseProvider(endpoint.DomainFilter{}), &ionos.Configuration{})
require.Equal(t, false, p.BaseProvider.GetDomainFilter().IsConfigured())
require.Equal(t, true, p.BaseProvider.GetDomainFilter().Match("a.de."))
}

func TestRecords(t *testing.T) {
Expand Down Expand Up @@ -120,7 +123,7 @@ func TestRecords(t *testing.T) {
allRecords: tc.givenRecords,
returnError: tc.givenError,
}
provider := &Provider{client: mockDnsClient, domainFilter: tc.givenDomainFilter}
provider := &Provider{client: mockDnsClient, BaseProvider: *provider.NewBaseProvider(tc.givenDomainFilter)}
endpoints, err := provider.Records(ctx)
if tc.expectedError != nil {
require.Error(t, err)
Expand Down Expand Up @@ -475,7 +478,7 @@ func TestApplyChanges(t *testing.T) {
zoneRecords: tc.givenZoneRecords,
returnError: tc.givenError,
}
provider := &Provider{client: mockDnsClient, domainFilter: tc.givenDomainFilter}
provider := &Provider{client: mockDnsClient, BaseProvider: *provider.NewBaseProvider(tc.givenDomainFilter)}
err := provider.ApplyChanges(ctx, tc.whenChanges)
if tc.expectedError != nil {
require.Error(t, err)
Expand Down Expand Up @@ -503,13 +506,6 @@ func TestApplyChanges(t *testing.T) {
}
}

func TestPropertyValuesEqual(t *testing.T) {
provider := &Provider{}
name := RandStringRunes(10)
require.True(t, provider.PropertyValuesEqual(name, "a", "a"))
require.False(t, provider.PropertyValuesEqual(name, "a", "b"))
}

func TestAdjustEndpoints(t *testing.T) {
provider := &Provider{}
endpoints := createEndpointSlice(rand.Intn(5), func(i int) (string, string, endpoint.TTL, []string) {
Expand Down
11 changes: 5 additions & 6 deletions internal/ionoscore/ionoscore.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,8 @@ import (
// Provider implements the DNS provider for IONOS DNS.
type Provider struct {
provider.BaseProvider
client DnsService
domainFilter endpoint.DomainFilter
dryRun bool
client DnsService
dryRun bool
}

// DnsService interface to the dns backend, also needed for creating mocks in tests
Expand Down Expand Up @@ -63,12 +62,12 @@ func (c DnsClient) DeleteRecord(ctx context.Context, zoneId string, recordId str
}

// NewProvider creates a new IONOS DNS provider.
func NewProvider(domainFilter endpoint.DomainFilter, configuration *ionos.Configuration) *Provider {
func NewProvider(baseProvider *provider.BaseProvider, configuration *ionos.Configuration) *Provider {
client := createClient(configuration)

prov := &Provider{
BaseProvider: *baseProvider,
client: DnsClient{client: client},
domainFilter: domainFilter,
dryRun: configuration.DryRun,
}

Expand Down Expand Up @@ -287,7 +286,7 @@ func (p *Provider) getZones(ctx context.Context) (map[string]string, error) {
result := map[string]string{}

for _, zone := range zones {
if p.domainFilter.Match(*zone.Name) {
if p.BaseProvider.GetDomainFilter().Match(*zone.Name) {
result[*zone.Id] = *zone.Name
}
}
Expand Down
Loading

0 comments on commit 60fea84

Please sign in to comment.