mirror of
https://github.com/kubernetes-sigs/external-dns.git
synced 2025-08-06 01:26:59 +02:00
gateway-httproute: add source
This commit is contained in:
parent
48203e64c9
commit
3a1d86be20
2
main.go
2
main.go
@ -114,6 +114,8 @@ func main() {
|
|||||||
IgnoreHostnameAnnotation: cfg.IgnoreHostnameAnnotation,
|
IgnoreHostnameAnnotation: cfg.IgnoreHostnameAnnotation,
|
||||||
IgnoreIngressTLSSpec: cfg.IgnoreIngressTLSSpec,
|
IgnoreIngressTLSSpec: cfg.IgnoreIngressTLSSpec,
|
||||||
IgnoreIngressRulesSpec: cfg.IgnoreIngressRulesSpec,
|
IgnoreIngressRulesSpec: cfg.IgnoreIngressRulesSpec,
|
||||||
|
GatewayNamespace: cfg.GatewayNamespace,
|
||||||
|
GatewayLabelFilter: cfg.GatewayLabelFilter,
|
||||||
Compatibility: cfg.Compatibility,
|
Compatibility: cfg.Compatibility,
|
||||||
PublishInternal: cfg.PublishInternal,
|
PublishInternal: cfg.PublishInternal,
|
||||||
PublishHostIP: cfg.PublishHostIP,
|
PublishHostIP: cfg.PublishHostIP,
|
||||||
|
@ -60,6 +60,8 @@ type Config struct {
|
|||||||
IgnoreHostnameAnnotation bool
|
IgnoreHostnameAnnotation bool
|
||||||
IgnoreIngressTLSSpec bool
|
IgnoreIngressTLSSpec bool
|
||||||
IgnoreIngressRulesSpec bool
|
IgnoreIngressRulesSpec bool
|
||||||
|
GatewayNamespace string
|
||||||
|
GatewayLabelFilter string
|
||||||
Compatibility string
|
Compatibility string
|
||||||
PublishInternal bool
|
PublishInternal bool
|
||||||
PublishHostIP bool
|
PublishHostIP bool
|
||||||
@ -204,6 +206,8 @@ var defaultConfig = &Config{
|
|||||||
IgnoreHostnameAnnotation: false,
|
IgnoreHostnameAnnotation: false,
|
||||||
IgnoreIngressTLSSpec: false,
|
IgnoreIngressTLSSpec: false,
|
||||||
IgnoreIngressRulesSpec: false,
|
IgnoreIngressRulesSpec: false,
|
||||||
|
GatewayNamespace: "",
|
||||||
|
GatewayLabelFilter: "",
|
||||||
Compatibility: "",
|
Compatibility: "",
|
||||||
PublishInternal: false,
|
PublishInternal: false,
|
||||||
PublishHostIP: false,
|
PublishHostIP: false,
|
||||||
@ -375,7 +379,7 @@ func (cfg *Config) ParseFlags(args []string) error {
|
|||||||
app.Flag("skipper-routegroup-groupversion", "The resource version for skipper routegroup").Default(source.DefaultRoutegroupVersion).StringVar(&cfg.SkipperRouteGroupVersion)
|
app.Flag("skipper-routegroup-groupversion", "The resource version for skipper routegroup").Default(source.DefaultRoutegroupVersion).StringVar(&cfg.SkipperRouteGroupVersion)
|
||||||
|
|
||||||
// Flags related to processing source
|
// Flags related to processing source
|
||||||
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, kong-tcpingress)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "pod", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "gloo-proxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host", "kong-tcpingress")
|
app.Flag("source", "The resource types that are queried for endpoints; specify multiple times for multiple sources (required, options: service, ingress, node, fake, connector, gateway-httproute, istio-gateway, istio-virtualservice, cloudfoundry, contour-ingressroute, contour-httpproxy, gloo-proxy, crd, empty, skipper-routegroup, openshift-route, ambassador-host, kong-tcpingress)").Required().PlaceHolder("source").EnumsVar(&cfg.Sources, "service", "ingress", "node", "pod", "gateway-httproute", "istio-gateway", "istio-virtualservice", "cloudfoundry", "contour-ingressroute", "contour-httpproxy", "gloo-proxy", "fake", "connector", "crd", "empty", "skipper-routegroup", "openshift-route", "ambassador-host", "kong-tcpingress")
|
||||||
app.Flag("openshift-router-name", "if source is openshift-route then you can pass the ingress controller name. Based on this name external-dns will select the respective router from the route status and map that routerCanonicalHostname to the route host while creating a CNAME record.").StringVar(&cfg.OCPRouterName)
|
app.Flag("openshift-router-name", "if source is openshift-route then you can pass the ingress controller name. Based on this name external-dns will select the respective router from the route status and map that routerCanonicalHostname to the route host while creating a CNAME record.").StringVar(&cfg.OCPRouterName)
|
||||||
app.Flag("namespace", "Limit sources of endpoints to a specific namespace (default: all namespaces)").Default(defaultConfig.Namespace).StringVar(&cfg.Namespace)
|
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("annotation-filter", "Filter sources managed by external-dns via annotation using label selector semantics (default: all sources)").Default(defaultConfig.AnnotationFilter).StringVar(&cfg.AnnotationFilter)
|
||||||
@ -384,6 +388,8 @@ func (cfg *Config) ParseFlags(args []string) error {
|
|||||||
app.Flag("combine-fqdn-annotation", "Combine FQDN template and Annotations instead of overwriting").BoolVar(&cfg.CombineFQDNAndAnnotation)
|
app.Flag("combine-fqdn-annotation", "Combine FQDN template and Annotations instead of overwriting").BoolVar(&cfg.CombineFQDNAndAnnotation)
|
||||||
app.Flag("ignore-hostname-annotation", "Ignore hostname annotation when generating DNS names, valid only when using fqdn-template is set (optional, default: false)").BoolVar(&cfg.IgnoreHostnameAnnotation)
|
app.Flag("ignore-hostname-annotation", "Ignore hostname annotation when generating DNS names, valid only when using fqdn-template is set (optional, default: false)").BoolVar(&cfg.IgnoreHostnameAnnotation)
|
||||||
app.Flag("ignore-ingress-tls-spec", "Ignore tls spec section in ingresses resources, applicable only for ingress sources (optional, default: false)").BoolVar(&cfg.IgnoreIngressTLSSpec)
|
app.Flag("ignore-ingress-tls-spec", "Ignore tls spec section in ingresses resources, applicable only for ingress sources (optional, default: false)").BoolVar(&cfg.IgnoreIngressTLSSpec)
|
||||||
|
app.Flag("gateway-namespace", "Limit Gateways of Route endpoints to a specific namespace (default: all namespaces)").StringVar(&cfg.GatewayNamespace)
|
||||||
|
app.Flag("gateway-label-filter", "Filter Gateways of Route endpoints via label selector (default: all gateways)").StringVar(&cfg.GatewayLabelFilter)
|
||||||
app.Flag("compatibility", "Process annotation semantics from legacy implementations (optional, options: mate, molecule, kops-dns-controller)").Default(defaultConfig.Compatibility).EnumVar(&cfg.Compatibility, "", "mate", "molecule", "kops-dns-controller")
|
app.Flag("compatibility", "Process annotation semantics from legacy implementations (optional, options: mate, molecule, kops-dns-controller)").Default(defaultConfig.Compatibility).EnumVar(&cfg.Compatibility, "", "mate", "molecule", "kops-dns-controller")
|
||||||
app.Flag("ignore-ingress-rules-spec", "Ignore rules spec section in ingresses resources, applicable only for ingress sources (optional, default: false)").BoolVar(&cfg.IgnoreIngressRulesSpec)
|
app.Flag("ignore-ingress-rules-spec", "Ignore rules spec section in ingresses resources, applicable only for ingress sources (optional, default: false)").BoolVar(&cfg.IgnoreIngressRulesSpec)
|
||||||
app.Flag("publish-internal-services", "Allow external-dns to publish DNS records for ClusterIP services (optional)").BoolVar(&cfg.PublishInternal)
|
app.Flag("publish-internal-services", "Allow external-dns to publish DNS records for ClusterIP services (optional)").BoolVar(&cfg.PublishInternal)
|
||||||
|
327
source/gateway.go
Normal file
327
source/gateway.go
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 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"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"k8s.io/apimachinery/pkg/types"
|
||||||
|
"k8s.io/apimachinery/pkg/util/wait"
|
||||||
|
cache "k8s.io/client-go/tools/cache"
|
||||||
|
"sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||||
|
gateway "sigs.k8s.io/gateway-api/pkg/client/clientset/gateway/versioned"
|
||||||
|
informers "sigs.k8s.io/gateway-api/pkg/client/informers/gateway/externalversions"
|
||||||
|
informers_v1a2 "sigs.k8s.io/gateway-api/pkg/client/informers/gateway/externalversions/apis/v1alpha2"
|
||||||
|
|
||||||
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
|
)
|
||||||
|
|
||||||
|
type gatewayRoute interface {
|
||||||
|
// Object returns the underlying Route object to be used by templates.
|
||||||
|
Object() kubeObject
|
||||||
|
// Metadata returns the Route's metadata.
|
||||||
|
Metadata() *metav1.ObjectMeta
|
||||||
|
// Hostnames returns the Route's specified hostnames.
|
||||||
|
Hostnames() []v1alpha2.Hostname
|
||||||
|
// Status returns the Route's status, including associated gateways.
|
||||||
|
Status() v1alpha2.RouteStatus
|
||||||
|
}
|
||||||
|
|
||||||
|
type newGatewayRouteInformerFunc func(informers.SharedInformerFactory) gatewayRouteInfomer
|
||||||
|
|
||||||
|
type gatewayRouteInfomer interface {
|
||||||
|
List(namespace string, selector labels.Selector) ([]gatewayRoute, error)
|
||||||
|
Informer() cache.SharedIndexInformer
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGatewayInformerFactory(client gateway.Interface, namespace string, labelSelector labels.Selector) informers.SharedInformerFactory {
|
||||||
|
var opts []informers.SharedInformerOption
|
||||||
|
if namespace != "" {
|
||||||
|
opts = append(opts, informers.WithNamespace(namespace))
|
||||||
|
}
|
||||||
|
if labelSelector != nil && !labelSelector.Empty() {
|
||||||
|
lbls := labelSelector.String()
|
||||||
|
opts = append(opts, informers.WithTweakListOptions(func(o *metav1.ListOptions) {
|
||||||
|
o.LabelSelector = lbls
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
return informers.NewSharedInformerFactoryWithOptions(client, 0, opts...)
|
||||||
|
}
|
||||||
|
|
||||||
|
type gatewayRouteSource struct {
|
||||||
|
gwNamespace string
|
||||||
|
gwLabels labels.Selector
|
||||||
|
gwInformer informers_v1a2.GatewayInformer
|
||||||
|
|
||||||
|
rtKind string
|
||||||
|
rtNamespace string
|
||||||
|
rtLabels labels.Selector
|
||||||
|
rtAnnotations labels.Selector
|
||||||
|
rtInformer gatewayRouteInfomer
|
||||||
|
|
||||||
|
fqdnTemplate *template.Template
|
||||||
|
combineFQDNAnnotation bool
|
||||||
|
ignoreHostnameAnnotation bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGatewayRouteSource(clients ClientGenerator, config *Config, kind string, newInformerFn newGatewayRouteInformerFunc) (Source, error) {
|
||||||
|
gwLabels, err := getLabelSelector(config.GatewayLabelFilter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rtLabels := config.LabelFilter
|
||||||
|
if rtLabels == nil {
|
||||||
|
rtLabels = labels.Everything()
|
||||||
|
}
|
||||||
|
rtAnnotations, err := getLabelSelector(config.AnnotationFilter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
tmpl, err := parseTemplate(config.FQDNTemplate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := clients.GatewayClient()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
informerFactory := newGatewayInformerFactory(client, config.GatewayNamespace, gwLabels)
|
||||||
|
gwInformer := informerFactory.Gateway().V1alpha2().Gateways() // TODO: gateway informer should be shared across gateway sources
|
||||||
|
gwInformer.Informer() // Register with factory before starting
|
||||||
|
|
||||||
|
rtInformerFactory := informerFactory
|
||||||
|
if config.Namespace != config.GatewayNamespace || !selectorsEqual(rtLabels, gwLabels) {
|
||||||
|
rtInformerFactory = newGatewayInformerFactory(client, config.Namespace, rtLabels)
|
||||||
|
}
|
||||||
|
rtInformer := newInformerFn(rtInformerFactory)
|
||||||
|
rtInformer.Informer() // Register with factory before starting
|
||||||
|
|
||||||
|
informerFactory.Start(wait.NeverStop)
|
||||||
|
if rtInformerFactory != informerFactory {
|
||||||
|
rtInformerFactory.Start(wait.NeverStop)
|
||||||
|
|
||||||
|
if err := waitForCacheSync(context.Background(), rtInformerFactory); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := waitForCacheSync(context.Background(), informerFactory); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
src := &gatewayRouteSource{
|
||||||
|
gwNamespace: config.GatewayNamespace,
|
||||||
|
gwLabels: gwLabels,
|
||||||
|
gwInformer: gwInformer,
|
||||||
|
|
||||||
|
rtKind: kind,
|
||||||
|
rtNamespace: config.Namespace,
|
||||||
|
rtLabels: rtLabels,
|
||||||
|
rtAnnotations: rtAnnotations,
|
||||||
|
rtInformer: rtInformer,
|
||||||
|
|
||||||
|
fqdnTemplate: tmpl,
|
||||||
|
combineFQDNAnnotation: config.CombineFQDNAndAnnotation,
|
||||||
|
ignoreHostnameAnnotation: config.IgnoreHostnameAnnotation,
|
||||||
|
}
|
||||||
|
return src, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (src *gatewayRouteSource) AddEventHandler(ctx context.Context, handler func()) {
|
||||||
|
log.Debugf("Adding event handler for %s", src.rtKind)
|
||||||
|
src.gwInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
|
||||||
|
src.rtInformer.Informer().AddEventHandler(eventHandlerFunc(handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (src *gatewayRouteSource) Endpoints(ctx context.Context) ([]*endpoint.Endpoint, error) {
|
||||||
|
var endpoints []*endpoint.Endpoint
|
||||||
|
routes, err := src.rtInformer.List(src.rtNamespace, src.rtLabels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gwList, err := src.gwInformer.Lister().Gateways(src.gwNamespace).List(src.gwLabels)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
gateways := gatewaysByRef(gwList)
|
||||||
|
for _, rt := range routes {
|
||||||
|
eps, err := src.endpoints(rt, gateways)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
endpoints = append(endpoints, eps...)
|
||||||
|
}
|
||||||
|
for _, ep := range endpoints {
|
||||||
|
sort.Sort(ep.Targets)
|
||||||
|
}
|
||||||
|
return endpoints, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (src *gatewayRouteSource) endpoints(rt gatewayRoute, gateways map[types.NamespacedName]*v1alpha2.Gateway) ([]*endpoint.Endpoint, error) {
|
||||||
|
// Filter by annotations.
|
||||||
|
meta := rt.Metadata()
|
||||||
|
annotations := meta.Annotations
|
||||||
|
if !src.rtAnnotations.Matches(labels.Set(meta.Annotations)) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check controller annotation to see if we are responsible.
|
||||||
|
if v, ok := meta.Annotations[controllerAnnotationKey]; ok && v != controllerAnnotationValue {
|
||||||
|
log.Debugf("Skipping %s %s/%s because controller value does not match, found: %s, required: %s",
|
||||||
|
src.rtKind, meta.Namespace, meta.Name, v, controllerAnnotationValue)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get hostnames.
|
||||||
|
hostnames, err := src.hostnames(rt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(hostnames) == 0 {
|
||||||
|
log.Debugf("No hostnames could be generated from %s %s/%s", src.rtKind, meta.Namespace, meta.Name)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get targets.
|
||||||
|
targets := src.targets(rt, gateways)
|
||||||
|
if len(targets) == 0 {
|
||||||
|
log.Debugf("No targets could be generated from %s %s/%s", src.rtKind, meta.Namespace, meta.Name)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create endpoints.
|
||||||
|
ttl, err := getTTLFromAnnotations(annotations)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
}
|
||||||
|
providerSpecific, setIdentifier := getProviderSpecificAnnotations(annotations)
|
||||||
|
var endpoints []*endpoint.Endpoint
|
||||||
|
for _, hostname := range hostnames {
|
||||||
|
endpoints = append(endpoints, endpointsForHostname(hostname, targets, ttl, providerSpecific, setIdentifier)...)
|
||||||
|
}
|
||||||
|
log.Debugf("Endpoints generated from %s %s/%s: %v", src.rtKind, meta.Namespace, meta.Name, endpoints)
|
||||||
|
|
||||||
|
kind := strings.ToLower(src.rtKind)
|
||||||
|
resourceKey := fmt.Sprintf("%s/%s/%s", kind, meta.Namespace, meta.Name)
|
||||||
|
for _, ep := range endpoints {
|
||||||
|
ep.Labels[endpoint.ResourceLabelKey] = resourceKey
|
||||||
|
}
|
||||||
|
return endpoints, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (src *gatewayRouteSource) hostnames(rt gatewayRoute) ([]string, error) {
|
||||||
|
var hostnames []string
|
||||||
|
for _, name := range rt.Hostnames() {
|
||||||
|
hostnames = append(hostnames, string(name))
|
||||||
|
}
|
||||||
|
meta := rt.Metadata()
|
||||||
|
// TODO: The ignore-hostname-annotation flag help says "valid only when using fqdn-template"
|
||||||
|
// but other sources don't check if fqdn-template is set. Which should it be?
|
||||||
|
if !src.ignoreHostnameAnnotation {
|
||||||
|
hostnames = append(hostnames, getHostnamesFromAnnotations(meta.Annotations)...)
|
||||||
|
}
|
||||||
|
// TODO: The combine-fqdn-annotation flag is similarly vague.
|
||||||
|
if src.fqdnTemplate != nil && (len(hostnames) == 0 || src.combineFQDNAnnotation) {
|
||||||
|
hosts, err := execTemplate(src.fqdnTemplate, rt.Object())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
hostnames = append(hostnames, hosts...)
|
||||||
|
}
|
||||||
|
return hostnames, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (src *gatewayRouteSource) targets(rt gatewayRoute, gateways map[types.NamespacedName]*v1alpha2.Gateway) endpoint.Targets {
|
||||||
|
var targets endpoint.Targets
|
||||||
|
meta := rt.Metadata()
|
||||||
|
for _, rps := range rt.Status().Parents {
|
||||||
|
ref := rps.ParentRef
|
||||||
|
if (ref.Group != nil && *ref.Group != "gateway.networking.k8s.io") || (ref.Kind != nil && *ref.Kind != "Gateway") {
|
||||||
|
log.Debugf("Unsupported parent %v/%v for %s %s/%s", ref.Group, ref.Kind, src.rtKind, meta.Namespace, meta.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
namespace := meta.Namespace
|
||||||
|
if ref.Namespace != nil {
|
||||||
|
namespace = string(*ref.Namespace)
|
||||||
|
}
|
||||||
|
gw, ok := gateways[types.NamespacedName{
|
||||||
|
Namespace: namespace,
|
||||||
|
Name: string(ref.Name),
|
||||||
|
}]
|
||||||
|
if !ok {
|
||||||
|
log.Debugf("Gateway %s/%s not found for %s %s/%s", namespace, ref.Name, src.rtKind, meta.Namespace, meta.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !gwRouteIsAdmitted(rps.Conditions) {
|
||||||
|
log.Debugf("Gateway %s/%s has not admitted %s %s/%s", namespace, ref.Name, src.rtKind, meta.Namespace, meta.Name)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for _, addr := range gw.Status.Addresses {
|
||||||
|
// TODO: Should we validate address type?
|
||||||
|
// The spec says it should always be an IP.
|
||||||
|
targets = append(targets, addr.Value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return targets
|
||||||
|
}
|
||||||
|
|
||||||
|
func gwRouteIsAdmitted(conds []metav1.Condition) bool {
|
||||||
|
for _, c := range conds {
|
||||||
|
if v1alpha2.RouteConditionType(c.Type) == v1alpha2.ConditionRouteAccepted {
|
||||||
|
return c.Status == metav1.ConditionTrue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func gatewaysByRef(list []*v1alpha2.Gateway) map[types.NamespacedName]*v1alpha2.Gateway {
|
||||||
|
if len(list) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
set := make(map[types.NamespacedName]*v1alpha2.Gateway, len(list))
|
||||||
|
for _, gw := range list {
|
||||||
|
set[types.NamespacedName{Namespace: gw.Namespace, Name: gw.Name}] = gw
|
||||||
|
}
|
||||||
|
return set
|
||||||
|
}
|
||||||
|
|
||||||
|
func selectorsEqual(a, b labels.Selector) bool {
|
||||||
|
if a == nil || b == nil {
|
||||||
|
return a == b
|
||||||
|
}
|
||||||
|
aReq, aOK := a.DeepCopySelector().Requirements()
|
||||||
|
bReq, bOK := b.DeepCopySelector().Requirements()
|
||||||
|
if aOK != bOK || len(aReq) != len(bReq) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
sort.Stable(labels.ByKey(aReq))
|
||||||
|
sort.Stable(labels.ByKey(bReq))
|
||||||
|
for i, r := range aReq {
|
||||||
|
if !r.Equal(bReq[i]) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
55
source/gateway_httproute.go
Normal file
55
source/gateway_httproute.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 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 (
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
"sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||||
|
informers "sigs.k8s.io/gateway-api/pkg/client/informers/gateway/externalversions"
|
||||||
|
informers_v1a2 "sigs.k8s.io/gateway-api/pkg/client/informers/gateway/externalversions/apis/v1alpha2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewGatewayHTTPRouteSource creates a new Gateway HTTPRoute source with the given config.
|
||||||
|
func NewGatewayHTTPRouteSource(clients ClientGenerator, config *Config) (Source, error) {
|
||||||
|
return newGatewayRouteSource(clients, config, "HTTPRoute", func(factory informers.SharedInformerFactory) gatewayRouteInfomer {
|
||||||
|
return &gatewayHTTPRouteInformer{factory.Gateway().V1alpha2().HTTPRoutes()}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type gatewayHTTPRoute struct{ route *v1alpha2.HTTPRoute }
|
||||||
|
|
||||||
|
func (rt *gatewayHTTPRoute) Object() kubeObject { return rt.route }
|
||||||
|
func (rt *gatewayHTTPRoute) Metadata() *metav1.ObjectMeta { return &rt.route.ObjectMeta }
|
||||||
|
func (rt *gatewayHTTPRoute) Hostnames() []v1alpha2.Hostname { return rt.route.Spec.Hostnames }
|
||||||
|
func (rt *gatewayHTTPRoute) Status() v1alpha2.RouteStatus { return rt.route.Status.RouteStatus }
|
||||||
|
|
||||||
|
type gatewayHTTPRouteInformer struct {
|
||||||
|
informers_v1a2.HTTPRouteInformer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (inf gatewayHTTPRouteInformer) List(namespace string, selector labels.Selector) ([]gatewayRoute, error) {
|
||||||
|
list, err := inf.HTTPRouteInformer.Lister().HTTPRoutes(namespace).List(selector)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
routes := make([]gatewayRoute, len(list))
|
||||||
|
for i, rt := range list {
|
||||||
|
routes[i] = &gatewayHTTPRoute{rt}
|
||||||
|
}
|
||||||
|
return routes, nil
|
||||||
|
}
|
664
source/gateway_httproute_test.go
Normal file
664
source/gateway_httproute_test.go
Normal file
@ -0,0 +1,664 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2021 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"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
kubefake "k8s.io/client-go/kubernetes/fake"
|
||||||
|
"sigs.k8s.io/external-dns/endpoint"
|
||||||
|
"sigs.k8s.io/gateway-api/apis/v1alpha2"
|
||||||
|
gatewayfake "sigs.k8s.io/gateway-api/pkg/client/clientset/gateway/versioned/fake"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mustGetLabelSelector(s string) labels.Selector {
|
||||||
|
v, err := getLabelSelector(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func gatewayStatus(ips ...string) v1alpha2.GatewayStatus {
|
||||||
|
typ := v1alpha2.IPAddressType
|
||||||
|
addrs := make([]v1alpha2.GatewayAddress, len(ips))
|
||||||
|
for i, ip := range ips {
|
||||||
|
addrs[i] = v1alpha2.GatewayAddress{Type: &typ, Value: ip}
|
||||||
|
}
|
||||||
|
return v1alpha2.GatewayStatus{Addresses: addrs}
|
||||||
|
}
|
||||||
|
|
||||||
|
func routeStatus(refs ...v1alpha2.ParentRef) v1alpha2.RouteStatus {
|
||||||
|
var v v1alpha2.RouteStatus
|
||||||
|
for _, ref := range refs {
|
||||||
|
v.Parents = append(v.Parents, v1alpha2.RouteParentStatus{
|
||||||
|
ParentRef: ref,
|
||||||
|
Conditions: []metav1.Condition{
|
||||||
|
{
|
||||||
|
Type: string(v1alpha2.ConditionRouteAccepted),
|
||||||
|
Status: metav1.ConditionTrue,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func httpRouteStatus(refs ...v1alpha2.ParentRef) v1alpha2.HTTPRouteStatus {
|
||||||
|
return v1alpha2.HTTPRouteStatus{RouteStatus: routeStatus(refs...)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gatewayParentRef(namespace, name string) v1alpha2.ParentRef {
|
||||||
|
group := v1alpha2.Group("gateway.networking.k8s.io")
|
||||||
|
kind := v1alpha2.Kind("Gateway")
|
||||||
|
return v1alpha2.ParentRef{
|
||||||
|
Group: &group,
|
||||||
|
Kind: &kind,
|
||||||
|
Name: v1alpha2.ObjectName(name),
|
||||||
|
Namespace: (*v1alpha2.Namespace)(&namespace),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestEndpoint(dnsName, recordType string, targets ...string) *endpoint.Endpoint {
|
||||||
|
return newTestEndpointWithTTL(dnsName, recordType, 0, targets...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func newTestEndpointWithTTL(dnsName, recordType string, ttl int64, targets ...string) *endpoint.Endpoint {
|
||||||
|
return &endpoint.Endpoint{
|
||||||
|
DNSName: dnsName,
|
||||||
|
Targets: append([]string(nil), targets...), // clone targets
|
||||||
|
RecordType: recordType,
|
||||||
|
RecordTTL: endpoint.TTL(ttl),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func joinTargets(targets ...[]string) []string {
|
||||||
|
var s []string
|
||||||
|
for _, v := range targets {
|
||||||
|
s = append(s, v...)
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGatewayHTTPRouteSourceEndpoints(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
objectMeta := func(namespace, name string) metav1.ObjectMeta {
|
||||||
|
return metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
Namespace: namespace,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
namespaces := func(names ...string) []*corev1.Namespace {
|
||||||
|
v := make([]*corev1.Namespace, len(names))
|
||||||
|
for i, name := range names {
|
||||||
|
v[i] = &corev1.Namespace{ObjectMeta: objectMeta("", name)}
|
||||||
|
}
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
hostnames := func(names ...v1alpha2.Hostname) []v1alpha2.Hostname { return names }
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
title string
|
||||||
|
config Config
|
||||||
|
namespaces []*corev1.Namespace
|
||||||
|
gateways []*v1alpha2.Gateway
|
||||||
|
routes []*v1alpha2.HTTPRoute
|
||||||
|
endpoints []*endpoint.Endpoint
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
title: "GatewayNamespace",
|
||||||
|
config: Config{
|
||||||
|
GatewayNamespace: "gateway-namespace",
|
||||||
|
},
|
||||||
|
namespaces: namespaces("gateway-namespace", "not-gateway-namespace", "route-namespace"),
|
||||||
|
gateways: []*v1alpha2.Gateway{
|
||||||
|
{
|
||||||
|
ObjectMeta: objectMeta("gateway-namespace", "test"),
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("1.2.3.4"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: objectMeta("not-gateway-namespace", "test"),
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("2.3.4.5"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routes: []*v1alpha2.HTTPRoute{{
|
||||||
|
ObjectMeta: objectMeta("route-namespace", "test"),
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("test.example.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus( // The route is attached to both gateways.
|
||||||
|
gatewayParentRef("gateway-namespace", "test"),
|
||||||
|
gatewayParentRef("not-gateway-namespace", "test"),
|
||||||
|
),
|
||||||
|
}},
|
||||||
|
endpoints: []*endpoint.Endpoint{
|
||||||
|
newTestEndpoint("test.example.internal", "A", "1.2.3.4"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "RouteNamespace",
|
||||||
|
config: Config{
|
||||||
|
Namespace: "route-namespace",
|
||||||
|
},
|
||||||
|
namespaces: namespaces("gateway-namespace", "route-namespace", "not-route-namespace"),
|
||||||
|
gateways: []*v1alpha2.Gateway{{
|
||||||
|
ObjectMeta: objectMeta("gateway-namespace", "test"),
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("1.2.3.4"),
|
||||||
|
}},
|
||||||
|
routes: []*v1alpha2.HTTPRoute{
|
||||||
|
{
|
||||||
|
ObjectMeta: objectMeta("route-namespace", "test"),
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("route-namespace.example.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("gateway-namespace", "test")),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: objectMeta("not-route-namespace", "test"),
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("not-route-namespace.example.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("gateway-namespace", "test")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endpoints: []*endpoint.Endpoint{
|
||||||
|
newTestEndpoint("route-namespace.example.internal", "A", "1.2.3.4"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "GatewayLabelFilter",
|
||||||
|
config: Config{
|
||||||
|
GatewayLabelFilter: "foo=bar",
|
||||||
|
},
|
||||||
|
namespaces: namespaces("default"),
|
||||||
|
gateways: []*v1alpha2.Gateway{
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "labels-match",
|
||||||
|
Namespace: "default",
|
||||||
|
Labels: map[string]string{"foo": "bar"},
|
||||||
|
},
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("1.2.3.4"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "labels-dont-match",
|
||||||
|
Namespace: "default",
|
||||||
|
Labels: map[string]string{"foo": "qux"},
|
||||||
|
},
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("2.3.4.5"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routes: []*v1alpha2.HTTPRoute{{
|
||||||
|
ObjectMeta: objectMeta("default", "test"),
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("test.example.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus( // The route is attached to both gateways.
|
||||||
|
gatewayParentRef("default", "labels-match"),
|
||||||
|
gatewayParentRef("default", "labels-dont-match"),
|
||||||
|
),
|
||||||
|
}},
|
||||||
|
endpoints: []*endpoint.Endpoint{
|
||||||
|
newTestEndpoint("test.example.internal", "A", "1.2.3.4"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "RouteLabelFilter",
|
||||||
|
config: Config{
|
||||||
|
LabelFilter: mustGetLabelSelector("foo=bar"),
|
||||||
|
},
|
||||||
|
namespaces: namespaces("default"),
|
||||||
|
gateways: []*v1alpha2.Gateway{{
|
||||||
|
ObjectMeta: objectMeta("default", "test"),
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("1.2.3.4"),
|
||||||
|
}},
|
||||||
|
routes: []*v1alpha2.HTTPRoute{
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "labels-match",
|
||||||
|
Namespace: "default",
|
||||||
|
Labels: map[string]string{"foo": "bar"},
|
||||||
|
},
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("labels-match.example.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "labels-dont-match",
|
||||||
|
Namespace: "default",
|
||||||
|
Labels: map[string]string{"foo": "qux"},
|
||||||
|
},
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("labels-dont-match.example.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endpoints: []*endpoint.Endpoint{
|
||||||
|
newTestEndpoint("labels-match.example.internal", "A", "1.2.3.4"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "RouteAnnotationFilter",
|
||||||
|
config: Config{
|
||||||
|
AnnotationFilter: "foo=bar",
|
||||||
|
},
|
||||||
|
namespaces: namespaces("default"),
|
||||||
|
gateways: []*v1alpha2.Gateway{{
|
||||||
|
ObjectMeta: objectMeta("default", "test"),
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("1.2.3.4"),
|
||||||
|
}},
|
||||||
|
routes: []*v1alpha2.HTTPRoute{
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "annotations-match",
|
||||||
|
Namespace: "default",
|
||||||
|
Annotations: map[string]string{"foo": "bar"},
|
||||||
|
},
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("annotations-match.example.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "annotations-dont-match",
|
||||||
|
Namespace: "default",
|
||||||
|
Annotations: map[string]string{"foo": "qux"},
|
||||||
|
},
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("annotations-dont-match.example.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endpoints: []*endpoint.Endpoint{
|
||||||
|
newTestEndpoint("annotations-match.example.internal", "A", "1.2.3.4"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "SkipControllerAnnotation",
|
||||||
|
config: Config{},
|
||||||
|
namespaces: namespaces("default"),
|
||||||
|
gateways: []*v1alpha2.Gateway{{
|
||||||
|
ObjectMeta: objectMeta("default", "test"),
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("1.2.3.4"),
|
||||||
|
}},
|
||||||
|
routes: []*v1alpha2.HTTPRoute{{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "api",
|
||||||
|
Namespace: "default",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
controllerAnnotationKey: "something-else",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("api.example.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||||
|
}},
|
||||||
|
endpoints: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "MultipleGateways",
|
||||||
|
config: Config{},
|
||||||
|
namespaces: namespaces("default"),
|
||||||
|
gateways: []*v1alpha2.Gateway{
|
||||||
|
{
|
||||||
|
ObjectMeta: objectMeta("default", "one"),
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("1.2.3.4"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: objectMeta("default", "two"),
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("2.3.4.5"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
routes: []*v1alpha2.HTTPRoute{{
|
||||||
|
ObjectMeta: objectMeta("default", "test"),
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("test.example.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(
|
||||||
|
gatewayParentRef("default", "one"),
|
||||||
|
gatewayParentRef("default", "two"),
|
||||||
|
),
|
||||||
|
}},
|
||||||
|
endpoints: []*endpoint.Endpoint{
|
||||||
|
newTestEndpoint("test.example.internal", "A", "1.2.3.4", "2.3.4.5"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "NoGateways",
|
||||||
|
config: Config{},
|
||||||
|
namespaces: namespaces("default"),
|
||||||
|
gateways: nil,
|
||||||
|
routes: []*v1alpha2.HTTPRoute{{
|
||||||
|
ObjectMeta: objectMeta("default", "test"),
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("example.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(),
|
||||||
|
}},
|
||||||
|
endpoints: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "NoHostnames",
|
||||||
|
config: Config{},
|
||||||
|
namespaces: namespaces("default"),
|
||||||
|
gateways: []*v1alpha2.Gateway{{
|
||||||
|
ObjectMeta: objectMeta("default", "test"),
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("1.2.3.4"),
|
||||||
|
}},
|
||||||
|
routes: []*v1alpha2.HTTPRoute{{
|
||||||
|
ObjectMeta: objectMeta("default", "no-hostame"),
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: nil,
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||||
|
}},
|
||||||
|
endpoints: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "HostnameAnnotation",
|
||||||
|
config: Config{},
|
||||||
|
namespaces: namespaces("default"),
|
||||||
|
gateways: []*v1alpha2.Gateway{{
|
||||||
|
ObjectMeta: objectMeta("default", "test"),
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("1.2.3.4"),
|
||||||
|
}},
|
||||||
|
routes: []*v1alpha2.HTTPRoute{
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "without-hostame",
|
||||||
|
Namespace: "default",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
hostnameAnnotationKey: "annotation.without-hostname.internal",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: nil,
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "with-hostame",
|
||||||
|
Namespace: "default",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
hostnameAnnotationKey: "annotation.with-hostname.internal",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("with-hostname.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endpoints: []*endpoint.Endpoint{
|
||||||
|
newTestEndpoint("annotation.without-hostname.internal", "A", "1.2.3.4"),
|
||||||
|
newTestEndpoint("annotation.with-hostname.internal", "A", "1.2.3.4"),
|
||||||
|
newTestEndpoint("with-hostname.internal", "A", "1.2.3.4"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "IgnoreHostnameAnnotation",
|
||||||
|
config: Config{
|
||||||
|
IgnoreHostnameAnnotation: true,
|
||||||
|
},
|
||||||
|
namespaces: namespaces("default"),
|
||||||
|
gateways: []*v1alpha2.Gateway{{
|
||||||
|
ObjectMeta: objectMeta("default", "test"),
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("1.2.3.4"),
|
||||||
|
}},
|
||||||
|
routes: []*v1alpha2.HTTPRoute{{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "with-hostame",
|
||||||
|
Namespace: "default",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
hostnameAnnotationKey: "annotation.with-hostname.internal",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("with-hostname.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||||
|
}},
|
||||||
|
endpoints: []*endpoint.Endpoint{
|
||||||
|
newTestEndpoint("with-hostname.internal", "A", "1.2.3.4"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "FQDNTemplate",
|
||||||
|
config: Config{
|
||||||
|
FQDNTemplate: "{{.Name}}.zero.internal, {{.Name}}.one.internal. , {{.Name}}.two.internal ",
|
||||||
|
},
|
||||||
|
namespaces: namespaces("default"),
|
||||||
|
gateways: []*v1alpha2.Gateway{{
|
||||||
|
ObjectMeta: objectMeta("default", "test"),
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("1.2.3.4"),
|
||||||
|
}},
|
||||||
|
routes: []*v1alpha2.HTTPRoute{
|
||||||
|
{
|
||||||
|
ObjectMeta: objectMeta("default", "fqdn-with-hostnames"),
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("fqdn-with-hostnames.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: objectMeta("default", "fqdn-without-hostnames"),
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: nil,
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endpoints: []*endpoint.Endpoint{
|
||||||
|
newTestEndpoint("fqdn-without-hostnames.zero.internal", "A", "1.2.3.4"),
|
||||||
|
newTestEndpoint("fqdn-without-hostnames.one.internal", "A", "1.2.3.4"),
|
||||||
|
newTestEndpoint("fqdn-without-hostnames.two.internal", "A", "1.2.3.4"),
|
||||||
|
newTestEndpoint("fqdn-with-hostnames.internal", "A", "1.2.3.4"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "CombineFQDN",
|
||||||
|
config: Config{
|
||||||
|
FQDNTemplate: "combine-{{.Name}}.internal",
|
||||||
|
CombineFQDNAndAnnotation: true,
|
||||||
|
},
|
||||||
|
namespaces: namespaces("default"),
|
||||||
|
gateways: []*v1alpha2.Gateway{{
|
||||||
|
ObjectMeta: objectMeta("default", "test"),
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("1.2.3.4"),
|
||||||
|
}},
|
||||||
|
routes: []*v1alpha2.HTTPRoute{{
|
||||||
|
ObjectMeta: objectMeta("default", "fqdn-with-hostnames"),
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("fqdn-with-hostnames.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||||
|
}},
|
||||||
|
endpoints: []*endpoint.Endpoint{
|
||||||
|
newTestEndpoint("fqdn-with-hostnames.internal", "A", "1.2.3.4"),
|
||||||
|
newTestEndpoint("combine-fqdn-with-hostnames.internal", "A", "1.2.3.4"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "TTL",
|
||||||
|
config: Config{},
|
||||||
|
namespaces: namespaces("default"),
|
||||||
|
gateways: []*v1alpha2.Gateway{{
|
||||||
|
ObjectMeta: objectMeta("default", "test"),
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("1.2.3.4"),
|
||||||
|
}},
|
||||||
|
routes: []*v1alpha2.HTTPRoute{
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "valid-ttl",
|
||||||
|
Namespace: "default",
|
||||||
|
Annotations: map[string]string{ttlAnnotationKey: "15s"},
|
||||||
|
},
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("valid-ttl.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "invalid-ttl",
|
||||||
|
Namespace: "default",
|
||||||
|
Annotations: map[string]string{ttlAnnotationKey: "abc"},
|
||||||
|
},
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("invalid-ttl.internal"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
endpoints: []*endpoint.Endpoint{
|
||||||
|
newTestEndpoint("invalid-ttl.internal", "A", "1.2.3.4"),
|
||||||
|
newTestEndpointWithTTL("valid-ttl.internal", "A", 15, "1.2.3.4"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "ProviderAnnotations",
|
||||||
|
config: Config{},
|
||||||
|
namespaces: namespaces("default"),
|
||||||
|
gateways: []*v1alpha2.Gateway{{
|
||||||
|
ObjectMeta: objectMeta("default", "test"),
|
||||||
|
Spec: v1alpha2.GatewaySpec{
|
||||||
|
Listeners: []v1alpha2.Listener{{Protocol: v1alpha2.HTTPProtocolType}},
|
||||||
|
},
|
||||||
|
Status: gatewayStatus("1.2.3.4"),
|
||||||
|
}},
|
||||||
|
routes: []*v1alpha2.HTTPRoute{{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "provider-annotations",
|
||||||
|
Namespace: "default",
|
||||||
|
Annotations: map[string]string{
|
||||||
|
SetIdentifierKey: "test-set-identifier",
|
||||||
|
aliasAnnotationKey: "true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Spec: v1alpha2.HTTPRouteSpec{
|
||||||
|
Hostnames: hostnames("provider-annotations.com"),
|
||||||
|
},
|
||||||
|
Status: httpRouteStatus(gatewayParentRef("default", "test")),
|
||||||
|
}},
|
||||||
|
endpoints: []*endpoint.Endpoint{
|
||||||
|
newTestEndpoint("provider-annotations.com", "A", "1.2.3.4").
|
||||||
|
WithProviderSpecific("alias", "true").
|
||||||
|
WithSetIdentifier("test-set-identifier"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
tt := tt
|
||||||
|
t.Run(tt.title, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
gwClient := gatewayfake.NewSimpleClientset()
|
||||||
|
for _, gw := range tt.gateways {
|
||||||
|
_, err := gwClient.GatewayV1alpha2().Gateways(gw.Namespace).Create(ctx, gw, metav1.CreateOptions{})
|
||||||
|
require.NoError(t, err, "failed to create Gateway")
|
||||||
|
|
||||||
|
}
|
||||||
|
for _, rt := range tt.routes {
|
||||||
|
_, err := gwClient.GatewayV1alpha2().HTTPRoutes(rt.Namespace).Create(ctx, rt, metav1.CreateOptions{})
|
||||||
|
require.NoError(t, err, "failed to create HTTPRoute")
|
||||||
|
}
|
||||||
|
kubeClient := kubefake.NewSimpleClientset()
|
||||||
|
for _, ns := range tt.namespaces {
|
||||||
|
_, err := kubeClient.CoreV1().Namespaces().Create(ctx, ns, metav1.CreateOptions{})
|
||||||
|
require.NoError(t, err, "failed to create Namespace")
|
||||||
|
}
|
||||||
|
|
||||||
|
clients := new(MockClientGenerator)
|
||||||
|
clients.On("GatewayClient").Return(gwClient, nil)
|
||||||
|
clients.On("KubeClient").Return(kubeClient, nil)
|
||||||
|
|
||||||
|
src, err := NewGatewayHTTPRouteSource(clients, &tt.config)
|
||||||
|
require.NoError(t, err, "failed to create Gateway HTTPRoute Source")
|
||||||
|
|
||||||
|
endpoints, err := src.Endpoints(ctx)
|
||||||
|
require.NoError(t, err, "failed to get Endpoints")
|
||||||
|
validateEndpoints(t, endpoints, tt.endpoints)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func strPtr(val string) *string { return &val }
|
||||||
|
|
||||||
|
func hostnamePtr(val v1alpha2.Hostname) *v1alpha2.Hostname { return &val }
|
@ -51,6 +51,8 @@ type Config struct {
|
|||||||
IgnoreHostnameAnnotation bool
|
IgnoreHostnameAnnotation bool
|
||||||
IgnoreIngressTLSSpec bool
|
IgnoreIngressTLSSpec bool
|
||||||
IgnoreIngressRulesSpec bool
|
IgnoreIngressRulesSpec bool
|
||||||
|
GatewayNamespace string
|
||||||
|
GatewayLabelFilter string
|
||||||
Compatibility string
|
Compatibility string
|
||||||
PublishInternal bool
|
PublishInternal bool
|
||||||
PublishHostIP bool
|
PublishHostIP bool
|
||||||
@ -225,6 +227,8 @@ func BuildWithConfig(ctx context.Context, source string, p ClientGenerator, cfg
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return NewPodSource(ctx, client, cfg.Namespace, cfg.Compatibility)
|
return NewPodSource(ctx, client, cfg.Namespace, cfg.Compatibility)
|
||||||
|
case "gateway-httproute":
|
||||||
|
return NewGatewayHTTPRouteSource(p, cfg)
|
||||||
case "istio-gateway":
|
case "istio-gateway":
|
||||||
kubernetesClient, err := p.KubeClient()
|
kubernetesClient, err := p.KubeClient()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user