2020-12-15 15:40:05 +00:00
package gateway
import (
"context"
"crypto/sha256"
"errors"
"fmt"
"net"
2022-12-22 14:02:05 +00:00
"net/http"
2020-12-15 15:40:05 +00:00
"os"
2022-11-28 14:48:05 +00:00
"regexp"
2020-12-15 15:40:05 +00:00
"sort"
"strconv"
"strings"
"time"
"github.com/cenkalti/backoff/v4"
"github.com/hashicorp/go-multierror"
"github.com/mitchellh/hashstructure"
2022-11-21 17:36:05 +00:00
"github.com/rs/zerolog/log"
2020-12-15 15:40:05 +00:00
ptypes "github.com/traefik/paerser/types"
2023-02-03 14:24:05 +00:00
"github.com/traefik/traefik/v3/pkg/config/dynamic"
"github.com/traefik/traefik/v3/pkg/job"
"github.com/traefik/traefik/v3/pkg/logs"
"github.com/traefik/traefik/v3/pkg/provider"
2023-03-21 11:00:46 +00:00
traefikv1alpha1 "github.com/traefik/traefik/v3/pkg/provider/kubernetes/crd/traefikio/v1alpha1"
2023-05-17 09:07:09 +00:00
"github.com/traefik/traefik/v3/pkg/provider/kubernetes/k8s"
2023-02-03 14:24:05 +00:00
"github.com/traefik/traefik/v3/pkg/safe"
"github.com/traefik/traefik/v3/pkg/tls"
2024-01-11 16:06:06 +00:00
"github.com/traefik/traefik/v3/pkg/types"
2020-12-15 15:40:05 +00:00
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
2024-01-09 09:28:05 +00:00
"k8s.io/utils/ptr"
2021-11-09 10:34:06 +00:00
"k8s.io/utils/strings/slices"
2024-01-09 09:28:05 +00:00
gatev1 "sigs.k8s.io/gateway-api/apis/v1"
2020-12-15 15:40:05 +00:00
)
2021-02-02 18:36:04 +00:00
const (
2021-11-09 10:34:06 +00:00
providerName = "kubernetesgateway"
kindGateway = "Gateway"
kindTraefikService = "TraefikService"
kindHTTPRoute = "HTTPRoute"
kindTCPRoute = "TCPRoute"
kindTLSRoute = "TLSRoute"
2021-02-02 18:36:04 +00:00
)
2020-12-15 15:40:05 +00:00
// Provider holds configurations of the provider.
type Provider struct {
Endpoint string ` description:"Kubernetes server endpoint (required for external cluster client)." json:"endpoint,omitempty" toml:"endpoint,omitempty" yaml:"endpoint,omitempty" `
2024-01-11 16:06:06 +00:00
Token types . FileOrContent ` description:"Kubernetes bearer token (not needed for in-cluster client). It accepts either a token value or a file path to the token." json:"token,omitempty" toml:"token,omitempty" yaml:"token,omitempty" loggable:"false" `
2020-12-15 15:40:05 +00:00
CertAuthFilePath string ` description:"Kubernetes certificate authority file path (not needed for in-cluster client)." json:"certAuthFilePath,omitempty" toml:"certAuthFilePath,omitempty" yaml:"certAuthFilePath,omitempty" `
Namespaces [ ] string ` description:"Kubernetes namespaces." json:"namespaces,omitempty" toml:"namespaces,omitempty" yaml:"namespaces,omitempty" export:"true" `
LabelSelector string ` description:"Kubernetes label selector to select specific GatewayClasses." json:"labelSelector,omitempty" toml:"labelSelector,omitempty" yaml:"labelSelector,omitempty" export:"true" `
ThrottleDuration ptypes . Duration ` description:"Kubernetes refresh throttle duration" json:"throttleDuration,omitempty" toml:"throttleDuration,omitempty" yaml:"throttleDuration,omitempty" export:"true" `
EntryPoints map [ string ] Entrypoint ` json:"-" toml:"-" yaml:"-" label:"-" file:"-" `
lastConfiguration safe . Safe
2023-05-15 14:38:05 +00:00
routerTransform k8s . RouterTransform
}
func ( p * Provider ) SetRouterTransform ( routerTransform k8s . RouterTransform ) {
p . routerTransform = routerTransform
}
2024-01-09 09:28:05 +00:00
func ( p * Provider ) applyRouterTransform ( ctx context . Context , rt * dynamic . Router , route * gatev1 . HTTPRoute ) {
2023-05-15 14:38:05 +00:00
if p . routerTransform == nil {
return
}
err := p . routerTransform . Apply ( ctx , rt , route . Annotations )
if err != nil {
2023-05-17 09:07:09 +00:00
log . Ctx ( ctx ) . Error ( ) . Err ( err ) . Msg ( "Apply router transform" )
2023-05-15 14:38:05 +00:00
}
2020-12-15 15:40:05 +00:00
}
// Entrypoint defines the available entry points.
type Entrypoint struct {
Address string
HasHTTPTLSConf bool
}
func ( p * Provider ) newK8sClient ( ctx context . Context ) ( * clientWrapper , error ) {
// Label selector validation
_ , err := labels . Parse ( p . LabelSelector )
if err != nil {
return nil , fmt . Errorf ( "invalid label selector: %q" , p . LabelSelector )
}
2021-07-15 15:20:08 +00:00
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
logger . Info ( ) . Msgf ( "Label selector is: %q" , p . LabelSelector )
2020-12-15 15:40:05 +00:00
var client * clientWrapper
switch {
case os . Getenv ( "KUBERNETES_SERVICE_HOST" ) != "" && os . Getenv ( "KUBERNETES_SERVICE_PORT" ) != "" :
2022-11-21 17:36:05 +00:00
logger . Info ( ) . Str ( "endpoint" , p . Endpoint ) . Msg ( "Creating in-cluster Provider client" )
2020-12-15 15:40:05 +00:00
client , err = newInClusterClient ( p . Endpoint )
case os . Getenv ( "KUBECONFIG" ) != "" :
2022-11-21 17:36:05 +00:00
logger . Info ( ) . Msgf ( "Creating cluster-external Provider client from KUBECONFIG %s" , os . Getenv ( "KUBECONFIG" ) )
2020-12-15 15:40:05 +00:00
client , err = newExternalClusterClientFromFile ( os . Getenv ( "KUBECONFIG" ) )
default :
2022-11-21 17:36:05 +00:00
logger . Info ( ) . Str ( "endpoint" , p . Endpoint ) . Msg ( "Creating cluster-external Provider client" )
2024-01-11 16:06:06 +00:00
client , err = newExternalClusterClient ( p . Endpoint , p . CertAuthFilePath , p . Token )
2020-12-15 15:40:05 +00:00
}
if err != nil {
return nil , err
}
2021-07-15 15:20:08 +00:00
2020-12-15 15:40:05 +00:00
client . labelSelector = p . LabelSelector
return client , nil
}
// Init the provider.
func ( p * Provider ) Init ( ) error {
return nil
}
2021-11-09 10:34:06 +00:00
// Provide allows the k8s provider to provide configurations to traefik using the given configuration channel.
2020-12-15 15:40:05 +00:00
func ( p * Provider ) Provide ( configurationChan chan <- dynamic . Message , pool * safe . Pool ) error {
2022-11-21 17:36:05 +00:00
logger := log . With ( ) . Str ( logs . ProviderName , providerName ) . Logger ( )
ctxLog := logger . WithContext ( context . Background ( ) )
2020-12-15 15:40:05 +00:00
k8sClient , err := p . newK8sClient ( ctxLog )
if err != nil {
return err
}
pool . GoCtx ( func ( ctxPool context . Context ) {
operation := func ( ) error {
eventsChan , err := k8sClient . WatchAll ( p . Namespaces , ctxPool . Done ( ) )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Msg ( "Error watching kubernetes events" )
2020-12-15 15:40:05 +00:00
timer := time . NewTimer ( 1 * time . Second )
select {
case <- timer . C :
return err
case <- ctxPool . Done ( ) :
return nil
}
}
throttleDuration := time . Duration ( p . ThrottleDuration )
throttledChan := throttleEvents ( ctxLog , throttleDuration , pool , eventsChan )
if throttledChan != nil {
eventsChan = throttledChan
}
for {
select {
case <- ctxPool . Done ( ) :
return nil
case event := <- eventsChan :
// Note that event is the *first* event that came in during this throttling interval -- if we're hitting our throttle, we may have dropped events.
// This is fine, because we don't treat different event types differently.
// But if we do in the future, we'll need to track more information about the dropped events.
conf := p . loadConfigurationFromGateway ( ctxLog , k8sClient )
confHash , err := hashstructure . Hash ( conf , nil )
switch {
case err != nil :
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Msg ( "Unable to hash the configuration" )
2020-12-15 15:40:05 +00:00
case p . lastConfiguration . Get ( ) == confHash :
2022-11-21 17:36:05 +00:00
logger . Debug ( ) . Msgf ( "Skipping Kubernetes event kind %T" , event )
2020-12-15 15:40:05 +00:00
default :
p . lastConfiguration . Set ( confHash )
configurationChan <- dynamic . Message {
ProviderName : providerName ,
Configuration : conf ,
}
}
// If we're throttling,
// we sleep here for the throttle duration to enforce that we don't refresh faster than our throttle.
// time.Sleep returns immediately if p.ThrottleDuration is 0 (no throttle).
time . Sleep ( throttleDuration )
}
}
}
notify := func ( err error , time time . Duration ) {
2022-11-30 08:50:05 +00:00
logger . Error ( ) . Err ( err ) . Msgf ( "Provider error, retrying in %s" , time )
2020-12-15 15:40:05 +00:00
}
err := backoff . RetryNotify ( safe . OperationWithRecover ( operation ) , backoff . WithContext ( job . NewBackOff ( backoff . NewExponentialBackOff ( ) ) , ctxPool ) , notify )
if err != nil {
2022-11-30 08:50:05 +00:00
logger . Error ( ) . Err ( err ) . Msg ( "Cannot retrieve data" )
2020-12-15 15:40:05 +00:00
}
} )
return nil
}
// TODO Handle errors and update resources statuses (gatewayClass, gateway).
func ( p * Provider ) loadConfigurationFromGateway ( ctx context . Context , client Client ) * dynamic . Configuration {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
2020-12-15 15:40:05 +00:00
gatewayClassNames := map [ string ] struct { } { }
gatewayClasses , err := client . GetGatewayClasses ( )
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Msg ( "Cannot find GatewayClasses" )
2020-12-15 15:40:05 +00:00
return & dynamic . Configuration {
2022-12-09 08:58:05 +00:00
HTTP : & dynamic . HTTPConfiguration {
Routers : map [ string ] * dynamic . Router { } ,
Middlewares : map [ string ] * dynamic . Middleware { } ,
Services : map [ string ] * dynamic . Service { } ,
ServersTransports : map [ string ] * dynamic . ServersTransport { } ,
2020-12-15 15:40:05 +00:00
} ,
TCP : & dynamic . TCPConfiguration {
2022-12-09 08:58:05 +00:00
Routers : map [ string ] * dynamic . TCPRouter { } ,
Middlewares : map [ string ] * dynamic . TCPMiddleware { } ,
Services : map [ string ] * dynamic . TCPService { } ,
ServersTransports : map [ string ] * dynamic . TCPServersTransport { } ,
2020-12-15 15:40:05 +00:00
} ,
2022-12-09 08:58:05 +00:00
UDP : & dynamic . UDPConfiguration {
Routers : map [ string ] * dynamic . UDPRouter { } ,
Services : map [ string ] * dynamic . UDPService { } ,
2020-12-15 15:40:05 +00:00
} ,
TLS : & dynamic . TLSConfiguration { } ,
}
}
for _ , gatewayClass := range gatewayClasses {
2021-11-09 10:34:06 +00:00
if gatewayClass . Spec . ControllerName == "traefik.io/gateway-controller" {
2020-12-15 15:40:05 +00:00
gatewayClassNames [ gatewayClass . Name ] = struct { } { }
err := client . UpdateGatewayClassStatus ( gatewayClass , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . GatewayClassConditionStatusAccepted ) ,
2020-12-15 15:40:05 +00:00
Status : metav1 . ConditionTrue ,
2024-01-22 14:30:05 +00:00
ObservedGeneration : gatewayClass . Generation ,
2020-12-15 15:40:05 +00:00
Reason : "Handled" ,
Message : "Handled by Traefik controller" ,
LastTransitionTime : metav1 . Now ( ) ,
} )
if err != nil {
2024-01-09 09:28:05 +00:00
logger . Error ( ) . Err ( err ) . Msgf ( "Failed to update %s condition" , gatev1 . GatewayClassConditionStatusAccepted )
2020-12-15 15:40:05 +00:00
}
}
}
cfgs := map [ string ] * dynamic . Configuration { }
// TODO check if we can only use the default filtering mechanism
for _ , gateway := range client . GetGateways ( ) {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx ) . With ( ) . Str ( "gateway" , gateway . Name ) . Str ( "namespace" , gateway . Namespace ) . Logger ( )
ctxLog := logger . WithContext ( ctx )
2020-12-15 15:40:05 +00:00
2021-11-09 10:34:06 +00:00
if _ , ok := gatewayClassNames [ string ( gateway . Spec . GatewayClassName ) ] ; ! ok {
2020-12-15 15:40:05 +00:00
continue
}
2021-05-20 09:50:12 +00:00
cfg , err := p . createGatewayConf ( ctxLog , client , gateway )
2020-12-15 15:40:05 +00:00
if err != nil {
2022-11-21 17:36:05 +00:00
logger . Error ( ) . Err ( err ) . Send ( )
2020-12-15 15:40:05 +00:00
continue
}
cfgs [ gateway . Name + gateway . Namespace ] = cfg
}
conf := provider . Merge ( ctx , cfgs )
conf . TLS = & dynamic . TLSConfiguration { }
for _ , cfg := range cfgs {
if conf . TLS == nil {
conf . TLS = & dynamic . TLSConfiguration { }
}
conf . TLS . Certificates = append ( conf . TLS . Certificates , cfg . TLS . Certificates ... )
for name , options := range cfg . TLS . Options {
if conf . TLS . Options == nil {
conf . TLS . Options = map [ string ] tls . Options { }
}
conf . TLS . Options [ name ] = options
}
for name , store := range cfg . TLS . Stores {
if conf . TLS . Stores == nil {
conf . TLS . Stores = map [ string ] tls . Store { }
}
conf . TLS . Stores [ name ] = store
}
}
return conf
}
2024-01-09 09:28:05 +00:00
func ( p * Provider ) createGatewayConf ( ctx context . Context , client Client , gateway * gatev1 . Gateway ) ( * dynamic . Configuration , error ) {
2020-12-15 15:40:05 +00:00
conf := & dynamic . Configuration {
2022-12-09 08:58:05 +00:00
HTTP : & dynamic . HTTPConfiguration {
Routers : map [ string ] * dynamic . Router { } ,
Middlewares : map [ string ] * dynamic . Middleware { } ,
Services : map [ string ] * dynamic . Service { } ,
ServersTransports : map [ string ] * dynamic . ServersTransport { } ,
2020-12-15 15:40:05 +00:00
} ,
TCP : & dynamic . TCPConfiguration {
2022-12-09 08:58:05 +00:00
Routers : map [ string ] * dynamic . TCPRouter { } ,
Middlewares : map [ string ] * dynamic . TCPMiddleware { } ,
Services : map [ string ] * dynamic . TCPService { } ,
ServersTransports : map [ string ] * dynamic . TCPServersTransport { } ,
2020-12-15 15:40:05 +00:00
} ,
2022-12-09 08:58:05 +00:00
UDP : & dynamic . UDPConfiguration {
Routers : map [ string ] * dynamic . UDPRouter { } ,
Services : map [ string ] * dynamic . UDPService { } ,
2020-12-15 15:40:05 +00:00
} ,
TLS : & dynamic . TLSConfiguration { } ,
}
tlsConfigs := make ( map [ string ] * tls . CertAndStores )
// GatewayReasonListenersNotValid is used when one or more
// Listeners have an invalid or unsupported configuration
// and cannot be configured on the Gateway.
2021-05-20 09:50:12 +00:00
listenerStatuses := p . fillGatewayConf ( ctx , client , gateway , conf , tlsConfigs )
2020-12-15 15:40:05 +00:00
2024-01-09 09:28:05 +00:00
gatewayStatus , errG := p . makeGatewayStatus ( gateway , listenerStatuses )
2020-12-15 15:40:05 +00:00
err := client . UpdateGatewayStatus ( gateway , gatewayStatus )
if err != nil {
return nil , fmt . Errorf ( "an error occurred while updating gateway status: %w" , err )
}
if errG != nil {
return nil , fmt . Errorf ( "an error occurred while creating gateway status: %w" , errG )
}
if len ( tlsConfigs ) > 0 {
conf . TLS . Certificates = append ( conf . TLS . Certificates , getTLSConfig ( tlsConfigs ) ... )
}
return conf , nil
}
2024-01-09 09:28:05 +00:00
func ( p * Provider ) fillGatewayConf ( ctx context . Context , client Client , gateway * gatev1 . Gateway , conf * dynamic . Configuration , tlsConfigs map [ string ] * tls . CertAndStores ) [ ] gatev1 . ListenerStatus {
2022-11-21 17:36:05 +00:00
logger := log . Ctx ( ctx )
2024-01-09 09:28:05 +00:00
listenerStatuses := make ( [ ] gatev1 . ListenerStatus , len ( gateway . Spec . Listeners ) )
2022-06-23 09:58:09 +00:00
allocatedListeners := make ( map [ string ] struct { } )
2020-12-15 15:40:05 +00:00
for i , listener := range gateway . Spec . Listeners {
2024-01-09 09:28:05 +00:00
listenerStatuses [ i ] = gatev1 . ListenerStatus {
2021-11-09 10:34:06 +00:00
Name : listener . Name ,
2024-01-09 09:28:05 +00:00
SupportedKinds : [ ] gatev1 . RouteGroupKind { } ,
2021-11-09 10:34:06 +00:00
Conditions : [ ] metav1 . Condition { } ,
2020-12-15 15:40:05 +00:00
}
2021-11-09 10:34:06 +00:00
supportedKinds , conditions := supportedRouteKinds ( listener . Protocol )
if len ( conditions ) > 0 {
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , conditions ... )
2020-12-15 15:40:05 +00:00
continue
}
2021-11-09 10:34:06 +00:00
listenerStatuses [ i ] . SupportedKinds = supportedKinds
2021-05-20 09:50:12 +00:00
2024-01-09 09:28:05 +00:00
routeKinds , conditions := getAllowedRouteKinds ( gateway , listener , supportedKinds )
2021-11-09 10:34:06 +00:00
if len ( conditions ) > 0 {
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , conditions ... )
2021-05-20 09:50:12 +00:00
continue
}
2022-06-23 09:58:09 +00:00
listenerKey := makeListenerKey ( listener )
if _ , ok := allocatedListeners [ listenerKey ] ; ok {
2021-05-20 09:50:12 +00:00
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionConflicted ) ,
2021-05-20 09:50:12 +00:00
Status : metav1 . ConditionTrue ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-05-20 09:50:12 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2022-06-23 09:58:09 +00:00
Reason : "DuplicateListener" ,
Message : "A listener with same protocol, port and hostname already exists" ,
2021-05-20 09:50:12 +00:00
} )
continue
}
2022-06-23 09:58:09 +00:00
allocatedListeners [ listenerKey ] = struct { } { }
2021-11-09 10:34:06 +00:00
ep , err := p . entryPointName ( listener . Port , listener . Protocol )
if err != nil {
// update "Detached" status with "PortUnavailable" reason
2021-05-20 09:50:12 +00:00
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionAccepted ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
2021-05-20 09:50:12 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonPortUnavailable ) ,
2021-11-09 10:34:06 +00:00
Message : fmt . Sprintf ( "Cannot find entryPoint for Gateway: %v" , err ) ,
2021-05-20 09:50:12 +00:00
} )
continue
}
2024-01-09 09:28:05 +00:00
if ( listener . Protocol == gatev1 . HTTPProtocolType || listener . Protocol == gatev1 . TCPProtocolType ) && listener . TLS != nil {
2020-12-15 15:40:05 +00:00
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionAccepted ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
2020-12-15 15:40:05 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "InvalidTLSConfiguration" , // TODO check the spec if a proper reason is introduced at some point
Message : "TLS configuration must no be defined when using HTTP or TCP protocol" ,
2020-12-15 15:40:05 +00:00
} )
continue
}
2021-05-20 09:50:12 +00:00
// TLS
2024-01-09 09:28:05 +00:00
if listener . Protocol == gatev1 . HTTPSProtocolType || listener . Protocol == gatev1 . TLSProtocolType {
if listener . TLS == nil || ( len ( listener . TLS . CertificateRefs ) == 0 && listener . TLS . Mode != nil && * listener . TLS . Mode != gatev1 . TLSModePassthrough ) {
2020-12-15 15:40:05 +00:00
// update "Detached" status with "UnsupportedProtocol" reason
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionAccepted ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
2020-12-15 15:40:05 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "InvalidTLSConfiguration" , // TODO check the spec if a proper reason is introduced at some point
Message : fmt . Sprintf ( "No TLS configuration for Gateway Listener %s:%d and protocol %q" ,
listener . Name , listener . Port , listener . Protocol ) ,
2020-12-15 15:40:05 +00:00
} )
continue
}
2024-01-09 09:28:05 +00:00
var tlsModeType gatev1 . TLSModeType
2021-07-15 15:20:08 +00:00
if listener . TLS . Mode != nil {
tlsModeType = * listener . TLS . Mode
}
2024-01-09 09:28:05 +00:00
isTLSPassthrough := tlsModeType == gatev1 . TLSModePassthrough
2021-11-09 10:34:06 +00:00
if isTLSPassthrough && len ( listener . TLS . CertificateRefs ) > 0 {
// https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.GatewayTLSConfig
2022-11-21 17:36:05 +00:00
logger . Warn ( ) . Msg ( "In case of Passthrough TLS mode, no TLS settings take effect as the TLS session from the client is NOT terminated at the Gateway" )
2021-05-20 09:50:12 +00:00
}
// Allowed configurations:
2021-11-09 10:34:06 +00:00
// Protocol TLS -> Passthrough -> TLSRoute/TCPRoute
// Protocol TLS -> Terminate -> TLSRoute/TCPRoute
2021-05-20 09:50:12 +00:00
// Protocol HTTPS -> Terminate -> HTTPRoute
2024-01-09 09:28:05 +00:00
if listener . Protocol == gatev1 . HTTPSProtocolType && isTLSPassthrough {
2020-12-15 15:40:05 +00:00
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionAccepted ) ,
Status : metav1 . ConditionFalse ,
ObservedGeneration : gateway . Generation ,
2020-12-15 15:40:05 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonUnsupportedProtocol ) ,
2021-11-09 10:34:06 +00:00
Message : "HTTPS protocol is not supported with TLS mode Passthrough" ,
2020-12-15 15:40:05 +00:00
} )
continue
}
2021-05-20 09:50:12 +00:00
if ! isTLSPassthrough {
2021-11-09 10:34:06 +00:00
if len ( listener . TLS . CertificateRefs ) == 0 {
2020-12-15 15:40:05 +00:00
// update "ResolvedRefs" status true with "InvalidCertificateRef" reason
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2020-12-15 15:40:05 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2020-12-15 15:40:05 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonInvalidCertificateRef ) ,
2021-11-09 10:34:06 +00:00
Message : "One TLS CertificateRef is required in Terminate mode" ,
2020-12-15 15:40:05 +00:00
} )
continue
}
2021-11-09 10:34:06 +00:00
// TODO Should we support multiple certificates?
certificateRef := listener . TLS . CertificateRefs [ 0 ]
if certificateRef . Kind == nil || * certificateRef . Kind != "Secret" ||
certificateRef . Group == nil || ( * certificateRef . Group != "" && * certificateRef . Group != "core" ) {
// update "ResolvedRefs" status true with "InvalidCertificateRef" reason
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-11-09 10:34:06 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-11-09 10:34:06 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonInvalidCertificateRef ) ,
2021-11-09 10:34:06 +00:00
Message : fmt . Sprintf ( "Unsupported TLS CertificateRef group/kind: %v/%v" , certificateRef . Group , certificateRef . Kind ) ,
} )
continue
}
// TODO Support ReferencePolicy to support cross namespace references.
if certificateRef . Namespace != nil && string ( * certificateRef . Namespace ) != gateway . Namespace {
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-11-09 10:34:06 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-11-09 10:34:06 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonInvalidCertificateRef ) ,
2021-11-09 10:34:06 +00:00
Message : "Cross namespace secrets are not supported" ,
} )
continue
}
configKey := gateway . Namespace + "/" + string ( certificateRef . Name )
2021-05-20 09:50:12 +00:00
if _ , tlsExists := tlsConfigs [ configKey ] ; ! tlsExists {
2021-11-09 10:34:06 +00:00
tlsConf , err := getTLS ( client , certificateRef . Name , gateway . Namespace )
2021-05-20 09:50:12 +00:00
if err != nil {
// update "ResolvedRefs" status true with "InvalidCertificateRef" reason
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-05-20 09:50:12 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-05-20 09:50:12 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonInvalidCertificateRef ) ,
2021-05-20 09:50:12 +00:00
Message : fmt . Sprintf ( "Error while retrieving certificate: %v" , err ) ,
} )
continue
}
tlsConfigs [ configKey ] = tlsConf
}
2020-12-15 15:40:05 +00:00
}
}
2021-11-09 10:34:06 +00:00
for _ , routeKind := range routeKinds {
switch routeKind . Kind {
case kindHTTPRoute :
2023-05-15 14:38:05 +00:00
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , p . gatewayHTTPRouteToHTTPConf ( ctx , ep , listener , gateway , client , conf ) ... )
2021-11-09 10:34:06 +00:00
case kindTCPRoute :
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , gatewayTCPRouteToTCPConf ( ctx , ep , listener , gateway , client , conf ) ... )
case kindTLSRoute :
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , gatewayTLSRouteToTCPConf ( ctx , ep , listener , gateway , client , conf ) ... )
}
2020-12-15 15:40:05 +00:00
}
2021-05-20 09:50:12 +00:00
}
return listenerStatuses
}
2020-12-15 15:40:05 +00:00
2024-01-09 09:28:05 +00:00
func ( p * Provider ) makeGatewayStatus ( gateway * gatev1 . Gateway , listenerStatuses [ ] gatev1 . ListenerStatus ) ( gatev1 . GatewayStatus , error ) {
2021-11-09 10:34:06 +00:00
// As Status.Addresses are not implemented yet, we initialize an empty array to follow the API expectations.
2024-01-09 09:28:05 +00:00
gatewayStatus := gatev1 . GatewayStatus {
Addresses : [ ] gatev1 . GatewayStatusAddress { } ,
2021-11-09 10:34:06 +00:00
}
2021-10-04 13:46:08 +00:00
2021-11-09 10:34:06 +00:00
var result error
for i , listener := range listenerStatuses {
if len ( listener . Conditions ) == 0 {
// GatewayConditionReady "Ready", GatewayConditionReason "ListenerReady"
listenerStatuses [ i ] . Conditions = append ( listenerStatuses [ i ] . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerReasonAccepted ) ,
2021-11-09 10:34:06 +00:00
Status : metav1 . ConditionTrue ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-10-04 13:46:08 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "ListenerReady" ,
Message : "No error found" ,
} )
continue
}
for _ , condition := range listener . Conditions {
result = multierror . Append ( result , errors . New ( condition . Message ) )
2021-10-04 13:46:08 +00:00
}
2021-07-15 15:20:08 +00:00
}
2021-11-09 10:34:06 +00:00
if result != nil {
// GatewayConditionReady "Ready", GatewayConditionReason "ListenersNotValid"
gatewayStatus . Conditions = append ( gatewayStatus . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . GatewayConditionAccepted ) ,
2021-11-09 10:34:06 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-11-09 10:34:06 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . GatewayReasonListenersNotValid ) ,
2021-11-09 10:34:06 +00:00
Message : "All Listeners must be valid" ,
} )
return gatewayStatus , result
}
gatewayStatus . Listeners = listenerStatuses
gatewayStatus . Conditions = append ( gatewayStatus . Conditions ,
2024-01-09 09:28:05 +00:00
// update "Accepted" status with "Accepted" reason
2021-11-09 10:34:06 +00:00
metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . GatewayConditionAccepted ) ,
2021-11-09 10:34:06 +00:00
Status : metav1 . ConditionTrue ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2024-01-22 14:30:05 +00:00
Reason : string ( gatev1 . GatewayReasonAccepted ) ,
Message : "Gateway successfully scheduled" ,
LastTransitionTime : metav1 . Now ( ) ,
} ,
// update "Programmed" status with "Programmed" reason
metav1 . Condition {
Type : string ( gatev1 . GatewayConditionProgrammed ) ,
Status : metav1 . ConditionTrue ,
ObservedGeneration : gateway . Generation ,
Reason : string ( gatev1 . GatewayReasonProgrammed ) ,
2024-01-09 09:28:05 +00:00
Message : "Gateway successfully scheduled" ,
2021-11-09 10:34:06 +00:00
LastTransitionTime : metav1 . Now ( ) ,
} ,
)
return gatewayStatus , nil
}
2024-01-09 09:28:05 +00:00
func ( p * Provider ) entryPointName ( port gatev1 . PortNumber , protocol gatev1 . ProtocolType ) ( string , error ) {
2021-11-09 10:34:06 +00:00
portStr := strconv . FormatInt ( int64 ( port ) , 10 )
for name , entryPoint := range p . EntryPoints {
if strings . HasSuffix ( entryPoint . Address , ":" + portStr ) {
// If the protocol is HTTP the entryPoint must have no TLS conf
2024-01-09 09:28:05 +00:00
// Not relevant for gatev1.TLSProtocolType && gatev1.TCPProtocolType
if protocol == gatev1 . HTTPProtocolType && entryPoint . HasHTTPTLSConf {
2021-11-09 10:34:06 +00:00
continue
}
return name , nil
}
}
return "" , fmt . Errorf ( "no matching entryPoint for port %d and protocol %q" , port , protocol )
}
2024-01-09 09:28:05 +00:00
func supportedRouteKinds ( protocol gatev1 . ProtocolType ) ( [ ] gatev1 . RouteGroupKind , [ ] metav1 . Condition ) {
group := gatev1 . Group ( gatev1 . GroupName )
2021-11-09 10:34:06 +00:00
switch protocol {
2024-01-09 09:28:05 +00:00
case gatev1 . TCPProtocolType :
return [ ] gatev1 . RouteGroupKind { { Kind : kindTCPRoute , Group : & group } } , nil
2021-11-09 10:34:06 +00:00
2024-01-09 09:28:05 +00:00
case gatev1 . HTTPProtocolType , gatev1 . HTTPSProtocolType :
return [ ] gatev1 . RouteGroupKind { { Kind : kindHTTPRoute , Group : & group } } , nil
2021-11-09 10:34:06 +00:00
2024-01-09 09:28:05 +00:00
case gatev1 . TLSProtocolType :
return [ ] gatev1 . RouteGroupKind {
2021-11-09 10:34:06 +00:00
{ Kind : kindTCPRoute , Group : & group } ,
{ Kind : kindTLSRoute , Group : & group } ,
} , nil
}
return nil , [ ] metav1 . Condition { {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionAccepted ) ,
Status : metav1 . ConditionFalse ,
2021-11-09 10:34:06 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonUnsupportedProtocol ) ,
2021-11-09 10:34:06 +00:00
Message : fmt . Sprintf ( "Unsupported listener protocol %q" , protocol ) ,
} }
}
2024-01-09 09:28:05 +00:00
func getAllowedRouteKinds ( gateway * gatev1 . Gateway , listener gatev1 . Listener , supportedKinds [ ] gatev1 . RouteGroupKind ) ( [ ] gatev1 . RouteGroupKind , [ ] metav1 . Condition ) {
2021-11-09 10:34:06 +00:00
if listener . AllowedRoutes == nil || len ( listener . AllowedRoutes . Kinds ) == 0 {
return supportedKinds , nil
}
var (
2024-01-09 09:28:05 +00:00
routeKinds [ ] gatev1 . RouteGroupKind
2021-11-09 10:34:06 +00:00
conditions [ ] metav1 . Condition
)
2024-01-09 09:28:05 +00:00
uniqRouteKinds := map [ gatev1 . Kind ] struct { } { }
2021-11-09 10:34:06 +00:00
for _ , routeKind := range listener . AllowedRoutes . Kinds {
var isSupported bool
for _ , kind := range supportedKinds {
if routeKind . Kind == kind . Kind && routeKind . Group != nil && * routeKind . Group == * kind . Group {
isSupported = true
break
}
}
if ! isSupported {
conditions = append ( conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionAccepted ) ,
2021-11-09 10:34:06 +00:00
Status : metav1 . ConditionTrue ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-11-09 10:34:06 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonInvalidRouteKinds ) ,
2021-11-09 10:34:06 +00:00
Message : fmt . Sprintf ( "Listener protocol %q does not support RouteGroupKind %v/%s" , listener . Protocol , routeKind . Group , routeKind . Kind ) ,
} )
continue
}
if _ , exists := uniqRouteKinds [ routeKind . Kind ] ; ! exists {
routeKinds = append ( routeKinds , routeKind )
uniqRouteKinds [ routeKind . Kind ] = struct { } { }
}
}
return routeKinds , conditions
}
2024-01-09 09:28:05 +00:00
func ( p * Provider ) gatewayHTTPRouteToHTTPConf ( ctx context . Context , ep string , listener gatev1 . Listener , gateway * gatev1 . Gateway , client Client , conf * dynamic . Configuration ) [ ] metav1 . Condition {
2021-11-09 10:34:06 +00:00
if listener . AllowedRoutes == nil {
// Should not happen due to validation.
return nil
}
namespaces , err := getRouteBindingSelectorNamespace ( client , gateway . Namespace , listener . AllowedRoutes . Namespaces )
2021-05-20 09:50:12 +00:00
if err != nil {
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
return [ ] metav1 . Condition { {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-05-20 09:50:12 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-05-20 09:50:12 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "InvalidRouteNamespacesSelector" , // Should never happen as the selector is validated by kubernetes
2021-10-04 13:46:08 +00:00
Message : fmt . Sprintf ( "Invalid route namespaces selector: %v" , err ) ,
} }
}
2021-11-09 10:34:06 +00:00
routes , err := client . GetHTTPRoutes ( namespaces )
2021-10-04 13:46:08 +00:00
if err != nil {
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
return [ ] metav1 . Condition { {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-10-04 13:46:08 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-10-04 13:46:08 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonRefNotPermitted ) ,
2021-11-09 10:34:06 +00:00
Message : fmt . Sprintf ( "Cannot fetch HTTPRoutes: %v" , err ) ,
2021-05-20 09:50:12 +00:00
} }
}
2021-11-09 10:34:06 +00:00
if len ( routes ) == 0 {
2022-11-21 17:36:05 +00:00
log . Ctx ( ctx ) . Debug ( ) . Msg ( "No HTTPRoutes found" )
2021-05-20 09:50:12 +00:00
return nil
}
var conditions [ ] metav1 . Condition
2021-11-09 10:34:06 +00:00
for _ , route := range routes {
if ! shouldAttach ( gateway , listener , route . Namespace , route . Spec . CommonRouteSpec ) {
continue
}
hostnames := matchingHostnames ( listener , route . Spec . Hostnames )
if len ( hostnames ) == 0 && listener . Hostname != nil && * listener . Hostname != "" && len ( route . Spec . Hostnames ) > 0 {
// TODO update the corresponding route parent status
// https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.TLSRoute
continue
}
hostRule , err := hostRule ( hostnames )
2020-12-15 15:40:05 +00:00
if err != nil {
2021-05-20 09:50:12 +00:00
conditions = append ( conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2020-12-15 15:40:05 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2020-12-15 15:40:05 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "InvalidRouteHostname" , // TODO check the spec if a proper reason is introduced at some point
Message : fmt . Sprintf ( "Skipping HTTPRoute %s: invalid hostname: %v" , route . Name , err ) ,
2020-12-15 15:40:05 +00:00
} )
continue
}
2021-11-09 10:34:06 +00:00
for _ , routeRule := range route . Spec . Rules {
2021-05-20 09:50:12 +00:00
rule , err := extractRule ( routeRule , hostRule )
if err != nil {
// update "ResolvedRefs" status true with "DroppedRoutes" reason
conditions = append ( conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-05-20 09:50:12 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-05-20 09:50:12 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "UnsupportedPathOrHeaderType" , // TODO check the spec if a proper reason is introduced at some point
Message : fmt . Sprintf ( "Skipping HTTPRoute %s: cannot generate rule: %v" , route . Name , err ) ,
2021-05-20 09:50:12 +00:00
} )
}
router := dynamic . Router {
Rule : rule ,
2024-01-23 10:34:05 +00:00
RuleSyntax : "v3" ,
2021-05-20 09:50:12 +00:00
EntryPoints : [ ] string { ep } ,
2020-12-15 15:40:05 +00:00
}
2024-01-09 09:28:05 +00:00
if listener . Protocol == gatev1 . HTTPSProtocolType && listener . TLS != nil {
2021-05-20 09:50:12 +00:00
// TODO support let's encrypt
router . TLS = & dynamic . RouterTLSConfig { }
}
2021-11-09 10:34:06 +00:00
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
routerName := route . Name + "-" + gateway . Name + "-" + ep
routerKey , err := makeRouterKey ( router . Rule , makeID ( route . Namespace , routerName ) )
2021-04-29 15:18:04 +00:00
if err != nil {
2021-05-20 09:50:12 +00:00
// update "ResolvedRefs" status true with "DroppedRoutes" reason
conditions = append ( conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-04-29 15:18:04 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-04-29 15:18:04 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "InvalidRouterKey" , // Should never happen
Message : fmt . Sprintf ( "Skipping HTTPRoute %s: cannot make router's key with rule %s: %v" , route . Name , router . Rule , err ) ,
2021-04-29 15:18:04 +00:00
} )
2021-05-20 09:50:12 +00:00
// TODO update the RouteStatus condition / deduplicate conditions on listener
2021-04-29 15:18:04 +00:00
continue
}
2020-12-15 15:40:05 +00:00
2022-12-22 14:02:05 +00:00
middlewares , err := loadMiddlewares ( listener , routerKey , routeRule . Filters )
if err != nil {
// update "ResolvedRefs" status true with "InvalidFilters" reason
conditions = append ( conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2022-12-22 14:02:05 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2022-12-22 14:02:05 +00:00
LastTransitionTime : metav1 . Now ( ) ,
Reason : "InvalidFilters" , // TODO check the spec if a proper reason is introduced at some point
Message : fmt . Sprintf ( "Cannot load HTTPRoute filter %s/%s: %v" , route . Namespace , route . Name , err ) ,
} )
// TODO update the RouteStatus condition / deduplicate conditions on listener
continue
}
for middlewareName , middleware := range middlewares {
conf . HTTP . Middlewares [ middlewareName ] = middleware
router . Middlewares = append ( router . Middlewares , middlewareName )
}
2021-11-09 10:34:06 +00:00
if len ( routeRule . BackendRefs ) == 0 {
2021-05-20 09:50:12 +00:00
continue
}
2021-11-09 10:34:06 +00:00
// Traefik internal service can be used only if there is only one BackendRef service reference.
if len ( routeRule . BackendRefs ) == 1 && isInternalService ( routeRule . BackendRefs [ 0 ] . BackendRef ) {
router . Service = string ( routeRule . BackendRefs [ 0 ] . Name )
2021-05-20 09:50:12 +00:00
} else {
2021-11-09 10:34:06 +00:00
wrrService , subServices , err := loadServices ( client , route . Namespace , routeRule . BackendRefs )
2020-12-15 15:40:05 +00:00
if err != nil {
// update "ResolvedRefs" status true with "DroppedRoutes" reason
2021-05-20 09:50:12 +00:00
conditions = append ( conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2020-12-15 15:40:05 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2020-12-15 15:40:05 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "InvalidBackendRefs" , // TODO check the spec if a proper reason is introduced at some point
Message : fmt . Sprintf ( "Cannot load HTTPRoute service %s/%s: %v" , route . Namespace , route . Name , err ) ,
2020-12-15 15:40:05 +00:00
} )
2021-05-20 09:50:12 +00:00
// TODO update the RouteStatus condition / deduplicate conditions on listener
2020-12-15 15:40:05 +00:00
continue
}
2021-05-20 09:50:12 +00:00
for svcName , svc := range subServices {
conf . HTTP . Services [ svcName ] = svc
2020-12-15 15:40:05 +00:00
}
2021-05-20 09:50:12 +00:00
serviceName := provider . Normalize ( routerKey + "-wrr" )
conf . HTTP . Services [ serviceName ] = wrrService
2020-12-15 15:40:05 +00:00
2021-05-20 09:50:12 +00:00
router . Service = serviceName
}
2020-12-15 15:40:05 +00:00
2023-05-15 14:38:05 +00:00
rt := & router
p . applyRouterTransform ( ctx , rt , route )
2021-05-20 09:50:12 +00:00
routerKey = provider . Normalize ( routerKey )
2023-05-15 14:38:05 +00:00
conf . HTTP . Routers [ routerKey ] = rt
2021-05-20 09:50:12 +00:00
}
}
2020-12-15 15:40:05 +00:00
2021-05-20 09:50:12 +00:00
return conditions
}
2020-12-15 15:40:05 +00:00
2024-01-09 09:28:05 +00:00
func gatewayTCPRouteToTCPConf ( ctx context . Context , ep string , listener gatev1 . Listener , gateway * gatev1 . Gateway , client Client , conf * dynamic . Configuration ) [ ] metav1 . Condition {
2021-11-09 10:34:06 +00:00
if listener . AllowedRoutes == nil {
// Should not happen due to validation.
return nil
2021-10-04 13:46:08 +00:00
}
2021-11-09 10:34:06 +00:00
namespaces , err := getRouteBindingSelectorNamespace ( client , gateway . Namespace , listener . AllowedRoutes . Namespaces )
2021-10-04 13:46:08 +00:00
if err != nil {
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
return [ ] metav1 . Condition { {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-10-04 13:46:08 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-10-04 13:46:08 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "InvalidRouteNamespacesSelector" , // TODO should never happen as the selector is validated by Kubernetes
2021-10-04 13:46:08 +00:00
Message : fmt . Sprintf ( "Invalid route namespaces selector: %v" , err ) ,
} }
2021-07-15 15:20:08 +00:00
}
2021-11-09 10:34:06 +00:00
routes , err := client . GetTCPRoutes ( namespaces )
2021-05-20 09:50:12 +00:00
if err != nil {
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
return [ ] metav1 . Condition { {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-05-20 09:50:12 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-05-20 09:50:12 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonRefNotPermitted ) ,
2021-11-09 10:34:06 +00:00
Message : fmt . Sprintf ( "Cannot fetch TCPRoutes: %v" , err ) ,
2021-05-20 09:50:12 +00:00
} }
}
2020-12-15 15:40:05 +00:00
2021-11-09 10:34:06 +00:00
if len ( routes ) == 0 {
2022-11-21 17:36:05 +00:00
log . Ctx ( ctx ) . Debug ( ) . Msg ( "No TCPRoutes found" )
2021-05-20 09:50:12 +00:00
return nil
}
2020-12-15 15:40:05 +00:00
2021-05-20 09:50:12 +00:00
var conditions [ ] metav1 . Condition
2021-11-09 10:34:06 +00:00
for _ , route := range routes {
if ! shouldAttach ( gateway , listener , route . Namespace , route . Spec . CommonRouteSpec ) {
2021-05-20 09:50:12 +00:00
continue
}
2021-11-09 10:34:06 +00:00
router := dynamic . TCPRouter {
2022-12-12 15:30:05 +00:00
Rule : "HostSNI(`*`)" ,
2021-11-09 10:34:06 +00:00
EntryPoints : [ ] string { ep } ,
2024-01-23 10:34:05 +00:00
RuleSyntax : "v3" ,
2021-11-09 10:34:06 +00:00
}
2021-05-20 09:50:12 +00:00
2024-01-09 09:28:05 +00:00
if listener . Protocol == gatev1 . TLSProtocolType && listener . TLS != nil {
2021-11-09 10:34:06 +00:00
// TODO support let's encrypt
router . TLS = & dynamic . RouterTCPTLSConfig {
2024-01-09 09:28:05 +00:00
Passthrough : listener . TLS . Mode != nil && * listener . TLS . Mode == gatev1 . TLSModePassthrough ,
2021-05-20 09:50:12 +00:00
}
2021-11-09 10:34:06 +00:00
}
2021-05-20 09:50:12 +00:00
2021-11-09 10:34:06 +00:00
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
routerName := route . Name + "-" + gateway . Name + "-" + ep
routerKey , err := makeRouterKey ( "" , makeID ( route . Namespace , routerName ) )
if err != nil {
// update "ResolvedRefs" status true with "DroppedRoutes" reason
conditions = append ( conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-11-09 10:34:06 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-11-09 10:34:06 +00:00
LastTransitionTime : metav1 . Now ( ) ,
Reason : "InvalidRouterKey" , // Should never happen
Message : fmt . Sprintf ( "Skipping TCPRoute %s: cannot make router's key with rule %s: %v" , route . Name , router . Rule , err ) ,
} )
2021-05-20 09:50:12 +00:00
2021-11-09 10:34:06 +00:00
// TODO update the RouteStatus condition / deduplicate conditions on listener
continue
}
2021-05-20 09:50:12 +00:00
2021-11-09 10:34:06 +00:00
routerKey = provider . Normalize ( routerKey )
var ruleServiceNames [ ] string
for i , rule := range route . Spec . Rules {
if rule . BackendRefs == nil {
// Should not happen due to validation.
// https://github.com/kubernetes-sigs/gateway-api/blob/v0.4.0/apis/v1alpha2/tcproute_types.go#L76
2021-05-20 09:50:12 +00:00
continue
}
2021-11-09 10:34:06 +00:00
wrrService , subServices , err := loadTCPServices ( client , route . Namespace , rule . BackendRefs )
2021-05-20 09:50:12 +00:00
if err != nil {
// update "ResolvedRefs" status true with "DroppedRoutes" reason
conditions = append ( conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-05-20 09:50:12 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-05-20 09:50:12 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "InvalidBackendRefs" , // TODO check the spec if a proper reason is introduced at some point
Message : fmt . Sprintf ( "Cannot load TCPRoute service %s/%s: %v" , route . Namespace , route . Name , err ) ,
2021-05-20 09:50:12 +00:00
} )
// TODO update the RouteStatus condition / deduplicate conditions on listener
continue
}
for svcName , svc := range subServices {
conf . TCP . Services [ svcName ] = svc
}
2020-12-15 15:40:05 +00:00
2021-11-09 10:34:06 +00:00
serviceName := fmt . Sprintf ( "%s-wrr-%d" , routerKey , i )
2021-05-20 09:50:12 +00:00
conf . TCP . Services [ serviceName ] = wrrService
2020-12-15 15:40:05 +00:00
2021-11-09 10:34:06 +00:00
ruleServiceNames = append ( ruleServiceNames , serviceName )
}
2021-05-20 09:50:12 +00:00
2021-11-09 10:34:06 +00:00
if len ( ruleServiceNames ) == 1 {
router . Service = ruleServiceNames [ 0 ]
2021-05-20 09:50:12 +00:00
conf . TCP . Routers [ routerKey ] = & router
2021-11-09 10:34:06 +00:00
continue
2021-05-20 09:50:12 +00:00
}
2021-11-09 10:34:06 +00:00
routeServiceKey := routerKey + "-wrr"
routeService := & dynamic . TCPService { Weighted : & dynamic . TCPWeightedRoundRobin { } }
for _ , name := range ruleServiceNames {
service := dynamic . TCPWRRService { Name : name }
service . SetDefaults ( )
routeService . Weighted . Services = append ( routeService . Weighted . Services , service )
}
conf . TCP . Services [ routeServiceKey ] = routeService
router . Service = routeServiceKey
conf . TCP . Routers [ routerKey ] = & router
2021-05-20 09:50:12 +00:00
}
return conditions
}
2024-01-09 09:28:05 +00:00
func gatewayTLSRouteToTCPConf ( ctx context . Context , ep string , listener gatev1 . Listener , gateway * gatev1 . Gateway , client Client , conf * dynamic . Configuration ) [ ] metav1 . Condition {
2021-11-09 10:34:06 +00:00
if listener . AllowedRoutes == nil {
// Should not happen due to validation.
return nil
2021-07-15 15:20:08 +00:00
}
2021-11-09 10:34:06 +00:00
namespaces , err := getRouteBindingSelectorNamespace ( client , gateway . Namespace , listener . AllowedRoutes . Namespaces )
2021-05-20 09:50:12 +00:00
if err != nil {
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
return [ ] metav1 . Condition { {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-05-20 09:50:12 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-05-20 09:50:12 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "InvalidRouteNamespacesSelector" , // TODO should never happen as the selector is validated by Kubernetes
2021-10-04 13:46:08 +00:00
Message : fmt . Sprintf ( "Invalid route namespaces selector: %v" , err ) ,
} }
}
2021-11-09 10:34:06 +00:00
routes , err := client . GetTLSRoutes ( namespaces )
2021-10-04 13:46:08 +00:00
if err != nil {
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason
return [ ] metav1 . Condition { {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-10-04 13:46:08 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-10-04 13:46:08 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2024-01-09 09:28:05 +00:00
Reason : string ( gatev1 . ListenerReasonRefNotPermitted ) ,
2021-11-09 10:34:06 +00:00
Message : fmt . Sprintf ( "Cannot fetch TLSRoutes: %v" , err ) ,
2021-05-20 09:50:12 +00:00
} }
}
2021-11-09 10:34:06 +00:00
if len ( routes ) == 0 {
2022-11-21 17:36:05 +00:00
log . Ctx ( ctx ) . Debug ( ) . Msg ( "No TLSRoutes found" )
2021-05-20 09:50:12 +00:00
return nil
}
var conditions [ ] metav1 . Condition
2021-11-09 10:34:06 +00:00
for _ , route := range routes {
if ! shouldAttach ( gateway , listener , route . Namespace , route . Spec . CommonRouteSpec ) {
continue
}
2021-05-20 09:50:12 +00:00
2021-11-09 10:34:06 +00:00
hostnames := matchingHostnames ( listener , route . Spec . Hostnames )
if len ( hostnames ) == 0 && listener . Hostname != nil && * listener . Hostname != "" && len ( route . Spec . Hostnames ) > 0 {
2022-12-12 15:30:05 +00:00
for _ , parent := range route . Status . Parents {
parent . Conditions = append ( parent . Conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . GatewayClassConditionStatusAccepted ) ,
2022-12-12 15:30:05 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
Reason : string ( gatev1 . ListenerConditionConflicted ) ,
2022-12-12 15:30:05 +00:00
Message : fmt . Sprintf ( "No hostname match between listener: %v and route: %v" , listener . Hostname , route . Spec . Hostnames ) ,
LastTransitionTime : metav1 . Now ( ) ,
} )
}
2021-11-09 10:34:06 +00:00
continue
}
2021-05-20 09:50:12 +00:00
2021-11-09 10:34:06 +00:00
rule , err := hostSNIRule ( hostnames )
if err != nil {
// update "ResolvedRefs" status true with "DroppedRoutes" reason
conditions = append ( conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-11-09 10:34:06 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-11-09 10:34:06 +00:00
LastTransitionTime : metav1 . Now ( ) ,
Reason : "InvalidHostnames" , // TODO check the spec if a proper reason is introduced at some point
Message : fmt . Sprintf ( "Skipping TLSRoute %s: cannot make route's SNI match: %v" , route . Name , err ) ,
} )
// TODO update the RouteStatus condition / deduplicate conditions on listener
continue
}
2021-05-20 09:50:12 +00:00
2021-11-09 10:34:06 +00:00
router := dynamic . TCPRouter {
Rule : rule ,
2024-01-23 10:34:05 +00:00
RuleSyntax : "v3" ,
2021-11-09 10:34:06 +00:00
EntryPoints : [ ] string { ep } ,
TLS : & dynamic . RouterTCPTLSConfig {
2024-01-09 09:28:05 +00:00
Passthrough : listener . TLS . Mode != nil && * listener . TLS . Mode == gatev1 . TLSModePassthrough ,
2021-11-09 10:34:06 +00:00
} ,
}
// Adding the gateway desc and the entryPoint desc prevents overlapping of routers build from the same routes.
routerName := route . Name + "-" + gateway . Name + "-" + ep
routerKey , err := makeRouterKey ( rule , makeID ( route . Namespace , routerName ) )
if err != nil {
// update "ResolvedRefs" status true with "DroppedRoutes" reason
conditions = append ( conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-11-09 10:34:06 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-11-09 10:34:06 +00:00
LastTransitionTime : metav1 . Now ( ) ,
Reason : "InvalidRouterKey" , // Should never happen
Message : fmt . Sprintf ( "Skipping TLSRoute %s: cannot make router's key with rule %s: %v" , route . Name , router . Rule , err ) ,
} )
// TODO update the RouteStatus condition / deduplicate conditions on listener
continue
}
routerKey = provider . Normalize ( routerKey )
2021-05-20 09:50:12 +00:00
2021-11-09 10:34:06 +00:00
var ruleServiceNames [ ] string
for i , routeRule := range route . Spec . Rules {
if len ( routeRule . BackendRefs ) == 0 {
// Should not happen due to validation.
// https://github.com/kubernetes-sigs/gateway-api/blob/v0.4.0/apis/v1alpha2/tlsroute_types.go#L120
2021-05-20 09:50:12 +00:00
continue
}
2021-11-09 10:34:06 +00:00
wrrService , subServices , err := loadTCPServices ( client , route . Namespace , routeRule . BackendRefs )
2021-05-20 09:50:12 +00:00
if err != nil {
// update "ResolvedRefs" status true with "DroppedRoutes" reason
conditions = append ( conditions , metav1 . Condition {
2024-01-09 09:28:05 +00:00
Type : string ( gatev1 . ListenerConditionResolvedRefs ) ,
2021-05-20 09:50:12 +00:00
Status : metav1 . ConditionFalse ,
2024-01-09 09:28:05 +00:00
ObservedGeneration : gateway . Generation ,
2021-05-20 09:50:12 +00:00
LastTransitionTime : metav1 . Now ( ) ,
2021-11-09 10:34:06 +00:00
Reason : "InvalidBackendRefs" , // TODO check the spec if a proper reason is introduced at some point
Message : fmt . Sprintf ( "Cannot load TLSRoute service %s/%s: %v" , route . Namespace , route . Name , err ) ,
2021-05-20 09:50:12 +00:00
} )
// TODO update the RouteStatus condition / deduplicate conditions on listener
continue
}
for svcName , svc := range subServices {
conf . TCP . Services [ svcName ] = svc
}
2021-11-09 10:34:06 +00:00
serviceName := fmt . Sprintf ( "%s-wrr-%d" , routerKey , i )
2021-05-20 09:50:12 +00:00
conf . TCP . Services [ serviceName ] = wrrService
2021-11-09 10:34:06 +00:00
ruleServiceNames = append ( ruleServiceNames , serviceName )
}
2021-05-20 09:50:12 +00:00
2021-11-09 10:34:06 +00:00
if len ( ruleServiceNames ) == 1 {
router . Service = ruleServiceNames [ 0 ]
2021-05-20 09:50:12 +00:00
conf . TCP . Routers [ routerKey ] = & router
2021-11-09 10:34:06 +00:00
continue
}
routeServiceKey := routerKey + "-wrr"
routeService := & dynamic . TCPService { Weighted : & dynamic . TCPWeightedRoundRobin { } }
for _ , name := range ruleServiceNames {
service := dynamic . TCPWRRService { Name : name }
service . SetDefaults ( )
routeService . Weighted . Services = append ( routeService . Weighted . Services , service )
2020-12-15 15:40:05 +00:00
}
2021-11-09 10:34:06 +00:00
conf . TCP . Services [ routeServiceKey ] = routeService
router . Service = routeServiceKey
conf . TCP . Routers [ routerKey ] = & router
2020-12-15 15:40:05 +00:00
}
2021-05-20 09:50:12 +00:00
return conditions
2020-12-15 15:40:05 +00:00
}
2021-11-09 10:34:06 +00:00
// Because of Kubernetes validation we admit that the given Hostnames are valid.
// https://github.com/kubernetes-sigs/gateway-api/blob/ff9883da4cad8554cd300394f725ab3a27502785/apis/v1alpha2/shared_types.go#L252
2024-01-09 09:28:05 +00:00
func matchingHostnames ( listener gatev1 . Listener , hostnames [ ] gatev1 . Hostname ) [ ] gatev1 . Hostname {
2021-11-09 10:34:06 +00:00
if listener . Hostname == nil || * listener . Hostname == "" {
return hostnames
2021-10-04 13:46:08 +00:00
}
2021-11-09 10:34:06 +00:00
if len ( hostnames ) == 0 {
2024-01-09 09:28:05 +00:00
return [ ] gatev1 . Hostname { * listener . Hostname }
2021-11-09 10:34:06 +00:00
}
2021-10-04 13:46:08 +00:00
2021-11-09 10:34:06 +00:00
listenerLabels := strings . Split ( string ( * listener . Hostname ) , "." )
2021-10-04 13:46:08 +00:00
2024-01-09 09:28:05 +00:00
var matches [ ] gatev1 . Hostname
2021-11-09 10:34:06 +00:00
for _ , hostname := range hostnames {
if hostname == * listener . Hostname {
matches = append ( matches , hostname )
continue
2021-10-04 13:46:08 +00:00
}
2021-11-09 10:34:06 +00:00
hostnameLabels := strings . Split ( string ( hostname ) , "." )
if len ( listenerLabels ) != len ( hostnameLabels ) {
continue
}
if ! slices . Equal ( listenerLabels [ 1 : ] , hostnameLabels [ 1 : ] ) {
continue
}
if listenerLabels [ 0 ] == "*" {
matches = append ( matches , hostname )
continue
}
if hostnameLabels [ 0 ] == "*" {
matches = append ( matches , * listener . Hostname )
continue
}
2021-10-04 13:46:08 +00:00
}
2021-11-09 10:34:06 +00:00
return matches
2021-10-04 13:46:08 +00:00
}
2024-01-09 09:28:05 +00:00
func shouldAttach ( gateway * gatev1 . Gateway , listener gatev1 . Listener , routeNamespace string , routeSpec gatev1 . CommonRouteSpec ) bool {
2021-11-09 10:34:06 +00:00
for _ , parentRef := range routeSpec . ParentRefs {
2024-01-09 09:28:05 +00:00
if parentRef . Group == nil || * parentRef . Group != gatev1 . GroupName {
2021-11-09 10:34:06 +00:00
continue
}
2020-12-15 15:40:05 +00:00
2021-11-09 10:34:06 +00:00
if parentRef . Kind == nil || * parentRef . Kind != kindGateway {
continue
}
2020-12-15 15:40:05 +00:00
2021-11-09 10:34:06 +00:00
if parentRef . SectionName != nil && * parentRef . SectionName != listener . Name {
2020-12-15 15:40:05 +00:00
continue
}
2021-11-09 10:34:06 +00:00
namespace := routeNamespace
if parentRef . Namespace != nil {
namespace = string ( * parentRef . Namespace )
}
if namespace == gateway . Namespace && string ( parentRef . Name ) == gateway . Name {
return true
2020-12-15 15:40:05 +00:00
}
}
2021-11-09 10:34:06 +00:00
return false
}
2020-12-15 15:40:05 +00:00
2024-01-09 09:28:05 +00:00
func getRouteBindingSelectorNamespace ( client Client , gatewayNamespace string , routeNamespaces * gatev1 . RouteNamespaces ) ( [ ] string , error ) {
2021-11-09 10:34:06 +00:00
if routeNamespaces == nil || routeNamespaces . From == nil {
return [ ] string { gatewayNamespace } , nil
2020-12-15 15:40:05 +00:00
}
2021-11-09 10:34:06 +00:00
switch * routeNamespaces . From {
2024-01-09 09:28:05 +00:00
case gatev1 . NamespacesFromAll :
2021-11-09 10:34:06 +00:00
return [ ] string { metav1 . NamespaceAll } , nil
2020-12-15 15:40:05 +00:00
2024-01-09 09:28:05 +00:00
case gatev1 . NamespacesFromSame :
2021-11-09 10:34:06 +00:00
return [ ] string { gatewayNamespace } , nil
2020-12-15 15:40:05 +00:00
2024-01-09 09:28:05 +00:00
case gatev1 . NamespacesFromSelector :
2021-11-09 10:34:06 +00:00
selector , err := metav1 . LabelSelectorAsSelector ( routeNamespaces . Selector )
if err != nil {
return nil , fmt . Errorf ( "malformed selector: %w" , err )
}
return client . GetNamespaces ( selector )
}
return nil , fmt . Errorf ( "unsupported RouteSelectType: %q" , * routeNamespaces . From )
2020-12-15 15:40:05 +00:00
}
2024-01-09 09:28:05 +00:00
func hostRule ( hostnames [ ] gatev1 . Hostname ) ( string , error ) {
2022-11-28 14:48:05 +00:00
var rules [ ] string
2021-04-29 15:18:04 +00:00
2021-11-09 10:34:06 +00:00
for _ , hostname := range hostnames {
2021-04-29 15:18:04 +00:00
host := string ( hostname )
// When unspecified, "", or *, all hostnames are matched.
// This field can be omitted for protocols that don't require hostname based matching.
// TODO Refactor this when building support for TLS options.
if host == "*" || host == "" {
return "" , nil
2020-12-15 15:40:05 +00:00
}
2021-04-29 15:18:04 +00:00
wildcard := strings . Count ( host , "*" )
if wildcard == 0 {
2022-11-28 14:48:05 +00:00
rules = append ( rules , fmt . Sprintf ( "Host(`%s`)" , host ) )
2021-04-29 15:18:04 +00:00
continue
}
2021-11-09 10:34:06 +00:00
// https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.Hostname
2021-04-29 15:18:04 +00:00
if ! strings . HasPrefix ( host , "*." ) || wildcard > 1 {
return "" , fmt . Errorf ( "invalid rule: %q" , host )
}
2022-11-28 14:48:05 +00:00
host = strings . Replace ( regexp . QuoteMeta ( host ) , ` \*\. ` , ` [a-zA-Z0-9-]+\. ` , 1 )
rules = append ( rules , fmt . Sprintf ( "HostRegexp(`^%s$`)" , host ) )
2020-12-15 15:40:05 +00:00
}
2022-11-28 14:48:05 +00:00
switch len ( rules ) {
case 0 :
return "" , nil
case 1 :
return rules [ 0 ] , nil
default :
return fmt . Sprintf ( "(%s)" , strings . Join ( rules , " || " ) ) , nil
2020-12-15 15:40:05 +00:00
}
}
2024-01-09 09:28:05 +00:00
func hostSNIRule ( hostnames [ ] gatev1 . Hostname ) ( string , error ) {
2022-12-12 15:30:05 +00:00
rules := make ( [ ] string , 0 , len ( hostnames ) )
2024-01-09 09:28:05 +00:00
uniqHostnames := map [ gatev1 . Hostname ] struct { } { }
2021-11-09 10:34:06 +00:00
for _ , hostname := range hostnames {
if len ( hostname ) == 0 {
continue
}
2021-05-20 09:50:12 +00:00
2021-11-09 10:34:06 +00:00
if _ , exists := uniqHostnames [ hostname ] ; exists {
continue
}
2021-05-20 09:50:12 +00:00
2022-12-12 15:30:05 +00:00
host := string ( hostname )
uniqHostnames [ hostname ] = struct { } { }
wildcard := strings . Count ( host , "*" )
if wildcard == 0 {
rules = append ( rules , fmt . Sprintf ( "HostSNI(`%s`)" , host ) )
continue
}
2021-05-20 09:50:12 +00:00
2022-12-12 15:30:05 +00:00
if ! strings . HasPrefix ( host , "*." ) || wildcard > 1 {
return "" , fmt . Errorf ( "invalid rule: %q" , host )
2021-05-20 09:50:12 +00:00
}
2021-11-09 10:34:06 +00:00
2022-12-12 15:30:05 +00:00
host = strings . Replace ( regexp . QuoteMeta ( host ) , ` \*\. ` , ` [a-zA-Z0-9-]+\. ` , 1 )
rules = append ( rules , fmt . Sprintf ( "HostSNIRegexp(`^%s$`)" , host ) )
2021-05-20 09:50:12 +00:00
}
2022-12-12 15:30:05 +00:00
if len ( hostnames ) == 0 || len ( rules ) == 0 {
2021-05-20 09:50:12 +00:00
return "HostSNI(`*`)" , nil
}
2022-12-12 15:30:05 +00:00
return strings . Join ( rules , " || " ) , nil
2021-05-20 09:50:12 +00:00
}
2024-01-09 09:28:05 +00:00
func extractRule ( routeRule gatev1 . HTTPRouteRule , hostRule string ) ( string , error ) {
2020-12-15 15:40:05 +00:00
var rule string
var matchesRules [ ] string
for _ , match := range routeRule . Matches {
2021-07-15 15:20:08 +00:00
if ( match . Path == nil || match . Path . Type == nil ) && match . Headers == nil {
2020-12-15 15:40:05 +00:00
continue
}
var matchRules [ ] string
2021-07-15 15:20:08 +00:00
2021-11-09 10:34:06 +00:00
if match . Path != nil && match . Path . Type != nil && match . Path . Value != nil {
// TODO handle other path types
2021-07-15 15:20:08 +00:00
switch * match . Path . Type {
2024-01-09 09:28:05 +00:00
case gatev1 . PathMatchExact :
2021-11-09 10:34:06 +00:00
matchRules = append ( matchRules , fmt . Sprintf ( "Path(`%s`)" , * match . Path . Value ) )
2024-01-09 09:28:05 +00:00
case gatev1 . PathMatchPathPrefix :
2021-11-09 10:34:06 +00:00
matchRules = append ( matchRules , fmt . Sprintf ( "PathPrefix(`%s`)" , * match . Path . Value ) )
2020-12-15 15:40:05 +00:00
default :
2021-07-15 15:20:08 +00:00
return "" , fmt . Errorf ( "unsupported path match %s" , * match . Path . Type )
2020-12-15 15:40:05 +00:00
}
}
2021-11-09 10:34:06 +00:00
headerRules , err := extractHeaderRules ( match . Headers )
if err != nil {
return "" , err
2020-12-15 15:40:05 +00:00
}
2021-11-09 10:34:06 +00:00
matchRules = append ( matchRules , headerRules ... )
2020-12-15 15:40:05 +00:00
matchesRules = append ( matchesRules , strings . Join ( matchRules , " && " ) )
}
// If no matches are specified, the default is a prefix
// path match on "/", which has the effect of matching every
// HTTP request.
if len ( routeRule . Matches ) == 0 {
matchesRules = append ( matchesRules , "PathPrefix(`/`)" )
}
if hostRule != "" {
if len ( matchesRules ) == 0 {
return hostRule , nil
}
rule += hostRule + " && "
}
if len ( matchesRules ) == 1 {
return rule + matchesRules [ 0 ] , nil
}
if len ( rule ) == 0 {
return strings . Join ( matchesRules , " || " ) , nil
}
return rule + "(" + strings . Join ( matchesRules , " || " ) + ")" , nil
}
2024-01-09 09:28:05 +00:00
func extractHeaderRules ( headers [ ] gatev1 . HTTPHeaderMatch ) ( [ ] string , error ) {
2021-11-09 10:34:06 +00:00
var headerRules [ ] string
2020-12-15 15:40:05 +00:00
2021-11-09 10:34:06 +00:00
// TODO handle other headers types
for _ , header := range headers {
if header . Type == nil {
// Should never happen due to kubernetes validation.
continue
}
2020-12-15 15:40:05 +00:00
2021-11-09 10:34:06 +00:00
switch * header . Type {
2024-01-09 09:28:05 +00:00
case gatev1 . HeaderMatchExact :
2024-01-23 10:34:05 +00:00
headerRules = append ( headerRules , fmt . Sprintf ( "Header(`%s`,`%s`)" , header . Name , header . Value ) )
2021-11-09 10:34:06 +00:00
default :
return nil , fmt . Errorf ( "unsupported header match type %s" , * header . Type )
2020-12-15 15:40:05 +00:00
}
}
2021-11-09 10:34:06 +00:00
return headerRules , nil
2020-12-15 15:40:05 +00:00
}
func makeRouterKey ( rule , name string ) ( string , error ) {
h := sha256 . New ( )
if _ , err := h . Write ( [ ] byte ( rule ) ) ; err != nil {
return "" , err
}
key := fmt . Sprintf ( "%s-%.10x" , name , h . Sum ( nil ) )
return key , nil
}
func makeID ( namespace , name string ) string {
if namespace == "" {
return name
}
return namespace + "-" + name
}
2024-01-09 09:28:05 +00:00
func getTLS ( k8sClient Client , secretName gatev1 . ObjectName , namespace string ) ( * tls . CertAndStores , error ) {
2021-11-09 10:34:06 +00:00
secret , exists , err := k8sClient . GetSecret ( namespace , string ( secretName ) )
2020-12-15 15:40:05 +00:00
if err != nil {
return nil , fmt . Errorf ( "failed to fetch secret %s/%s: %w" , namespace , secretName , err )
}
if ! exists {
return nil , fmt . Errorf ( "secret %s/%s does not exist" , namespace , secretName )
}
2021-11-09 10:34:06 +00:00
cert , key , err := getCertificateBlocks ( secret , namespace , string ( secretName ) )
2020-12-15 15:40:05 +00:00
if err != nil {
return nil , err
}
return & tls . CertAndStores {
Certificate : tls . Certificate {
2024-01-11 16:06:06 +00:00
CertFile : types . FileOrContent ( cert ) ,
KeyFile : types . FileOrContent ( key ) ,
2020-12-15 15:40:05 +00:00
} ,
} , nil
}
func getTLSConfig ( tlsConfigs map [ string ] * tls . CertAndStores ) [ ] * tls . CertAndStores {
var secretNames [ ] string
for secretName := range tlsConfigs {
secretNames = append ( secretNames , secretName )
}
sort . Strings ( secretNames )
var configs [ ] * tls . CertAndStores
for _ , secretName := range secretNames {
configs = append ( configs , tlsConfigs [ secretName ] )
}
return configs
}
func getCertificateBlocks ( secret * corev1 . Secret , namespace , secretName string ) ( string , string , error ) {
var missingEntries [ ] string
tlsCrtData , tlsCrtExists := secret . Data [ "tls.crt" ]
if ! tlsCrtExists {
missingEntries = append ( missingEntries , "tls.crt" )
}
tlsKeyData , tlsKeyExists := secret . Data [ "tls.key" ]
if ! tlsKeyExists {
missingEntries = append ( missingEntries , "tls.key" )
}
if len ( missingEntries ) > 0 {
return "" , "" , fmt . Errorf ( "secret %s/%s is missing the following TLS data entries: %s" ,
namespace , secretName , strings . Join ( missingEntries , ", " ) )
}
cert := string ( tlsCrtData )
if cert == "" {
missingEntries = append ( missingEntries , "tls.crt" )
}
key := string ( tlsKeyData )
if key == "" {
missingEntries = append ( missingEntries , "tls.key" )
}
if len ( missingEntries ) > 0 {
return "" , "" , fmt . Errorf ( "secret %s/%s contains the following empty TLS data entries: %s" ,
namespace , secretName , strings . Join ( missingEntries , ", " ) )
}
return cert , key , nil
}
// loadServices is generating a WRR service, even when there is only one target.
2024-01-09 09:28:05 +00:00
func loadServices ( client Client , namespace string , backendRefs [ ] gatev1 . HTTPBackendRef ) ( * dynamic . Service , map [ string ] * dynamic . Service , error ) {
2020-12-15 15:40:05 +00:00
services := map [ string ] * dynamic . Service { }
wrrSvc := & dynamic . Service {
Weighted : & dynamic . WeightedRoundRobin {
Services : [ ] dynamic . WRRService { } ,
} ,
}
2021-11-09 10:34:06 +00:00
for _ , backendRef := range backendRefs {
if backendRef . Group == nil || backendRef . Kind == nil {
// Should not happen as this is validated by kubernetes
continue
}
2021-02-02 18:36:04 +00:00
2021-11-09 10:34:06 +00:00
if isInternalService ( backendRef . BackendRef ) {
return nil , nil , fmt . Errorf ( "traefik internal service %s is not allowed in a WRR loadbalancer" , backendRef . BackendRef . Name )
}
2021-02-02 18:36:04 +00:00
2024-01-09 09:28:05 +00:00
weight := int ( ptr . Deref ( backendRef . Weight , 1 ) )
2021-02-02 18:36:04 +00:00
2023-03-20 14:38:08 +00:00
if isTraefikService ( backendRef . BackendRef ) {
2021-11-09 10:34:06 +00:00
wrrSvc . Weighted . Services = append ( wrrSvc . Weighted . Services , dynamic . WRRService { Name : string ( backendRef . Name ) , Weight : & weight } )
2021-02-02 18:36:04 +00:00
continue
}
2021-11-09 10:34:06 +00:00
if * backendRef . Group != "" && * backendRef . Group != "core" && * backendRef . Kind != "Service" {
return nil , nil , fmt . Errorf ( "unsupported HTTPBackendRef %s/%s/%s" , * backendRef . Group , * backendRef . Kind , backendRef . Name )
2020-12-15 15:40:05 +00:00
}
2022-11-16 10:38:07 +00:00
lb := & dynamic . ServersLoadBalancer { }
lb . SetDefaults ( )
svc := dynamic . Service { LoadBalancer : lb }
2020-12-15 15:40:05 +00:00
2021-11-09 10:34:06 +00:00
// TODO support cross namespace through ReferencePolicy
service , exists , err := client . GetService ( namespace , string ( backendRef . Name ) )
2020-12-15 15:40:05 +00:00
if err != nil {
return nil , nil , err
}
if ! exists {
return nil , nil , errors . New ( "service not found" )
}
2021-11-09 10:34:06 +00:00
if len ( service . Spec . Ports ) > 1 && backendRef . Port == nil {
2020-12-15 15:40:05 +00:00
// If the port is unspecified and the backend is a Service
// object consisting of multiple port definitions, the route
// must be dropped from the Gateway. The controller should
// raise the "ResolvedRefs" condition on the Gateway with the
2021-03-15 08:44:03 +00:00
// "DroppedRoutes" reason. The gateway status for this route
2020-12-15 15:40:05 +00:00
// should be updated with a condition that describes the error
// more specifically.
2022-11-21 17:36:05 +00:00
log . Error ( ) . Msg ( "A multiple ports Kubernetes Service cannot be used if unspecified backendRef.Port" )
2020-12-15 15:40:05 +00:00
continue
}
var portSpec corev1 . ServicePort
var match bool
for _ , p := range service . Spec . Ports {
2021-11-09 10:34:06 +00:00
if backendRef . Port == nil || p . Port == int32 ( * backendRef . Port ) {
2020-12-15 15:40:05 +00:00
portSpec = p
match = true
break
}
}
if ! match {
return nil , nil , errors . New ( "service port not found" )
}
2021-11-09 10:34:06 +00:00
endpoints , endpointsExists , endpointsErr := client . GetEndpoints ( namespace , string ( backendRef . Name ) )
2020-12-15 15:40:05 +00:00
if endpointsErr != nil {
return nil , nil , endpointsErr
}
if ! endpointsExists {
return nil , nil , errors . New ( "endpoints not found" )
}
if len ( endpoints . Subsets ) == 0 {
return nil , nil , errors . New ( "subset not found" )
}
var port int32
var portStr string
for _ , subset := range endpoints . Subsets {
for _ , p := range subset . Ports {
2021-05-20 09:50:12 +00:00
if portSpec . Name == p . Name {
2020-12-15 15:40:05 +00:00
port = p . Port
break
}
}
if port == 0 {
return nil , nil , errors . New ( "cannot define a port" )
}
2021-05-20 09:50:12 +00:00
protocol := getProtocol ( portSpec )
2020-12-15 15:40:05 +00:00
portStr = strconv . FormatInt ( int64 ( port ) , 10 )
for _ , addr := range subset . Addresses {
svc . LoadBalancer . Servers = append ( svc . LoadBalancer . Servers , dynamic . Server {
URL : fmt . Sprintf ( "%s://%s" , protocol , net . JoinHostPort ( addr . IP , portStr ) ) ,
} )
}
}
serviceName := provider . Normalize ( makeID ( service . Namespace , service . Name ) + "-" + portStr )
services [ serviceName ] = & svc
wrrSvc . Weighted . Services = append ( wrrSvc . Weighted . Services , dynamic . WRRService { Name : serviceName , Weight : & weight } )
}
2021-02-02 18:36:04 +00:00
if len ( wrrSvc . Weighted . Services ) == 0 {
2020-12-15 15:40:05 +00:00
return nil , nil , errors . New ( "no service has been created" )
}
return wrrSvc , services , nil
}
2021-05-20 09:50:12 +00:00
// loadTCPServices is generating a WRR service, even when there is only one target.
2024-01-09 09:28:05 +00:00
func loadTCPServices ( client Client , namespace string , backendRefs [ ] gatev1 . BackendRef ) ( * dynamic . TCPService , map [ string ] * dynamic . TCPService , error ) {
2021-05-20 09:50:12 +00:00
services := map [ string ] * dynamic . TCPService { }
wrrSvc := & dynamic . TCPService {
Weighted : & dynamic . TCPWeightedRoundRobin {
Services : [ ] dynamic . TCPWRRService { } ,
} ,
}
2021-11-09 10:34:06 +00:00
for _ , backendRef := range backendRefs {
if backendRef . Group == nil || backendRef . Kind == nil {
// Should not happen as this is validated by kubernetes
continue
}
2021-05-20 09:50:12 +00:00
2021-11-09 10:34:06 +00:00
if isInternalService ( backendRef ) {
return nil , nil , fmt . Errorf ( "traefik internal service %s is not allowed in a WRR loadbalancer" , backendRef . Name )
}
2021-05-20 09:50:12 +00:00
2024-01-09 09:28:05 +00:00
weight := int ( ptr . Deref ( backendRef . Weight , 1 ) )
2021-05-20 09:50:12 +00:00
2023-03-20 14:38:08 +00:00
if isTraefikService ( backendRef ) {
2021-11-09 10:34:06 +00:00
wrrSvc . Weighted . Services = append ( wrrSvc . Weighted . Services , dynamic . TCPWRRService { Name : string ( backendRef . Name ) , Weight : & weight } )
2021-05-20 09:50:12 +00:00
continue
}
2021-11-09 10:34:06 +00:00
if * backendRef . Group != "" && * backendRef . Group != "core" && * backendRef . Kind != "Service" {
return nil , nil , fmt . Errorf ( "unsupported BackendRef %s/%s/%s" , * backendRef . Group , * backendRef . Kind , backendRef . Name )
2021-05-20 09:50:12 +00:00
}
svc := dynamic . TCPService {
LoadBalancer : & dynamic . TCPServersLoadBalancer { } ,
}
2021-11-09 10:34:06 +00:00
service , exists , err := client . GetService ( namespace , string ( backendRef . Name ) )
2021-05-20 09:50:12 +00:00
if err != nil {
return nil , nil , err
}
if ! exists {
return nil , nil , errors . New ( "service not found" )
}
2021-11-09 10:34:06 +00:00
if len ( service . Spec . Ports ) > 1 && backendRef . Port == nil {
2021-05-20 09:50:12 +00:00
// If the port is unspecified and the backend is a Service
// object consisting of multiple port definitions, the route
// must be dropped from the Gateway. The controller should
// raise the "ResolvedRefs" condition on the Gateway with the
// "DroppedRoutes" reason. The gateway status for this route
// should be updated with a condition that describes the error
// more specifically.
2022-11-21 17:36:05 +00:00
log . Error ( ) . Msg ( "A multiple ports Kubernetes Service cannot be used if unspecified backendRef.Port" )
2021-05-20 09:50:12 +00:00
continue
}
var portSpec corev1 . ServicePort
var match bool
for _ , p := range service . Spec . Ports {
2021-11-09 10:34:06 +00:00
if backendRef . Port == nil || p . Port == int32 ( * backendRef . Port ) {
2021-05-20 09:50:12 +00:00
portSpec = p
match = true
break
}
}
if ! match {
return nil , nil , errors . New ( "service port not found" )
}
2021-11-09 10:34:06 +00:00
endpoints , endpointsExists , endpointsErr := client . GetEndpoints ( namespace , string ( backendRef . Name ) )
2021-05-20 09:50:12 +00:00
if endpointsErr != nil {
return nil , nil , endpointsErr
}
if ! endpointsExists {
return nil , nil , errors . New ( "endpoints not found" )
}
if len ( endpoints . Subsets ) == 0 {
return nil , nil , errors . New ( "subset not found" )
}
var port int32
var portStr string
for _ , subset := range endpoints . Subsets {
for _ , p := range subset . Ports {
if portSpec . Name == p . Name {
port = p . Port
break
}
}
if port == 0 {
return nil , nil , errors . New ( "cannot define a port" )
}
portStr = strconv . FormatInt ( int64 ( port ) , 10 )
for _ , addr := range subset . Addresses {
svc . LoadBalancer . Servers = append ( svc . LoadBalancer . Servers , dynamic . TCPServer {
Address : net . JoinHostPort ( addr . IP , portStr ) ,
} )
}
}
serviceName := provider . Normalize ( makeID ( service . Namespace , service . Name ) + "-" + portStr )
services [ serviceName ] = & svc
wrrSvc . Weighted . Services = append ( wrrSvc . Weighted . Services , dynamic . TCPWRRService { Name : serviceName , Weight : & weight } )
}
if len ( wrrSvc . Weighted . Services ) == 0 {
return nil , nil , errors . New ( "no service has been created" )
}
return wrrSvc , services , nil
}
2024-01-09 09:28:05 +00:00
func loadMiddlewares ( listener gatev1 . Listener , prefix string , filters [ ] gatev1 . HTTPRouteFilter ) ( map [ string ] * dynamic . Middleware , error ) {
2022-12-22 14:02:05 +00:00
middlewares := make ( map [ string ] * dynamic . Middleware )
// The spec allows for an empty string in which case we should use the
// scheme of the request which in this case is the listener scheme.
var listenerScheme string
switch listener . Protocol {
2024-01-09 09:28:05 +00:00
case gatev1 . HTTPProtocolType :
2022-12-22 14:02:05 +00:00
listenerScheme = "http"
2024-01-09 09:28:05 +00:00
case gatev1 . HTTPSProtocolType :
2022-12-22 14:02:05 +00:00
listenerScheme = "https"
default :
return nil , fmt . Errorf ( "invalid listener protocol %s" , listener . Protocol )
}
for i , filter := range filters {
var middleware * dynamic . Middleware
switch filter . Type {
2024-01-09 09:28:05 +00:00
case gatev1 . HTTPRouteFilterRequestRedirect :
2022-12-22 14:02:05 +00:00
var err error
middleware , err = createRedirectRegexMiddleware ( listenerScheme , filter . RequestRedirect )
if err != nil {
return nil , fmt . Errorf ( "creating RedirectRegex middleware: %w" , err )
}
default :
// As per the spec:
// https://gateway-api.sigs.k8s.io/api-types/httproute/#filters-optional
// In all cases where incompatible or unsupported filters are
// specified, implementations MUST add a warning condition to
// status.
return nil , fmt . Errorf ( "unsupported filter %s" , filter . Type )
}
middlewareName := provider . Normalize ( fmt . Sprintf ( "%s-%s-%d" , prefix , strings . ToLower ( string ( filter . Type ) ) , i ) )
middlewares [ middlewareName ] = middleware
}
return middlewares , nil
}
2024-01-09 09:28:05 +00:00
func createRedirectRegexMiddleware ( scheme string , filter * gatev1 . HTTPRequestRedirectFilter ) ( * dynamic . Middleware , error ) {
2022-12-22 14:02:05 +00:00
// Use the HTTPRequestRedirectFilter scheme if defined.
filterScheme := scheme
if filter . Scheme != nil {
filterScheme = * filter . Scheme
}
if filterScheme != "http" && filterScheme != "https" {
return nil , fmt . Errorf ( "invalid scheme %s" , filterScheme )
}
statusCode := http . StatusFound
if filter . StatusCode != nil {
statusCode = * filter . StatusCode
}
if statusCode != http . StatusMovedPermanently && statusCode != http . StatusFound {
return nil , fmt . Errorf ( "invalid status code %d" , statusCode )
}
port := "${port}"
if filter . Port != nil {
port = fmt . Sprintf ( ":%d" , * filter . Port )
}
hostname := "${hostname}"
if filter . Hostname != nil && * filter . Hostname != "" {
hostname = string ( * filter . Hostname )
}
return & dynamic . Middleware {
RedirectRegex : & dynamic . RedirectRegex {
Regex : ` ^[a-z]+:\/\/(?P<userInfo>.+@)?(?P<hostname>\[[\w:\.]+\]|[\w\._-]+)(?P<port>:\d+)?\/(?P<path>.*) ` ,
Replacement : fmt . Sprintf ( "%s://${userinfo}%s%s/${path}" , filterScheme , hostname , port ) ,
Permanent : statusCode == http . StatusMovedPermanently ,
} ,
} , nil
}
2021-05-20 09:50:12 +00:00
func getProtocol ( portSpec corev1 . ServicePort ) string {
2020-12-15 15:40:05 +00:00
protocol := "http"
2021-05-20 09:50:12 +00:00
if portSpec . Port == 443 || strings . HasPrefix ( portSpec . Name , "https" ) {
2020-12-15 15:40:05 +00:00
protocol = "https"
}
return protocol
}
func throttleEvents ( ctx context . Context , throttleDuration time . Duration , pool * safe . Pool , eventsChan <- chan interface { } ) chan interface { } {
if throttleDuration == 0 {
return nil
}
// Create a buffered channel to hold the pending event (if we're delaying processing the event due to throttling)
eventsChanBuffered := make ( chan interface { } , 1 )
// Run a goroutine that reads events from eventChan and does a non-blocking write to pendingEvent.
// This guarantees that writing to eventChan will never block,
// and that pendingEvent will have something in it if there's been an event since we read from that channel.
pool . GoCtx ( func ( ctxPool context . Context ) {
for {
select {
case <- ctxPool . Done ( ) :
return
case nextEvent := <- eventsChan :
select {
case eventsChanBuffered <- nextEvent :
default :
// We already have an event in eventsChanBuffered, so we'll do a refresh as soon as our throttle allows us to.
// It's fine to drop the event and keep whatever's in the buffer -- we don't do different things for different events
2022-11-21 17:36:05 +00:00
log . Ctx ( ctx ) . Debug ( ) . Msgf ( "Dropping event kind %T due to throttling" , nextEvent )
2020-12-15 15:40:05 +00:00
}
}
}
} )
return eventsChanBuffered
}
2021-02-02 18:36:04 +00:00
2024-01-09 09:28:05 +00:00
func isTraefikService ( ref gatev1 . BackendRef ) bool {
2021-11-09 10:34:06 +00:00
if ref . Kind == nil || ref . Group == nil {
return false
}
2023-03-22 15:40:06 +00:00
return * ref . Group == traefikv1alpha1 . GroupName && * ref . Kind == kindTraefikService
2023-03-20 14:38:08 +00:00
}
2024-01-09 09:28:05 +00:00
func isInternalService ( ref gatev1 . BackendRef ) bool {
2023-03-20 14:38:08 +00:00
return isTraefikService ( ref ) && strings . HasSuffix ( string ( ref . Name ) , "@internal" )
2021-02-02 18:36:04 +00:00
}
2022-06-23 09:58:09 +00:00
// makeListenerKey joins protocol, hostname, and port of a listener into a string key.
2024-01-09 09:28:05 +00:00
func makeListenerKey ( l gatev1 . Listener ) string {
var hostname gatev1 . Hostname
2022-06-23 09:58:09 +00:00
if l . Hostname != nil {
hostname = * l . Hostname
}
return fmt . Sprintf ( "%s|%s|%d" , l . Protocol , hostname , l . Port )
}