Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: tag filter move to pkg:filters #5042

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
@@ -39,6 +39,7 @@ import (
"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/pkg/apis/externaldns"
"sigs.k8s.io/external-dns/pkg/apis/externaldns/validation"
"sigs.k8s.io/external-dns/pkg/filters"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/provider"
"sigs.k8s.io/external-dns/provider/akamai"
@@ -186,7 +187,7 @@ func main() {
zoneNameFilter := endpoint.NewDomainFilter(cfg.ZoneNameFilter)
zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter)
zoneTagFilter := filters.NewZoneTagFilter(cfg.AWSZoneTagFilter)

var p provider.Provider
switch cfg.Provider {
27 changes: 27 additions & 0 deletions pkg/filters/filters.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package filters

import (
"sigs.k8s.io/external-dns/pkg/filters/zonetagfilter"
)

var (
NewZoneTagFilter = zonetagfilter.NewZoneTagFilter
)

type ZoneTagFilter = zonetagfilter.ZoneTagFilter
69 changes: 69 additions & 0 deletions pkg/filters/zonetagfilter/testdoubles_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package zonetagfilter

import "fmt"

type filterZoneTags struct {
ZoneTagFilter
inputTags map[string]string
}

// generateTagFilterAndZoneTagsForMatch generates filter tags and zone tags that do match.
func generateTagFilterAndZoneTagsForMatch(filter, zone int) filterZoneTags {
return generateTagFilterAndZoneTags(filter, zone, true)
}

// generateTagFilterAndZoneTagsForNotMatch generates filter tags and zone tags that do not match.
func generateTagFilterAndZoneTagsForNotMatch(filter, zone int) filterZoneTags {
return generateTagFilterAndZoneTags(filter, zone, false)
}

// generateTagFilterAndZoneTags generates filter tags and zone tags based on the match parameter.
func generateTagFilterAndZoneTags(filter, zone int, match bool) filterZoneTags {
validate(filter, zone)
toFilterTags := make([]string, 0, filter)
inputTags := make(map[string]string, zone)

for i := 0; i < filter; i++ {
tagIndex := i
if !match {
tagIndex += 50
}
toFilterTags = append(toFilterTags, fmt.Sprintf("tag-%d=value-%d", tagIndex, i))
}

for i := 0; i < zone; i++ {
tagIndex := i
if !match {
// Make sure the input tags are different from the filter tags
tagIndex += 2
}
inputTags[fmt.Sprintf("tag-%d", i)] = fmt.Sprintf("value-%d", tagIndex)
}

return filterZoneTags{NewZoneTagFilter(toFilterTags), inputTags}
}

func validate(filter int, zone int) {
if zone > 50 {
panic("zone number is too high")
}
if filter > zone {
panic("filter number is too high")
}
}
Original file line number Diff line number Diff line change
@@ -14,44 +14,52 @@ See the License for the specific language governing permissions and
limitations under the License.
*/

package provider
package zonetagfilter

import (
"strings"
)

// ZoneTagFilter holds a list of zone tags to filter by
type ZoneTagFilter struct {
zoneTags []string
tagsMap map[string]string
}

// NewZoneTagFilter returns a new ZoneTagFilter given a list of zone tags
func NewZoneTagFilter(tags []string) ZoneTagFilter {
if len(tags) == 1 && len(tags[0]) == 0 {
tags = []string{}
}
return ZoneTagFilter{zoneTags: tags}
z := ZoneTagFilter{}
z.tagsMap = make(map[string]string, len(tags))
// tags pre-processing, to make sure the pre-processing is not happening at the time of filtering
for _, tag := range tags {
parts := strings.SplitN(tag, "=", 2)
key := strings.TrimSpace(parts[0])
if key == "" {
continue
}
if len(parts) == 2 {
value := strings.TrimSpace(parts[1])
z.tagsMap[key] = value
} else {
z.tagsMap[key] = ""
}
}
return z
}

// Match checks whether a zone's set of tags matches the provided tag values
func (f ZoneTagFilter) Match(tagsMap map[string]string) bool {
for _, tagFilter := range f.zoneTags {
filterParts := strings.SplitN(tagFilter, "=", 2)
switch len(filterParts) {
case 1:
if _, hasTag := tagsMap[filterParts[0]]; !hasTag {
return false
}
case 2:
if value, hasTag := tagsMap[filterParts[0]]; !hasTag || value != filterParts[1] {
return false
}
for key, v := range f.tagsMap {
if value, hasTag := tagsMap[key]; !hasTag || (v != "" && value != v) {
return false
}
}
return true
}

// IsEmpty returns true if there are no tags for the filter
func (f ZoneTagFilter) IsEmpty() bool {
return len(f.zoneTags) == 0
return len(f.tagsMap) == 0
}
153 changes: 153 additions & 0 deletions pkg/filters/zonetagfilter/zone_tag_filter_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package zonetagfilter

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
)

var basicZoneTags = []struct {
name string
tagsFilter []string
zoneTags map[string]string
matches bool
}{
{
"single tag no match", []string{"tag1=value1"}, map[string]string{"tag0": "value0"}, false,
},
{
"single tag matches", []string{"tag1=value1"}, map[string]string{"tag1": "value1"}, true,
},
{
"multiple tags no value match", []string{"tag1=value1"}, map[string]string{"tag0": "value0", "tag1": "value2"}, false,
},
{
"multiple tags matches", []string{"tag1=value1"}, map[string]string{"tag0": "value0", "tag1": "value1"}, true,
},
{
"tag name no match", []string{"tag1"}, map[string]string{"tag0": "value0"}, false,
},
{
"tag name matches", []string{"tag1"}, map[string]string{"tag1": "value1"}, true,
},
{
"multiple filter no match", []string{"tag1=value1", "tag2=value2"}, map[string]string{"tag1": "value1"}, false,
},
{
"multiple filter matches", []string{"tag1=value1", "tag2=value2"}, map[string]string{"tag2": "value2", "tag1": "value1", "tag3": "value3"}, true,
},
{
"empty tag filter matches all", []string{""}, map[string]string{"tag0": "value0"}, true,
},
{
"tag filter with empty key is ignored", []string{"tag1=value1", "=haha"}, map[string]string{"tag1": "value1"}, true,
},
}

func TestZoneTagFilterMatch(t *testing.T) {
for _, tc := range basicZoneTags {
filter := NewZoneTagFilter(tc.tagsFilter)
t.Run(tc.name, func(t *testing.T) {
assert.Equal(t, tc.matches, filter.Match(tc.zoneTags))
})
}
}

func TestZoneTagFilterNotSupportedFormat(t *testing.T) {
tests := []struct {
desc string
tags []string
want map[string]string
}{
{desc: "multiple or separate values with commas", tags: []string{"key1=val1,key2=val2"}, want: map[string]string{"key1": "val1,key2=val2"}},
{desc: "exclude tag", tags: []string{"!key1"}, want: map[string]string{"!key1": ""}},
{desc: "exclude tags", tags: []string{"!key1=val"}, want: map[string]string{"!key1": "val"}},
}
for _, tc := range tests {
t.Run(fmt.Sprintf("%s", tc.desc), func(t *testing.T) {
got := NewZoneTagFilter(tc.tags)
assert.Equal(t, tc.want, got.tagsMap)
})
}
}

func TestZoneTagFilterMatchGeneratedValues(t *testing.T) {
tests := []struct {
filters int
zones int
source filterZoneTags
}{
{10, 30, generateTagFilterAndZoneTagsForMatch(10, 30)},
{5, 40, generateTagFilterAndZoneTagsForMatch(5, 40)},
{30, 50, generateTagFilterAndZoneTagsForMatch(30, 50)},
}
for _, tc := range tests {
t.Run(fmt.Sprintf("filters:%d zones:%d", tc.filters, tc.zones), func(t *testing.T) {
assert.True(t, tc.source.ZoneTagFilter.Match(tc.source.inputTags))
})
}
}

func TestZoneTagFilterNotMatchGeneratedValues(t *testing.T) {
tests := []struct {
filters int
zones int
source filterZoneTags
}{
{10, 30, generateTagFilterAndZoneTagsForNotMatch(10, 30)},
{5, 40, generateTagFilterAndZoneTagsForNotMatch(5, 40)},
{30, 50, generateTagFilterAndZoneTagsForNotMatch(30, 50)},
}
for _, tc := range tests {
t.Run(fmt.Sprintf("filters:%d zones:%d", tc.filters, tc.zones), func(t *testing.T) {
assert.False(t, tc.source.ZoneTagFilter.Match(tc.source.inputTags))
})
}
}

func BenchmarkZoneTagFilterMatchBasic(b *testing.B) {
for _, tc := range basicZoneTags {
zoneTagFilter := NewZoneTagFilter(tc.tagsFilter)
for range b.N {
zoneTagFilter.Match(tc.zoneTags)
}
}
}

var benchFixtures = []struct {
source filterZoneTags
}{
// match
{generateTagFilterAndZoneTagsForMatch(10, 30)},
{generateTagFilterAndZoneTagsForMatch(5, 40)},
{generateTagFilterAndZoneTagsForMatch(30, 50)},
// no match
{generateTagFilterAndZoneTagsForNotMatch(10, 30)},
{generateTagFilterAndZoneTagsForNotMatch(5, 40)},
{generateTagFilterAndZoneTagsForNotMatch(30, 50)},
}

func BenchmarkZoneTagFilterComplex(b *testing.B) {
for _, tc := range benchFixtures {
for range b.N {
tc.source.ZoneTagFilter.Match(tc.source.inputTags)
}
}
}
5 changes: 3 additions & 2 deletions provider/aws/aws.go
Original file line number Diff line number Diff line change
@@ -32,6 +32,7 @@ import (
log "github.com/sirupsen/logrus"

"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/pkg/filters"
"sigs.k8s.io/external-dns/plan"
"sigs.k8s.io/external-dns/provider"
)
@@ -254,7 +255,7 @@ type AWSProvider struct {
// filter hosted zones by type (e.g. private or public)
zoneTypeFilter provider.ZoneTypeFilter
// filter hosted zones by tags
zoneTagFilter provider.ZoneTagFilter
zoneTagFilter filters.ZoneTagFilter
// extend filter for subdomains in the zone (e.g. first.us-east-1.example.com)
zoneMatchParent bool
preferCNAME bool
@@ -268,7 +269,7 @@ type AWSConfig struct {
DomainFilter endpoint.DomainFilter
ZoneIDFilter provider.ZoneIDFilter
ZoneTypeFilter provider.ZoneTypeFilter
ZoneTagFilter provider.ZoneTagFilter
ZoneTagFilter filters.ZoneTagFilter
ZoneMatchParent bool
BatchChangeSize int
BatchChangeSizeBytes int
Loading