Skip to content

Commit

Permalink
feat: add regex domain filters
Browse files Browse the repository at this point in the history
Signed-off-by: Enrique Gonzalez <goga.enrique@gmail.com>
  • Loading branch information
offzale committed Apr 9, 2020
1 parent d0c776b commit c5e0227
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 4 deletions.
48 changes: 45 additions & 3 deletions endpoint/domain_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ limitations under the License.
package endpoint

import (
"regexp"
"strings"

log "github.com/sirupsen/logrus"
)

// DomainFilter holds a lists of valid domain names
Expand All @@ -26,6 +29,10 @@ type DomainFilter struct {
Filters []string
// exclude define what domains not to match
exclude []string
// regex defines a regular expression to match the domains
regex string
// regexExclusion defines a regular expression to exclude the domains matched
regexExclusion string
}

// prepareFilters provides consistent trimming for filters/exclude params
Expand All @@ -39,16 +46,26 @@ func prepareFilters(filters []string) []string {

// NewDomainFilterWithExclusions returns a new DomainFilter, given a list of matches and exclusions
func NewDomainFilterWithExclusions(domainFilters []string, excludeDomains []string) DomainFilter {
return DomainFilter{prepareFilters(domainFilters), prepareFilters(excludeDomains)}
return DomainFilter{prepareFilters(domainFilters), prepareFilters(excludeDomains), "", ""}
}

// NewDomainFilter returns a new DomainFilter given a comma separated list of domains
func NewDomainFilter(domainFilters []string) DomainFilter {
return DomainFilter{prepareFilters(domainFilters), []string{}}
return DomainFilter{prepareFilters(domainFilters), []string{}, "", ""}
}

// NewRegexDomainFilter returns a new DomainFilter given a regular expression
func NewRegexDomainFilter(regexDomainFilter string, regexDomainExclusion string) DomainFilter {
return DomainFilter{[]string{}, []string{}, regexDomainFilter, regexDomainExclusion}
}

// Match checks whether a domain can be found in the DomainFilter.
// RegexFilter takes precedence over Filters
func (df DomainFilter) Match(domain string) bool {
if df.regex != "" {
return matchRegex(df.regex, df.regexExclusion, domain)
}

return matchFilter(df.Filters, domain, true) && !matchFilter(df.exclude, domain, false)
}

Expand Down Expand Up @@ -78,9 +95,34 @@ func matchFilter(filters []string, domain string, emptyval bool) bool {
return false
}

// matchRegex determines if a domain matches the configured regular expressions in the DomainFilter.
// The negativeRegex, if set, takes precedence over regex. Therefore,
// matchRegex returns true when only regex regular expression matches the domain.
// Otherwise, if either negativeRegex matches or regex does not match the domain, it will return false.
func matchRegex(regex string, negativeRegex string, domain string) bool {
strippedDomain := strings.ToLower(strings.TrimSuffix(domain, "."))

if negativeRegex != "" {
match, err := regexp.MatchString(negativeRegex, strippedDomain)
if err != nil {
log.Errorf("Failed to filter domain %s with the regex-exclusion filter: %v", domain, err)
}
if match {
return false
}
}
match, err := regexp.MatchString(regex, strippedDomain)
if err != nil {
log.Errorf("Failed to filter domain %s with the regex filter: %v", domain, err)
}
return match
}

// IsConfigured returns true if DomainFilter is configured, false otherwise
func (df DomainFilter) IsConfigured() bool {
if len(df.Filters) == 1 {
if df.regex != "" {
return true
} else if len(df.Filters) == 1 {
return df.Filters[0] != ""
}
return len(df.Filters) > 0
Expand Down
8 changes: 7 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,13 @@ func main() {
// Combine multiple sources into a single, deduplicated source.
endpointsSource := source.NewDedupSource(source.NewMultiSource(sources))

domainFilter := endpoint.NewDomainFilterWithExclusions(cfg.DomainFilter, cfg.ExcludeDomains)
// RegexDomainFilter overrides DomainFilter
var domainFilter endpoint.DomainFilter
if cfg.RegexDomainFilter != "" {
domainFilter = endpoint.NewRegexDomainFilter(cfg.RegexDomainFilter, cfg.RegexDomainExclusion)
} else {
domainFilter = endpoint.NewDomainFilterWithExclusions(cfg.DomainFilter, cfg.ExcludeDomains)
}
zoneIDFilter := provider.NewZoneIDFilter(cfg.ZoneIDFilter)
zoneTypeFilter := provider.NewZoneTypeFilter(cfg.AWSZoneType)
zoneTagFilter := provider.NewZoneTagFilter(cfg.AWSZoneTagFilter)
Expand Down
6 changes: 6 additions & 0 deletions pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ type Config struct {
GoogleBatchChangeInterval time.Duration
DomainFilter []string
ExcludeDomains []string
RegexDomainFilter string
RegexDomainExclusion string
ZoneIDFilter []string
AlibabaCloudConfigFile string
AlibabaCloudZoneType string
Expand Down Expand Up @@ -164,6 +166,8 @@ var defaultConfig = &Config{
GoogleBatchChangeInterval: time.Second,
DomainFilter: []string{},
ExcludeDomains: []string{},
RegexDomainFilter: "",
RegexDomainExclusion: "",
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "",
AWSZoneTagFilter: []string{},
Expand Down Expand Up @@ -315,6 +319,8 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("provider", "The DNS provider where the DNS records will be created (required, options: aws, aws-sd, google, azure, azure-dns, azure-private-dns, cloudflare, rcodezero, digitalocean, dnsimple, akamai, infoblox, dyn, designate, coredns, skydns, inmemory, ovh, pdns, oci, exoscale, linode, rfc2136, ns1, transip, vinyldns, rdns)").Required().PlaceHolder("provider").EnumVar(&cfg.Provider, "aws", "aws-sd", "google", "azure", "azure-dns", "azure-private-dns", "alibabacloud", "cloudflare", "rcodezero", "digitalocean", "dnsimple", "akamai", "infoblox", "dyn", "designate", "coredns", "skydns", "inmemory", "ovh", "pdns", "oci", "exoscale", "linode", "rfc2136", "ns1", "transip", "vinyldns", "rdns")
app.Flag("domain-filter", "Limit possible target zones by a domain suffix; specify multiple times for multiple domains (optional)").Default("").StringsVar(&cfg.DomainFilter)
app.Flag("exclude-domains", "Exclude subdomains (optional)").Default("").StringsVar(&cfg.ExcludeDomains)
app.Flag("regex-domain-filter", "Limit possible domains and target zones by a Regex filter; Overrides domain-filter (optional)").Default("").StringVar(&cfg.RegexDomainFilter)
app.Flag("regex-domain-exclusion", "Regex filter that excludes domains and target zones matched by regex-domain-filter (optional)").Default("").StringVar(&cfg.RegexDomainExclusion)
app.Flag("zone-id-filter", "Filter target zones by hosted zone id; specify multiple times for multiple zones (optional)").Default("").StringsVar(&cfg.ZoneIDFilter)
app.Flag("google-project", "When using the Google provider, current project is auto-detected, when running on GCP. Specify other project with this. Must be specified when running outside GCP.").Default(defaultConfig.GoogleProject).StringVar(&cfg.GoogleProject)
app.Flag("google-batch-change-size", "When using the Google provider, set the maximum number of changes that will be applied in each batch.").Default(strconv.Itoa(defaultConfig.GoogleBatchChangeSize)).IntVar(&cfg.GoogleBatchChangeSize)
Expand Down
8 changes: 8 additions & 0 deletions pkg/apis/externaldns/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ var (
GoogleBatchChangeInterval: time.Second,
DomainFilter: []string{""},
ExcludeDomains: []string{""},
RegexDomainFilter: "",
RegexDomainExclusion: "",
ZoneIDFilter: []string{""},
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "",
Expand Down Expand Up @@ -117,6 +119,8 @@ var (
GoogleBatchChangeInterval: time.Second * 2,
DomainFilter: []string{"example.org", "company.com"},
ExcludeDomains: []string{"xapi.example.org", "xapi.company.com"},
RegexDomainFilter: "(example\\.org|company\\.com)$",
RegexDomainExclusion: "xapi\\.(example\\.org|company\\.com)$",
ZoneIDFilter: []string{"/hostedzone/ZTST1", "/hostedzone/ZTST2"},
AlibabaCloudConfigFile: "/etc/kubernetes/alibaba-cloud.json",
AWSZoneType: "private",
Expand Down Expand Up @@ -247,6 +251,8 @@ func TestParseFlags(t *testing.T) {
"--domain-filter=company.com",
"--exclude-domains=xapi.example.org",
"--exclude-domains=xapi.company.com",
"--regex-domain-filter=(example\\.org|company\\.com)$",
"--regex-domain-exclusion=xapi\\.(example\\.org|company\\.com)$",
"--zone-id-filter=/hostedzone/ZTST1",
"--zone-id-filter=/hostedzone/ZTST2",
"--aws-zone-type=private",
Expand Down Expand Up @@ -325,6 +331,8 @@ func TestParseFlags(t *testing.T) {
"EXTERNAL_DNS_OVH_ENDPOINT": "ovh-ca",
"EXTERNAL_DNS_DOMAIN_FILTER": "example.org\ncompany.com",
"EXTERNAL_DNS_EXCLUDE_DOMAINS": "xapi.example.org\nxapi.company.com",
"EXTERNAL_DNS_REGEX_DOMAIN_FILTER": "(example\\.org|company\\.com)$",
"EXTERNAL_DNS_REGEX_DOMAIN_EXCLUSION": "xapi\\.(example\\.org|company\\.com)$",
"EXTERNAL_DNS_PDNS_SERVER": "http://ns.example.com:8081",
"EXTERNAL_DNS_PDNS_API_KEY": "some-secret-key",
"EXTERNAL_DNS_PDNS_TLS_ENABLED": "1",
Expand Down

0 comments on commit c5e0227

Please sign in to comment.