From 5221b1d5257820d2721c98ef5c8d319220b49023 Mon Sep 17 00:00:00 2001 From: Hugome Date: Wed, 29 Jul 2020 03:25:23 +0200 Subject: [PATCH] feat: add gloo proxy source --- docs/tutorials/gloo-proxy.md | 101 +++++++++ main.go | 1 + pkg/apis/externaldns/types.go | 8 +- pkg/apis/externaldns/types_test.go | 4 + source/gloo.go | 200 ++++++++++++++++++ source/gloo_test.go | 320 +++++++++++++++++++++++++++++ source/store.go | 11 + 7 files changed, 643 insertions(+), 2 deletions(-) create mode 100644 docs/tutorials/gloo-proxy.md create mode 100644 source/gloo.go create mode 100644 source/gloo_test.go diff --git a/docs/tutorials/gloo-proxy.md b/docs/tutorials/gloo-proxy.md new file mode 100644 index 0000000000..4938feb211 --- /dev/null +++ b/docs/tutorials/gloo-proxy.md @@ -0,0 +1,101 @@ +# Configuring ExternalDNS to use the Gloo Proxy Source +This tutorial describes how to configure ExternalDNS to use the Gloo Proxy source. +It is meant to supplement the other provider-specific setup tutorials. + +### Manifest (for clusters without RBAC enabled) +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns +spec: + strategy: + type: Recreate + selector: + matchLabels: + app: external-dns + template: + metadata: + labels: + app: external-dns + spec: + containers: + - name: external-dns + # update this to the desired external-dns version + image: k8s.gcr.io/external-dns/external-dns:v0.7.6 + args: + - --source=gloo-proxy + - --gloo-namespace=custom-gloo-system # gloo system namespace. Omit to use the default (gloo-system) + - --provider=aws + - --registry=txt + - --txt-owner-id=my-identifier +``` + +### Manifest (for clusters with RBAC enabled) +Could be change if you have mulitple sources + +```yaml +apiVersion: v1 +kind: ServiceAccount +metadata: + name: external-dns +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRole +metadata: + name: external-dns +rules: +- apiGroups: [""] + resources: ["services","endpoints","pods"] + verbs: ["get","watch","list"] +- apiGroups: [""] + resources: ["nodes"] + verbs: ["list","watch"] +- apiGroups: ["gloo.solo.io"] + resources: ["proxies"] + verbs: ["get","watch","list"] +- apiGroups: ["gateway.solo.io"] + resources: ["virtualservices"] + verbs: ["get", "list", "watch"] +--- +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: external-dns-viewer +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: external-dns +subjects: +- kind: ServiceAccount + name: external-dns + namespace: default +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: external-dns +spec: + strategy: + type: Recreate + selector: + matchLabels: + app: external-dns + template: + metadata: + labels: + app: external-dns + spec: + serviceAccountName: external-dns + containers: + - name: external-dns + # update this to the desired external-dns version + image: k8s.gcr.io/external-dns/external-dns:v0.7.6 + args: + - --source=gloo-proxy + - --gloo-namespace=custom-gloo-system # gloo system namespace. Omit to use the default (gloo-system) + - --provider=aws + - --registry=txt + - --txt-owner-id=my-identifier +``` + diff --git a/main.go b/main.go index 09860c08d2..75dc338686 100644 --- a/main.go +++ b/main.go @@ -120,6 +120,7 @@ func main() { CFUsername: cfg.CFUsername, CFPassword: cfg.CFPassword, ContourLoadBalancerService: cfg.ContourLoadBalancerService, + GlooNamespace: cfg.GlooNamespace, SkipperRouteGroupVersion: cfg.SkipperRouteGroupVersion, RequestTimeout: cfg.RequestTimeout, } diff --git a/pkg/apis/externaldns/types.go b/pkg/apis/externaldns/types.go index b8d43f4620..1384730d6d 100644 --- a/pkg/apis/externaldns/types.go +++ b/pkg/apis/externaldns/types.go @@ -45,6 +45,7 @@ type Config struct { KubeConfig string RequestTimeout time.Duration ContourLoadBalancerService string + GlooNamespace string SkipperRouteGroupVersion string Sources []string Namespace string @@ -167,6 +168,7 @@ var defaultConfig = &Config{ KubeConfig: "", RequestTimeout: time.Second * 30, ContourLoadBalancerService: "heptio-contour/contour", + GlooNamespace: "gloo-system", SkipperRouteGroupVersion: "zalando.org/v1", Sources: nil, Namespace: "", @@ -328,12 +330,14 @@ func (cfg *Config) ParseFlags(args []string) error { // Flags related to Contour app.Flag("contour-load-balancer", "The fully-qualified name of the Contour load balancer service. (default: heptio-contour/contour)").Default("heptio-contour/contour").StringVar(&cfg.ContourLoadBalancerService) + // Flags related to Gloo + app.Flag("gloo-namespace", "Gloo namespace. (default: gloo-system)").Default("gloo-system").StringVar(&cfg.GlooNamespace) + // Flags related to Skipper RouteGroup app.Flag("skipper-routegroup-groupversion", "The resource version for skipper routegroup").Default(source.DefaultRoutegroupVersion).StringVar(&cfg.SkipperRouteGroupVersion) // Flags related to processing sources - app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host") - + app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, gloo-proxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "gloo-proxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host") app.Flag("namespace", "Limit sources of endpoints to a specific namespace (default: all namespaces)").Default(defaultConfig.Namespace).StringVar(&cfg.Namespace) app.Flag("annotation-filter", "Filter sources managed by external-dns via annotation using label selector semantics (default: all sources)").Default(defaultConfig.AnnotationFilter).StringVar(&cfg.AnnotationFilter) app.Flag("label-filter", "Filter sources managed by external-dns via label selector when listing all resources; currently only supported by source CRD").Default(defaultConfig.LabelFilter).StringVar(&cfg.LabelFilter) diff --git a/pkg/apis/externaldns/types_test.go b/pkg/apis/externaldns/types_test.go index df37180d40..83f853692f 100644 --- a/pkg/apis/externaldns/types_test.go +++ b/pkg/apis/externaldns/types_test.go @@ -35,6 +35,7 @@ var ( KubeConfig: "", RequestTimeout: time.Second * 30, ContourLoadBalancerService: "heptio-contour/contour", + GlooNamespace: "gloo-system", SkipperRouteGroupVersion: "zalando.org/v1", Sources: []string{"service"}, Namespace: "", @@ -114,6 +115,7 @@ var ( KubeConfig: "/some/path", RequestTimeout: time.Second * 77, ContourLoadBalancerService: "heptio-contour-other/contour-other", + GlooNamespace: "gloo-not-system", SkipperRouteGroupVersion: "zalando.org/v2", Sources: []string{"service", "ingress", "connector"}, Namespace: "namespace", @@ -220,6 +222,7 @@ func TestParseFlags(t *testing.T) { "--kubeconfig=/some/path", "--request-timeout=77s", "--contour-load-balancer=heptio-contour-other/contour-other", + "--gloo-namespace=gloo-not-system", "--skipper-routegroup-groupversion=zalando.org/v2", "--source=service", "--source=ingress", @@ -317,6 +320,7 @@ func TestParseFlags(t *testing.T) { "EXTERNAL_DNS_KUBECONFIG": "/some/path", "EXTERNAL_DNS_REQUEST_TIMEOUT": "77s", "EXTERNAL_DNS_CONTOUR_LOAD_BALANCER": "heptio-contour-other/contour-other", + "EXTERNAL_DNS_GLOO_NAMESPACE": "gloo-not-system", "EXTERNAL_DNS_SKIPPER_ROUTEGROUP_GROUPVERSION": "zalando.org/v2", "EXTERNAL_DNS_SOURCE": "service\ningress\nconnector", "EXTERNAL_DNS_NAMESPACE": "namespace", diff --git a/source/gloo.go b/source/gloo.go new file mode 100644 index 0000000000..845ccdee05 --- /dev/null +++ b/source/gloo.go @@ -0,0 +1,200 @@ +/* +Copyright 2020n The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package source + +import ( + "context" + "encoding/json" + "strings" + + log "github.com/sirupsen/logrus" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/client-go/dynamic" + "k8s.io/client-go/kubernetes" + + "sigs.k8s.io/external-dns/endpoint" +) + +var ( + proxyGVR = schema.GroupVersionResource{ + Group: "gloo.solo.io", + Version: "v1", + Resource: "proxies", + } + virtualServiceGVR = schema.GroupVersionResource{ + Group: "gateway.solo.io", + Version: "v1", + Resource: "virtualservices", + } +) + +// Basic redefinition of "Proxy" CRD : https://github.com/solo-io/gloo/blob/v1.4.6/projects/gloo/pkg/api/v1/proxy.pb.go +type proxy struct { + metav1.TypeMeta `json:",inline"` + Metadata metav1.ObjectMeta `json:"metadata,omitempty"` + Spec proxySpec `json:"spec,omitempty"` +} + +type proxySpec struct { + Listeners []proxySpecListener `json:"listeners,omitempty"` +} + +type proxySpecListener struct { + HTTPListener proxySpecHTTPListener `json:"httpListener,omitempty"` +} + +type proxySpecHTTPListener struct { + VirtualHosts []proxyVirtualHost `json:"virtualHosts,omitempty"` +} + +type proxyVirtualHost struct { + Domains []string `json:"domains,omitempty"` + Metadata proxyVirtualHostMetadata `json:"metadata,omitempty"` +} + +type proxyVirtualHostMetadata struct { + Source []proxyVirtualHostMetadataSource `json:"sources,omitempty"` +} + +type proxyVirtualHostMetadataSource struct { + Kind string `json:"kind,omitempty"` + Name string `json:"name,omitempty"` + Namespace string `json:"namespace,omitempty"` +} + +type glooSource struct { + dynamicKubeClient dynamic.Interface + kubeClient kubernetes.Interface + glooNamespace string +} + +// NewGlooSource creates a new glooSource with the given config +func NewGlooSource(dynamicKubeClient dynamic.Interface, kubeClient kubernetes.Interface, glooNamespace string) (Source, error) { + return &glooSource{ + dynamicKubeClient, + kubeClient, + glooNamespace, + }, nil +} + +func (gs *glooSource) AddEventHandler(ctx context.Context, handler func()) { +} + +// Endpoints returns endpoint objects +func (gs *glooSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) { + endpoints := []*endpoint.Endpoint{} + + proxies, err := gs.dynamicKubeClient.Resource(proxyGVR).Namespace(gs.glooNamespace).List(ctx, metav1.ListOptions{}) + if err != nil { + return nil, err + } + for _, obj := range proxies.Items { + proxy := proxy{} + jsonString, err := obj.MarshalJSON() + if err != nil { + return nil, err + } + err = json.Unmarshal(jsonString, &proxy) + if err != nil { + return nil, err + } + log.Debugf("Gloo: Find %s proxy", proxy.Metadata.Name) + proxyTargets, err := gs.proxyTargets(ctx, proxy.Metadata.Name) + 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) + if err != nil { + return nil, err + } + log.Debugf("Gloo[%s]: Generate %d endpoint(s)", proxy.Metadata.Name, len(proxyEndpoints)) + endpoints = append(endpoints, proxyEndpoints...) + } + return endpoints, nil +} + +func (gs *glooSource) generateEndpointsFromProxy(ctx context.Context, proxy *proxy, targets endpoint.Targets) ([]*endpoint.Endpoint, error) { + endpoints := []*endpoint.Endpoint{} + for _, listener := range proxy.Spec.Listeners { + for _, virtualHost := range listener.HTTPListener.VirtualHosts { + annotations, err := gs.annotationsFromProxySource(ctx, virtualHost) + if err != nil { + return nil, err + } + ttl, err := getTTLFromAnnotations(annotations) + if err != nil { + return nil, err + } + providerSpecific, setIdentifier := getProviderSpecificAnnotations(annotations) + for _, domain := range virtualHost.Domains { + endpoints = append(endpoints, endpointsForHostname(strings.TrimSuffix(domain, "."), targets, ttl, providerSpecific, setIdentifier)...) + } + } + } + return endpoints, nil +} + +func (gs *glooSource) annotationsFromProxySource(ctx context.Context, virtualHost proxyVirtualHost) (map[string]string, error) { + annotations := 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() { + annotations[key] = value + } + } + } + return annotations, nil +} + +func (gs *glooSource) proxyTargets(ctx context.Context, name string) (endpoint.Targets, error) { + svc, err := gs.kubeClient.CoreV1().Services(gs.glooNamespace).Get(ctx, name, metav1.GetOptions{}) + if err != nil { + return nil, err + } + + var targets endpoint.Targets + switch svc.Spec.Type { + case corev1.ServiceTypeLoadBalancer: + for _, lb := range svc.Status.LoadBalancer.Ingress { + if lb.IP != "" { + targets = append(targets, lb.IP) + } + if lb.Hostname != "" { + targets = append(targets, lb.Hostname) + } + } + default: + log.WithField("gateway", name).WithField("service", svc).Warn("Gloo: Proxy service type not supported") + } + return targets, nil +} + +func sourceKind(kind string) *schema.GroupVersionResource { + switch kind { + case "*v1.VirtualService": + return &virtualServiceGVR + } + return nil +} diff --git a/source/gloo_test.go b/source/gloo_test.go new file mode 100644 index 0000000000..dfc4d9436f --- /dev/null +++ b/source/gloo_test.go @@ -0,0 +1,320 @@ +/* +Copyright 2020n The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package source + +import ( + "context" + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" + 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/runtime" + fakeDynamic "k8s.io/client-go/dynamic/fake" + fakeKube "k8s.io/client-go/kubernetes/fake" + "sigs.k8s.io/external-dns/endpoint" +) + +// This is a compile-time validation that glooSource is a Source. +var _ Source = &glooSource{} + +const defaultGlooNamespace = "gloo-system" + +// Internal proxy test +var internalProxy = proxy{ + TypeMeta: metav1.TypeMeta{ + APIVersion: proxyGVR.GroupVersion().String(), + Kind: "Proxy", + }, + Metadata: metav1.ObjectMeta{ + Name: "internal", + Namespace: defaultGlooNamespace, + }, + Spec: proxySpec{ + Listeners: []proxySpecListener{ + { + HTTPListener: proxySpecHTTPListener{ + VirtualHosts: []proxyVirtualHost{ + { + Domains: []string{"a.test", "b.test"}, + Metadata: proxyVirtualHostMetadata{ + Source: []proxyVirtualHostMetadataSource{ + { + Kind: "*v1.Unknown", + Name: "my-unknown-svc", + Namespace: "unknown", + }, + }, + }, + }, + { + Domains: []string{"c.test"}, + Metadata: proxyVirtualHostMetadata{ + Source: []proxyVirtualHostMetadataSource{ + { + Kind: "*v1.VirtualService", + Name: "my-internal-svc", + Namespace: "internal", + }, + }, + }, + }, + }, + }, + }, + }, + }, +} + +var internalProxySvc = corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: internalProxy.Metadata.Name, + Namespace: internalProxy.Metadata.Namespace, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + }, + Status: corev1.ServiceStatus{ + LoadBalancer: corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{ + corev1.LoadBalancerIngress{ + IP: "203.0.113.1", + }, + corev1.LoadBalancerIngress{ + IP: "203.0.113.2", + }, + corev1.LoadBalancerIngress{ + IP: "203.0.113.3", + }, + }, + }, + }, +} + +var internalProxySource = metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: virtualServiceGVR.GroupVersion().String(), + Kind: "VirtualService", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: internalProxy.Spec.Listeners[0].HTTPListener.VirtualHosts[1].Metadata.Source[0].Name, + Namespace: internalProxy.Spec.Listeners[0].HTTPListener.VirtualHosts[1].Metadata.Source[0].Namespace, + Annotations: map[string]string{ + "external-dns.alpha.kubernetes.io/ttl": "42", + "external-dns.alpha.kubernetes.io/aws-geolocation-country-code": "LU", + "external-dns.alpha.kubernetes.io/set-identifier": "identifier", + }, + }, +} + +// External proxy test +var externalProxy = proxy{ + TypeMeta: metav1.TypeMeta{ + APIVersion: proxyGVR.GroupVersion().String(), + Kind: "Proxy", + }, + Metadata: metav1.ObjectMeta{ + Name: "external", + Namespace: defaultGlooNamespace, + }, + Spec: proxySpec{ + Listeners: []proxySpecListener{ + { + HTTPListener: proxySpecHTTPListener{ + VirtualHosts: []proxyVirtualHost{ + { + Domains: []string{"d.test"}, + Metadata: proxyVirtualHostMetadata{ + Source: []proxyVirtualHostMetadataSource{ + { + Kind: "*v1.Unknown", + Name: "my-unknown-svc", + Namespace: "unknown", + }, + }, + }, + }, + { + Domains: []string{"e.test"}, + Metadata: proxyVirtualHostMetadata{ + Source: []proxyVirtualHostMetadataSource{ + { + Kind: "*v1.VirtualService", + Name: "my-external-svc", + Namespace: "external", + }, + }, + }, + }, + }, + }, + }, + }, + }, +} + +var externalProxySvc = corev1.Service{ + ObjectMeta: metav1.ObjectMeta{ + Name: externalProxy.Metadata.Name, + Namespace: externalProxy.Metadata.Namespace, + }, + Spec: corev1.ServiceSpec{ + Type: corev1.ServiceTypeLoadBalancer, + }, + Status: corev1.ServiceStatus{ + LoadBalancer: corev1.LoadBalancerStatus{ + Ingress: []corev1.LoadBalancerIngress{ + corev1.LoadBalancerIngress{ + Hostname: "a.example.org", + }, + corev1.LoadBalancerIngress{ + Hostname: "b.example.org", + }, + corev1.LoadBalancerIngress{ + Hostname: "c.example.org", + }, + }, + }, + }, +} + +var externalProxySource = metav1.PartialObjectMetadata{ + TypeMeta: metav1.TypeMeta{ + APIVersion: virtualServiceGVR.GroupVersion().String(), + Kind: "VirtualService", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: externalProxy.Spec.Listeners[0].HTTPListener.VirtualHosts[1].Metadata.Source[0].Name, + Namespace: externalProxy.Spec.Listeners[0].HTTPListener.VirtualHosts[1].Metadata.Source[0].Namespace, + Annotations: map[string]string{ + "external-dns.alpha.kubernetes.io/ttl": "24", + "external-dns.alpha.kubernetes.io/aws-geolocation-country-code": "JP", + "external-dns.alpha.kubernetes.io/set-identifier": "identifier-external", + }, + }, +} + +func TestGlooSource(t *testing.T) { + fakeKubernetesClient := fakeKube.NewSimpleClientset() + fakeDynamicClient := fakeDynamic.NewSimpleDynamicClient(runtime.NewScheme()) + + source, err := NewGlooSource(fakeDynamicClient, fakeKubernetesClient, defaultGlooNamespace) + assert.NoError(t, err) + assert.NotNil(t, source) + + internalProxyUnstructured := unstructured.Unstructured{} + externalProxyUnstructured := unstructured.Unstructured{} + + internalProxySourceUnstructured := unstructured.Unstructured{} + externalProxySourceUnstructured := unstructured.Unstructured{} + + internalProxyAsJSON, err := json.Marshal(internalProxy) + assert.NoError(t, err) + + externalProxyAsJSON, err := json.Marshal(externalProxy) + assert.NoError(t, err) + + internalProxySvcAsJSON, err := json.Marshal(internalProxySource) + assert.NoError(t, err) + + externalProxySvcAsJSON, err := json.Marshal(externalProxySource) + assert.NoError(t, err) + + assert.NoError(t, internalProxyUnstructured.UnmarshalJSON(internalProxyAsJSON)) + assert.NoError(t, externalProxyUnstructured.UnmarshalJSON(externalProxyAsJSON)) + + assert.NoError(t, internalProxySourceUnstructured.UnmarshalJSON(internalProxySvcAsJSON)) + assert.NoError(t, externalProxySourceUnstructured.UnmarshalJSON(externalProxySvcAsJSON)) + + // Create proxy resources + _, err = fakeDynamicClient.Resource(proxyGVR).Namespace(defaultGlooNamespace).Create(context.Background(), &internalProxyUnstructured, metav1.CreateOptions{}) + assert.NoError(t, err) + _, err = fakeDynamicClient.Resource(proxyGVR).Namespace(defaultGlooNamespace).Create(context.Background(), &externalProxyUnstructured, 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) + + // Create proxy service resources + _, 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) + + endpoints, err := source.Endpoints(context.Background()) + assert.NoError(t, err) + assert.Len(t, endpoints, 5) + assert.Equal(t, endpoints, []*endpoint.Endpoint{ + &endpoint.Endpoint{ + DNSName: "a.test", + Targets: []string{internalProxySvc.Status.LoadBalancer.Ingress[0].IP, internalProxySvc.Status.LoadBalancer.Ingress[1].IP, internalProxySvc.Status.LoadBalancer.Ingress[2].IP}, + RecordType: endpoint.RecordTypeA, + RecordTTL: 0, + Labels: endpoint.Labels{}, + ProviderSpecific: endpoint.ProviderSpecific{}, + }, + &endpoint.Endpoint{ + DNSName: "b.test", + Targets: []string{internalProxySvc.Status.LoadBalancer.Ingress[0].IP, internalProxySvc.Status.LoadBalancer.Ingress[1].IP, internalProxySvc.Status.LoadBalancer.Ingress[2].IP}, + RecordType: endpoint.RecordTypeA, + RecordTTL: 0, + Labels: endpoint.Labels{}, + ProviderSpecific: endpoint.ProviderSpecific{}, + }, + &endpoint.Endpoint{ + DNSName: "c.test", + Targets: []string{internalProxySvc.Status.LoadBalancer.Ingress[0].IP, internalProxySvc.Status.LoadBalancer.Ingress[1].IP, internalProxySvc.Status.LoadBalancer.Ingress[2].IP}, + RecordType: endpoint.RecordTypeA, + SetIdentifier: "identifier", + RecordTTL: 42, + Labels: endpoint.Labels{}, + ProviderSpecific: endpoint.ProviderSpecific{ + endpoint.ProviderSpecificProperty{ + Name: "aws/geolocation-country-code", + Value: "LU", + }, + }, + }, + &endpoint.Endpoint{ + DNSName: "d.test", + Targets: []string{externalProxySvc.Status.LoadBalancer.Ingress[0].Hostname, externalProxySvc.Status.LoadBalancer.Ingress[1].Hostname, externalProxySvc.Status.LoadBalancer.Ingress[2].Hostname}, + RecordType: endpoint.RecordTypeCNAME, + RecordTTL: 0, + Labels: endpoint.Labels{}, + ProviderSpecific: endpoint.ProviderSpecific{}, + }, + &endpoint.Endpoint{ + DNSName: "e.test", + Targets: []string{externalProxySvc.Status.LoadBalancer.Ingress[0].Hostname, externalProxySvc.Status.LoadBalancer.Ingress[1].Hostname, externalProxySvc.Status.LoadBalancer.Ingress[2].Hostname}, + RecordType: endpoint.RecordTypeCNAME, + SetIdentifier: "identifier-external", + RecordTTL: 24, + Labels: endpoint.Labels{}, + ProviderSpecific: endpoint.ProviderSpecific{ + endpoint.ProviderSpecificProperty{ + Name: "aws/geolocation-country-code", + Value: "JP", + }, + }, + }, + }) +} diff --git a/source/store.go b/source/store.go index 0cb6641fb4..0c934c4072 100644 --- a/source/store.go +++ b/source/store.go @@ -61,6 +61,7 @@ type Config struct { CFUsername string CFPassword string ContourLoadBalancerService string + GlooNamespace string SkipperRouteGroupVersion string RequestTimeout time.Duration } @@ -239,6 +240,16 @@ func BuildWithConfig(source string, p ClientGenerator, cfg *Config) (Source, err return nil, err } return NewContourHTTPProxySource(dynamicClient, cfg.Namespace, cfg.AnnotationFilter, cfg.FQDNTemplate, cfg.CombineFQDNAndAnnotation, cfg.IgnoreHostnameAnnotation) + case "gloo-proxy": + kubernetesClient, err := p.KubeClient() + if err != nil { + return nil, err + } + dynamicClient, err := p.DynamicKubernetesClient() + if err != nil { + return nil, err + } + return NewGlooSource(dynamicClient, kubernetesClient, cfg.GlooNamespace) case "openshift-route": ocpClient, err := p.OpenShiftClient() if err != nil {