Added support for new traefik CRDs

Signed-off-by: Thomas Kosiewski <thomas.kosiewski@loft.sh>
This commit is contained in:
Thomas Kosiewski 2023-06-14 18:08:10 +02:00
parent f0ac655838
commit bd67905307
No known key found for this signature in database
GPG Key ID: B6B62A55E38F54E8
2 changed files with 896 additions and 18 deletions

View File

@ -42,16 +42,31 @@ import (
var (
ingressrouteGVR = schema.GroupVersionResource{
Group: "traefik.containo.us",
Group: "traefik.io",
Version: "v1alpha1",
Resource: "ingressroutes",
}
ingressrouteTCPGVR = schema.GroupVersionResource{
Group: "traefik.containo.us",
Group: "traefik.io",
Version: "v1alpha1",
Resource: "ingressroutetcps",
}
ingressrouteUDPGVR = schema.GroupVersionResource{
Group: "traefik.io",
Version: "v1alpha1",
Resource: "ingressrouteudps",
}
oldIngressrouteGVR = schema.GroupVersionResource{
Group: "traefik.containo.us",
Version: "v1alpha1",
Resource: "ingressroutes",
}
oldIngressrouteTCPGVR = schema.GroupVersionResource{
Group: "traefik.containo.us",
Version: "v1alpha1",
Resource: "ingressroutetcps",
}
oldIngressrouteUDPGVR = schema.GroupVersionResource{
Group: "traefik.containo.us",
Version: "v1alpha1",
Resource: "ingressrouteudps",
@ -64,14 +79,17 @@ var (
)
type traefikSource struct {
annotationFilter string
dynamicKubeClient dynamic.Interface
ingressRouteInformer informers.GenericInformer
ingressRouteTcpInformer informers.GenericInformer
ingressRouteUdpInformer informers.GenericInformer
kubeClient kubernetes.Interface
namespace string
unstructuredConverter *unstructuredConverter
annotationFilter string
dynamicKubeClient dynamic.Interface
ingressRouteInformer informers.GenericInformer
ingressRouteTcpInformer informers.GenericInformer
ingressRouteUdpInformer informers.GenericInformer
oldIngressRouteInformer informers.GenericInformer
oldIngressRouteTcpInformer informers.GenericInformer
oldIngressRouteUdpInformer informers.GenericInformer
kubeClient kubernetes.Interface
namespace string
unstructuredConverter *unstructuredConverter
}
func NewTraefikSource(ctx context.Context, dynamicKubeClient dynamic.Interface, kubeClient kubernetes.Interface, namespace string, annotationFilter string) (Source, error) {
@ -81,6 +99,9 @@ func NewTraefikSource(ctx context.Context, dynamicKubeClient dynamic.Interface,
ingressRouteInformer := informerFactory.ForResource(ingressrouteGVR)
ingressRouteTcpInformer := informerFactory.ForResource(ingressrouteTCPGVR)
ingressRouteUdpInformer := informerFactory.ForResource(ingressrouteUDPGVR)
oldIngressRouteInformer := informerFactory.ForResource(oldIngressrouteGVR)
oldIngressRouteTcpInformer := informerFactory.ForResource(oldIngressrouteTCPGVR)
oldIngressRouteUdpInformer := informerFactory.ForResource(oldIngressrouteUDPGVR)
// Add default resource event handlers to properly initialize informers.
ingressRouteInformer.Informer().AddEventHandler(
@ -98,6 +119,21 @@ func NewTraefikSource(ctx context.Context, dynamicKubeClient dynamic.Interface,
AddFunc: func(obj interface{}) {},
},
)
oldIngressRouteInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {},
},
)
oldIngressRouteTcpInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {},
},
)
oldIngressRouteUdpInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {},
},
)
informerFactory.Start((ctx.Done()))
@ -112,14 +148,17 @@ func NewTraefikSource(ctx context.Context, dynamicKubeClient dynamic.Interface,
}
return &traefikSource{
annotationFilter: annotationFilter,
dynamicKubeClient: dynamicKubeClient,
ingressRouteInformer: ingressRouteInformer,
ingressRouteTcpInformer: ingressRouteTcpInformer,
ingressRouteUdpInformer: ingressRouteUdpInformer,
kubeClient: kubeClient,
namespace: namespace,
unstructuredConverter: uc,
annotationFilter: annotationFilter,
dynamicKubeClient: dynamicKubeClient,
ingressRouteInformer: ingressRouteInformer,
ingressRouteTcpInformer: ingressRouteTcpInformer,
ingressRouteUdpInformer: ingressRouteUdpInformer,
oldIngressRouteInformer: oldIngressRouteInformer,
oldIngressRouteTcpInformer: oldIngressRouteTcpInformer,
oldIngressRouteUdpInformer: oldIngressRouteUdpInformer,
kubeClient: kubeClient,
namespace: namespace,
unstructuredConverter: uc,
}, nil
}
@ -130,18 +169,33 @@ func (ts *traefikSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, e
if err != nil {
return nil, err
}
oldIngressRouteEndpoints, err := ts.oldIngressRouteEndpoints()
if err != nil {
return nil, err
}
ingressRouteTCPEndpoints, err := ts.ingressRouteTCPEndpoints()
if err != nil {
return nil, err
}
oldIngressRouteTCPEndpoints, err := ts.oldIngressRouteTCPEndpoints()
if err != nil {
return nil, err
}
ingressRouteUDPEndpoints, err := ts.ingressRouteUDPEndpoints()
if err != nil {
return nil, err
}
oldIngressRouteUDPEndpoints, err := ts.oldIngressRouteUDPEndpoints()
if err != nil {
return nil, err
}
endpoints = append(endpoints, ingressRouteEndpoints...)
endpoints = append(endpoints, ingressRouteTCPEndpoints...)
endpoints = append(endpoints, ingressRouteUDPEndpoints...)
endpoints = append(endpoints, oldIngressRouteEndpoints...)
endpoints = append(endpoints, oldIngressRouteTCPEndpoints...)
endpoints = append(endpoints, oldIngressRouteUDPEndpoints...)
for _, ep := range endpoints {
sort.Sort(ep.Targets)
@ -312,6 +366,168 @@ func (ts *traefikSource) ingressRouteUDPEndpoints() ([]*endpoint.Endpoint, error
return endpoints, nil
}
// oldIngressRouteEndpoints extracts endpoints from all IngressRoute objects
func (ts *traefikSource) oldIngressRouteEndpoints() ([]*endpoint.Endpoint, error) {
var endpoints []*endpoint.Endpoint
irs, err := ts.oldIngressRouteInformer.Lister().ByNamespace(ts.namespace).List(labels.Everything())
if err != nil {
return nil, err
}
var ingressRoutes []*IngressRoute
for _, ingressRouteObj := range irs {
unstructuredHost, ok := ingressRouteObj.(*unstructured.Unstructured)
if !ok {
return nil, errors.New("could not convert IngressRoute object to unstructured")
}
ingressRoute := &IngressRoute{}
err := ts.unstructuredConverter.scheme.Convert(unstructuredHost, ingressRoute, nil)
if err != nil {
return nil, err
}
ingressRoutes = append(ingressRoutes, ingressRoute)
}
ingressRoutes, err = ts.filterIngressRouteByAnnotation(ingressRoutes)
if err != nil {
return nil, errors.Wrap(err, "failed to filter IngressRoute")
}
for _, ingressRoute := range ingressRoutes {
var targets endpoint.Targets
targets = append(targets, getTargetsFromTargetAnnotation(ingressRoute.Annotations)...)
fullname := fmt.Sprintf("%s/%s", ingressRoute.Namespace, ingressRoute.Name)
ingressEndpoints, err := ts.endpointsFromIngressRoute(ingressRoute, targets)
if err != nil {
return nil, err
}
if len(ingressEndpoints) == 0 {
log.Debugf("No endpoints could be generated from Host %s", fullname)
continue
}
log.Debugf("Endpoints generated from IngressRoute: %s: %v", fullname, ingressEndpoints)
ts.setResourceLabelIngressRoute(ingressRoute, ingressEndpoints)
ts.setDualstackLabelIngressRoute(ingressRoute, ingressEndpoints)
endpoints = append(endpoints, ingressEndpoints...)
}
return endpoints, nil
}
// oldIngressRouteTCPEndpoints extracts endpoints from all IngressRouteTCP objects
func (ts *traefikSource) oldIngressRouteTCPEndpoints() ([]*endpoint.Endpoint, error) {
var endpoints []*endpoint.Endpoint
irs, err := ts.oldIngressRouteTcpInformer.Lister().ByNamespace(ts.namespace).List(labels.Everything())
if err != nil {
return nil, err
}
var ingressRouteTCPs []*IngressRouteTCP
for _, ingressRouteTCPObj := range irs {
unstructuredHost, ok := ingressRouteTCPObj.(*unstructured.Unstructured)
if !ok {
return nil, errors.New("could not convert IngressRouteTCP object to unstructured")
}
ingressRouteTCP := &IngressRouteTCP{}
err := ts.unstructuredConverter.scheme.Convert(unstructuredHost, ingressRouteTCP, nil)
if err != nil {
return nil, err
}
ingressRouteTCPs = append(ingressRouteTCPs, ingressRouteTCP)
}
ingressRouteTCPs, err = ts.filterIngressRouteTcpByAnnotations(ingressRouteTCPs)
if err != nil {
return nil, errors.Wrap(err, "failed to filter IngressRouteTCP")
}
for _, ingressRouteTCP := range ingressRouteTCPs {
var targets endpoint.Targets
targets = append(targets, getTargetsFromTargetAnnotation(ingressRouteTCP.Annotations)...)
fullname := fmt.Sprintf("%s/%s", ingressRouteTCP.Namespace, ingressRouteTCP.Name)
ingressEndpoints, err := ts.endpointsFromIngressRouteTCP(ingressRouteTCP, targets)
if err != nil {
return nil, err
}
if len(ingressEndpoints) == 0 {
log.Debugf("No endpoints could be generated from Host %s", fullname)
continue
}
log.Debugf("Endpoints generated from IngressRouteTCP: %s: %v", fullname, ingressEndpoints)
ts.setResourceLabelIngressRouteTCP(ingressRouteTCP, ingressEndpoints)
ts.setDualstackLabelIngressRouteTCP(ingressRouteTCP, ingressEndpoints)
endpoints = append(endpoints, ingressEndpoints...)
}
return endpoints, nil
}
// oldIngressRouteUDPEndpoints extracts endpoints from all IngressRouteUDP objects
func (ts *traefikSource) oldIngressRouteUDPEndpoints() ([]*endpoint.Endpoint, error) {
var endpoints []*endpoint.Endpoint
irs, err := ts.oldIngressRouteUdpInformer.Lister().ByNamespace(ts.namespace).List(labels.Everything())
if err != nil {
return nil, err
}
var ingressRouteUDPs []*IngressRouteUDP
for _, ingressRouteUDPObj := range irs {
unstructuredHost, ok := ingressRouteUDPObj.(*unstructured.Unstructured)
if !ok {
return nil, errors.New("could not convert IngressRouteUDP object to unstructured")
}
ingressRoute := &IngressRouteUDP{}
err := ts.unstructuredConverter.scheme.Convert(unstructuredHost, ingressRoute, nil)
if err != nil {
return nil, err
}
ingressRouteUDPs = append(ingressRouteUDPs, ingressRoute)
}
ingressRouteUDPs, err = ts.filterIngressRouteUdpByAnnotations(ingressRouteUDPs)
if err != nil {
return nil, errors.Wrap(err, "failed to filter IngressRouteUDP")
}
for _, ingressRouteUDP := range ingressRouteUDPs {
var targets endpoint.Targets
targets = append(targets, getTargetsFromTargetAnnotation(ingressRouteUDP.Annotations)...)
fullname := fmt.Sprintf("%s/%s", ingressRouteUDP.Namespace, ingressRouteUDP.Name)
ingressEndpoints, err := ts.endpointsFromIngressRouteUDP(ingressRouteUDP, targets)
if err != nil {
return nil, err
}
if len(ingressEndpoints) == 0 {
log.Debugf("No endpoints could be generated from Host %s", fullname)
continue
}
log.Debugf("Endpoints generated from IngressRouteUDP: %s: %v", fullname, ingressEndpoints)
ts.setResourceLabelIngressRouteUDP(ingressRouteUDP, ingressEndpoints)
ts.setDualstackLabelIngressRouteUDP(ingressRouteUDP, ingressEndpoints)
endpoints = append(endpoints, ingressEndpoints...)
}
return endpoints, nil
}
// filterIngressRouteByAnnotation filters a list of IngressRoute by a given annotation selector.
func (ts *traefikSource) filterIngressRouteByAnnotation(ingressRoutes []*IngressRoute) ([]*IngressRoute, error) {
labelSelector, err := metav1.ParseToLabelSelector(ts.annotationFilter)
@ -544,10 +760,13 @@ func (ts *traefikSource) AddEventHandler(ctx context.Context, handler func()) {
// https://github.com/kubernetes/kubernetes/issues/79610
log.Debug("Adding event handler for IngressRoute")
ts.ingressRouteInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
ts.oldIngressRouteInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
log.Debug("Adding event handler for IngressRouteTCP")
ts.ingressRouteTcpInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
ts.oldIngressRouteTcpInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
log.Debug("Adding event handler for IngressRouteUDP")
ts.ingressRouteUdpInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
ts.oldIngressRouteUdpInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
}
// newTraefikUnstructuredConverter returns a new unstructuredConverter initialized
@ -558,8 +777,11 @@ func newTraefikUnstructuredConverter() (*unstructuredConverter, error) {
// Add the core types we need
uc.scheme.AddKnownTypes(ingressrouteGVR.GroupVersion(), &IngressRoute{}, &IngressRouteList{})
uc.scheme.AddKnownTypes(oldIngressrouteGVR.GroupVersion(), &IngressRoute{}, &IngressRouteList{})
uc.scheme.AddKnownTypes(ingressrouteTCPGVR.GroupVersion(), &IngressRouteTCP{}, &IngressRouteTCPList{})
uc.scheme.AddKnownTypes(oldIngressrouteTCPGVR.GroupVersion(), &IngressRouteTCP{}, &IngressRouteTCPList{})
uc.scheme.AddKnownTypes(ingressrouteUDPGVR.GroupVersion(), &IngressRouteUDP{}, &IngressRouteUDPList{})
uc.scheme.AddKnownTypes(oldIngressrouteUDPGVR.GroupVersion(), &IngressRouteUDP{}, &IngressRouteUDPList{})
if err := scheme.AddToScheme(uc.scheme); err != nil {
return nil, err
}

View File

@ -283,6 +283,9 @@ func TestTraefikProxyIngressRouteEndpoints(t *testing.T) {
scheme.AddKnownTypes(ingressrouteGVR.GroupVersion(), &IngressRoute{}, &IngressRouteList{})
scheme.AddKnownTypes(ingressrouteTCPGVR.GroupVersion(), &IngressRouteTCP{}, &IngressRouteTCPList{})
scheme.AddKnownTypes(ingressrouteUDPGVR.GroupVersion(), &IngressRouteUDP{}, &IngressRouteUDPList{})
scheme.AddKnownTypes(oldIngressrouteGVR.GroupVersion(), &IngressRoute{}, &IngressRouteList{})
scheme.AddKnownTypes(oldIngressrouteTCPGVR.GroupVersion(), &IngressRouteTCP{}, &IngressRouteTCPList{})
scheme.AddKnownTypes(oldIngressrouteUDPGVR.GroupVersion(), &IngressRouteUDP{}, &IngressRouteUDPList{})
fakeDynamicClient := fakeDynamic.NewSimpleDynamicClient(scheme)
ir := unstructured.Unstructured{}
@ -525,6 +528,9 @@ func TestTraefikProxyIngressRouteTCPEndpoints(t *testing.T) {
scheme.AddKnownTypes(ingressrouteGVR.GroupVersion(), &IngressRoute{}, &IngressRouteList{})
scheme.AddKnownTypes(ingressrouteTCPGVR.GroupVersion(), &IngressRouteTCP{}, &IngressRouteTCPList{})
scheme.AddKnownTypes(ingressrouteUDPGVR.GroupVersion(), &IngressRouteUDP{}, &IngressRouteUDPList{})
scheme.AddKnownTypes(oldIngressrouteGVR.GroupVersion(), &IngressRoute{}, &IngressRouteList{})
scheme.AddKnownTypes(oldIngressrouteTCPGVR.GroupVersion(), &IngressRouteTCP{}, &IngressRouteTCPList{})
scheme.AddKnownTypes(oldIngressrouteUDPGVR.GroupVersion(), &IngressRouteUDP{}, &IngressRouteUDPList{})
fakeDynamicClient := fakeDynamic.NewSimpleDynamicClient(scheme)
ir := unstructured.Unstructured{}
@ -643,6 +649,9 @@ func TestTraefikProxyIngressRouteUDPEndpoints(t *testing.T) {
scheme.AddKnownTypes(ingressrouteGVR.GroupVersion(), &IngressRoute{}, &IngressRouteList{})
scheme.AddKnownTypes(ingressrouteTCPGVR.GroupVersion(), &IngressRouteTCP{}, &IngressRouteTCPList{})
scheme.AddKnownTypes(ingressrouteUDPGVR.GroupVersion(), &IngressRouteUDP{}, &IngressRouteUDPList{})
scheme.AddKnownTypes(oldIngressrouteGVR.GroupVersion(), &IngressRoute{}, &IngressRouteList{})
scheme.AddKnownTypes(oldIngressrouteTCPGVR.GroupVersion(), &IngressRouteTCP{}, &IngressRouteTCPList{})
scheme.AddKnownTypes(oldIngressrouteUDPGVR.GroupVersion(), &IngressRouteUDP{}, &IngressRouteUDPList{})
fakeDynamicClient := fakeDynamic.NewSimpleDynamicClient(scheme)
ir := unstructured.Unstructured{}
@ -672,3 +681,650 @@ func TestTraefikProxyIngressRouteUDPEndpoints(t *testing.T) {
})
}
}
func TestTraefikProxyOldIngressRouteEndpoints(t *testing.T) {
t.Parallel()
for _, ti := range []struct {
title string
ingressRoute IngressRoute
expected []*endpoint.Endpoint
}{
{
title: "IngressRoute with hostname annotation",
ingressRoute: IngressRoute{
TypeMeta: metav1.TypeMeta{
APIVersion: oldIngressrouteGVR.GroupVersion().String(),
Kind: "IngressRoute",
},
ObjectMeta: metav1.ObjectMeta{
Name: "ingressroute-annotation",
Namespace: defaultTraefikNamespace,
Annotations: map[string]string{
"external-dns.alpha.kubernetes.io/hostname": "a.example.com",
"external-dns.alpha.kubernetes.io/target": "target.domain.tld",
"kubernetes.io/ingress.class": "traefik",
},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "a.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressroute/traefik/ingressroute-annotation",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
},
},
{
title: "IngressRoute with host rule",
ingressRoute: IngressRoute{
TypeMeta: metav1.TypeMeta{
APIVersion: oldIngressrouteGVR.GroupVersion().String(),
Kind: "IngressRoute",
},
ObjectMeta: metav1.ObjectMeta{
Name: "ingressroute-host-match",
Namespace: defaultTraefikNamespace,
Annotations: map[string]string{
"external-dns.alpha.kubernetes.io/target": "target.domain.tld",
"kubernetes.io/ingress.class": "traefik",
},
},
Spec: traefikIngressRouteSpec{
Routes: []traefikRoute{
{
Match: "Host(`b.example.com`)",
},
},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "b.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressroute/traefik/ingressroute-host-match",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
},
},
{
title: "IngressRoute with hostheader rule",
ingressRoute: IngressRoute{
TypeMeta: metav1.TypeMeta{
APIVersion: oldIngressrouteGVR.GroupVersion().String(),
Kind: "IngressRoute",
},
ObjectMeta: metav1.ObjectMeta{
Name: "ingressroute-hostheader-match",
Namespace: defaultTraefikNamespace,
Annotations: map[string]string{
"external-dns.alpha.kubernetes.io/target": "target.domain.tld",
"kubernetes.io/ingress.class": "traefik",
},
},
Spec: traefikIngressRouteSpec{
Routes: []traefikRoute{
{
Match: "HostHeader(`c.example.com`)",
},
},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "c.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressroute/traefik/ingressroute-hostheader-match",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
},
},
{
title: "IngressRoute with multiple host rules",
ingressRoute: IngressRoute{
TypeMeta: metav1.TypeMeta{
APIVersion: oldIngressrouteGVR.GroupVersion().String(),
Kind: "IngressRoute",
},
ObjectMeta: metav1.ObjectMeta{
Name: "ingressroute-multi-host-match",
Namespace: defaultTraefikNamespace,
Annotations: map[string]string{
"external-dns.alpha.kubernetes.io/target": "target.domain.tld",
"kubernetes.io/ingress.class": "traefik",
},
},
Spec: traefikIngressRouteSpec{
Routes: []traefikRoute{
{
Match: "Host(`d.example.com`) || Host(`e.example.com`)",
},
},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "d.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressroute/traefik/ingressroute-multi-host-match",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
{
DNSName: "e.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressroute/traefik/ingressroute-multi-host-match",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
},
},
{
title: "IngressRoute with multiple host rules and annotation",
ingressRoute: IngressRoute{
TypeMeta: metav1.TypeMeta{
APIVersion: oldIngressrouteGVR.GroupVersion().String(),
Kind: "IngressRoute",
},
ObjectMeta: metav1.ObjectMeta{
Name: "ingressroute-multi-host-annotations-match",
Namespace: defaultTraefikNamespace,
Annotations: map[string]string{
"external-dns.alpha.kubernetes.io/hostname": "f.example.com",
"external-dns.alpha.kubernetes.io/target": "target.domain.tld",
"kubernetes.io/ingress.class": "traefik",
},
},
Spec: traefikIngressRouteSpec{
Routes: []traefikRoute{
{
Match: "Host(`g.example.com`, `h.example.com`)",
},
},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "f.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressroute/traefik/ingressroute-multi-host-annotations-match",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
{
DNSName: "g.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressroute/traefik/ingressroute-multi-host-annotations-match",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
{
DNSName: "h.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressroute/traefik/ingressroute-multi-host-annotations-match",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
},
},
{
title: "IngressRoute omit wildcard",
ingressRoute: IngressRoute{
TypeMeta: metav1.TypeMeta{
APIVersion: oldIngressrouteGVR.GroupVersion().String(),
Kind: "IngressRoute",
},
ObjectMeta: metav1.ObjectMeta{
Name: "ingressroute-omit-wildcard-host",
Namespace: defaultTraefikNamespace,
Annotations: map[string]string{
"external-dns.alpha.kubernetes.io/target": "target.domain.tld",
"kubernetes.io/ingress.class": "traefik",
},
},
Spec: traefikIngressRouteSpec{
Routes: []traefikRoute{
{
Match: "Host(`*`)",
},
},
},
},
expected: nil,
},
} {
ti := ti
t.Run(ti.title, func(t *testing.T) {
t.Parallel()
fakeKubernetesClient := fakeKube.NewSimpleClientset()
scheme := runtime.NewScheme()
scheme.AddKnownTypes(ingressrouteGVR.GroupVersion(), &IngressRoute{}, &IngressRouteList{})
scheme.AddKnownTypes(ingressrouteTCPGVR.GroupVersion(), &IngressRouteTCP{}, &IngressRouteTCPList{})
scheme.AddKnownTypes(ingressrouteUDPGVR.GroupVersion(), &IngressRouteUDP{}, &IngressRouteUDPList{})
scheme.AddKnownTypes(oldIngressrouteGVR.GroupVersion(), &IngressRoute{}, &IngressRouteList{})
scheme.AddKnownTypes(oldIngressrouteTCPGVR.GroupVersion(), &IngressRouteTCP{}, &IngressRouteTCPList{})
scheme.AddKnownTypes(oldIngressrouteUDPGVR.GroupVersion(), &IngressRouteUDP{}, &IngressRouteUDPList{})
fakeDynamicClient := fakeDynamic.NewSimpleDynamicClient(scheme)
ir := unstructured.Unstructured{}
ingressRouteAsJSON, err := json.Marshal(ti.ingressRoute)
assert.NoError(t, err)
assert.NoError(t, ir.UnmarshalJSON(ingressRouteAsJSON))
// Create proxy resources
_, err = fakeDynamicClient.Resource(oldIngressrouteGVR).Namespace(defaultTraefikNamespace).Create(context.Background(), &ir, metav1.CreateOptions{})
assert.NoError(t, err)
source, err := NewTraefikSource(context.TODO(), fakeDynamicClient, fakeKubernetesClient, defaultTraefikNamespace, "kubernetes.io/ingress.class=traefik")
assert.NoError(t, err)
assert.NotNil(t, source)
count := &unstructured.UnstructuredList{}
for len(count.Items) < 1 {
count, _ = fakeDynamicClient.Resource(oldIngressrouteGVR).Namespace(defaultTraefikNamespace).List(context.Background(), metav1.ListOptions{})
}
endpoints, err := source.Endpoints(context.Background())
assert.NoError(t, err)
assert.Len(t, endpoints, len(ti.expected))
assert.Equal(t, endpoints, ti.expected)
})
}
}
func TestTraefikProxyOldIngressRouteTCPEndpoints(t *testing.T) {
t.Parallel()
for _, ti := range []struct {
title string
ingressRouteTCP IngressRouteTCP
expected []*endpoint.Endpoint
}{
{
title: "IngressRouteTCP with hostname annotation",
ingressRouteTCP: IngressRouteTCP{
TypeMeta: metav1.TypeMeta{
APIVersion: oldIngressrouteTCPGVR.GroupVersion().String(),
Kind: "IngressRouteTCP",
},
ObjectMeta: metav1.ObjectMeta{
Name: "ingressroutetcp-annotation",
Namespace: defaultTraefikNamespace,
Annotations: map[string]string{
"external-dns.alpha.kubernetes.io/hostname": "a.example.com",
"external-dns.alpha.kubernetes.io/target": "target.domain.tld",
"kubernetes.io/ingress.class": "traefik",
},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "a.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressroutetcp/traefik/ingressroutetcp-annotation",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
},
},
{
title: "IngressRouteTCP with host sni rule",
ingressRouteTCP: IngressRouteTCP{
TypeMeta: metav1.TypeMeta{
APIVersion: oldIngressrouteTCPGVR.GroupVersion().String(),
Kind: "IngressRouteTCP",
},
ObjectMeta: metav1.ObjectMeta{
Name: "ingressroutetcp-hostsni-match",
Namespace: defaultTraefikNamespace,
Annotations: map[string]string{
"external-dns.alpha.kubernetes.io/target": "target.domain.tld",
"kubernetes.io/ingress.class": "traefik",
},
},
Spec: traefikIngressRouteTCPSpec{
Routes: []traefikRouteTCP{
{
Match: "HostSNI(`b.example.com`)",
},
},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "b.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressroutetcp/traefik/ingressroutetcp-hostsni-match",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
},
},
{
title: "IngressRouteTCP with multiple host sni rules",
ingressRouteTCP: IngressRouteTCP{
TypeMeta: metav1.TypeMeta{
APIVersion: oldIngressrouteTCPGVR.GroupVersion().String(),
Kind: "IngressRouteTCP",
},
ObjectMeta: metav1.ObjectMeta{
Name: "ingressroutetcp-multi-host-match",
Namespace: defaultTraefikNamespace,
Annotations: map[string]string{
"external-dns.alpha.kubernetes.io/target": "target.domain.tld",
"kubernetes.io/ingress.class": "traefik",
},
},
Spec: traefikIngressRouteTCPSpec{
Routes: []traefikRouteTCP{
{
Match: "HostSNI(`d.example.com`) || HostSNI(`e.example.com`)",
},
},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "d.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressroutetcp/traefik/ingressroutetcp-multi-host-match",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
{
DNSName: "e.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressroutetcp/traefik/ingressroutetcp-multi-host-match",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
},
},
{
title: "IngressRouteTCP with multiple host sni rules and annotation",
ingressRouteTCP: IngressRouteTCP{
TypeMeta: metav1.TypeMeta{
APIVersion: oldIngressrouteTCPGVR.GroupVersion().String(),
Kind: "IngressRouteTCP",
},
ObjectMeta: metav1.ObjectMeta{
Name: "ingressroutetcp-multi-host-annotations-match",
Namespace: defaultTraefikNamespace,
Annotations: map[string]string{
"external-dns.alpha.kubernetes.io/hostname": "f.example.com",
"external-dns.alpha.kubernetes.io/target": "target.domain.tld",
"kubernetes.io/ingress.class": "traefik",
},
},
Spec: traefikIngressRouteTCPSpec{
Routes: []traefikRouteTCP{
{
Match: "HostSNI(`g.example.com`, `h.example.com`)",
},
},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "f.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressroutetcp/traefik/ingressroutetcp-multi-host-annotations-match",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
{
DNSName: "g.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressroutetcp/traefik/ingressroutetcp-multi-host-annotations-match",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
{
DNSName: "h.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressroutetcp/traefik/ingressroutetcp-multi-host-annotations-match",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
},
},
{
title: "IngressRouteTCP omit wildcard host sni",
ingressRouteTCP: IngressRouteTCP{
TypeMeta: metav1.TypeMeta{
APIVersion: oldIngressrouteTCPGVR.GroupVersion().String(),
Kind: "IngressRouteTCP",
},
ObjectMeta: metav1.ObjectMeta{
Name: "ingressroutetcp-omit-wildcard-host",
Namespace: defaultTraefikNamespace,
Annotations: map[string]string{
"external-dns.alpha.kubernetes.io/target": "target.domain.tld",
"kubernetes.io/ingress.class": "traefik",
},
},
Spec: traefikIngressRouteTCPSpec{
Routes: []traefikRouteTCP{
{
Match: "HostSNI(`*`)",
},
},
},
},
expected: nil,
},
} {
ti := ti
t.Run(ti.title, func(t *testing.T) {
t.Parallel()
fakeKubernetesClient := fakeKube.NewSimpleClientset()
scheme := runtime.NewScheme()
scheme.AddKnownTypes(ingressrouteGVR.GroupVersion(), &IngressRoute{}, &IngressRouteList{})
scheme.AddKnownTypes(ingressrouteTCPGVR.GroupVersion(), &IngressRouteTCP{}, &IngressRouteTCPList{})
scheme.AddKnownTypes(ingressrouteUDPGVR.GroupVersion(), &IngressRouteUDP{}, &IngressRouteUDPList{})
scheme.AddKnownTypes(oldIngressrouteGVR.GroupVersion(), &IngressRoute{}, &IngressRouteList{})
scheme.AddKnownTypes(oldIngressrouteTCPGVR.GroupVersion(), &IngressRouteTCP{}, &IngressRouteTCPList{})
scheme.AddKnownTypes(oldIngressrouteUDPGVR.GroupVersion(), &IngressRouteUDP{}, &IngressRouteUDPList{})
fakeDynamicClient := fakeDynamic.NewSimpleDynamicClient(scheme)
ir := unstructured.Unstructured{}
ingressRouteAsJSON, err := json.Marshal(ti.ingressRouteTCP)
assert.NoError(t, err)
assert.NoError(t, ir.UnmarshalJSON(ingressRouteAsJSON))
// Create proxy resources
_, err = fakeDynamicClient.Resource(oldIngressrouteTCPGVR).Namespace(defaultTraefikNamespace).Create(context.Background(), &ir, metav1.CreateOptions{})
assert.NoError(t, err)
source, err := NewTraefikSource(context.TODO(), fakeDynamicClient, fakeKubernetesClient, defaultTraefikNamespace, "kubernetes.io/ingress.class=traefik")
assert.NoError(t, err)
assert.NotNil(t, source)
count := &unstructured.UnstructuredList{}
for len(count.Items) < 1 {
count, _ = fakeDynamicClient.Resource(oldIngressrouteTCPGVR).Namespace(defaultTraefikNamespace).List(context.Background(), metav1.ListOptions{})
}
endpoints, err := source.Endpoints(context.Background())
assert.NoError(t, err)
assert.Len(t, endpoints, len(ti.expected))
assert.Equal(t, endpoints, ti.expected)
})
}
}
func TestTraefikProxyOldIngressRouteUDPEndpoints(t *testing.T) {
t.Parallel()
for _, ti := range []struct {
title string
ingressRouteUDP IngressRouteUDP
expected []*endpoint.Endpoint
}{
{
title: "IngressRouteTCP with hostname annotation",
ingressRouteUDP: IngressRouteUDP{
TypeMeta: metav1.TypeMeta{
APIVersion: oldIngressrouteUDPGVR.GroupVersion().String(),
Kind: "IngressRouteUDP",
},
ObjectMeta: metav1.ObjectMeta{
Name: "ingressrouteudp-annotation",
Namespace: defaultTraefikNamespace,
Annotations: map[string]string{
"external-dns.alpha.kubernetes.io/hostname": "a.example.com",
"external-dns.alpha.kubernetes.io/target": "target.domain.tld",
"kubernetes.io/ingress.class": "traefik",
},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "a.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressrouteudp/traefik/ingressrouteudp-annotation",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
},
},
{
title: "IngressRouteTCP with multiple hostname annotation",
ingressRouteUDP: IngressRouteUDP{
TypeMeta: metav1.TypeMeta{
APIVersion: oldIngressrouteUDPGVR.GroupVersion().String(),
Kind: "IngressRouteUDP",
},
ObjectMeta: metav1.ObjectMeta{
Name: "ingressrouteudp-multi-annotation",
Namespace: defaultTraefikNamespace,
Annotations: map[string]string{
"external-dns.alpha.kubernetes.io/hostname": "a.example.com, b.example.com",
"external-dns.alpha.kubernetes.io/target": "target.domain.tld",
"kubernetes.io/ingress.class": "traefik",
},
},
},
expected: []*endpoint.Endpoint{
{
DNSName: "a.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressrouteudp/traefik/ingressrouteudp-multi-annotation",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
{
DNSName: "b.example.com",
Targets: []string{"target.domain.tld"},
RecordType: endpoint.RecordTypeCNAME,
RecordTTL: 0,
Labels: endpoint.Labels{
"resource": "ingressrouteudp/traefik/ingressrouteudp-multi-annotation",
},
ProviderSpecific: endpoint.ProviderSpecific{},
},
},
},
} {
ti := ti
t.Run(ti.title, func(t *testing.T) {
t.Parallel()
fakeKubernetesClient := fakeKube.NewSimpleClientset()
scheme := runtime.NewScheme()
scheme.AddKnownTypes(ingressrouteGVR.GroupVersion(), &IngressRoute{}, &IngressRouteList{})
scheme.AddKnownTypes(ingressrouteTCPGVR.GroupVersion(), &IngressRouteTCP{}, &IngressRouteTCPList{})
scheme.AddKnownTypes(ingressrouteUDPGVR.GroupVersion(), &IngressRouteUDP{}, &IngressRouteUDPList{})
scheme.AddKnownTypes(oldIngressrouteGVR.GroupVersion(), &IngressRoute{}, &IngressRouteList{})
scheme.AddKnownTypes(oldIngressrouteTCPGVR.GroupVersion(), &IngressRouteTCP{}, &IngressRouteTCPList{})
scheme.AddKnownTypes(oldIngressrouteUDPGVR.GroupVersion(), &IngressRouteUDP{}, &IngressRouteUDPList{})
fakeDynamicClient := fakeDynamic.NewSimpleDynamicClient(scheme)
ir := unstructured.Unstructured{}
ingressRouteAsJSON, err := json.Marshal(ti.ingressRouteUDP)
assert.NoError(t, err)
assert.NoError(t, ir.UnmarshalJSON(ingressRouteAsJSON))
// Create proxy resources
_, err = fakeDynamicClient.Resource(oldIngressrouteUDPGVR).Namespace(defaultTraefikNamespace).Create(context.Background(), &ir, metav1.CreateOptions{})
assert.NoError(t, err)
source, err := NewTraefikSource(context.TODO(), fakeDynamicClient, fakeKubernetesClient, defaultTraefikNamespace, "kubernetes.io/ingress.class=traefik")
assert.NoError(t, err)
assert.NotNil(t, source)
count := &unstructured.UnstructuredList{}
for len(count.Items) < 1 {
count, _ = fakeDynamicClient.Resource(oldIngressrouteUDPGVR).Namespace(defaultTraefikNamespace).List(context.Background(), metav1.ListOptions{})
}
endpoints, err := source.Endpoints(context.Background())
assert.NoError(t, err)
assert.Len(t, endpoints, len(ti.expected))
assert.Equal(t, endpoints, ti.expected)
})
}
}