Skip to content
Merged
Show file tree
Hide file tree
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
54 changes: 27 additions & 27 deletions docs/advanced/fqdn-templating.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,33 +33,33 @@ The template uses the following data from the source object (e.g., a `Service` o

<!-- TODO: generate from code -->

| Source | Description | FQDN Supported |
|:-----------------------|:----------------------------------------------------------------|:--------------:|
| `ambassador-host` | Queries Ambassador Host resources for endpoints. | ❌ |
| `cloudfoundry` | Queries Cloud Foundry resources for endpoints. | ❌ |
| `connector` | Queries a custom connector source for endpoints. | ❌ |
| `contour-httpproxy` | Queries Contour HTTPProxy resources for endpoints. | ✅ |
| `crd` | Queries Custom Resource Definitions (CRDs) for endpoints. | ❌ |
| `empty` | Uses an empty source, typically for testing or no-op scenarios. | ❌ |
| `f5-transportserver` | Queries F5 TransportServer resources for endpoints. | ❌ |
| `f5-virtualserver` | Queries F5 VirtualServer resources for endpoints. | ❌ |
| `fake` | Uses a fake source for testing purposes. | ❌ |
| `gateway-grpcroute` | Queries GRPCRoute resources from the Gateway API. | ✅ |
| `gateway-httproute` | Queries HTTPRoute resources from the Gateway API. | ✅ |
| `gateway-tcproute` | Queries TCPRoute resources from the Gateway API. | ✅ |
| `gateway-tlsroute` | Queries TLSRoute resources from the Gateway API. | ❌ |
| `gateway-udproute` | Queries UDPRoute resources from the Gateway API. | ❌ |
| `gloo-proxy` | Queries Gloo Proxy resources for endpoints. | ❌ |
| `ingress` | Queries Kubernetes Ingress resources for endpoints. | ✅ |
| `istio-gateway` | Queries Istio Gateway resources for endpoints. | ✅ |
| `istio-virtualservice` | Queries Istio VirtualService resources for endpoints. | ✅ |
| `kong-tcpingress` | Queries Kong TCPIngress resources for endpoints. | ❌ |
| `node` | Queries Kubernetes Node resources for endpoints. | ✅ |
| `openshift-route` | Queries OpenShift Route resources for endpoints. | ✅ |
| `pod` | Queries Kubernetes Pod resources for endpoints. | |
| `service` | Queries Kubernetes Service resources for endpoints. | ✅ |
| `skipper-routegroup` | Queries Skipper RouteGroup resources for endpoints. | ✅ |
| `traefik-proxy` | Queries Traefik Proxy resources for endpoints. | ❌ |
| Source | Description | FQDN Supported | FQDN Combine |
|:-----------------------|:----------------------------------------------------------------|:--------------:|:------------:|
| `ambassador-host` | Queries Ambassador Host resources for endpoints. | ❌ | ❌ |
| `cloudfoundry` | Queries Cloud Foundry resources for endpoints. | ❌ | ❌ |
| `connector` | Queries a custom connector source for endpoints. | ❌ | ❌ |
| `contour-httpproxy` | Queries Contour HTTPProxy resources for endpoints. | ✅ | ✅ |
| `crd` | Queries Custom Resource Definitions (CRDs) for endpoints. | ❌ | ❌ |
| `empty` | Uses an empty source, typically for testing or no-op scenarios. | ❌ | ❌ |
| `f5-transportserver` | Queries F5 TransportServer resources for endpoints. | ❌ | ❌ |
| `f5-virtualserver` | Queries F5 VirtualServer resources for endpoints. | ❌ | ❌ |
| `fake` | Uses a fake source for testing purposes. | ❌ | ❌ |
| `gateway-grpcroute` | Queries GRPCRoute resources from the Gateway API. | ✅ | ❌ |
| `gateway-httproute` | Queries HTTPRoute resources from the Gateway API. | ✅ | ❌ |
| `gateway-tcproute` | Queries TCPRoute resources from the Gateway API. | ✅ | ❌ |
| `gateway-tlsroute` | Queries TLSRoute resources from the Gateway API. | ❌ | ❌ |
| `gateway-udproute` | Queries UDPRoute resources from the Gateway API. | ❌ | ❌ |
| `gloo-proxy` | Queries Gloo Proxy resources for endpoints. | ❌ | ❌ |
| `ingress` | Queries Kubernetes Ingress resources for endpoints. | ✅ | ✅ |
| `istio-gateway` | Queries Istio Gateway resources for endpoints. | ✅ | ✅ |
| `istio-virtualservice` | Queries Istio VirtualService resources for endpoints. | ✅ | ✅ |
| `kong-tcpingress` | Queries Kong TCPIngress resources for endpoints. | ❌ | ❌ |
| `node` | Queries Kubernetes Node resources for endpoints. | ✅ | ❌ |
| `openshift-route` | Queries OpenShift Route resources for endpoints. | ✅ | ✅ |
| `pod` | Queries Kubernetes Pod resources for endpoints. | ✅ | ✅ |
| `service` | Queries Kubernetes Service resources for endpoints. | ✅ | ✅ |
| `skipper-routegroup` | Queries Skipper RouteGroup resources for endpoints. | ✅ | ✅ |
| `traefik-proxy` | Queries Traefik IngressRoute resources for endpoints. | ❌ | ❌ |

## Custom Functions

Expand Down
75 changes: 67 additions & 8 deletions source/pod.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ package source

import (
"context"
"fmt"
"maps"
"text/template"

log "github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
Expand All @@ -27,14 +30,19 @@ import (
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/cache"

"sigs.k8s.io/external-dns/source/fqdn"

"sigs.k8s.io/external-dns/endpoint"
"sigs.k8s.io/external-dns/source/annotations"
"sigs.k8s.io/external-dns/source/informers"
)

type podSource struct {
client kubernetes.Interface
namespace string
client kubernetes.Interface
namespace string
fqdnTemplate *template.Template
combineFQDNAnnotation bool

podInformer coreinformers.PodInformer
nodeInformer coreinformers.NodeInformer
compatibility string
Expand All @@ -43,18 +51,27 @@ type podSource struct {
}

// NewPodSource creates a new podSource with the given config.
func NewPodSource(ctx context.Context, kubeClient kubernetes.Interface, namespace string, compatibility string, ignoreNonHostNetworkPods bool, podSourceDomain string) (Source, error) {
func NewPodSource(
ctx context.Context,
kubeClient kubernetes.Interface,
namespace string,
compatibility string,
ignoreNonHostNetworkPods bool,
podSourceDomain string,
fqdnTemplate string,
combineFqdnAnnotation bool,
) (Source, error) {
informerFactory := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 0, kubeinformers.WithNamespace(namespace))
podInformer := informerFactory.Core().V1().Pods()
nodeInformer := informerFactory.Core().V1().Nodes()

podInformer.Informer().AddEventHandler(
_, _ = podInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
},
},
)
nodeInformer.Informer().AddEventHandler(
_, _ = nodeInformer.Informer().AddEventHandler(
cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
},
Expand All @@ -68,6 +85,11 @@ func NewPodSource(ctx context.Context, kubeClient kubernetes.Interface, namespac
return nil, err
}

tmpl, err := fqdn.ParseTemplate(fqdnTemplate)
if err != nil {
return nil, err
}

return &podSource{
client: kubeClient,
podInformer: podInformer,
Expand All @@ -76,22 +98,35 @@ func NewPodSource(ctx context.Context, kubeClient kubernetes.Interface, namespac
compatibility: compatibility,
ignoreNonHostNetworkPods: ignoreNonHostNetworkPods,
podSourceDomain: podSourceDomain,
fqdnTemplate: tmpl,
combineFQDNAnnotation: combineFqdnAnnotation,
}, nil
}

func (*podSource) AddEventHandler(ctx context.Context, handler func()) {
func (*podSource) AddEventHandler(_ context.Context, _ func()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, do you know why this function does nothing? (Other sources often add the handler to informers)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. We need to fix that as well. Related PR #5274

We need to review and fix all the handlers. It's a useful feature. I'm not too sure how it differ from adding handlers in constructur, that the only gotcha

}

func (ps *podSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
func (ps *podSource) Endpoints(_ context.Context) ([]*endpoint.Endpoint, error) {
pods, err := ps.podInformer.Lister().Pods(ps.namespace).List(labels.Everything())
if err != nil {
return nil, err
}

endpointMap := make(map[endpoint.EndpointKey][]string)
for _, pod := range pods {
ps.addPodEndpointsToEndpointMap(endpointMap, pod)
if ps.fqdnTemplate == nil || ps.combineFQDNAnnotation {
ps.addPodEndpointsToEndpointMap(endpointMap, pod)
}

if ps.fqdnTemplate != nil {
fqdnHosts, err := ps.hostsFromTemplate(pod)
if err != nil {
return nil, err
}
maps.Copy(endpointMap, fqdnHosts)
}
}

var endpoints []*endpoint.Endpoint
for key, targets := range endpointMap {
endpoints = append(endpoints, endpoint.NewEndpoint(key.DNSName, key.RecordType, targets...))
Expand Down Expand Up @@ -180,6 +215,30 @@ func (ps *podSource) addPodNodeEndpointsToEndpointMap(endpointMap map[endpoint.E
}
}

func (ps *podSource) hostsFromTemplate(pod *corev1.Pod) (map[endpoint.EndpointKey][]string, error) {
hosts, err := fqdn.ExecTemplate(ps.fqdnTemplate, pod)
if err != nil {
return nil, fmt.Errorf("skipping generating endpoints from template for pod %s: %w", pod.Name, err)
}

result := make(map[endpoint.EndpointKey][]string)
for _, target := range hosts {
for _, address := range pod.Status.PodIPs {
if address.IP == "" {
log.Debugf("skipping pod %q. PodIP is empty with phase %q", pod.Name, pod.Status.Phase)
continue
}
key := endpoint.EndpointKey{
DNSName: target,
RecordType: suitableType(address.IP),
}
result[key] = append(result[key], address.IP)
}
}

return result, nil
}

func addTargetsToEndpointMap(endpointMap map[endpoint.EndpointKey][]string, targets []string, domainList ...string) {
for _, domain := range domainList {
for _, target := range targets {
Expand Down
Loading
Loading