blob: a00ef9165b1afa99200c936ac856c63bc637a17a [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 controller
import (
"cmp"
"context"
"encoding/pem"
"errors"
"fmt"
"path"
"reflect"
"slices"
"strings"
"github.com/go-logr/logr"
"github.com/samber/lo"
corev1 "k8s.io/api/core/v1"
discoveryv1 "k8s.io/api/discovery/v1"
networkingv1 "k8s.io/api/networking/v1"
k8serrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
k8stypes "k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
"sigs.k8s.io/gateway-api/apis/v1beta1"
"github.com/apache/apisix-ingress-controller/api/v1alpha1"
apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
"github.com/apache/apisix-ingress-controller/internal/controller/config"
"github.com/apache/apisix-ingress-controller/internal/controller/indexer"
"github.com/apache/apisix-ingress-controller/internal/provider"
"github.com/apache/apisix-ingress-controller/internal/types"
"github.com/apache/apisix-ingress-controller/internal/utils"
)
const (
KindGateway = "Gateway"
KindHTTPRoute = "HTTPRoute"
KindTCPRoute = "TCPRoute"
KindUDPRoute = "UDPRoute"
KindGRPCRoute = "GRPCRoute"
KindGatewayClass = "GatewayClass"
KindIngress = "Ingress"
KindIngressClass = "IngressClass"
KindGatewayProxy = "GatewayProxy"
KindSecret = "Secret"
KindService = "Service"
KindApisixRoute = "ApisixRoute"
KindApisixGlobalRule = "ApisixGlobalRule"
KindApisixPluginConfig = "ApisixPluginConfig"
KindPod = "Pod"
KindApisixTls = "ApisixTls"
KindApisixConsumer = "ApisixConsumer"
)
const defaultIngressClassAnnotation = "ingressclass.kubernetes.io/is-default-class"
var (
ErrNoMatchingListenerHostname = errors.New("no matching hostnames in listener")
)
var (
enableReferenceGrant bool
)
func SetEnableReferenceGrant(enable bool) {
enableReferenceGrant = enable
}
func GetEnableReferenceGrant() bool {
return enableReferenceGrant
}
// IsDefaultIngressClass returns whether an IngressClass is the default IngressClass.
func IsDefaultIngressClass(obj client.Object) bool {
if ingressClass, ok := obj.(*networkingv1.IngressClass); ok {
return ingressClass.Annotations[defaultIngressClassAnnotation] == "true"
}
return false
}
func acceptedMessage(kind string) string {
return fmt.Sprintf("the %s has been accepted by the apisix-ingress-controller", kind)
}
func MergeCondition(conditions []metav1.Condition, newCondition metav1.Condition) []metav1.Condition {
if newCondition.LastTransitionTime.IsZero() {
newCondition.LastTransitionTime = metav1.Now()
}
newConditions := []metav1.Condition{}
for _, condition := range conditions {
if condition.Type != newCondition.Type {
newConditions = append(newConditions, condition)
}
}
newConditions = append(newConditions, newCondition)
return newConditions
}
func setGatewayCondition(gw *gatewayv1.Gateway, newCondition metav1.Condition) {
gw.Status.Conditions = MergeCondition(gw.Status.Conditions, newCondition)
}
func setListenerCondition(gw *gatewayv1.Gateway, listenerName string, newCondition metav1.Condition) {
for i, listener := range gw.Status.Listeners {
if listener.Name == gatewayv1.SectionName(listenerName) {
gw.Status.Listeners[i].Conditions = MergeCondition(listener.Conditions, newCondition)
return
}
}
}
func reconcileGatewaysMatchGatewayClass(gatewayClass client.Object, gateways []gatewayv1.Gateway) (recs []reconcile.Request) {
for _, gateway := range gateways {
if string(gateway.Spec.GatewayClassName) == gatewayClass.GetName() {
recs = append(recs, reconcile.Request{
NamespacedName: client.ObjectKey{
Name: gateway.GetName(),
Namespace: gateway.GetNamespace(),
},
})
}
}
return
}
func IsConditionPresentAndEqual(conditions []metav1.Condition, condition metav1.Condition) bool {
for _, cond := range conditions {
if cond.Type == condition.Type &&
cond.Reason == condition.Reason &&
cond.Status == condition.Status &&
cond.ObservedGeneration == condition.ObservedGeneration {
return true
}
}
return false
}
func SetGatewayConditionAccepted(gw *gatewayv1.Gateway, status bool, message string) (ok bool) {
condition := metav1.Condition{
Type: string(gatewayv1.GatewayConditionAccepted),
Status: ConditionStatus(status),
Reason: string(gatewayv1.GatewayReasonAccepted),
ObservedGeneration: gw.GetGeneration(),
Message: message,
LastTransitionTime: metav1.Now(),
}
if !IsConditionPresentAndEqual(gw.Status.Conditions, condition) {
setGatewayCondition(gw, condition)
ok = true
}
return
}
func SetGatewayListenerConditionAccepted(gw *gatewayv1.Gateway, listenerName string, status bool, message string) (ok bool) {
conditionStatus := metav1.ConditionTrue
if !status {
conditionStatus = metav1.ConditionFalse
}
condition := metav1.Condition{
Type: string(gatewayv1.ListenerConditionAccepted),
Status: conditionStatus,
Reason: string(gatewayv1.ListenerConditionAccepted),
ObservedGeneration: gw.GetGeneration(),
Message: message,
LastTransitionTime: metav1.Now(),
}
if !IsConditionPresentAndEqual(gw.Status.Conditions, condition) {
setListenerCondition(gw, listenerName, condition)
ok = true
}
return
}
func SetGatewayListenerConditionProgrammed(gw *gatewayv1.Gateway, listenerName string, status bool, message string) (ok bool) {
condition := metav1.Condition{
Type: string(gatewayv1.ListenerConditionProgrammed),
Status: ConditionStatus(status),
Reason: string(gatewayv1.ListenerReasonProgrammed),
ObservedGeneration: gw.GetGeneration(),
Message: message,
LastTransitionTime: metav1.Now(),
}
if !IsConditionPresentAndEqual(gw.Status.Conditions, condition) {
setListenerCondition(gw, listenerName, condition)
ok = true
}
return
}
func SetGatewayListenerConditionResolvedRefs(gw *gatewayv1.Gateway, listenerName string, status bool, message string) (ok bool) {
condition := metav1.Condition{
Type: string(gatewayv1.ListenerConditionResolvedRefs),
Status: ConditionStatus(status),
Reason: string(gatewayv1.ListenerReasonResolvedRefs),
ObservedGeneration: gw.GetGeneration(),
Message: message,
LastTransitionTime: metav1.Now(),
}
if !IsConditionPresentAndEqual(gw.Status.Conditions, condition) {
setListenerCondition(gw, listenerName, condition)
ok = true
}
return
}
func SetGatewayConditionProgrammed(gw *gatewayv1.Gateway, status bool, message string) (ok bool) {
condition := metav1.Condition{
Type: string(gatewayv1.GatewayConditionProgrammed),
Status: ConditionStatus(status),
Reason: string(gatewayv1.GatewayReasonProgrammed),
ObservedGeneration: gw.GetGeneration(),
Message: message,
LastTransitionTime: metav1.Now(),
}
if !IsConditionPresentAndEqual(gw.Status.Conditions, condition) {
setGatewayCondition(gw, condition)
ok = true
}
return
}
func ConditionStatus(status bool) metav1.ConditionStatus {
if status {
return metav1.ConditionTrue
}
return metav1.ConditionFalse
}
func SetRouteConditionAccepted(routeParentStatus *gatewayv1.RouteParentStatus, generation int64, status bool, message string) {
condition := metav1.Condition{
Type: string(gatewayv1.RouteConditionAccepted),
Status: ConditionStatus(status),
Reason: string(gatewayv1.RouteReasonAccepted),
ObservedGeneration: generation,
Message: message,
LastTransitionTime: metav1.Now(),
}
if message == ErrNoMatchingListenerHostname.Error() {
condition.Reason = string(gatewayv1.RouteReasonNoMatchingListenerHostname)
}
if !IsConditionPresentAndEqual(routeParentStatus.Conditions, condition) && !slices.ContainsFunc(routeParentStatus.Conditions, func(item metav1.Condition) bool {
return item.Type == condition.Type && item.Status == metav1.ConditionFalse && condition.Status == metav1.ConditionTrue
}) {
routeParentStatus.Conditions = MergeCondition(routeParentStatus.Conditions, condition)
}
}
// SetRouteConditionResolvedRefs sets the ResolvedRefs condition with proper reason based on error type
func SetRouteConditionResolvedRefs(routeParentStatus *gatewayv1.RouteParentStatus, generation int64, err error) {
var condition = metav1.Condition{
Type: string(gatewayv1.RouteConditionResolvedRefs),
Status: metav1.ConditionTrue,
ObservedGeneration: generation,
LastTransitionTime: metav1.Now(),
Reason: string(gatewayv1.RouteReasonResolvedRefs),
Message: "backendRefs are resolved",
}
if err != nil {
condition.Status = metav1.ConditionFalse
condition.Message = err.Error()
var re types.ReasonError
if errors.As(err, &re) {
condition.Reason = re.Reason
}
}
if !IsConditionPresentAndEqual(routeParentStatus.Conditions, condition) {
routeParentStatus.Conditions = MergeCondition(routeParentStatus.Conditions, condition)
}
}
func SetRouteParentRef(routeParentStatus *gatewayv1.RouteParentStatus, gatewayName string, namespace string) {
kind := gatewayv1.Kind(KindGateway)
group := gatewayv1.Group(gatewayv1.GroupName)
ns := gatewayv1.Namespace(namespace)
routeParentStatus.ParentRef = gatewayv1.ParentReference{
Kind: &kind,
Group: &group,
Name: gatewayv1.ObjectName(gatewayName),
Namespace: &ns,
}
routeParentStatus.ControllerName = gatewayv1.GatewayController(config.ControllerConfig.ControllerName)
}
func ParseRouteParentRefs(
ctx context.Context,
mgrc client.Client,
log logr.Logger,
route client.Object,
parentRefs []gatewayv1.ParentReference,
) ([]RouteParentRefContext, error) {
gateways := make([]RouteParentRefContext, 0)
for _, parentRef := range parentRefs {
namespace := route.GetNamespace()
if parentRef.Namespace != nil {
namespace = string(*parentRef.Namespace)
}
name := string(parentRef.Name)
if parentRef.Kind != nil && *parentRef.Kind != KindGateway {
continue
}
gateway := gatewayv1.Gateway{}
if err := mgrc.Get(ctx, client.ObjectKey{
Namespace: namespace,
Name: name,
}, &gateway); err != nil {
if client.IgnoreNotFound(err) == nil {
continue
}
return nil, fmt.Errorf("failed to retrieve gateway for route: %w", err)
}
gatewayClass := gatewayv1.GatewayClass{}
if err := mgrc.Get(ctx, client.ObjectKey{
Name: string(gateway.Spec.GatewayClassName),
}, &gatewayClass); err != nil {
if client.IgnoreNotFound(err) == nil {
continue
}
return nil, fmt.Errorf("failed to retrieve gatewayclass for gateway: %w", err)
}
if string(gatewayClass.Spec.ControllerName) != config.ControllerConfig.ControllerName {
continue
}
matched := false
reason := gatewayv1.RouteReasonNoMatchingParent
var listenerName string
var matchedListener gatewayv1.Listener
for _, listener := range gateway.Spec.Listeners {
if parentRef.SectionName != nil {
if *parentRef.SectionName != "" && *parentRef.SectionName != listener.Name {
continue
}
}
if parentRef.Port != nil {
if *parentRef.Port != listener.Port {
continue
}
}
if ok, _ := routeMatchesListenerType(route, listener); !ok {
continue
}
if !routeHostnamesIntersectsWithListenerHostname(route, listener) {
reason = gatewayv1.RouteReasonNoMatchingListenerHostname
continue
}
listenerName = string(listener.Name)
ok, err := routeMatchesListenerAllowedRoutes(ctx, mgrc, route, listener.AllowedRoutes, gateway.Namespace, parentRef.Namespace)
if err != nil {
log.Error(err, "failed matching listener to a route for gateway",
"listener", string(listener.Name),
"route", route.GetName(),
"gateway", gateway.Name)
}
if !ok {
reason = gatewayv1.RouteReasonNotAllowedByListeners
continue
}
// TODO: check if the listener status is programmed
matched = true
matchedListener = listener
break
}
if matched {
gateways = append(gateways, RouteParentRefContext{
Gateway: &gateway,
ListenerName: listenerName,
Listener: &matchedListener,
Conditions: []metav1.Condition{{
Type: string(gatewayv1.RouteConditionAccepted),
Status: metav1.ConditionTrue,
Reason: string(gatewayv1.RouteReasonAccepted),
ObservedGeneration: route.GetGeneration(),
}},
})
} else {
gateways = append(gateways, RouteParentRefContext{
Gateway: &gateway,
ListenerName: listenerName,
Listener: &matchedListener,
Conditions: []metav1.Condition{{
Type: string(gatewayv1.RouteConditionAccepted),
Status: metav1.ConditionFalse,
Reason: string(reason),
ObservedGeneration: route.GetGeneration(),
}},
})
}
}
return gateways, nil
}
func SetApisixCRDConditionAccepted(status *apiv2.ApisixStatus, generation int64, err error) {
var condition = metav1.Condition{
Type: string(apiv2.ConditionTypeAccepted),
Status: metav1.ConditionTrue,
ObservedGeneration: generation,
LastTransitionTime: metav1.Now(),
Reason: string(apiv2.ConditionReasonAccepted),
}
if err != nil {
condition.Status = metav1.ConditionFalse
condition.Reason = string(apiv2.ConditionReasonInvalidSpec)
condition.Message = err.Error()
var re types.ReasonError
if errors.As(err, &re) {
condition.Reason = re.Reason
}
}
status.Conditions = []metav1.Condition{condition}
}
func checkRouteAcceptedByListener(
ctx context.Context,
mgrc client.Client,
route client.Object,
gateway gatewayv1.Gateway,
listener gatewayv1.Listener,
parentRef gatewayv1.ParentReference,
) (bool, gatewayv1.RouteConditionReason, error) {
if parentRef.SectionName != nil {
if *parentRef.SectionName != "" && *parentRef.SectionName != listener.Name {
return false, gatewayv1.RouteReasonNoMatchingParent, nil
}
}
if parentRef.Port != nil {
if *parentRef.Port != listener.Port {
return false, gatewayv1.RouteReasonNoMatchingParent, nil
}
}
if ok, err := routeMatchesListenerType(route, listener); !ok {
return false, gatewayv1.RouteReasonNoMatchingParent, err
}
if !routeHostnamesIntersectsWithListenerHostname(route, listener) {
return false, gatewayv1.RouteReasonNoMatchingListenerHostname, nil
}
if ok, err := routeMatchesListenerAllowedRoutes(ctx, mgrc, route, listener.AllowedRoutes, gateway.Namespace, parentRef.Namespace); err != nil {
return false, gatewayv1.RouteReasonNotAllowedByListeners, fmt.Errorf("failed matching listener %s to a route %s for gateway %s: %w",
listener.Name, route.GetName(), gateway.Name, err,
)
} else if !ok {
return false, gatewayv1.RouteReasonNotAllowedByListeners, nil
}
return true, gatewayv1.RouteReasonAccepted, nil
}
func routeHostnamesIntersectsWithListenerHostname(route client.Object, listener gatewayv1.Listener) bool {
switch r := route.(type) {
case *gatewayv1.HTTPRoute:
return listenerHostnameIntersectWithRouteHostnames(listener, r.Spec.Hostnames)
case *gatewayv1alpha2.TCPRoute, *gatewayv1alpha2.UDPRoute:
return true // TCPRoute and UDPRoute don't have Hostnames to match
case *gatewayv1.GRPCRoute:
return listenerHostnameIntersectWithRouteHostnames(listener, r.Spec.Hostnames)
case *gatewayv1alpha2.TLSRoute:
return listenerHostnameIntersectWithRouteHostnames(listener, r.Spec.Hostnames)
default:
return false
}
}
func listenerHostnameIntersectWithRouteHostnames(listener gatewayv1.Listener, hostnames []gatewayv1.Hostname) bool {
if len(hostnames) == 0 {
return true
}
// if the listener has no hostname, all hostnames automatically intersect
if listener.Hostname == nil || *listener.Hostname == "" {
return true
}
// iterate over all the hostnames and check that at least one intersect with the listener hostname
for _, hostname := range hostnames {
if HostnamesIntersect(string(*listener.Hostname), string(hostname)) {
return true
}
}
return false
}
func HostnamesIntersect(a, b string) bool {
return HostnamesMatch(a, b) || HostnamesMatch(b, a)
}
// HostnamesMatch checks that the hostnameB matches the hostnameA. HostnameA is treated as mask
// to be checked against the hostnameB.
func HostnamesMatch(hostnameA, hostnameB string) bool {
// the hostnames are in the form of "foo.bar.com"; split them
// in a slice of substrings
hostnameALabels := strings.Split(hostnameA, ".")
hostnameBLabels := strings.Split(hostnameB, ".")
var a, b int
var wildcard bool
// iterate over the parts of both the hostnames
for a, b = 0, 0; a < len(hostnameALabels) && b < len(hostnameBLabels); a, b = a+1, b+1 {
var matchFound bool
// if the current part of B is a wildcard, we need to find the first
// A part that matches with the following B part
if wildcard {
for ; b < len(hostnameBLabels); b++ {
if hostnameALabels[a] == hostnameBLabels[b] {
matchFound = true
break
}
}
}
// if no match was found, the hostnames don't match
if wildcard && !matchFound {
return false
}
// check if at least on of the current parts are a wildcard; if so, continue
if hostnameALabels[a] == "*" {
wildcard = true
continue
}
// reset the wildcard variables
wildcard = false
// if the current a part is different from the b part, the hostnames are incompatible
if hostnameALabels[a] != hostnameBLabels[b] {
return false
}
}
return len(hostnameBLabels)-b == len(hostnameALabels)-a
}
func routeMatchesListenerAllowedRoutes(
ctx context.Context,
mgrc client.Client,
route client.Object,
allowedRoutes *gatewayv1.AllowedRoutes,
gatewayNamespace string,
parentRefNamespace *gatewayv1.Namespace,
) (bool, error) {
if allowedRoutes == nil {
return true, nil
}
if !isRouteKindAllowed(route, allowedRoutes.Kinds) {
return false, fmt.Errorf("route %s/%s is not allowed in the kind", route.GetNamespace(), route.GetName())
}
if !isRouteNamespaceAllowed(ctx, route, mgrc, gatewayNamespace, parentRefNamespace, allowedRoutes.Namespaces) {
return false, fmt.Errorf("route %s/%s is not allowed in the namespace", route.GetNamespace(), route.GetName())
}
return true, nil
}
func isRouteKindAllowed(route client.Object, kinds []gatewayv1.RouteGroupKind) (ok bool) {
ok = true
if len(kinds) > 0 {
_, ok = lo.Find(kinds, func(rgk gatewayv1.RouteGroupKind) bool {
gvk := route.GetObjectKind().GroupVersionKind()
return (rgk.Group != nil && string(*rgk.Group) == gvk.Group) && string(rgk.Kind) == gvk.Kind
})
}
return
}
func isRouteNamespaceAllowed(
ctx context.Context,
route client.Object,
mgrc client.Client,
gatewayNamespace string,
parentRefNamespace *gatewayv1.Namespace,
routeNamespaces *gatewayv1.RouteNamespaces,
) bool {
if routeNamespaces == nil || routeNamespaces.From == nil {
return true
}
switch *routeNamespaces.From {
case gatewayv1.NamespacesFromAll:
return true
case gatewayv1.NamespacesFromSame:
if parentRefNamespace == nil {
return gatewayNamespace == route.GetNamespace()
}
return route.GetNamespace() == string(*parentRefNamespace)
case gatewayv1.NamespacesFromSelector:
namespace := corev1.Namespace{}
if err := mgrc.Get(ctx, client.ObjectKey{Name: route.GetNamespace()}, &namespace); err != nil {
return false
}
s, err := metav1.LabelSelectorAsSelector(routeNamespaces.Selector)
if err != nil {
return false
}
return s.Matches(labels.Set(namespace.Labels))
default:
return true
}
}
func routeMatchesListenerType(route client.Object, listener gatewayv1.Listener) (bool, error) {
switch route.(type) {
case *gatewayv1.HTTPRoute, *gatewayv1.GRPCRoute:
if listener.Protocol != gatewayv1.HTTPProtocolType && listener.Protocol != gatewayv1.HTTPSProtocolType {
return false, nil
}
if listener.Protocol == gatewayv1.HTTPSProtocolType {
if listener.TLS == nil {
return false, nil
}
if listener.TLS.Mode != nil && *listener.TLS.Mode != gatewayv1.TLSModeTerminate {
return false, nil
}
}
case *gatewayv1alpha2.TCPRoute:
if listener.Protocol != gatewayv1.TCPProtocolType {
return false, nil
}
case *gatewayv1alpha2.UDPRoute:
if listener.Protocol != gatewayv1.UDPProtocolType {
return false, nil
}
case *gatewayv1alpha2.TLSRoute:
if listener.Protocol != gatewayv1.TLSProtocolType {
return false, nil
}
default:
return false, fmt.Errorf("unsupported route type %T", route)
}
return true, nil
}
func getAttachedRoutesForListener(ctx context.Context, mgrc client.Client, gateway gatewayv1.Gateway, listener gatewayv1.Listener) (int32, error) {
routes := []types.RouteAdapter{}
routeList := []client.ObjectList{}
listOption := client.MatchingFields{
indexer.ParentRefs: indexer.GenIndexKey(gateway.Namespace, gateway.Name),
}
if listener.AllowedRoutes != nil && listener.AllowedRoutes.Kinds != nil {
for _, rgk := range listener.AllowedRoutes.Kinds {
if rgk.Group != nil && *rgk.Group != gatewayv1.GroupName {
continue
}
switch rgk.Kind {
case types.KindHTTPRoute:
routeList = append(routeList, &gatewayv1.HTTPRouteList{})
case types.KindGRPCRoute:
routeList = append(routeList, &gatewayv1.GRPCRouteList{})
case types.KindTCPRoute:
routeList = append(routeList, &gatewayv1alpha2.TCPRouteList{})
case types.KindUDPRoute:
routeList = append(routeList, &gatewayv1alpha2.UDPRouteList{})
case types.KindTLSRoute:
routeList = append(routeList, &gatewayv1alpha2.TLSRouteList{})
}
}
} else {
switch listener.Protocol {
case gatewayv1.HTTPProtocolType, gatewayv1.HTTPSProtocolType:
routeList = append(routeList, &gatewayv1.HTTPRouteList{}, &gatewayv1.GRPCRouteList{})
case gatewayv1.TCPProtocolType:
routeList = append(routeList, &gatewayv1alpha2.TCPRouteList{})
case gatewayv1.UDPProtocolType:
routeList = append(routeList, &gatewayv1alpha2.UDPRouteList{})
case gatewayv1.TLSProtocolType:
routeList = append(routeList, &gatewayv1alpha2.TLSRouteList{})
}
}
for _, rl := range routeList {
if err := mgrc.List(ctx, rl, listOption); err != nil {
return 0, fmt.Errorf("failed to list %T: %w", rl, err)
}
routes = append(routes, types.NewRouteListAdapter(rl)...)
}
var attachedRoutes int32
for _, route := range routes {
if !checkStatusParent(route.GetParentStatuses(), route.GetNamespace(), gateway) {
continue
}
for _, parentRef := range route.GetParentRefs() {
ok, _, err := checkRouteAcceptedByListener(
ctx,
mgrc,
route.GetObject(),
gateway,
listener,
parentRef,
)
if err != nil {
return 0, err
}
if ok {
attachedRoutes++
}
}
}
return attachedRoutes, nil
}
func checkStatusParent(parents []gatewayv1.RouteParentStatus, routeNamespace string, gateway gatewayv1.Gateway) bool {
return lo.ContainsBy(parents, func(parentStatus gatewayv1.RouteParentStatus) bool {
parentRef := parentStatus.ParentRef
if parentRef.Group != nil && *parentRef.Group != gatewayv1.GroupName {
return false
}
if parentRef.Kind != nil && *parentRef.Kind != KindGateway {
return false
}
gatewayNamespace := routeNamespace
if parentRef.Namespace != nil {
gatewayNamespace = string(*parentRef.Namespace)
}
return gateway.Namespace == gatewayNamespace && gateway.Name == string(parentRef.Name)
})
}
func getListenerStatus(
ctx context.Context,
mrgc client.Client,
gateway *gatewayv1.Gateway,
) ([]gatewayv1.ListenerStatus, error) {
statusArray := make([]gatewayv1.ListenerStatus, 0, len(gateway.Spec.Listeners))
for i, listener := range gateway.Spec.Listeners {
attachedRoutes, err := getAttachedRoutesForListener(ctx, mrgc, *gateway, listener)
if err != nil {
return nil, err
}
var (
now = metav1.Now()
conditionProgrammed = metav1.Condition{
Type: string(gatewayv1.ListenerConditionProgrammed),
Status: metav1.ConditionTrue,
ObservedGeneration: gateway.GetGeneration(),
LastTransitionTime: now,
Reason: string(gatewayv1.ListenerReasonProgrammed),
}
conditionAccepted = metav1.Condition{
Type: string(gatewayv1.ListenerConditionAccepted),
Status: metav1.ConditionTrue,
ObservedGeneration: gateway.GetGeneration(),
LastTransitionTime: now,
Reason: string(gatewayv1.ListenerReasonAccepted),
}
conditionConflicted = metav1.Condition{
Type: string(gatewayv1.ListenerConditionConflicted),
Status: metav1.ConditionTrue,
ObservedGeneration: gateway.GetGeneration(),
LastTransitionTime: now,
Reason: string(gatewayv1.ListenerReasonNoConflicts),
}
conditionResolvedRefs = metav1.Condition{
Type: string(gatewayv1.ListenerConditionResolvedRefs),
Status: metav1.ConditionTrue,
ObservedGeneration: gateway.GetGeneration(),
LastTransitionTime: now,
Reason: string(gatewayv1.ListenerReasonResolvedRefs),
}
supportedKinds = []gatewayv1.RouteGroupKind{}
)
if listener.AllowedRoutes == nil || listener.AllowedRoutes.Kinds == nil {
group := gatewayv1.Group(gatewayv1.GroupName)
supportedKinds = []gatewayv1.RouteGroupKind{}
switch listener.Protocol {
case gatewayv1.TLSProtocolType:
supportedKinds = append(supportedKinds, gatewayv1.RouteGroupKind{
Group: &group,
Kind: types.KindTLSRoute,
})
case gatewayv1.TCPProtocolType:
supportedKinds = append(supportedKinds, gatewayv1.RouteGroupKind{
Group: &group,
Kind: types.KindTCPRoute,
})
case gatewayv1.UDPProtocolType:
supportedKinds = append(supportedKinds, gatewayv1.RouteGroupKind{
Group: &group,
Kind: types.KindUDPRoute,
})
case gatewayv1.HTTPProtocolType, gatewayv1.HTTPSProtocolType:
supportedKinds = append(supportedKinds, []gatewayv1.RouteGroupKind{
{
Group: &group,
Kind: types.KindGRPCRoute,
},
{
Group: &group,
Kind: types.KindHTTPRoute,
},
}...)
}
} else {
for _, kind := range listener.AllowedRoutes.Kinds {
if kind.Group != nil && *kind.Group != gatewayv1.GroupName {
conditionResolvedRefs.Status = metav1.ConditionFalse
conditionResolvedRefs.Reason = string(gatewayv1.ListenerReasonInvalidRouteKinds)
continue
}
switch kind.Kind {
case KindHTTPRoute, types.KindGRPCRoute, types.KindTLSRoute, types.KindTCPRoute, types.KindUDPRoute:
supportedKinds = append(supportedKinds, kind)
default:
conditionResolvedRefs.Status = metav1.ConditionFalse
conditionResolvedRefs.Reason = string(gatewayv1.ListenerReasonInvalidRouteKinds)
}
}
}
if listener.TLS != nil {
// TODO: support TLS
var (
secret corev1.Secret
)
for _, ref := range listener.TLS.CertificateRefs {
if ref.Group != nil && *ref.Group != corev1.GroupName {
conditionResolvedRefs.Status = metav1.ConditionFalse
conditionResolvedRefs.Reason = string(gatewayv1.ListenerReasonInvalidCertificateRef)
conditionResolvedRefs.Message = fmt.Sprintf(`Invalid Group, expect "", got "%s"`, *ref.Group)
conditionProgrammed.Status = metav1.ConditionFalse
conditionProgrammed.Reason = string(gatewayv1.ListenerReasonInvalid)
break
}
if ref.Kind != nil && *ref.Kind != KindSecret {
conditionResolvedRefs.Status = metav1.ConditionFalse
conditionResolvedRefs.Reason = string(gatewayv1.ListenerReasonInvalidCertificateRef)
conditionResolvedRefs.Message = fmt.Sprintf(`Invalid Kind, expect "Secret", got "%s"`, *ref.Kind)
conditionProgrammed.Status = metav1.ConditionFalse
conditionProgrammed.Reason = string(gatewayv1.ListenerReasonInvalid)
break
}
if permitted := checkReferenceGrant(ctx,
mrgc,
v1beta1.ReferenceGrantFrom{
Group: gatewayv1.GroupName,
Kind: KindGateway,
Namespace: v1beta1.Namespace(gateway.Namespace),
},
gatewayv1.ObjectReference{
Group: corev1.GroupName,
Kind: KindSecret,
Name: ref.Name,
Namespace: ref.Namespace,
},
); !permitted {
conditionResolvedRefs.Status = metav1.ConditionFalse
conditionResolvedRefs.Reason = string(gatewayv1.ListenerReasonRefNotPermitted)
conditionResolvedRefs.Message = "certificateRefs cross namespaces is not permitted"
conditionProgrammed.Status = metav1.ConditionFalse
conditionProgrammed.Reason = string(gatewayv1.ListenerReasonInvalid)
break
}
secretNN := k8stypes.NamespacedName{
Namespace: string(*cmp.Or(ref.Namespace, (*gatewayv1.Namespace)(&gateway.Namespace))),
Name: string(ref.Name),
}
if err := mrgc.Get(ctx, secretNN, &secret); err != nil {
conditionResolvedRefs.Status = metav1.ConditionFalse
conditionResolvedRefs.Reason = string(gatewayv1.ListenerReasonInvalidCertificateRef)
conditionResolvedRefs.Message = err.Error()
conditionProgrammed.Status = metav1.ConditionFalse
conditionProgrammed.Reason = string(gatewayv1.ListenerReasonInvalid)
break
}
if cause, ok := isTLSSecretValid(&secret); !ok {
conditionResolvedRefs.Status = metav1.ConditionFalse
conditionResolvedRefs.Reason = string(gatewayv1.ListenerReasonInvalidCertificateRef)
conditionResolvedRefs.Message = fmt.Sprintf("Malformed Secret referenced: %s", cause)
conditionProgrammed.Status = metav1.ConditionFalse
conditionProgrammed.Reason = string(gatewayv1.ListenerReasonInvalid)
break
}
}
}
status := gatewayv1.ListenerStatus{
Name: listener.Name,
Conditions: []metav1.Condition{
conditionProgrammed,
conditionAccepted,
conditionConflicted,
conditionResolvedRefs,
},
SupportedKinds: supportedKinds,
AttachedRoutes: attachedRoutes,
}
changed := false
if len(gateway.Status.Listeners) > i {
if gateway.Status.Listeners[i].AttachedRoutes != attachedRoutes {
changed = true
}
for _, condition := range status.Conditions {
if !IsConditionPresentAndEqual(gateway.Status.Listeners[i].Conditions, condition) {
changed = true
break
}
}
} else {
changed = true
}
if !changed {
status = gateway.Status.Listeners[i]
}
statusArray = append(statusArray, status)
}
return statusArray, nil
}
// SplitMetaNamespaceKey returns the namespace and name that
// MetaNamespaceKeyFunc encoded into key.
func SplitMetaNamespaceKey(key string) (namespace, name string, err error) {
parts := strings.Split(key, "/")
switch len(parts) {
case 1:
// name only, no namespace
return "", parts[0], nil
case 2:
// namespace and name
return parts[0], parts[1], nil
}
return "", "", fmt.Errorf("unexpected key format: %q", key)
}
func ProcessGatewayProxy(r client.Client, log logr.Logger, tctx *provider.TranslateContext, gateway *gatewayv1.Gateway, rk types.NamespacedNameKind) error {
if gateway == nil {
return nil
}
infra := gateway.Spec.Infrastructure
if infra == nil || infra.ParametersRef == nil {
return nil
}
gatewayKind := utils.NamespacedNameKind(gateway)
ns := gateway.GetNamespace()
paramRef := infra.ParametersRef
if string(paramRef.Group) == v1alpha1.GroupVersion.Group && string(paramRef.Kind) == KindGatewayProxy {
gatewayProxy := &v1alpha1.GatewayProxy{}
if err := r.Get(context.Background(), client.ObjectKey{
Namespace: ns,
Name: paramRef.Name,
}, gatewayProxy); err != nil {
log.Error(err, "failed to get GatewayProxy", "namespace", ns, "name", paramRef.Name)
return err
} else {
log.Info("found GatewayProxy for Gateway", "namespace", gateway.Namespace, "name", gateway.Name)
tctx.GatewayProxies[gatewayKind] = *gatewayProxy
tctx.ResourceParentRefs[rk] = append(tctx.ResourceParentRefs[rk], gatewayKind)
// Process provider secrets if provider exists
if prov := gatewayProxy.Spec.Provider; prov != nil && prov.Type == v1alpha1.ProviderTypeControlPlane {
if cp := prov.ControlPlane; cp != nil {
if cp.Auth.Type == v1alpha1.AuthTypeAdminKey &&
cp.Auth.AdminKey != nil &&
cp.Auth.AdminKey.ValueFrom != nil &&
cp.Auth.AdminKey.ValueFrom.SecretKeyRef != nil {
secretRef := cp.Auth.AdminKey.ValueFrom.SecretKeyRef
secret := &corev1.Secret{}
if err := r.Get(context.Background(), client.ObjectKey{
Namespace: ns,
Name: secretRef.Name,
}, secret); err != nil {
log.Error(err, "failed to get secret for GatewayProxy provider",
"namespace", ns,
"name", secretRef.Name)
return err
}
log.Info("found secret for GatewayProxy provider", "gateway", gateway.Name, "gatewayproxy", gatewayProxy.Name, "secret", secretRef.Name)
tctx.Secrets[k8stypes.NamespacedName{
Namespace: ns,
Name: secretRef.Name,
}] = secret
}
if cp.Service != nil {
if err := addProviderEndpointsToTranslateContext(tctx, r, log, k8stypes.NamespacedName{
Namespace: gatewayProxy.GetNamespace(),
Name: cp.Service.Name,
}); err != nil {
return err
}
}
}
}
}
}
_, ok := tctx.GatewayProxies[gatewayKind]
if !ok {
return fmt.Errorf("no gateway proxy found for gateway: %s", gateway.Name)
}
return nil
}
// FullTypeName returns the fully qualified name of the type of the given value.
func FullTypeName(a any) string {
typeOf := reflect.TypeOf(a)
pkgPath := typeOf.PkgPath()
name := typeOf.String()
if typeOf.Kind() == reflect.Ptr {
pkgPath = typeOf.Elem().PkgPath()
}
return path.Join(path.Dir(pkgPath), name)
}
// filterHostnames accepts a list of gateways and an HTTPRoute, and returns a copy of the HTTPRoute with only the hostnames that match the listener hostnames of the gateways.
// If the HTTPRoute hostnames do not intersect with the listener hostnames of the gateways, it returns an ErrNoMatchingListenerHostname error.
func filterHostnames(gateways []RouteParentRefContext, httpRoute *gatewayv1.HTTPRoute) (*gatewayv1.HTTPRoute, error) {
filteredHostnames := make([]gatewayv1.Hostname, 0)
// If the HTTPRoute does not specify hostnames, we use the union of the listener hostnames of all supported gateways
// If any supported listener does not specify a hostname, the HTTPRoute hostnames remain empty to match any hostname
if len(httpRoute.Spec.Hostnames) == 0 {
hostnames, matchAnyHost := getUnionOfGatewayHostnames(gateways)
if matchAnyHost {
return httpRoute, nil
}
filteredHostnames = hostnames
} else {
// If the HTTPRoute specifies hostnames, we need to find the intersection with the gateway listener hostnames
for _, hostname := range httpRoute.Spec.Hostnames {
if hostnameMatching := getMinimumHostnameIntersection(gateways, hostname); hostnameMatching != "" {
filteredHostnames = append(filteredHostnames, hostnameMatching)
}
}
if len(filteredHostnames) == 0 {
return httpRoute, ErrNoMatchingListenerHostname
}
}
httpRoute.Spec.Hostnames = filteredHostnames
return httpRoute, nil
}
// getUnionOfGatewayHostnames returns the union of the hostnames specified in all supported gateways
// The second return value indicates whether any listener can match any hostname
func getUnionOfGatewayHostnames(gateways []RouteParentRefContext) ([]gatewayv1.Hostname, bool) {
hostnames := make([]gatewayv1.Hostname, 0)
for _, gateway := range gateways {
if gateway.ListenerName != "" {
// If a listener name is specified, only check that listener
for _, listener := range gateway.Gateway.Spec.Listeners {
if string(listener.Name) == gateway.ListenerName {
// If a listener does not specify a hostname, it can match any hostname
if listener.Hostname == nil {
return nil, true
}
hostnames = append(hostnames, *listener.Hostname)
break
}
}
} else {
// Otherwise, check all listeners
for _, listener := range gateway.Gateway.Spec.Listeners {
// Only consider listeners that can effectively configure hostnames (HTTP, HTTPS, or TLS)
if isListenerHostnameEffective(listener) {
if listener.Hostname == nil {
return nil, true
}
hostnames = append(hostnames, *listener.Hostname)
}
}
}
}
return hostnames, false
}
// getMinimumHostnameIntersection returns the smallest intersection hostname
// - If the listener hostname is empty, return the HTTPRoute hostname
// - If the listener hostname is a wildcard of the HTTPRoute hostname, return the HTTPRoute hostname
// - If the HTTPRoute hostname is a wildcard of the listener hostname, return the listener hostname
// - If the HTTPRoute hostname and listener hostname are the same, return it
// - If none of the above, return an empty string
func getMinimumHostnameIntersection(gateways []RouteParentRefContext, hostname gatewayv1.Hostname) gatewayv1.Hostname {
for _, gateway := range gateways {
for _, listener := range gateway.Gateway.Spec.Listeners {
// If a listener name is specified, only check that listener
// If the listener name is not specified, check all listeners
if gateway.ListenerName == "" || gateway.ListenerName == string(listener.Name) {
if listener.Hostname == nil || *listener.Hostname == "" {
return hostname
}
if HostnamesMatch(string(*listener.Hostname), string(hostname)) {
return hostname
}
if HostnamesMatch(string(hostname), string(*listener.Hostname)) {
return *listener.Hostname
}
}
}
}
return ""
}
// isListenerHostnameEffective checks if a listener can specify a hostname to match the hostname in the request
// Basically, check if the listener uses HTTP, HTTPS, or TLS protocol
func isListenerHostnameEffective(listener gatewayv1.Listener) bool {
return listener.Protocol == gatewayv1.HTTPProtocolType ||
listener.Protocol == gatewayv1.HTTPSProtocolType ||
listener.Protocol == gatewayv1.TLSProtocolType
}
func isRouteAccepted(gateways []RouteParentRefContext) bool {
for _, gateway := range gateways {
for _, condition := range gateway.Conditions {
if condition.Type == string(gatewayv1.RouteConditionAccepted) && condition.Status == metav1.ConditionTrue {
return true
}
}
}
return false
}
func isTLSSecretValid(secret *corev1.Secret) (string, bool) {
var ok bool
var crt, key []byte
if crt, ok = secret.Data["tls.crt"]; !ok {
return "Missing tls.crt", false
}
if key, ok = secret.Data["tls.key"]; !ok {
return "Missing tls.key", false
}
if p, _ := pem.Decode(crt); p == nil {
return "Malformed PEM tls.crt", false
}
if p, _ := pem.Decode(key); p == nil {
return "Malformed PEM tls.key", false
}
return "", true
}
func referenceGrantPredicates(kind gatewayv1.Kind) predicate.Funcs {
var filter = func(obj client.Object) bool {
grant, ok := obj.(*v1beta1.ReferenceGrant)
if !ok {
return false
}
for _, from := range grant.Spec.From {
if from.Kind == kind && string(from.Group) == gatewayv1.GroupName {
return true
}
}
return false
}
predicates := predicate.NewPredicateFuncs(filter)
predicates.UpdateFunc = func(e event.UpdateEvent) bool {
return filter(e.ObjectOld) || filter(e.ObjectNew)
}
return predicates
}
func checkReferenceGrant(ctx context.Context, cli client.Client, obj v1beta1.ReferenceGrantFrom, ref gatewayv1.ObjectReference) bool {
if ref.Namespace == nil || *ref.Namespace == obj.Namespace {
return true
}
if !GetEnableReferenceGrant() {
return false
}
var grantList v1beta1.ReferenceGrantList
if err := cli.List(ctx, &grantList, client.InNamespace(*ref.Namespace)); err != nil {
return false
}
for _, grant := range grantList.Items {
if grant.Namespace == string(*ref.Namespace) {
for _, from := range grant.Spec.From {
if obj == from {
for _, to := range grant.Spec.To {
if to.Group == ref.Group && to.Kind == ref.Kind && (to.Name == nil || *to.Name == ref.Name) {
return true
}
}
}
}
}
}
return false
}
func ListRequests(
ctx context.Context,
c client.Client,
logger logr.Logger,
listObj client.ObjectList,
opts ...client.ListOption,
) []reconcile.Request {
return ListMatchingRequests(
ctx,
c,
logger,
listObj,
func(obj client.Object) bool {
return true
},
opts...,
)
}
func ListMatchingRequests(
ctx context.Context,
c client.Client,
logger logr.Logger,
listObj client.ObjectList,
matchFunc func(obj client.Object) bool,
opts ...client.ListOption,
) []reconcile.Request {
if err := c.List(ctx, listObj, opts...); err != nil {
logger.Error(err, "failed to list resource")
return nil
}
items, err := meta.ExtractList(listObj)
if err != nil {
logger.Error(err, "failed to extract list items")
return nil
}
var requests []reconcile.Request
for _, item := range items {
obj, ok := item.(client.Object)
if !ok {
continue
}
if matchFunc(obj) {
requests = append(requests, reconcile.Request{
NamespacedName: utils.NamespacedName(obj),
})
}
}
return requests
}
func listIngressClassRequestsForGatewayProxy(
ctx context.Context,
c client.Client,
obj client.Object,
logger logr.Logger,
listFunc func(context.Context, client.Object) []reconcile.Request,
) []reconcile.Request {
gatewayProxy, ok := obj.(*v1alpha1.GatewayProxy)
if !ok {
return nil
}
ingressClassList := &networkingv1.IngressClassList{}
if err := c.List(ctx, ingressClassList, client.MatchingFields{
indexer.IngressClassParametersRef: indexer.GenIndexKey(gatewayProxy.GetNamespace(), gatewayProxy.GetName()),
}); err != nil {
logger.Error(err, "failed to list ingress classes for gateway proxy", "gatewayproxy", gatewayProxy.GetName())
return nil
}
requestSet := make(map[string]reconcile.Request)
for _, ingressClass := range ingressClassList.Items {
for _, req := range listFunc(ctx, &ingressClass) {
requestSet[req.String()] = req
}
}
requests := make([]reconcile.Request, 0, len(requestSet))
for _, req := range requestSet {
requests = append(requests, req)
}
return requests
}
func matchesIngressController(obj client.Object) bool {
ingressClass, ok := obj.(*networkingv1.IngressClass)
if !ok {
return false
}
return matchesController(ingressClass.Spec.Controller)
}
func ProcessIngressClassParameters(tctx *provider.TranslateContext, c client.Client, log logr.Logger, object client.Object, ingressClass *networkingv1.IngressClass) error {
if ingressClass == nil || ingressClass.Spec.Parameters == nil {
return nil
}
ingressClassKind := utils.NamespacedNameKind(ingressClass)
objKind := utils.NamespacedNameKind(object)
parameters := ingressClass.Spec.Parameters
// check if the parameters reference GatewayProxy
if parameters.APIGroup != nil && *parameters.APIGroup == v1alpha1.GroupVersion.Group && parameters.Kind == KindGatewayProxy {
ns := utils.GetIngressClassParametersNamespace(*ingressClass)
gatewayProxy := &v1alpha1.GatewayProxy{}
if err := c.Get(tctx, client.ObjectKey{
Namespace: ns,
Name: parameters.Name,
}, gatewayProxy); err != nil {
log.Error(err, "failed to get GatewayProxy", "namespace", ns, "name", parameters.Name)
return err
}
log.V(1).Info("found GatewayProxy for IngressClass", "ingressClass", ingressClass.Name, "gatewayproxy", gatewayProxy.Name)
tctx.GatewayProxies[ingressClassKind] = *gatewayProxy
tctx.ResourceParentRefs[objKind] = append(tctx.ResourceParentRefs[objKind], ingressClassKind)
// check if the provider field references a secret
if gatewayProxy.Spec.Provider != nil && gatewayProxy.Spec.Provider.Type == v1alpha1.ProviderTypeControlPlane {
if cp := gatewayProxy.Spec.Provider.ControlPlane; cp != nil {
// process control plane provider auth
if cp.Auth.Type == v1alpha1.AuthTypeAdminKey &&
cp.Auth.AdminKey != nil &&
cp.Auth.AdminKey.ValueFrom != nil &&
cp.Auth.AdminKey.ValueFrom.SecretKeyRef != nil {
secretRef := cp.Auth.AdminKey.ValueFrom.SecretKeyRef
secret := &corev1.Secret{}
if err := c.Get(tctx, client.ObjectKey{
Namespace: ns,
Name: secretRef.Name,
}, secret); err != nil {
log.Error(err, "failed to get secret for GatewayProxy provider",
"namespace", ns,
"name", secretRef.Name)
return err
}
log.Info("found secret for GatewayProxy provider",
"ingressClass", ingressClass.Name,
"gatewayproxy", gatewayProxy.Name,
"secret", secretRef.Name)
tctx.Secrets[k8stypes.NamespacedName{
Namespace: ns,
Name: secretRef.Name,
}] = secret
}
// process control plane provider service
if cp.Service != nil {
if err := addProviderEndpointsToTranslateContext(tctx, c, log, client.ObjectKey{
Namespace: gatewayProxy.GetNamespace(),
Name: cp.Service.Name,
}); err != nil {
return err
}
}
}
}
}
return nil
}
func FindMatchingIngressClass(ctx context.Context, c client.Client, log logr.Logger, obj client.Object) (*networkingv1.IngressClass, error) {
ingressClassName := ExtractIngressClass(obj)
return FindMatchingIngressClassByName(ctx, c, log, ingressClassName)
}
func FindMatchingIngressClassByName(ctx context.Context, c client.Client, log logr.Logger, ingressClassName string) (*networkingv1.IngressClass, error) {
if ingressClassName == "" {
// Check for default ingress class
ingressClassList := &networkingv1.IngressClassList{}
if err := c.List(ctx, ingressClassList, client.MatchingFields{
indexer.IngressClass: config.GetControllerName(),
}); err != nil {
log.Error(err, "failed to list ingress classes")
return nil, err
}
// Find the ingress class that is marked as default
for _, ic := range ingressClassList.Items {
if IsDefaultIngressClass(&ic) && matchesController(ic.Spec.Controller) {
return &ic, nil
}
}
return nil, errors.New("no default ingress class found")
}
// Check if the specified ingress class is controlled by us
var ingressClass networkingv1.IngressClass
if err := c.Get(ctx, client.ObjectKey{Name: ingressClassName}, &ingressClass); err != nil {
return nil, err
}
if matchesController(ingressClass.Spec.Controller) {
return &ingressClass, nil
}
return nil, errors.New("ingress class is not controlled by us")
}
// distinctRequests distinct the requests
func distinctRequests(requests []reconcile.Request) []reconcile.Request {
uniqueRequests := make(map[string]reconcile.Request)
for _, request := range requests {
uniqueRequests[request.String()] = request
}
distinctRequests := make([]reconcile.Request, 0, len(uniqueRequests))
for _, request := range uniqueRequests {
distinctRequests = append(distinctRequests, request)
}
return distinctRequests
}
func addProviderEndpointsToTranslateContext(tctx *provider.TranslateContext, c client.Client, log logr.Logger, serviceNN k8stypes.NamespacedName) error {
log.V(1).Info("to process provider endpoints by provider.service", "service", serviceNN)
var (
service corev1.Service
)
if err := c.Get(tctx, serviceNN, &service); err != nil {
log.Error(err, "failed to get service from GatewayProxy provider", "service", serviceNN)
return err
}
tctx.Services[serviceNN] = &service
// get es
var (
esList discoveryv1.EndpointSliceList
)
if err := c.List(tctx, &esList,
client.InNamespace(serviceNN.Namespace),
client.MatchingLabels{
discoveryv1.LabelServiceName: serviceNN.Name,
}); err != nil {
log.Error(err, "failed to get endpoints for GatewayProxy provider", "endpoints", serviceNN)
return err
}
tctx.EndpointSlices[serviceNN] = esList.Items
return nil
}
func TypePredicate[T client.Object]() func(obj client.Object) bool {
return func(obj client.Object) bool {
_, ok := obj.(T)
return ok
}
}
func MatchConsumerGatewayRef(ctx context.Context, c client.Client, log logr.Logger, consumer *v1alpha1.Consumer) bool {
if consumer.Spec.GatewayRef.Name == "" {
return false
}
if consumer.Spec.GatewayRef.Kind != nil && *consumer.Spec.GatewayRef.Kind != KindGateway {
return false
}
if consumer.Spec.GatewayRef.Group != nil && *consumer.Spec.GatewayRef.Group != gatewayv1.GroupName {
return false
}
ns := consumer.GetNamespace()
if consumer.Spec.GatewayRef.Namespace != nil {
ns = *consumer.Spec.GatewayRef.Namespace
}
gateway := &gatewayv1.Gateway{}
if err := c.Get(context.Background(), client.ObjectKey{
Name: consumer.Spec.GatewayRef.Name,
Namespace: ns,
}, gateway); err != nil {
if !k8serrors.IsNotFound(err) {
log.Error(err, "failed to get gateway", "gateway", consumer.Spec.GatewayRef.Name)
}
return false
}
gatewayClass := &gatewayv1.GatewayClass{}
if err := c.Get(context.Background(), client.ObjectKey{Name: string(gateway.Spec.GatewayClassName)}, gatewayClass); err != nil {
if !k8serrors.IsNotFound(err) {
log.Error(err, "failed to get gateway class", "gateway", gateway.GetName(), "gatewayclass", gateway.Spec.GatewayClassName)
}
return false
}
return matchesController(string(gatewayClass.Spec.ControllerName))
}
func GetGatewayProxyByIngressClass(ctx context.Context, r client.Client, ingressClass *networkingv1.IngressClass) (*v1alpha1.GatewayProxy, error) {
if ingressClass.Spec.Parameters == nil {
return nil, nil
}
if ingressClass.Spec.Parameters.APIGroup == nil ||
*ingressClass.Spec.Parameters.APIGroup != v1alpha1.GroupVersion.Group ||
ingressClass.Spec.Parameters.Kind != KindGatewayProxy {
return nil, nil
}
namespace := utils.GetIngressClassParametersNamespace(*ingressClass)
gatewayProxy := new(v1alpha1.GatewayProxy)
if err := r.Get(ctx, client.ObjectKey{
Namespace: namespace,
Name: ingressClass.Spec.Parameters.Name,
}, gatewayProxy); err != nil {
return nil, fmt.Errorf("failed to get gateway proxy: %w", err)
}
return gatewayProxy, nil
}
func GetGatewayProxyByGateway(ctx context.Context, r client.Client, gateway *gatewayv1.Gateway) (*v1alpha1.GatewayProxy, error) {
if gateway == nil {
return nil, nil
}
infra := gateway.Spec.Infrastructure
if infra == nil || infra.ParametersRef == nil {
return nil, nil
}
ns := gateway.GetNamespace()
paramRef := infra.ParametersRef
if string(paramRef.Group) != v1alpha1.GroupVersion.Group || string(paramRef.Kind) != KindGatewayProxy {
return nil, nil
}
gatewayProxy := &v1alpha1.GatewayProxy{}
if err := r.Get(context.Background(), client.ObjectKey{
Namespace: ns,
Name: paramRef.Name,
}, gatewayProxy); err != nil {
return nil, fmt.Errorf("failed to get GatewayProxy: %w", err)
}
return gatewayProxy, nil
}
func MatchesIngressClassPredicate(c client.Client, log logr.Logger) predicate.Funcs {
predicateFuncs := predicate.NewPredicateFuncs(func(obj client.Object) bool {
return MatchesIngressClass(c, log, obj)
})
predicateFuncs.UpdateFunc = func(e event.UpdateEvent) bool {
return MatchesIngressClass(c, log, e.ObjectOld) || MatchesIngressClass(c, log, e.ObjectNew)
}
return predicateFuncs
}
func MatchesIngressClass(c client.Client, log logr.Logger, obj client.Object) bool {
_, err := FindMatchingIngressClass(context.Background(), c, log, obj)
return err == nil
}
func ExtractIngressClass(obj client.Object) string {
switch v := obj.(type) {
case *networkingv1.Ingress:
return types.GetEffectiveIngressClassName(v)
case *apiv2.ApisixConsumer:
return v.Spec.IngressClassName
case *apiv2.ApisixRoute:
return v.Spec.IngressClassName
case *apiv2.ApisixTls:
return v.Spec.IngressClassName
case *apiv2.ApisixPluginConfig:
return v.Spec.IngressClassName
case *apiv2.ApisixUpstream:
return v.Spec.IngressClassName
case *apiv2.ApisixGlobalRule:
return v.Spec.IngressClassName
default:
panic(fmt.Errorf("unhandled object type %T for extracting ingress class", obj))
}
}