mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-11-28 16:31:23 +01:00
feat: add support for ingress backed GlooEdge Gateway (#5909)
This commit is contained in:
parent
031dc9ffd4
commit
62f4d7d5f8
@ -22,6 +22,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- Add option to set `annotationPrefix` ([#5889](https://github.com/kubernetes-sigs/external-dns/pull/5889)) _@lexfrei_
|
||||
|
||||
### Changed
|
||||
|
||||
- Grant `networking.k8s.io/ingresses` and `gateway.solo.io/gateways` permissions when using `gloo-proxy` source. ([#5909](https://github.com/kubernetes-sigs/external-dns/pull/5909)) _@cucxabong_
|
||||
|
||||
## [v1.19.0] - 2025-09-08
|
||||
|
||||
### Added
|
||||
|
||||
@ -26,7 +26,7 @@ rules:
|
||||
resources: ["endpointslices"]
|
||||
verbs: ["get","watch","list"]
|
||||
{{- end }}
|
||||
{{- if or (has "ingress" .Values.sources) (has "istio-gateway" .Values.sources) (has "istio-virtualservice" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources) }}
|
||||
{{- if or (has "ingress" .Values.sources) (has "istio-gateway" .Values.sources) (has "istio-virtualservice" .Values.sources) (has "contour-httpproxy" .Values.sources) (has "openshift-route" .Values.sources) (has "skipper-routegroup" .Values.sources) (has "gloo-proxy" .Values.sources) }}
|
||||
- apiGroups: ["extensions","networking.k8s.io"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get","watch","list"]
|
||||
@ -99,7 +99,7 @@ rules:
|
||||
{{- end }}
|
||||
{{- if has "gloo-proxy" .Values.sources }}
|
||||
- apiGroups: ["gloo.solo.io","gateway.solo.io"]
|
||||
resources: ["proxies","virtualservices"]
|
||||
resources: ["proxies","virtualservices","gateways"]
|
||||
verbs: ["get","watch","list"]
|
||||
{{- end }}
|
||||
{{- if has "kong-tcpingress" .Values.sources }}
|
||||
|
||||
@ -520,3 +520,27 @@ tests:
|
||||
resources: ["virtualservices"]
|
||||
verbs: ["get","watch","list"]
|
||||
template: clusterrole.yaml
|
||||
- it: should create default RBAC rules for 'GlooEdge' when 'gloo-proxy' is set
|
||||
set:
|
||||
sources:
|
||||
- gloo-proxy
|
||||
asserts:
|
||||
- template: clusterrole.yaml
|
||||
equal:
|
||||
path: rules
|
||||
value:
|
||||
- apiGroups: [""]
|
||||
resources: ["nodes"]
|
||||
verbs: ["list","watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: [""]
|
||||
resources: ["services"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: ["extensions","networking.k8s.io"]
|
||||
resources: ["ingresses"]
|
||||
verbs: ["get","watch","list"]
|
||||
- apiGroups: ["gloo.solo.io","gateway.solo.io"]
|
||||
resources: ["proxies","virtualservices","gateways"]
|
||||
verbs: ["get","watch","list"]
|
||||
|
||||
@ -151,14 +151,108 @@ If the annotation is not present, use the domains from both the spec and annotat
|
||||
|
||||
## external-dns.alpha.kubernetes.io/ingress
|
||||
|
||||
This annotation allows ExternalDNS to work with Istio Gateways that don't have a public IP.
|
||||
This annotation allows ExternalDNS to work with Istio & GlooEdge Gateways that don't have a public IP.
|
||||
|
||||
It can be used to address a specific architectural pattern, when a Kubernetes Ingress directs all public traffic to the Istio Gateway:
|
||||
It can be used to address a specific architectural pattern, when a Kubernetes Ingress directs all public traffic to an Istio or GlooEdge Gateway:
|
||||
|
||||
- **The Challenge**: By default, ExternalDNS sources the public IP address for a DNS record from a Service of type LoadBalancer.
|
||||
However, in some service mesh setups, the Istio Gateway's Service is of type ClusterIP, with all public traffic routed to it via a separate Kubernetes Ingress object. This setup leaves the Gateway without a public IP that ExternalDNS can discover.
|
||||
However, in some setups, the Gateway's Service is of type ClusterIP, with all public traffic routed to it via a separate Kubernetes Ingress object. This setup leaves the Gateway without a public IP that ExternalDNS can discover.
|
||||
|
||||
- **The Solution**: The annotation on the Istio Gateway tells ExternalDNS to ignore the Gateway's Service IP. Instead, it directs ExternalDNS to a specified Ingress resource to find the target LoadBalancer IP address.
|
||||
- **The Solution**: The annotation on the Istio/GlooEdge Gateway tells ExternalDNS to ignore the Gateway's Service IP. Instead, it directs ExternalDNS to a specified Ingress resource to find the target LoadBalancer IP address.
|
||||
|
||||
### Use Cases for `external-dns.alpha.kubernetes.io/ingress` annotation
|
||||
|
||||
#### Getting target from Ingress backed Gloo Gateway
|
||||
|
||||
```yml
|
||||
apiVersion: gateway.solo.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/ingress: gateway-proxy
|
||||
labels:
|
||||
app: gloo
|
||||
name: gateway-proxy
|
||||
namespace: gloo-system
|
||||
spec:
|
||||
bindAddress: '::'
|
||||
bindPort: 8080
|
||||
options: {}
|
||||
proxyNames:
|
||||
- gateway-proxy
|
||||
ssl: false
|
||||
useProxyProto: false
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: gateway-proxy
|
||||
namespace: gloo-system
|
||||
spec:
|
||||
ingressClassName: alb
|
||||
rules:
|
||||
- host: cool-service.example.com
|
||||
http:
|
||||
paths:
|
||||
- backend:
|
||||
service:
|
||||
name: gateway-proxy
|
||||
port:
|
||||
name: http
|
||||
path: /
|
||||
pathType: Prefix
|
||||
status:
|
||||
loadBalancer:
|
||||
ingress:
|
||||
- hostname: k8s-alb-c4aa37c880-740590208.us-east-1.elb.amazonaws.com
|
||||
---
|
||||
# This object is generated by GlooEdge Control Plane from Gateway and VirtualService.
|
||||
# We have no direct control on this resource
|
||||
apiVersion: gloo.solo.io/v1
|
||||
kind: Proxy
|
||||
metadata:
|
||||
labels:
|
||||
created_by: gloo-gateway
|
||||
name: gateway-proxy
|
||||
namespace: gloo-system
|
||||
spec:
|
||||
listeners:
|
||||
- bindAddress: '::'
|
||||
bindPort: 8080
|
||||
httpListener:
|
||||
virtualHosts:
|
||||
- domains:
|
||||
- cool-service.example.com
|
||||
metadataStatic:
|
||||
sources:
|
||||
- observedGeneration: "6652"
|
||||
resourceKind: '*v1.VirtualService'
|
||||
resourceRef:
|
||||
name: cool-service
|
||||
namespace: gloo-system
|
||||
name: cool-service
|
||||
routes:
|
||||
- matchers:
|
||||
- prefix: /
|
||||
metadataStatic:
|
||||
sources:
|
||||
- observedGeneration: "6652"
|
||||
resourceKind: '*v1.VirtualService'
|
||||
resourceRef:
|
||||
name: cool-service
|
||||
namespace: gloo-system
|
||||
upgrades:
|
||||
- websocket: {}
|
||||
metadataStatic:
|
||||
sources:
|
||||
- observedGeneration: "6111"
|
||||
resourceKind: '*v1.Gateway'
|
||||
resourceRef:
|
||||
name: gateway-proxy
|
||||
namespace: gloo-system
|
||||
name: listener-::-8080
|
||||
useProxyProto: false
|
||||
```
|
||||
|
||||
## external-dns.alpha.kubernetes.io/internal-hostname
|
||||
|
||||
|
||||
@ -104,3 +104,52 @@ spec:
|
||||
- --registry=txt
|
||||
- --txt-owner-id=my-identifier
|
||||
```
|
||||
|
||||
## Gateway Annotation
|
||||
|
||||
To support setups where an Ingress resource is used to provision an external LB you can add the following annotation to your Gateway
|
||||
|
||||
**Note:** The Ingress namespace can be omitted if its in the same namespace as the gateway
|
||||
|
||||
```bash
|
||||
$ cat <<EOF | kubectl apply -f -
|
||||
apiVersion: gloo.solo.io/v1
|
||||
kind: Proxy
|
||||
metadata:
|
||||
labels:
|
||||
created_by: gloo-gateway
|
||||
name: gateway-proxy
|
||||
namespace: gloo-system
|
||||
spec:
|
||||
listeners:
|
||||
- bindAddress: '::'
|
||||
metadataStatic:
|
||||
sources:
|
||||
- resourceKind: '*v1.Gateway'
|
||||
resourceRef:
|
||||
name: gateway-proxy
|
||||
namespace: gloo-system
|
||||
---
|
||||
apiVersion: gateway.solo.io/v1
|
||||
kind: Gateway
|
||||
metadata:
|
||||
annotations:
|
||||
external-dns.alpha.kubernetes.io/ingress: "$ingressNamespace/$ingressName"
|
||||
labels:
|
||||
app: gloo
|
||||
name: gateway-proxy
|
||||
namespace: gloo-system
|
||||
spec: {}
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
labels:
|
||||
gateway-proxy-id: gateway-proxy
|
||||
gloo: gateway-proxy
|
||||
name: gateway-proxy
|
||||
namespace: gloo-system
|
||||
spec:
|
||||
ingressClassName: alb
|
||||
EOF
|
||||
```
|
||||
|
||||
@ -25,12 +25,20 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/dynamic"
|
||||
"k8s.io/client-go/dynamic/dynamicinformer"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
kubeinformers "k8s.io/client-go/informers"
|
||||
coreinformers "k8s.io/client-go/informers/core/v1"
|
||||
netinformers "k8s.io/client-go/informers/networking/v1"
|
||||
|
||||
"sigs.k8s.io/external-dns/endpoint"
|
||||
"sigs.k8s.io/external-dns/source/annotations"
|
||||
"sigs.k8s.io/external-dns/source/informers"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -44,6 +52,11 @@ var (
|
||||
Version: "v1",
|
||||
Resource: "virtualservices",
|
||||
}
|
||||
gatewayGVR = schema.GroupVersionResource{
|
||||
Group: "gateway.solo.io",
|
||||
Version: "v1",
|
||||
Resource: "gateways",
|
||||
}
|
||||
)
|
||||
|
||||
// Basic redefinition of "Proxy" CRD : https://github.com/solo-io/gloo/blob/v1.4.6/projects/gloo/pkg/api/v1/proxy.pb.go
|
||||
@ -58,7 +71,22 @@ type proxySpec struct {
|
||||
}
|
||||
|
||||
type proxySpecListener struct {
|
||||
HTTPListener proxySpecHTTPListener `json:"httpListener,omitempty"`
|
||||
HTTPListener proxySpecHTTPListener `json:"httpListener,omitempty"`
|
||||
MetadataStatic proxyMetadataStatic `json:"metadataStatic,omitempty"`
|
||||
}
|
||||
|
||||
type proxyMetadataStatic struct {
|
||||
Source []proxyMetadataStaticSource `json:"sources,omitempty"`
|
||||
}
|
||||
|
||||
type proxyMetadataStaticSource struct {
|
||||
ResourceKind string `json:"resourceKind,omitempty"`
|
||||
ResourceRef proxyMetadataStaticSourceResourceRef `json:"resourceRef,omitempty"`
|
||||
}
|
||||
|
||||
type proxyMetadataStaticSourceResourceRef struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Namespace string `json:"namespace,omitempty"`
|
||||
}
|
||||
|
||||
type proxySpecHTTPListener struct {
|
||||
@ -96,17 +124,49 @@ type proxyVirtualHostMetadataSourceResourceRef struct {
|
||||
}
|
||||
|
||||
type glooSource struct {
|
||||
dynamicKubeClient dynamic.Interface
|
||||
kubeClient kubernetes.Interface
|
||||
glooNamespaces []string
|
||||
serviceInformer coreinformers.ServiceInformer
|
||||
ingressInformer netinformers.IngressInformer
|
||||
proxyInformer kubeinformers.GenericInformer
|
||||
virtualServiceInformer kubeinformers.GenericInformer
|
||||
gatewayInformer kubeinformers.GenericInformer
|
||||
glooNamespaces []string
|
||||
}
|
||||
|
||||
// NewGlooSource creates a new glooSource with the given config
|
||||
func NewGlooSource(dynamicKubeClient dynamic.Interface, kubeClient kubernetes.Interface,
|
||||
func NewGlooSource(ctx context.Context, dynamicKubeClient dynamic.Interface, kubeClient kubernetes.Interface,
|
||||
glooNamespaces []string) (Source, error) {
|
||||
informerFactory := kubeinformers.NewSharedInformerFactory(kubeClient, 0)
|
||||
serviceInformer := informerFactory.Core().V1().Services()
|
||||
ingressInformer := informerFactory.Networking().V1().Ingresses()
|
||||
|
||||
_, _ = serviceInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
|
||||
_, _ = ingressInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
|
||||
|
||||
dynamicInformerFactory := dynamicinformer.NewDynamicSharedInformerFactory(dynamicKubeClient, 0)
|
||||
|
||||
proxyInformer := dynamicInformerFactory.ForResource(proxyGVR)
|
||||
virtualServiceInformer := dynamicInformerFactory.ForResource(virtualServiceGVR)
|
||||
gatewayInformer := dynamicInformerFactory.ForResource(gatewayGVR)
|
||||
|
||||
_, _ = proxyInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
|
||||
_, _ = virtualServiceInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
|
||||
_, _ = gatewayInformer.Informer().AddEventHandler(informers.DefaultEventHandler())
|
||||
|
||||
informerFactory.Start(ctx.Done())
|
||||
dynamicInformerFactory.Start(ctx.Done())
|
||||
if err := informers.WaitForCacheSync(ctx, informerFactory); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := informers.WaitForDynamicCacheSync(ctx, dynamicInformerFactory); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &glooSource{
|
||||
dynamicKubeClient,
|
||||
kubeClient,
|
||||
serviceInformer,
|
||||
ingressInformer,
|
||||
proxyInformer,
|
||||
virtualServiceInformer,
|
||||
gatewayInformer,
|
||||
glooNamespaces,
|
||||
}, nil
|
||||
}
|
||||
@ -119,32 +179,45 @@ func (gs *glooSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
|
||||
endpoints := []*endpoint.Endpoint{}
|
||||
|
||||
for _, ns := range gs.glooNamespaces {
|
||||
proxies, err := gs.dynamicKubeClient.Resource(proxyGVR).Namespace(ns).List(ctx, metav1.ListOptions{})
|
||||
proxyObjects, err := gs.proxyInformer.Lister().ByNamespace(ns).List(labels.Everything())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, obj := range proxies.Items {
|
||||
proxy := proxy{}
|
||||
jsonString, err := obj.MarshalJSON()
|
||||
|
||||
for _, obj := range proxyObjects {
|
||||
unstructuredObj, ok := obj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(unstructuredObj.Object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = json.Unmarshal(jsonString, &proxy)
|
||||
if err != nil {
|
||||
|
||||
var proxy proxy
|
||||
if err = json.Unmarshal(jsonData, &proxy); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("Gloo: Find %s proxy", proxy.Metadata.Name)
|
||||
|
||||
proxyTargets := annotations.TargetsFromTargetAnnotation(proxy.Metadata.Annotations)
|
||||
if len(proxyTargets) == 0 {
|
||||
proxyTargets, err = gs.proxyTargets(ctx, proxy.Metadata.Name, ns)
|
||||
proxyTargets, err = gs.targetsFromGatewayIngress(&proxy)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(proxyTargets) == 0 {
|
||||
proxyTargets, err = gs.proxyTargets(proxy.Metadata.Name, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
log.Debugf("Gloo[%s]: Find %d target(s) (%+v)", proxy.Metadata.Name, len(proxyTargets), proxyTargets)
|
||||
|
||||
proxyEndpoints, err := gs.generateEndpointsFromProxy(ctx, &proxy, proxyTargets)
|
||||
proxyEndpoints, err := gs.generateEndpointsFromProxy(&proxy, proxyTargets)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -155,14 +228,14 @@ func (gs *glooSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, erro
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func (gs *glooSource) generateEndpointsFromProxy(ctx context.Context, proxy *proxy, targets endpoint.Targets) ([]*endpoint.Endpoint, error) {
|
||||
func (gs *glooSource) generateEndpointsFromProxy(proxy *proxy, targets endpoint.Targets) ([]*endpoint.Endpoint, error) {
|
||||
endpoints := []*endpoint.Endpoint{}
|
||||
|
||||
resource := fmt.Sprintf("proxy/%s/%s", proxy.Metadata.Namespace, proxy.Metadata.Name)
|
||||
|
||||
for _, listener := range proxy.Spec.Listeners {
|
||||
for _, virtualHost := range listener.HTTPListener.VirtualHosts {
|
||||
ants, err := gs.annotationsFromProxySource(ctx, virtualHost)
|
||||
ants, err := gs.annotationsFromProxySource(virtualHost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -176,37 +249,53 @@ func (gs *glooSource) generateEndpointsFromProxy(ctx context.Context, proxy *pro
|
||||
return endpoints, nil
|
||||
}
|
||||
|
||||
func (gs *glooSource) annotationsFromProxySource(ctx context.Context, virtualHost proxyVirtualHost) (map[string]string, error) {
|
||||
func (gs *glooSource) annotationsFromProxySource(virtualHost proxyVirtualHost) (map[string]string, error) {
|
||||
ants := map[string]string{}
|
||||
for _, src := range virtualHost.Metadata.Source {
|
||||
kind := sourceKind(src.Kind)
|
||||
if kind != nil {
|
||||
source, err := gs.dynamicKubeClient.Resource(*kind).Namespace(src.Namespace).Get(ctx, src.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for key, value := range source.GetAnnotations() {
|
||||
ants[key] = value
|
||||
}
|
||||
if src.Kind != "*v1.VirtualService" {
|
||||
log.Debugf("Unsupported listener source. Expecting '*v1.VirtualService', got (%s)", src.Kind)
|
||||
continue
|
||||
}
|
||||
|
||||
virtualServiceObj, err := gs.virtualServiceInformer.Lister().ByNamespace(src.Namespace).Get(src.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
unstructuredVirtualService, ok := virtualServiceObj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
log.Error("unexpected object: it is not *unstructured.Unstructured")
|
||||
continue
|
||||
}
|
||||
|
||||
for key, value := range unstructuredVirtualService.GetAnnotations() {
|
||||
ants[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
for _, src := range virtualHost.MetadataStatic.Source {
|
||||
kind := sourceKind(src.ResourceKind)
|
||||
if kind != nil {
|
||||
source, err := gs.dynamicKubeClient.Resource(*kind).Namespace(src.ResourceRef.Namespace).Get(ctx, src.ResourceRef.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for key, value := range source.GetAnnotations() {
|
||||
ants[key] = value
|
||||
}
|
||||
if src.ResourceKind != "*v1.VirtualService" {
|
||||
log.Debugf("Unsupported listener source. Expecting '*v1.VirtualService', got (%s)", src.ResourceKind)
|
||||
continue
|
||||
}
|
||||
virtualServiceObj, err := gs.virtualServiceInformer.Lister().ByNamespace(src.ResourceRef.Namespace).Get(src.ResourceRef.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
unstructuredVirtualService, ok := virtualServiceObj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
log.Error("unexpected object: it is not *unstructured.Unstructured")
|
||||
continue
|
||||
}
|
||||
|
||||
for key, value := range unstructuredVirtualService.GetAnnotations() {
|
||||
ants[key] = value
|
||||
}
|
||||
}
|
||||
return ants, nil
|
||||
}
|
||||
|
||||
func (gs *glooSource) proxyTargets(ctx context.Context, name string, namespace string) (endpoint.Targets, error) {
|
||||
svc, err := gs.kubeClient.CoreV1().Services(namespace).Get(ctx, name, metav1.GetOptions{})
|
||||
func (gs *glooSource) proxyTargets(name string, namespace string) (endpoint.Targets, error) {
|
||||
svc, err := gs.serviceInformer.Lister().Services(namespace).Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -228,9 +317,48 @@ func (gs *glooSource) proxyTargets(ctx context.Context, name string, namespace s
|
||||
return targets, nil
|
||||
}
|
||||
|
||||
func sourceKind(kind string) *schema.GroupVersionResource {
|
||||
if kind == "*v1.VirtualService" {
|
||||
return &virtualServiceGVR
|
||||
func (gs *glooSource) targetsFromGatewayIngress(proxy *proxy) (endpoint.Targets, error) {
|
||||
targets := make(endpoint.Targets, 0)
|
||||
|
||||
for _, listener := range proxy.Spec.Listeners {
|
||||
for _, source := range listener.MetadataStatic.Source {
|
||||
if source.ResourceKind != "*v1.Gateway" {
|
||||
log.Debugf("Unsupported listener source. Expecting '*v1.Gateway', got (%s)", source.ResourceKind)
|
||||
continue
|
||||
}
|
||||
gatewayObj, err := gs.gatewayInformer.Lister().ByNamespace(source.ResourceRef.Namespace).Get(source.ResourceRef.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
unstructuredGateway, ok := gatewayObj.(*unstructured.Unstructured)
|
||||
if !ok {
|
||||
log.Error("unexpected object: it is not *unstructured.Unstructured")
|
||||
continue
|
||||
}
|
||||
|
||||
if ingressStr, ok := unstructuredGateway.GetAnnotations()[annotations.Ingress]; ok && ingressStr != "" {
|
||||
namespace, name, err := ParseIngress(ingressStr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse Ingress annotation on Gateway (%s/%s): %w", unstructuredGateway.GetNamespace(), unstructuredGateway.GetName(), err)
|
||||
}
|
||||
if namespace == "" {
|
||||
namespace = unstructuredGateway.GetNamespace()
|
||||
}
|
||||
|
||||
ingress, err := gs.ingressInformer.Lister().Ingresses(namespace).Get(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, lb := range ingress.Status.LoadBalancer.Ingress {
|
||||
if lb.IP != "" {
|
||||
targets = append(targets, lb.IP)
|
||||
} else if lb.Hostname != "" {
|
||||
targets = append(targets, lb.Hostname)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return targets, nil
|
||||
}
|
||||
|
||||
@ -19,10 +19,12 @@ package source
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@ -212,7 +214,7 @@ var externalProxySource = metav1.PartialObjectMetadata{
|
||||
}
|
||||
|
||||
// Proxy with metadata static test
|
||||
var proxyMetadataStatic = proxy{
|
||||
var proxyWithMetadataStatic = proxy{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: proxyGVR.GroupVersion().String(),
|
||||
Kind: "Proxy",
|
||||
@ -261,10 +263,10 @@ var proxyMetadataStatic = proxy{
|
||||
},
|
||||
}
|
||||
|
||||
var proxyMetadataStaticSvc = corev1.Service{
|
||||
var proxyWithMetadataStaticSvc = corev1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: proxyMetadataStatic.Metadata.Name,
|
||||
Namespace: proxyMetadataStatic.Metadata.Namespace,
|
||||
Name: proxyWithMetadataStatic.Metadata.Name,
|
||||
Namespace: proxyWithMetadataStatic.Metadata.Namespace,
|
||||
},
|
||||
Spec: corev1.ServiceSpec{
|
||||
Type: corev1.ServiceTypeLoadBalancer,
|
||||
@ -286,14 +288,14 @@ var proxyMetadataStaticSvc = corev1.Service{
|
||||
},
|
||||
}
|
||||
|
||||
var proxyMetadataStaticSource = metav1.PartialObjectMetadata{
|
||||
var proxyWithMetadataStaticSource = metav1.PartialObjectMetadata{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: virtualServiceGVR.GroupVersion().String(),
|
||||
Kind: "VirtualService",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: proxyMetadataStatic.Spec.Listeners[0].HTTPListener.VirtualHosts[1].MetadataStatic.Source[0].ResourceRef.Name,
|
||||
Namespace: proxyMetadataStatic.Spec.Listeners[0].HTTPListener.VirtualHosts[1].MetadataStatic.Source[0].ResourceRef.Namespace,
|
||||
Name: proxyWithMetadataStatic.Spec.Listeners[0].HTTPListener.VirtualHosts[1].MetadataStatic.Source[0].ResourceRef.Name,
|
||||
Namespace: proxyWithMetadataStatic.Spec.Listeners[0].HTTPListener.VirtualHosts[1].MetadataStatic.Source[0].ResourceRef.Namespace,
|
||||
Annotations: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/ttl": "420",
|
||||
"external-dns.alpha.kubernetes.io/aws-geolocation-country-code": "ES",
|
||||
@ -392,21 +394,98 @@ var targetAnnotatedProxySource = metav1.PartialObjectMetadata{
|
||||
},
|
||||
}
|
||||
|
||||
// Proxy backed by Ingress
|
||||
var gatewayIngressAnnotatedProxy = proxy{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: proxyGVR.GroupVersion().String(),
|
||||
Kind: "Proxy",
|
||||
},
|
||||
Metadata: metav1.ObjectMeta{
|
||||
Name: "gateway-ingress-annotated",
|
||||
Namespace: defaultGlooNamespace,
|
||||
},
|
||||
Spec: proxySpec{
|
||||
Listeners: []proxySpecListener{
|
||||
{
|
||||
HTTPListener: proxySpecHTTPListener{
|
||||
VirtualHosts: []proxyVirtualHost{
|
||||
{
|
||||
Domains: []string{"k.test"},
|
||||
MetadataStatic: proxyVirtualHostMetadataStatic{
|
||||
Source: []proxyVirtualHostMetadataStaticSource{
|
||||
{
|
||||
ResourceKind: "*v1.Unknown",
|
||||
ResourceRef: proxyVirtualHostMetadataSourceResourceRef{
|
||||
Name: "my-unknown-svc",
|
||||
Namespace: "unknown",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
MetadataStatic: proxyMetadataStatic{
|
||||
Source: []proxyMetadataStaticSource{
|
||||
{
|
||||
ResourceKind: "*v1.Gateway",
|
||||
ResourceRef: proxyMetadataStaticSourceResourceRef{
|
||||
Name: "gateway-ingress-annotated",
|
||||
Namespace: defaultGlooNamespace,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var gatewayIngressAnnotatedProxyGateway = metav1.PartialObjectMetadata{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: gatewayGVR.GroupVersion().String(),
|
||||
Kind: "Gateway",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: gatewayIngressAnnotatedProxy.Spec.Listeners[0].MetadataStatic.Source[0].ResourceRef.Name,
|
||||
Namespace: gatewayIngressAnnotatedProxy.Spec.Listeners[0].MetadataStatic.Source[0].ResourceRef.Namespace,
|
||||
Annotations: map[string]string{
|
||||
"external-dns.alpha.kubernetes.io/ingress": fmt.Sprintf("%s/%s", gatewayIngressAnnotatedProxy.Spec.Listeners[0].MetadataStatic.Source[0].ResourceRef.Namespace, gatewayIngressAnnotatedProxy.Spec.Listeners[0].MetadataStatic.Source[0].ResourceRef.Name),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var gatewayIngressAnnotatedProxyIngress = networkingv1.Ingress{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: gatewayIngressAnnotatedProxy.Spec.Listeners[0].MetadataStatic.Source[0].ResourceRef.Name,
|
||||
Namespace: gatewayIngressAnnotatedProxy.Spec.Listeners[0].MetadataStatic.Source[0].ResourceRef.Namespace,
|
||||
},
|
||||
Status: networkingv1.IngressStatus{
|
||||
LoadBalancer: networkingv1.IngressLoadBalancerStatus{
|
||||
Ingress: []networkingv1.IngressLoadBalancerIngress{
|
||||
{
|
||||
Hostname: "example.com",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func TestGlooSource(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
fakeKubernetesClient := fakeKube.NewSimpleClientset()
|
||||
fakeDynamicClient := fakeDynamic.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(),
|
||||
map[schema.GroupVersionResource]string{
|
||||
proxyGVR: "ProxyList",
|
||||
proxyGVR: "ProxyList",
|
||||
virtualServiceGVR: "VirtualServiceList",
|
||||
gatewayGVR: "GatewayList",
|
||||
})
|
||||
|
||||
source, err := NewGlooSource(fakeDynamicClient, fakeKubernetesClient, []string{defaultGlooNamespace})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, source)
|
||||
|
||||
internalProxyUnstructured := unstructured.Unstructured{}
|
||||
externalProxyUnstructured := unstructured.Unstructured{}
|
||||
gatewayIngressAnnotatedProxyUnstructured := unstructured.Unstructured{}
|
||||
gatewayIngressAnnotatedProxyGatewayUnstructured := unstructured.Unstructured{}
|
||||
proxyMetadataStaticUnstructured := unstructured.Unstructured{}
|
||||
targetAnnotatedProxyUnstructured := unstructured.Unstructured{}
|
||||
|
||||
@ -421,7 +500,13 @@ func TestGlooSource(t *testing.T) {
|
||||
externalProxyAsJSON, err := json.Marshal(externalProxy)
|
||||
assert.NoError(t, err)
|
||||
|
||||
proxyMetadataStaticAsJSON, err := json.Marshal(proxyMetadataStatic)
|
||||
gatewayIngressAnnotatedProxyAsJSON, err := json.Marshal(gatewayIngressAnnotatedProxy)
|
||||
assert.NoError(t, err)
|
||||
|
||||
gatewayIngressAnnotatedProxyGatewayAsJSON, err := json.Marshal(gatewayIngressAnnotatedProxyGateway)
|
||||
assert.NoError(t, err)
|
||||
|
||||
proxyMetadataStaticAsJSON, err := json.Marshal(proxyWithMetadataStatic)
|
||||
assert.NoError(t, err)
|
||||
|
||||
targetAnnotatedProxyAsJSON, err := json.Marshal(targetAnnotatedProxy)
|
||||
@ -433,7 +518,7 @@ func TestGlooSource(t *testing.T) {
|
||||
externalProxySvcAsJSON, err := json.Marshal(externalProxySource)
|
||||
assert.NoError(t, err)
|
||||
|
||||
proxyMetadataStaticSvcAsJSON, err := json.Marshal(proxyMetadataStaticSource)
|
||||
proxyMetadataStaticSvcAsJSON, err := json.Marshal(proxyWithMetadataStaticSource)
|
||||
assert.NoError(t, err)
|
||||
|
||||
targetAnnotatedProxySvcAsJSON, err := json.Marshal(targetAnnotatedProxySource)
|
||||
@ -441,6 +526,8 @@ func TestGlooSource(t *testing.T) {
|
||||
|
||||
assert.NoError(t, internalProxyUnstructured.UnmarshalJSON(internalProxyAsJSON))
|
||||
assert.NoError(t, externalProxyUnstructured.UnmarshalJSON(externalProxyAsJSON))
|
||||
assert.NoError(t, gatewayIngressAnnotatedProxyUnstructured.UnmarshalJSON(gatewayIngressAnnotatedProxyAsJSON))
|
||||
assert.NoError(t, gatewayIngressAnnotatedProxyGatewayUnstructured.UnmarshalJSON(gatewayIngressAnnotatedProxyGatewayAsJSON))
|
||||
assert.NoError(t, proxyMetadataStaticUnstructured.UnmarshalJSON(proxyMetadataStaticAsJSON))
|
||||
assert.NoError(t, targetAnnotatedProxyUnstructured.UnmarshalJSON(targetAnnotatedProxyAsJSON))
|
||||
|
||||
@ -449,6 +536,18 @@ func TestGlooSource(t *testing.T) {
|
||||
assert.NoError(t, proxyMetadataStaticSourceUnstructured.UnmarshalJSON(proxyMetadataStaticSvcAsJSON))
|
||||
assert.NoError(t, targetAnnotatedProxySourceUnstructured.UnmarshalJSON(targetAnnotatedProxySvcAsJSON))
|
||||
|
||||
_, err = fakeKubernetesClient.CoreV1().Services(internalProxySvc.GetNamespace()).Create(context.Background(), &internalProxySvc, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeKubernetesClient.CoreV1().Services(externalProxySvc.GetNamespace()).Create(context.Background(), &externalProxySvc, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeKubernetesClient.CoreV1().Services(proxyWithMetadataStaticSvc.GetNamespace()).Create(context.Background(), &proxyWithMetadataStaticSvc, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeKubernetesClient.CoreV1().Services(targetAnnotatedProxySvc.GetNamespace()).Create(context.Background(), &targetAnnotatedProxySvc, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = fakeKubernetesClient.NetworkingV1().Ingresses(gatewayIngressAnnotatedProxyIngress.GetNamespace()).Create(context.Background(), &gatewayIngressAnnotatedProxyIngress, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create proxy resources
|
||||
_, err = fakeDynamicClient.Resource(proxyGVR).Namespace(defaultGlooNamespace).Create(context.Background(), &internalProxyUnstructured, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
@ -458,30 +557,31 @@ func TestGlooSource(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeDynamicClient.Resource(proxyGVR).Namespace(defaultGlooNamespace).Create(context.Background(), &targetAnnotatedProxyUnstructured, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeDynamicClient.Resource(proxyGVR).Namespace(defaultGlooNamespace).Create(context.Background(), &gatewayIngressAnnotatedProxyUnstructured, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create proxy source
|
||||
_, err = fakeDynamicClient.Resource(virtualServiceGVR).Namespace(internalProxySource.Namespace).Create(context.Background(), &internalProxySourceUnstructured, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeDynamicClient.Resource(virtualServiceGVR).Namespace(externalProxySource.Namespace).Create(context.Background(), &externalProxySourceUnstructured, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeDynamicClient.Resource(virtualServiceGVR).Namespace(proxyMetadataStaticSource.Namespace).Create(context.Background(), &proxyMetadataStaticSourceUnstructured, metav1.CreateOptions{})
|
||||
_, err = fakeDynamicClient.Resource(virtualServiceGVR).Namespace(proxyWithMetadataStaticSource.Namespace).Create(context.Background(), &proxyMetadataStaticSourceUnstructured, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeDynamicClient.Resource(virtualServiceGVR).Namespace(targetAnnotatedProxySource.Namespace).Create(context.Background(), &targetAnnotatedProxySourceUnstructured, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Create proxy service resources
|
||||
_, err = fakeKubernetesClient.CoreV1().Services(internalProxySvc.GetNamespace()).Create(context.Background(), &internalProxySvc, metav1.CreateOptions{})
|
||||
// Create gateway resource
|
||||
_, err = fakeDynamicClient.Resource(gatewayGVR).Namespace(gatewayIngressAnnotatedProxyGateway.Namespace).Create(context.Background(), &gatewayIngressAnnotatedProxyGatewayUnstructured, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeKubernetesClient.CoreV1().Services(externalProxySvc.GetNamespace()).Create(context.Background(), &externalProxySvc, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeKubernetesClient.CoreV1().Services(proxyMetadataStaticSvc.GetNamespace()).Create(context.Background(), &proxyMetadataStaticSvc, metav1.CreateOptions{})
|
||||
assert.NoError(t, err)
|
||||
_, err = fakeKubernetesClient.CoreV1().Services(targetAnnotatedProxySvc.GetNamespace()).Create(context.Background(), &targetAnnotatedProxySvc, metav1.CreateOptions{})
|
||||
|
||||
source, err := NewGlooSource(context.TODO(), fakeDynamicClient, fakeKubernetesClient, []string{defaultGlooNamespace})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, source)
|
||||
|
||||
endpoints, err := source.Endpoints(context.Background())
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, endpoints, 10)
|
||||
assert.Len(t, endpoints, 11)
|
||||
|
||||
assert.ElementsMatch(t, endpoints, []*endpoint.Endpoint{
|
||||
{
|
||||
DNSName: "a.test",
|
||||
@ -537,7 +637,7 @@ func TestGlooSource(t *testing.T) {
|
||||
},
|
||||
{
|
||||
DNSName: "f.test",
|
||||
Targets: []string{proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[0].IP, proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[1].IP, proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[2].IP},
|
||||
Targets: []string{proxyWithMetadataStaticSvc.Status.LoadBalancer.Ingress[0].IP, proxyWithMetadataStaticSvc.Status.LoadBalancer.Ingress[1].IP, proxyWithMetadataStaticSvc.Status.LoadBalancer.Ingress[2].IP},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: 0,
|
||||
Labels: endpoint.Labels{},
|
||||
@ -545,7 +645,7 @@ func TestGlooSource(t *testing.T) {
|
||||
},
|
||||
{
|
||||
DNSName: "g.test",
|
||||
Targets: []string{proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[0].IP, proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[1].IP, proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[2].IP},
|
||||
Targets: []string{proxyWithMetadataStaticSvc.Status.LoadBalancer.Ingress[0].IP, proxyWithMetadataStaticSvc.Status.LoadBalancer.Ingress[1].IP, proxyWithMetadataStaticSvc.Status.LoadBalancer.Ingress[2].IP},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
RecordTTL: 0,
|
||||
Labels: endpoint.Labels{},
|
||||
@ -553,7 +653,7 @@ func TestGlooSource(t *testing.T) {
|
||||
},
|
||||
{
|
||||
DNSName: "h.test",
|
||||
Targets: []string{proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[0].IP, proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[1].IP, proxyMetadataStaticSvc.Status.LoadBalancer.Ingress[2].IP},
|
||||
Targets: []string{proxyWithMetadataStaticSvc.Status.LoadBalancer.Ingress[0].IP, proxyWithMetadataStaticSvc.Status.LoadBalancer.Ingress[1].IP, proxyWithMetadataStaticSvc.Status.LoadBalancer.Ingress[2].IP},
|
||||
RecordType: endpoint.RecordTypeA,
|
||||
SetIdentifier: "identifier",
|
||||
RecordTTL: 420,
|
||||
@ -586,5 +686,12 @@ func TestGlooSource(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
DNSName: "k.test",
|
||||
Targets: []string{gatewayIngressAnnotatedProxyIngress.Status.LoadBalancer.Ingress[0].Hostname},
|
||||
RecordType: endpoint.RecordTypeCNAME,
|
||||
RecordTTL: 0,
|
||||
Labels: endpoint.Labels{},
|
||||
ProviderSpecific: endpoint.ProviderSpecific{}},
|
||||
})
|
||||
}
|
||||
|
||||
@ -522,7 +522,7 @@ func buildGlooProxySource(ctx context.Context, p ClientGenerator, cfg *Config) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewGlooSource(dynamicClient, kubernetesClient, cfg.GlooNamespaces)
|
||||
return NewGlooSource(ctx, dynamicClient, kubernetesClient, cfg.GlooNamespaces)
|
||||
}
|
||||
|
||||
func buildTraefikProxySource(ctx context.Context, p ClientGenerator, cfg *Config) (Source, error) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user