diff --git a/README.md b/README.md index 9807e8a..299493b 100644 --- a/README.md +++ b/README.md @@ -7,26 +7,24 @@ [ExternalDNS](https://github.com/kubernetes-sigs/external-dns) is a Kubernetes add-on for automatically managing DNS records for Kubernetes ingresses and services by using different DNS providers. This webhook provider allows you to automate DNS records from your Kubernetes clusters into your MikroTik router. -Supported DNS record types: +Supported DNS record types: `A`, `AAAA`, `CNAME`, `MX`, `NS`, `SRV`, `TXT` -- A -- AAAA -- CNAME -- MX -- NS -- SRV -- TXT +For examples of creating DNS records either via CRDs or via Ingress/Service annotations, check out the [`example/` directory](./example/). ## 🎯 Requirements -- ExternalDNS >= v0.14.0 -- Mikrotik RouterOS (tested on 7.14.3 stable) +> [!Note] +> `v0.15.0` of ExternalDNS added support for `providerSpecific` annotations in Ingress/Service objects for webhook providers. +> +> While older versions of ExternalDNS may work, but support for this feature will not be present. + +- ExternalDNS >= `v0.15.0` +- Mikrotik RouterOS (tested on `7.16` stable) ## 🚫 Limitations - Currently, `DNSEndpoints` with multiple `targets` are *technically* not supported. Only one record will be created with the first target from the list, but eDNS will keep trying to update your DNS record in RouterOS, constantly sending `PUT` requests. - The `Disabled` option on DNS records is currently ignored -- Support for `providerSpecific` annotations on `Ingress` objects is not **yet** supported. ## ⚙️ Configuration Options diff --git a/example/ingress/sample.yaml b/example/ingress/sample.yaml index 5ad35e9..e67449a 100644 --- a/example/ingress/sample.yaml +++ b/example/ingress/sample.yaml @@ -41,7 +41,9 @@ metadata: annotations: external-dns.alpha.kubernetes.io/hostname: "example.com" external-dns.alpha.kubernetes.io/ttl: "60" - external-dns.alpha.kubernetes.io/webhook-comment: "This is a comment" + external-dns.alpha.kubernetes.io/webhook-comment: "This is a static DNS record created via ingress annotations!" + external-dns.alpha.kubernetes.io/webhook-address-list: "4.5.6.7" + external-dns.alpha.kubernetes.io/webhook-match-subdomain: "false" spec: rules: - host: example.com diff --git a/example/records/complex-record.yaml b/example/records/complex-record.yaml index 665abbd..aede3e3 100644 --- a/example/records/complex-record.yaml +++ b/example/records/complex-record.yaml @@ -1,5 +1,4 @@ --- ---- apiVersion: externaldns.k8s.io/v1alpha1 kind: DNSEndpoint metadata: diff --git a/example/service/service.yaml b/example/service/service.yaml index 3db6206..73de870 100644 --- a/example/service/service.yaml +++ b/example/service/service.yaml @@ -26,8 +26,10 @@ metadata: name: test annotations: external-dns.alpha.kubernetes.io/hostname: nginx2.example.com - external-dns.alpha.kubernetes.io/ttl: "600" - external-dns.alpha.kubernetes.io/webhook-comment: "This is a comment" + external-dns.alpha.kubernetes.io/ttl: "1800" + external-dns.alpha.kubernetes.io/webhook-comment: "This is a static DNS record created via service annotations!" + external-dns.alpha.kubernetes.io/webhook-address-list: "6.7.8.9" + external-dns.alpha.kubernetes.io/webhook-match-subdomain: "true" spec: ports: - port: 80 diff --git a/example/values.yaml b/example/values.yaml index ca136fc..5348482 100644 --- a/example/values.yaml +++ b/example/values.yaml @@ -2,12 +2,15 @@ fullnameOverride: external-dns-mikrotik logLevel: debug -policy: sync -sources: ["ingress", "service"] +logFormat: text interval: 1s +sources: ["ingress", "service", "crd"] +registry: txt txtOwnerId: default txtPrefix: k8s. domainFilters: ["example.com"] +excludeDomains: [] +policy: sync provider: name: webhook @@ -57,9 +60,6 @@ provider: extraArgs: - --ignore-ingress-tls-spec - - --source=crd - - --crd-source-apiversion=externaldns.k8s.io/v1alpha1 - - --crd-source-kind=DNSEndpoint - --managed-record-types=A - --managed-record-types=AAAA - --managed-record-types=CNAME @@ -67,13 +67,3 @@ extraArgs: - --managed-record-types=MX - --managed-record-types=SRV - --managed-record-types=NS - -rbac: - create: true - additionalPermissions: - - apiGroups: ["externaldns.k8s.io"] - resources: ["dnsendpoints"] - verbs: ["get", "watch", "list"] - - apiGroups: ["externaldns.k8s.io"] - resources: ["dnsendpoints/status"] - verbs: ["*"] diff --git a/internal/mikrotik/provider.go b/internal/mikrotik/provider.go index 59bcc02..ad5f6af 100644 --- a/internal/mikrotik/provider.go +++ b/internal/mikrotik/provider.go @@ -66,6 +66,8 @@ func (p *MikrotikProvider) Records(ctx context.Context) ([]*endpoint.Endpoint, e // ApplyChanges applies a given set of changes in the DNS provider. func (p *MikrotikProvider) ApplyChanges(ctx context.Context, changes *plan.Changes) error { + changes = cleanupChanges(changes) + for _, endpoint := range append(changes.UpdateOld, changes.Delete...) { if err := p.client.DeleteDNSRecord(endpoint); err != nil { return err @@ -85,3 +87,91 @@ func (p *MikrotikProvider) ApplyChanges(ctx context.Context, changes *plan.Chang func (p *MikrotikProvider) GetDomainFilter() endpoint.DomainFilterInterface { return p.domainFilter } + +// ================================================================================================ +// UTILS +// ================================================================================================ +func getProviderSpecific(ep *endpoint.Endpoint, ps string) string { + value, valueExists := ep.GetProviderSpecificProperty(ps) + if !valueExists { + value, _ = ep.GetProviderSpecificProperty(fmt.Sprintf("webhook/%s", ps)) + } + return value +} + +func isEndpointMatching(a *endpoint.Endpoint, b *endpoint.Endpoint) bool { + if a.DNSName != b.DNSName || a.Targets[0] != b.Targets[0] || a.RecordTTL != b.RecordTTL { + return false + } + + aComment := getProviderSpecific(a, "comment") + bComment := getProviderSpecific(b, "comment") + if aComment != bComment { + return false + } + + aMatchSubdomain := getProviderSpecific(a, "match-subdomain") + if aMatchSubdomain == "" { + aMatchSubdomain = "false" + } + bMatchSubdomain := getProviderSpecific(b, "match-subdomain") + if bMatchSubdomain == "" { + bMatchSubdomain = "false" + } + if aMatchSubdomain != bMatchSubdomain { + return false + } + + aAddressList := getProviderSpecific(a, "address-list") + bAddressList := getProviderSpecific(b, "address-list") + if aAddressList != bAddressList { + return false + } + + aRegexp := getProviderSpecific(a, "regexp") + bRegexp := getProviderSpecific(b, "regexp") + return aRegexp == bRegexp +} + +func contains(haystack []*endpoint.Endpoint, needle *endpoint.Endpoint) bool { + for _, v := range haystack { + if isEndpointMatching(needle, v) { + return true + } + } + return false +} + +func cleanupChanges(changes *plan.Changes) *plan.Changes { + // Initialize new plan -> we don't really need to worry about Create or Delete changes. + // Only updates are sketchy + newChanges := &plan.Changes{ + Create: changes.Create, + Delete: changes.Delete, + UpdateOld: []*endpoint.Endpoint{}, + UpdateNew: []*endpoint.Endpoint{}, + } + + duplicates := []*endpoint.Endpoint{} + + for _, old := range changes.UpdateOld { + for _, new := range changes.UpdateNew { + if isEndpointMatching(old, new) { + duplicates = append(duplicates, old) + } + } + } + + for _, old := range changes.UpdateOld { + if !contains(duplicates, old) { + newChanges.UpdateOld = append(newChanges.UpdateOld, old) + } + } + for _, new := range changes.UpdateNew { + if !contains(duplicates, new) { + newChanges.UpdateNew = append(newChanges.UpdateNew, new) + } + } + + return newChanges +} diff --git a/internal/mikrotik/provider_test.go b/internal/mikrotik/provider_test.go new file mode 100644 index 0000000..6222c25 --- /dev/null +++ b/internal/mikrotik/provider_test.go @@ -0,0 +1,527 @@ +package mikrotik + +import ( + "testing" + + "sigs.k8s.io/external-dns/endpoint" + "sigs.k8s.io/external-dns/plan" +) + +func TestGetProviderSpecific(t *testing.T) { + tests := []struct { + name string + endpoint *endpoint.Endpoint + property string + expectedValue string + }{ + { + name: "Direct property exists", + endpoint: &endpoint.Endpoint{ + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "direct-comment"}, + }, + }, + property: "comment", + expectedValue: "direct-comment", + }, + { + name: "Prefixed property exists", + endpoint: &endpoint.Endpoint{ + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "webhook/comment", Value: "prefixed-comment"}, + }, + }, + property: "comment", + expectedValue: "prefixed-comment", + }, + { + name: "Both properties exist - direct takes precedence", + endpoint: &endpoint.Endpoint{ + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "direct-comment"}, + {Name: "webhook/comment", Value: "prefixed-comment"}, + }, + }, + property: "comment", + expectedValue: "direct-comment", + }, + { + name: "Neither property exists", + endpoint: &endpoint.Endpoint{ + ProviderSpecific: endpoint.ProviderSpecific{}, + }, + property: "comment", + expectedValue: "", + }, + { + name: "Weong key selected", + endpoint: &endpoint.Endpoint{ + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "direct-comment"}, + }, + }, + property: "address-list", + expectedValue: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + value := getProviderSpecific(tt.endpoint, tt.property) + if value != tt.expectedValue { + t.Errorf("Expected %q, got %q", tt.expectedValue, value) + } + }) + } +} + +func TestIsEndpointMatching(t *testing.T) { + tests := []struct { + name string + endpointA *endpoint.Endpoint + endpointB *endpoint.Endpoint + expectedMatch bool + }{ + // MATCHING CASES + { + name: "Matching basic properties", + endpointA: &endpoint.Endpoint{ + DNSName: "example.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + }, + endpointB: &endpoint.Endpoint{ + DNSName: "example.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + }, + expectedMatch: true, + }, + { + name: "Matching provider-specific", + endpointA: &endpoint.Endpoint{ + DNSName: "example.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "match"}, + {Name: "match-subdomain", Value: "true"}, + {Name: "address-list", Value: "default"}, + {Name: "regexp", Value: ".*"}, + }, + }, + endpointB: &endpoint.Endpoint{ + DNSName: "example.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "webhook/comment", Value: "match"}, + {Name: "webhook/match-subdomain", Value: "true"}, + {Name: "webhook/address-list", Value: "default"}, + {Name: "webhook/regexp", Value: ".*"}, + }, + }, + expectedMatch: true, + }, + + // EDGE CASES + { + name: "Match-Subdomain: 'false' and unspecified should match", + endpointA: &endpoint.Endpoint{ + DNSName: "example.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "match-subdomain", Value: "false"}, + }, + }, + endpointB: &endpoint.Endpoint{ + DNSName: "example.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{}, // unspecified match-subdomain + }, + expectedMatch: true, + }, + + // MISMATCH CASES + { + name: "Provider-specific properties do not match", + endpointA: &endpoint.Endpoint{ + DNSName: "example.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "mismatch"}, + }, + }, + endpointB: &endpoint.Endpoint{ + DNSName: "example.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "webhook/comment", Value: "different"}, + }, + }, + expectedMatch: false, + }, + { + name: "Mismatch in DNSName", + endpointA: &endpoint.Endpoint{ + DNSName: "example1.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + }, + endpointB: &endpoint.Endpoint{ + DNSName: "example2.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + }, + expectedMatch: false, + }, + { + name: "Mismatch in Target", + endpointA: &endpoint.Endpoint{ + DNSName: "example.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + }, + endpointB: &endpoint.Endpoint{ + DNSName: "example.com", + Targets: endpoint.NewTargets("192.0.2.2"), + RecordTTL: endpoint.TTL(3600), + }, + expectedMatch: false, + }, + { + name: "Mismatch in TTL", + endpointA: &endpoint.Endpoint{ + DNSName: "example.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + }, + endpointB: &endpoint.Endpoint{ + DNSName: "example.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3601), + }, + expectedMatch: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + match := isEndpointMatching(tt.endpointA, tt.endpointB) + if match != tt.expectedMatch { + t.Errorf("Expected %v, got %v", tt.expectedMatch, match) + } + }) + } +} + +func TestContains(t *testing.T) { + tests := []struct { + name string + haystack []*endpoint.Endpoint + needle *endpoint.Endpoint + expectContain bool + }{ + { + name: "Needle exists in haystack", + haystack: []*endpoint.Endpoint{ + { + DNSName: "example1.com", + Targets: endpoint.NewTargets("192.2.2.1"), + RecordTTL: endpoint.TTL(36), + }, + { + DNSName: "example.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "test"}, + }, + }, + { + DNSName: "example2.com", + Targets: endpoint.NewTargets("192.1.2.1"), + RecordTTL: endpoint.TTL(360), + }, + }, + needle: &endpoint.Endpoint{ + DNSName: "example.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "webhook/comment", Value: "test"}, + }, + }, + expectContain: true, + }, + { + name: "Needle does not exist in haystack", + haystack: []*endpoint.Endpoint{ + { + DNSName: "example1.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + }, + { + DNSName: "example2.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + }, + { + DNSName: "example3.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + }, + }, + needle: &endpoint.Endpoint{ + DNSName: "example.org", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + }, + expectContain: false, + }, + { + name: "Haystack is empty", + haystack: []*endpoint.Endpoint{}, + needle: &endpoint.Endpoint{ + DNSName: "example.com", + Targets: endpoint.NewTargets("192.0.2.1"), + RecordTTL: endpoint.TTL(3600), + }, + expectContain: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + contains := contains(tt.haystack, tt.needle) + if contains != tt.expectContain { + t.Errorf("Expected %v, got %v", tt.expectContain, contains) + } + }) + } +} + +func TestCleanupChanges(t *testing.T) { + tests := []struct { + name string + inputChanges *plan.Changes + expectedChanges *plan.Changes + }{ + { + name: "Multiple matching records - all should be cleaned up", + inputChanges: &plan.Changes{ + UpdateOld: []*endpoint.Endpoint{ + { + DNSName: "example.com", + Targets: endpoint.NewTargets("1.1.1.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "test comment"}, + {Name: "address-list", Value: "main"}, + {Name: "match-subdomain", Value: ".*"}, + }, + }, + { + DNSName: "example.org", + Targets: endpoint.NewTargets("2.2.2.2"), + RecordTTL: endpoint.TTL(300), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "another comment"}, + {Name: "address-list", Value: "secondary"}, + {Name: "match-subdomain", Value: "*.example.com"}, + }, + }, + }, + UpdateNew: []*endpoint.Endpoint{ + { + DNSName: "example.com", + Targets: endpoint.NewTargets("1.1.1.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "webhook/comment", Value: "test comment"}, + {Name: "address-list", Value: "main"}, + {Name: "match-subdomain", Value: ".*"}, + }, + }, + { + DNSName: "example.org", + Targets: endpoint.NewTargets("2.2.2.2"), + RecordTTL: endpoint.TTL(300), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "webhook/comment", Value: "another comment"}, + {Name: "address-list", Value: "secondary"}, + {Name: "match-subdomain", Value: "*.example.com"}, + }, + }, + }, + }, + expectedChanges: &plan.Changes{}, + }, + { + name: "Some matching, some different - only partial cleanup", + inputChanges: &plan.Changes{ + UpdateOld: []*endpoint.Endpoint{ + { + DNSName: "matching.com", + Targets: endpoint.NewTargets("1.1.1.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "old comment"}, + }, + }, + { + DNSName: "different.org", + Targets: endpoint.NewTargets("2.2.2.2"), + RecordTTL: endpoint.TTL(300), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "old comment"}, + }, + }, + }, + UpdateNew: []*endpoint.Endpoint{ + { + DNSName: "matching.com", + Targets: endpoint.NewTargets("1.1.1.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "webhook/comment", Value: "old comment"}, + }, + }, + { + DNSName: "different.org", + Targets: endpoint.NewTargets("2.2.2.2"), + RecordTTL: endpoint.TTL(300), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "webhook/comment", Value: "new comment"}, + }, + }, + }, + }, + expectedChanges: &plan.Changes{ + UpdateOld: []*endpoint.Endpoint{ + { + DNSName: "different.org", + Targets: endpoint.NewTargets("2.2.2.2"), + RecordTTL: endpoint.TTL(300), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "old comment"}, + }, + }, + }, + UpdateNew: []*endpoint.Endpoint{ + { + DNSName: "different.org", + Targets: endpoint.NewTargets("2.2.2.2"), + RecordTTL: endpoint.TTL(300), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "new comment"}, + }, + }, + }, + }, + }, + { + name: "Different comments across multiple records - no cleanup", + inputChanges: &plan.Changes{ + UpdateOld: []*endpoint.Endpoint{ + { + DNSName: "example.com", + Targets: endpoint.NewTargets("1.1.1.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "old comment"}, + }, + }, + { + DNSName: "example.net", + Targets: endpoint.NewTargets("3.3.3.3"), + RecordTTL: endpoint.TTL(120), + }, + }, + UpdateNew: []*endpoint.Endpoint{ + { + DNSName: "example.com", + Targets: endpoint.NewTargets("1.1.1.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "webhook/comment", Value: "new comment"}, + }, + }, + { + DNSName: "example.net", + Targets: endpoint.NewTargets("3.3.3.3"), + RecordTTL: endpoint.TTL(120), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "new comment"}, + }, + }, + }, + }, + expectedChanges: &plan.Changes{ + UpdateOld: []*endpoint.Endpoint{ + { + DNSName: "example.com", + Targets: endpoint.NewTargets("1.1.1.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "old comment"}, + }, + }, + { + DNSName: "example.net", + Targets: endpoint.NewTargets("3.3.3.3"), + RecordTTL: endpoint.TTL(120), + }, + }, + UpdateNew: []*endpoint.Endpoint{ + { + DNSName: "example.com", + Targets: endpoint.NewTargets("1.1.1.1"), + RecordTTL: endpoint.TTL(3600), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "webhook/comment", Value: "new comment"}, + }, + }, + { + DNSName: "example.net", + Targets: endpoint.NewTargets("3.3.3.3"), + RecordTTL: endpoint.TTL(120), + ProviderSpecific: endpoint.ProviderSpecific{ + {Name: "comment", Value: "new comment"}, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + outputChanges := cleanupChanges(tt.inputChanges) + + if len(outputChanges.UpdateOld) != len(tt.expectedChanges.UpdateOld) { + t.Errorf("Expected UpdateOld length %d, got %d", len(tt.expectedChanges.UpdateOld), len(outputChanges.UpdateOld)) + } + if len(outputChanges.UpdateNew) != len(tt.expectedChanges.UpdateNew) { + t.Errorf("Expected UpdateNew length %d, got %d", len(tt.expectedChanges.UpdateNew), len(outputChanges.UpdateNew)) + } + + for i := range tt.expectedChanges.UpdateOld { + if !isEndpointMatching(outputChanges.UpdateOld[i], tt.expectedChanges.UpdateOld[i]) { + t.Errorf("Expected endpoint: %v , got %v", tt.expectedChanges.UpdateOld[i], outputChanges.UpdateOld[i]) + } + } + + for i := range tt.expectedChanges.UpdateNew { + if !isEndpointMatching(outputChanges.UpdateNew[i], tt.expectedChanges.UpdateNew[i]) { + t.Errorf("Expected endpoint: %v , got %v", tt.expectedChanges.UpdateNew[i], outputChanges.UpdateNew[i]) + } + } + }) + } +} diff --git a/internal/mikrotik/record.go b/internal/mikrotik/record.go index f554523..ac87615 100644 --- a/internal/mikrotik/record.go +++ b/internal/mikrotik/record.go @@ -138,16 +138,16 @@ func NewDNSRecord(endpoint *endpoint.Endpoint) (*DNSRecord, error) { for _, providerSpecific := range endpoint.ProviderSpecific { switch providerSpecific.Name { - case "comment": + case "comment", "webhook/comment": record.Comment = providerSpecific.Value log.Debugf("Comment set to: %s", record.Comment) - case "regexp": + case "regexp", "webhook/regexp": record.Regexp = providerSpecific.Value log.Debugf("Regexp set to: %s", record.Regexp) - case "match-subdomain": + case "match-subdomain", "webhook/match-subdomain": record.MatchSubdomain = providerSpecific.Value log.Debugf("MatchSubdomain set to: %s", record.MatchSubdomain) - case "address-list": + case "address-list", "webhook/address-list": record.AddressList = providerSpecific.Value log.Debugf("AddressList set to: %s", record.AddressList) default: