mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-07 10:06:57 +02:00
implemented annotationFilter for node source
This commit is contained in:
parent
a491d8f6a2
commit
16194ca9cf
@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/util/wait"
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
kubeinformers "k8s.io/client-go/informers"
|
kubeinformers "k8s.io/client-go/informers"
|
||||||
@ -36,12 +37,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type nodeSource struct {
|
type nodeSource struct {
|
||||||
client kubernetes.Interface
|
client kubernetes.Interface
|
||||||
fqdnTemplate *template.Template
|
annotationFilter string
|
||||||
nodeInformer coreinformers.NodeInformer
|
fqdnTemplate *template.Template
|
||||||
|
nodeInformer coreinformers.NodeInformer
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNodeSource(kubeClient kubernetes.Interface, fqdnTemplate string) (Source, error) {
|
func NewNodeSource(kubeClient kubernetes.Interface, annotationFilter, fqdnTemplate string) (Source, error) {
|
||||||
var (
|
var (
|
||||||
tmpl *template.Template
|
tmpl *template.Template
|
||||||
err error
|
err error
|
||||||
@ -82,9 +84,10 @@ func NewNodeSource(kubeClient kubernetes.Interface, fqdnTemplate string) (Source
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &nodeSource{
|
return &nodeSource{
|
||||||
client: kubeClient,
|
client: kubeClient,
|
||||||
fqdnTemplate: tmpl,
|
annotationFilter: annotationFilter,
|
||||||
nodeInformer: nodeInformer,
|
fqdnTemplate: tmpl,
|
||||||
|
nodeInformer: nodeInformer,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,6 +98,11 @@ func (ns *nodeSource) Endpoints() ([]*endpoint.Endpoint, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nodes, err = ns.filterByAnnotations(nodes)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
endpoints := []*endpoint.Endpoint{}
|
endpoints := []*endpoint.Endpoint{}
|
||||||
|
|
||||||
// create endpoints for all nodes
|
// create endpoints for all nodes
|
||||||
@ -149,3 +157,34 @@ func (ns *nodeSource) nodeAddress(node *v1.Node) (string, error) {
|
|||||||
|
|
||||||
return "", fmt.Errorf("could not find node address for %s", node.Name)
|
return "", fmt.Errorf("could not find node address for %s", node.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// filterByAnnotations filters a list of nodes by a given annotation selector.
|
||||||
|
func (ns *nodeSource) filterByAnnotations(nodes []*v1.Node) ([]*v1.Node, error) {
|
||||||
|
labelSelector, err := metav1.ParseToLabelSelector(ns.annotationFilter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
selector, err := metav1.LabelSelectorAsSelector(labelSelector)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// empty filter returns original list
|
||||||
|
if selector.Empty() {
|
||||||
|
return nodes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
filteredList := []*v1.Node{}
|
||||||
|
|
||||||
|
for _, node := range nodes {
|
||||||
|
// convert the node's annotations to an equivalent label selector
|
||||||
|
annotations := labels.Set(node.Annotations)
|
||||||
|
|
||||||
|
// include node if its annotations match the selector
|
||||||
|
if selector.Matches(annotations) {
|
||||||
|
filteredList = append(filteredList, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return filteredList, nil
|
||||||
|
}
|
||||||
|
@ -14,16 +14,17 @@ import (
|
|||||||
func TestNodeSource(t *testing.T) {
|
func TestNodeSource(t *testing.T) {
|
||||||
//suite.Run(t, new(ServiceSuite))
|
//suite.Run(t, new(ServiceSuite))
|
||||||
//t.Run("Interface", testServiceSourceImplementsSource)
|
//t.Run("Interface", testServiceSourceImplementsSource)
|
||||||
//t.Run("NewNodeSource", testNodeSourceNewNodeSource)
|
t.Run("NewNodeSource", testNodeSourceNewNodeSource)
|
||||||
t.Run("Endpoints", testNodeSourceEndpoints)
|
t.Run("Endpoints", testNodeSourceEndpoints)
|
||||||
}
|
}
|
||||||
|
|
||||||
// testNodeSourceNewNodeSource tests that NewNodeService doesn't return an error.
|
// testNodeSourceNewNodeSource tests that NewNodeService doesn't return an error.
|
||||||
func testNodeSourceNewNodeSource(t *testing.T) {
|
func testNodeSourceNewNodeSource(t *testing.T) {
|
||||||
for _, ti := range []struct {
|
for _, ti := range []struct {
|
||||||
title string
|
title string
|
||||||
fqdnTemplate string
|
annotationFilter string
|
||||||
expectError bool
|
fqdnTemplate string
|
||||||
|
expectError bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
title: "invalid template",
|
title: "invalid template",
|
||||||
@ -39,10 +40,16 @@ func testNodeSourceNewNodeSource(t *testing.T) {
|
|||||||
expectError: false,
|
expectError: false,
|
||||||
fqdnTemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com",
|
fqdnTemplate: "{{.Name}}-{{.Namespace}}.ext-dns.test.com",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "non-empty annotation filter label",
|
||||||
|
expectError: false,
|
||||||
|
annotationFilter: "kubernetes.io/ingress.class=nginx",
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(ti.title, func(t *testing.T) {
|
t.Run(ti.title, func(t *testing.T) {
|
||||||
_, err := NewNodeSource(
|
_, err := NewNodeSource(
|
||||||
fake.NewSimpleClientset(),
|
fake.NewSimpleClientset(),
|
||||||
|
ti.annotationFilter,
|
||||||
ti.fqdnTemplate,
|
ti.fqdnTemplate,
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -58,66 +65,72 @@ func testNodeSourceNewNodeSource(t *testing.T) {
|
|||||||
// testNodeSourceEndpoints tests that various node generate the correct endpoints.
|
// testNodeSourceEndpoints tests that various node generate the correct endpoints.
|
||||||
func testNodeSourceEndpoints(t *testing.T) {
|
func testNodeSourceEndpoints(t *testing.T) {
|
||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
title string
|
title string
|
||||||
fqdnTemplate string
|
annotationFilter string
|
||||||
nodeName string
|
fqdnTemplate string
|
||||||
nodeAddresses []v1.NodeAddress
|
nodeName string
|
||||||
labels map[string]string
|
nodeAddresses []v1.NodeAddress
|
||||||
annotations map[string]string
|
labels map[string]string
|
||||||
expected []*endpoint.Endpoint
|
annotations map[string]string
|
||||||
expectError bool
|
expected []*endpoint.Endpoint
|
||||||
|
expectError bool
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"node with short hostname returns one endpoint",
|
"node with short hostname returns one endpoint",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
"node1",
|
"node1",
|
||||||
[]v1.NodeAddress{{v1.NodeExternalIP, "1.2.3.4"}},
|
[]v1.NodeAddress{{v1.NodeExternalIP, "1.2.3.4"}},
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
[]*endpoint.Endpoint{
|
[]*endpoint.Endpoint{
|
||||||
{RecordType: "A", DNSName: "node1", Targets: endpoint.Targets{"1.2.3.4"}},
|
{RecordType: "A", DNSName: "node1", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"node with fqdn returns one endpoint",
|
"node with fqdn returns one endpoint",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
"node1.example.org",
|
"node1.example.org",
|
||||||
[]v1.NodeAddress{{v1.NodeExternalIP, "1.2.3.4"}},
|
[]v1.NodeAddress{{v1.NodeExternalIP, "1.2.3.4"}},
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
[]*endpoint.Endpoint{
|
[]*endpoint.Endpoint{
|
||||||
{RecordType: "A", DNSName: "node1.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
{RecordType: "A", DNSName: "node1.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"node with fqdn template returns endpoint with expanded hostname",
|
"node with fqdn template returns endpoint with expanded hostname",
|
||||||
|
"",
|
||||||
"{{.Name}}.example.org",
|
"{{.Name}}.example.org",
|
||||||
"node1",
|
"node1",
|
||||||
[]v1.NodeAddress{{v1.NodeExternalIP, "1.2.3.4"}},
|
[]v1.NodeAddress{{v1.NodeExternalIP, "1.2.3.4"}},
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
[]*endpoint.Endpoint{
|
[]*endpoint.Endpoint{
|
||||||
{RecordType: "A", DNSName: "node1.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
{RecordType: "A", DNSName: "node1.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"node with fqdn and fqdn template returns one endpoint",
|
"node with fqdn and fqdn template returns one endpoint",
|
||||||
|
"",
|
||||||
"{{.Name}}.example.org",
|
"{{.Name}}.example.org",
|
||||||
"node1.example.org",
|
"node1.example.org",
|
||||||
[]v1.NodeAddress{{v1.NodeExternalIP, "1.2.3.4"}},
|
[]v1.NodeAddress{{v1.NodeExternalIP, "1.2.3.4"}},
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
[]*endpoint.Endpoint{
|
[]*endpoint.Endpoint{
|
||||||
{RecordType: "A", DNSName: "node1.example.org.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
{RecordType: "A", DNSName: "node1.example.org.example.org", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"node with both external and internal IP returns an endpoint with external IP",
|
"node with both external and internal IP returns an endpoint with external IP",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
"node1",
|
"node1",
|
||||||
[]v1.NodeAddress{{v1.NodeExternalIP, "1.2.3.4"}, {v1.NodeInternalIP, "2.3.4.5"}},
|
[]v1.NodeAddress{{v1.NodeExternalIP, "1.2.3.4"}, {v1.NodeInternalIP, "2.3.4.5"}},
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
@ -127,9 +140,10 @@ func testNodeSourceEndpoints(t *testing.T) {
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"node with only internal IP returns an endpoint with internal IP",
|
"node with only internal IP returns an endpoint with internal IP",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
"node1",
|
"node1",
|
||||||
[]v1.NodeAddress{{v1.NodeInternalIP, "2.3.4.5"}},
|
[]v1.NodeAddress{{v1.NodeInternalIP, "2.3.4.5"}},
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
@ -139,9 +153,10 @@ func testNodeSourceEndpoints(t *testing.T) {
|
|||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"node with neither external nor internal IP returns no endpoints",
|
"node with neither external nor internal IP returns no endpoints",
|
||||||
"",
|
"",
|
||||||
|
"",
|
||||||
"node1",
|
"node1",
|
||||||
[]v1.NodeAddress{},
|
[]v1.NodeAddress{},
|
||||||
map[string]string{},
|
map[string]string{},
|
||||||
@ -149,6 +164,49 @@ func testNodeSourceEndpoints(t *testing.T) {
|
|||||||
[]*endpoint.Endpoint{},
|
[]*endpoint.Endpoint{},
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"annotated node without annotation filter returns endpoint",
|
||||||
|
"",
|
||||||
|
"",
|
||||||
|
"node1",
|
||||||
|
[]v1.NodeAddress{{v1.NodeExternalIP, "1.2.3.4"}},
|
||||||
|
map[string]string{},
|
||||||
|
map[string]string{
|
||||||
|
"service.beta.kubernetes.io/external-traffic": "OnlyLocal",
|
||||||
|
},
|
||||||
|
[]*endpoint.Endpoint{
|
||||||
|
{RecordType: "A", DNSName: "node1", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"annotated node with matching annotation filter returns endpoint",
|
||||||
|
"service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)",
|
||||||
|
"",
|
||||||
|
"node1",
|
||||||
|
[]v1.NodeAddress{{v1.NodeExternalIP, "1.2.3.4"}},
|
||||||
|
map[string]string{},
|
||||||
|
map[string]string{
|
||||||
|
"service.beta.kubernetes.io/external-traffic": "OnlyLocal",
|
||||||
|
},
|
||||||
|
[]*endpoint.Endpoint{
|
||||||
|
{RecordType: "A", DNSName: "node1", Targets: endpoint.Targets{"1.2.3.4"}},
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"annotated node with non-matching annotation filter returns endpoint",
|
||||||
|
"service.beta.kubernetes.io/external-traffic in (Global, OnlyLocal)",
|
||||||
|
"",
|
||||||
|
"node1",
|
||||||
|
[]v1.NodeAddress{{v1.NodeExternalIP, "1.2.3.4"}},
|
||||||
|
map[string]string{},
|
||||||
|
map[string]string{
|
||||||
|
"service.beta.kubernetes.io/external-traffic": "SomethingElse",
|
||||||
|
},
|
||||||
|
[]*endpoint.Endpoint{},
|
||||||
|
false,
|
||||||
|
},
|
||||||
} {
|
} {
|
||||||
t.Run(tc.title, func(t *testing.T) {
|
t.Run(tc.title, func(t *testing.T) {
|
||||||
// Create a Kubernetes testing client
|
// Create a Kubernetes testing client
|
||||||
@ -171,6 +229,7 @@ func testNodeSourceEndpoints(t *testing.T) {
|
|||||||
// Create our object under test and get the endpoints.
|
// Create our object under test and get the endpoints.
|
||||||
client, err := NewNodeSource(
|
client, err := NewNodeSource(
|
||||||
kubernetes,
|
kubernetes,
|
||||||
|
tc.annotationFilter,
|
||||||
tc.fqdnTemplate,
|
tc.fqdnTemplate,
|
||||||
)
|
)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
@ -158,7 +158,7 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewNodeSource(client, cfg.FQDNTemplate)
|
return NewNodeSource(client, cfg.AnnotationFilter, cfg.FQDNTemplate)
|
||||||
case "service":
|
case "service":
|
||||||
client, err := p.KubeClient()
|
client, err := p.KubeClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user