feat: add support for ingress backed GlooEdge Gateway (#5909)

This commit is contained in:
Quan Hoang 2025-11-16 18:33:38 +00:00 committed by GitHub
parent 031dc9ffd4
commit 62f4d7d5f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 481 additions and 75 deletions

View File

@ -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

View File

@ -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 }}

View File

@ -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"]

View File

@ -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

View File

@ -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
```

View File

@ -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
}

View File

@ -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{}},
})
}

View File

@ -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) {