Support for cross-namespace references / GatewayAPI ReferenceGrants

This commit is contained in:
Pascal Hofmann 2024-01-30 16:44:05 +01:00 committed by GitHub
parent 8b77f0c2dd
commit 9be523d772
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 940 additions and 67 deletions

View file

@ -153,17 +153,16 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
RequiredConsecutiveSuccesses: 0, RequiredConsecutiveSuccesses: 0,
}, },
SupportedFeatures: sets.New[ksuite.SupportedFeature](). SupportedFeatures: sets.New[ksuite.SupportedFeature]().
Insert(ksuite.GatewayCoreFeatures.UnsortedList()...), Insert(ksuite.GatewayCoreFeatures.UnsortedList()...).
Insert(ksuite.ReferenceGrantCoreFeatures.UnsortedList()...),
EnableAllSupportedFeatures: false, EnableAllSupportedFeatures: false,
RunTest: *k8sConformanceRunTest, RunTest: *k8sConformanceRunTest,
// Until the feature are all supported, following tests are skipped. // Until the feature are all supported, following tests are skipped.
SkipTests: []string{ SkipTests: []string{
"HTTPExactPathMatching", "HTTPExactPathMatching",
"HTTPRouteHostnameIntersection", "HTTPRouteHostnameIntersection",
"GatewaySecretReferenceGrantAllInNamespace",
"HTTPRouteListenerHostnameMatching", "HTTPRouteListenerHostnameMatching",
"HTTPRouteRequestHeaderModifier", "HTTPRouteRequestHeaderModifier",
"GatewaySecretInvalidReferenceGrant",
"GatewayClassObservedGenerationBump", "GatewayClassObservedGenerationBump",
"HTTPRouteInvalidNonExistentBackendRef", "HTTPRouteInvalidNonExistentBackendRef",
"GatewayWithAttachedRoutes", "GatewayWithAttachedRoutes",
@ -171,14 +170,11 @@ func (s *K8sConformanceSuite) TestK8sGatewayAPIConformance() {
"HTTPRouteDisallowedKind", "HTTPRouteDisallowedKind",
"HTTPRouteInvalidReferenceGrant", "HTTPRouteInvalidReferenceGrant",
"HTTPRouteObservedGenerationBump", "HTTPRouteObservedGenerationBump",
"GatewayInvalidRouteKind",
"TLSRouteSimpleSameNamespace", "TLSRouteSimpleSameNamespace",
"TLSRouteInvalidReferenceGrant", "TLSRouteInvalidReferenceGrant",
"HTTPRouteInvalidCrossNamespaceParentRef", "HTTPRouteInvalidCrossNamespaceParentRef",
"HTTPRouteInvalidParentRefNotMatchingSectionName", "HTTPRouteInvalidParentRefNotMatchingSectionName",
"GatewaySecretReferenceGrantSpecific",
"GatewayModifyListeners", "GatewayModifyListeners",
"GatewaySecretMissingReferenceGrant",
"GatewayInvalidTLSConfiguration", "GatewayInvalidTLSConfiguration",
"HTTPRouteInvalidCrossNamespaceBackendRef", "HTTPRouteInvalidCrossNamespaceBackendRef",
"HTTPRouteMatchingAcrossRoutes", "HTTPRouteMatchingAcrossRoutes",

View file

@ -19,6 +19,7 @@ import (
"k8s.io/client-go/tools/clientcmd" "k8s.io/client-go/tools/clientcmd"
gatev1 "sigs.k8s.io/gateway-api/apis/v1" gatev1 "sigs.k8s.io/gateway-api/apis/v1"
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
gateclientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned" gateclientset "sigs.k8s.io/gateway-api/pkg/client/clientset/versioned"
gateinformers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions" gateinformers "sigs.k8s.io/gateway-api/pkg/client/informers/externalversions"
) )
@ -34,13 +35,7 @@ func (reh *resourceEventHandler) OnAdd(obj interface{}, isInInitialList bool) {
} }
func (reh *resourceEventHandler) OnUpdate(oldObj, newObj interface{}) { func (reh *resourceEventHandler) OnUpdate(oldObj, newObj interface{}) {
switch oldObj.(type) { eventHandlerFunc(reh.ev, newObj)
case *gatev1.GatewayClass:
// Skip update for gateway classes. We only manage addition or deletion for this cluster-wide resource.
return
default:
eventHandlerFunc(reh.ev, newObj)
}
} }
func (reh *resourceEventHandler) OnDelete(obj interface{}) { func (reh *resourceEventHandler) OnDelete(obj interface{}) {
@ -59,6 +54,7 @@ type Client interface {
GetHTTPRoutes(namespaces []string) ([]*gatev1.HTTPRoute, error) GetHTTPRoutes(namespaces []string) ([]*gatev1.HTTPRoute, error)
GetTCPRoutes(namespaces []string) ([]*gatev1alpha2.TCPRoute, error) GetTCPRoutes(namespaces []string) ([]*gatev1alpha2.TCPRoute, error)
GetTLSRoutes(namespaces []string) ([]*gatev1alpha2.TLSRoute, error) GetTLSRoutes(namespaces []string) ([]*gatev1alpha2.TLSRoute, error)
GetReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error)
GetService(namespace, name string) (*corev1.Service, bool, error) GetService(namespace, name string) (*corev1.Service, bool, error)
GetSecret(namespace, name string) (*corev1.Secret, bool, error) GetSecret(namespace, name string) (*corev1.Secret, bool, error)
GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error) GetEndpoints(namespace, name string) (*corev1.Endpoints, bool, error)
@ -189,9 +185,6 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
return nil, err return nil, err
} }
// TODO manage Reference Policy
// https://gateway-api.sigs.k8s.io/v1alpha2/references/spec/#gateway.networking.k8s.io/v1alpha2.ReferencePolicy
for _, ns := range namespaces { for _, ns := range namespaces {
factoryGateway := gateinformers.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, gateinformers.WithNamespace(ns)) factoryGateway := gateinformers.NewSharedInformerFactoryWithOptions(c.csGateway, resyncPeriod, gateinformers.WithNamespace(ns))
_, err = factoryGateway.Gateway().V1().Gateways().Informer().AddEventHandler(eventHandler) _, err = factoryGateway.Gateway().V1().Gateways().Informer().AddEventHandler(eventHandler)
@ -210,6 +203,10 @@ func (c *clientWrapper) WatchAll(namespaces []string, stopCh <-chan struct{}) (<
if err != nil { if err != nil {
return nil, err return nil, err
} }
_, err = factoryGateway.Gateway().V1beta1().ReferenceGrants().Informer().AddEventHandler(eventHandler)
if err != nil {
return nil, err
}
factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns)) factoryKube := kinformers.NewSharedInformerFactoryWithOptions(c.csKube, resyncPeriod, kinformers.WithNamespace(ns))
_, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler) _, err = factoryKube.Core().V1().Services().Informer().AddEventHandler(eventHandler)
@ -363,6 +360,21 @@ func (c *clientWrapper) GetTLSRoutes(namespaces []string) ([]*gatev1alpha2.TLSRo
return tlsRoutes, nil return tlsRoutes, nil
} }
func (c *clientWrapper) GetReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error) {
if !c.isWatchedNamespace(namespace) {
log.Warn().Msgf("Failed to get ReferenceGrants: %q is not within watched namespaces", namespace)
return nil, fmt.Errorf("failed to get ReferenceGrants: namespace %s is not within watched namespaces", namespace)
}
referenceGrants, err := c.factoriesGateway[c.lookupNamespace(namespace)].Gateway().V1beta1().ReferenceGrants().Lister().ReferenceGrants(namespace).List(labels.Everything())
if err != nil {
return nil, err
}
return referenceGrants, nil
}
func (c *clientWrapper) GetGateways() []*gatev1.Gateway { func (c *clientWrapper) GetGateways() []*gatev1.Gateway {
var result []*gatev1.Gateway var result []*gatev1.Gateway
@ -388,7 +400,7 @@ func (c *clientWrapper) UpdateGatewayClassStatus(gatewayClass *gatev1.GatewayCla
var newConditions []metav1.Condition var newConditions []metav1.Condition
for _, cond := range gc.Status.Conditions { for _, cond := range gc.Status.Conditions {
// No update for identical condition. // No update for identical condition.
if cond.Type == condition.Type && cond.Status == condition.Status { if cond.Type == condition.Type && cond.Status == condition.Status && cond.ObservedGeneration == condition.ObservedGeneration {
return nil return nil
} }
@ -470,7 +482,7 @@ func conditionsEquals(conditionsA, conditionsB []metav1.Condition) bool {
for _, conditionA := range conditionsA { for _, conditionA := range conditionsA {
for _, conditionB := range conditionsB { for _, conditionB := range conditionsB {
if conditionA.Type == conditionB.Type { if conditionA.Type == conditionB.Type {
if conditionA.Reason != conditionB.Reason || conditionA.Status != conditionB.Status || conditionA.Message != conditionB.Message { if conditionA.Reason != conditionB.Reason || conditionA.Status != conditionB.Status || conditionA.Message != conditionB.Message || conditionA.ObservedGeneration != conditionB.ObservedGeneration {
return false return false
} }
conditionMatches++ conditionMatches++

View file

@ -12,6 +12,7 @@ import (
kscheme "k8s.io/client-go/kubernetes/scheme" kscheme "k8s.io/client-go/kubernetes/scheme"
gatev1 "sigs.k8s.io/gateway-api/apis/v1" gatev1 "sigs.k8s.io/gateway-api/apis/v1"
gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2" gatev1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
) )
var _ Client = (*clientMock)(nil) var _ Client = (*clientMock)(nil)
@ -23,6 +24,11 @@ func init() {
panic(err) panic(err)
} }
err = gatev1beta1.AddToScheme(kscheme.Scheme)
if err != nil {
panic(err)
}
err = gatev1.AddToScheme(kscheme.Scheme) err = gatev1.AddToScheme(kscheme.Scheme)
if err != nil { if err != nil {
panic(err) panic(err)
@ -39,11 +45,12 @@ type clientMock struct {
apiSecretError error apiSecretError error
apiEndpointsError error apiEndpointsError error
gatewayClasses []*gatev1.GatewayClass gatewayClasses []*gatev1.GatewayClass
gateways []*gatev1.Gateway gateways []*gatev1.Gateway
httpRoutes []*gatev1.HTTPRoute httpRoutes []*gatev1.HTTPRoute
tcpRoutes []*gatev1alpha2.TCPRoute tcpRoutes []*gatev1alpha2.TCPRoute
tlsRoutes []*gatev1alpha2.TLSRoute tlsRoutes []*gatev1alpha2.TLSRoute
referenceGrants []*gatev1beta1.ReferenceGrant
watchChan chan interface{} watchChan chan interface{}
} }
@ -78,6 +85,8 @@ func newClientMock(paths ...string) clientMock {
c.tcpRoutes = append(c.tcpRoutes, o) c.tcpRoutes = append(c.tcpRoutes, o)
case *gatev1alpha2.TLSRoute: case *gatev1alpha2.TLSRoute:
c.tlsRoutes = append(c.tlsRoutes, o) c.tlsRoutes = append(c.tlsRoutes, o)
case *gatev1beta1.ReferenceGrant:
c.referenceGrants = append(c.referenceGrants, o)
default: default:
panic(fmt.Sprintf("Unknown runtime object %+v %T", o, o)) panic(fmt.Sprintf("Unknown runtime object %+v %T", o, o))
} }
@ -190,6 +199,16 @@ func (c clientMock) GetTLSRoutes(namespaces []string) ([]*gatev1alpha2.TLSRoute,
return tlsRoutes, nil return tlsRoutes, nil
} }
func (c clientMock) GetReferenceGrants(namespace string) ([]*gatev1beta1.ReferenceGrant, error) {
var referenceGrants []*gatev1beta1.ReferenceGrant
for _, referenceGrant := range c.referenceGrants {
if inNamespace(referenceGrant.ObjectMeta, namespace) {
referenceGrants = append(referenceGrants, referenceGrant)
}
}
return referenceGrants, nil
}
func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, error) { func (c clientMock) GetService(namespace, name string) (*corev1.Service, bool, error) {
if c.apiServiceError != nil { if c.apiServiceError != nil {
return nil, false, c.apiServiceError return nil, false, c.apiServiceError

View file

@ -0,0 +1,78 @@
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: secret-from-default
namespace: secret-namespace
spec:
from:
- group: gateway.networking.k8s.io
kind: Gateway
namespace: default
to:
- group: ""
kind: Secret
---
apiVersion: v1
kind: Secret
metadata:
name: supersecret
namespace: secret-namespace
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
---
kind: GatewayClass
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway-class
spec:
controllerName: traefik.io/gateway-controller
---
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: my-gateway-class
listeners: # Use GatewayClass defaults for listener definition.
- name: tls
protocol: TLS
port: 9000
hostname: foo.example.com
tls:
mode: Terminate # Default mode
certificateRefs:
- kind: Secret
name: supersecret
namespace: secret-namespace
group: ""
allowedRoutes:
kinds:
- kind: TCPRoute
group: gateway.networking.k8s.io
namespaces:
from: Same
---
kind: TCPRoute
apiVersion: gateway.networking.k8s.io/v1alpha2
metadata:
name: tcp-app-1
namespace: default
spec:
parentRefs:
- name: my-gateway
kind: Gateway
group: gateway.networking.k8s.io
rules:
- backendRefs:
- name: whoamitcp
port: 9000
weight: 1
kind: Service
group: ""

View file

@ -0,0 +1,64 @@
---
apiVersion: v1
kind: Secret
metadata:
name: supersecret
namespace: secret-namespace
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
---
kind: GatewayClass
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway-class
spec:
controllerName: traefik.io/gateway-controller
---
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: my-gateway-class
listeners: # Use GatewayClass defaults for listener definition.
- name: tls
protocol: TLS
port: 9000
hostname: foo.example.com
tls:
mode: Terminate # Default mode
certificateRefs:
- kind: Secret
name: supersecret
namespace: secret-namespace
group: ""
allowedRoutes:
kinds:
- kind: TCPRoute
group: gateway.networking.k8s.io
namespaces:
from: Same
---
kind: TCPRoute
apiVersion: gateway.networking.k8s.io/v1alpha2
metadata:
name: tcp-app-1
namespace: default
spec:
parentRefs:
- name: my-gateway
kind: Gateway
group: gateway.networking.k8s.io
rules:
- backendRefs:
- name: whoamitcp
port: 9000
weight: 1
kind: Service
group: ""

View file

@ -0,0 +1,78 @@
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: secret-from-default
namespace: secret-namespace
spec:
from:
- group: gateway.networking.k8s.io
kind: Gateway
namespace: differentnamespace
to:
- group: ""
kind: Secret
---
apiVersion: v1
kind: Secret
metadata:
name: supersecret
namespace: secret-namespace
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
---
kind: GatewayClass
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway-class
spec:
controllerName: traefik.io/gateway-controller
---
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: my-gateway-class
listeners: # Use GatewayClass defaults for listener definition.
- name: tls
protocol: TLS
port: 9000
hostname: foo.example.com
tls:
mode: Terminate # Default mode
certificateRefs:
- kind: Secret
name: supersecret
namespace: secret-namespace
group: ""
allowedRoutes:
kinds:
- kind: TCPRoute
group: gateway.networking.k8s.io
namespaces:
from: Same
---
kind: TCPRoute
apiVersion: gateway.networking.k8s.io/v1alpha2
metadata:
name: tcp-app-1
namespace: default
spec:
parentRefs:
- name: my-gateway
kind: Gateway
group: gateway.networking.k8s.io
rules:
- backendRefs:
- name: whoamitcp
port: 9000
weight: 1
kind: Service
group: ""

View file

@ -0,0 +1,79 @@
---
apiVersion: gateway.networking.k8s.io/v1beta1
kind: ReferenceGrant
metadata:
name: secret-from-default
namespace: secret-namespace
spec:
from:
- group: gateway.networking.k8s.io
kind: Gateway
namespace: default
to:
- group: ""
kind: Secret
name: differentsecret
---
apiVersion: v1
kind: Secret
metadata:
name: supersecret
namespace: secret-namespace
data:
tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0=
tls.key: LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCi0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0=
---
kind: GatewayClass
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway-class
spec:
controllerName: traefik.io/gateway-controller
---
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1
metadata:
name: my-gateway
namespace: default
spec:
gatewayClassName: my-gateway-class
listeners: # Use GatewayClass defaults for listener definition.
- name: tls
protocol: TLS
port: 9000
hostname: foo.example.com
tls:
mode: Terminate # Default mode
certificateRefs:
- kind: Secret
name: supersecret
namespace: secret-namespace
group: ""
allowedRoutes:
kinds:
- kind: TCPRoute
group: gateway.networking.k8s.io
namespaces:
from: Same
---
kind: TCPRoute
apiVersion: gateway.networking.k8s.io/v1alpha2
metadata:
name: tcp-app-1
namespace: default
spec:
parentRefs:
- name: my-gateway
kind: Gateway
group: gateway.networking.k8s.io
rules:
- backendRefs:
- name: whoamitcp
port: 9000
weight: 1
kind: Service
group: ""

View file

@ -34,11 +34,14 @@ import (
"k8s.io/utils/ptr" "k8s.io/utils/ptr"
"k8s.io/utils/strings/slices" "k8s.io/utils/strings/slices"
gatev1 "sigs.k8s.io/gateway-api/apis/v1" gatev1 "sigs.k8s.io/gateway-api/apis/v1"
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
) )
const ( const (
providerName = "kubernetesgateway" providerName = "kubernetesgateway"
groupCore = "core"
kindGateway = "Gateway" kindGateway = "Gateway"
kindTraefikService = "TraefikService" kindTraefikService = "TraefikService"
kindHTTPRoute = "HTTPRoute" kindHTTPRoute = "HTTPRoute"
@ -348,6 +351,7 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *
Name: listener.Name, Name: listener.Name,
SupportedKinds: []gatev1.RouteGroupKind{}, SupportedKinds: []gatev1.RouteGroupKind{},
Conditions: []metav1.Condition{}, Conditions: []metav1.Condition{},
// AttachedRoutes: 0 TODO Set to number of Routes associated with a Listener regardless of Gateway or Route status
} }
supportedKinds, conditions := supportedRouteKinds(listener.Protocol) supportedKinds, conditions := supportedRouteKinds(listener.Protocol)
@ -356,9 +360,8 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *
continue continue
} }
listenerStatuses[i].SupportedKinds = supportedKinds
routeKinds, conditions := getAllowedRouteKinds(gateway, listener, supportedKinds) routeKinds, conditions := getAllowedRouteKinds(gateway, listener, supportedKinds)
listenerStatuses[i].SupportedKinds = routeKinds
if len(conditions) > 0 { if len(conditions) > 0 {
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, conditions...) listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, conditions...)
continue continue
@ -474,7 +477,7 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *
certificateRef := listener.TLS.CertificateRefs[0] certificateRef := listener.TLS.CertificateRefs[0]
if certificateRef.Kind == nil || *certificateRef.Kind != "Secret" || if certificateRef.Kind == nil || *certificateRef.Kind != "Secret" ||
certificateRef.Group == nil || (*certificateRef.Group != "" && *certificateRef.Group != "core") { certificateRef.Group == nil || (*certificateRef.Group != "" && *certificateRef.Group != groupCore) {
// update "ResolvedRefs" status true with "InvalidCertificateRef" reason // update "ResolvedRefs" status true with "InvalidCertificateRef" reason
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{
Type: string(gatev1.ListenerConditionResolvedRefs), Type: string(gatev1.ListenerConditionResolvedRefs),
@ -482,43 +485,74 @@ func (p *Provider) fillGatewayConf(ctx context.Context, client Client, gateway *
ObservedGeneration: gateway.Generation, ObservedGeneration: gateway.Generation,
LastTransitionTime: metav1.Now(), LastTransitionTime: metav1.Now(),
Reason: string(gatev1.ListenerReasonInvalidCertificateRef), Reason: string(gatev1.ListenerReasonInvalidCertificateRef),
Message: fmt.Sprintf("Unsupported TLS CertificateRef group/kind: %v/%v", certificateRef.Group, certificateRef.Kind), Message: fmt.Sprintf("Unsupported TLS CertificateRef group/kind: %s/%s", groupToString(certificateRef.Group), kindToString(certificateRef.Kind)),
}) })
continue continue
} }
// TODO Support ReferencePolicy to support cross namespace references. certificateNamespace := gateway.Namespace
if certificateRef.Namespace != nil && string(*certificateRef.Namespace) != gateway.Namespace { if certificateRef.Namespace != nil && string(*certificateRef.Namespace) != gateway.Namespace {
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ certificateNamespace = string(*certificateRef.Namespace)
Type: string(gatev1.ListenerConditionResolvedRefs),
Status: metav1.ConditionFalse,
ObservedGeneration: gateway.Generation,
LastTransitionTime: metav1.Now(),
Reason: string(gatev1.ListenerReasonInvalidCertificateRef),
Message: "Cross namespace secrets are not supported",
})
continue
} }
configKey := gateway.Namespace + "/" + string(certificateRef.Name) if certificateNamespace != gateway.Namespace {
if _, tlsExists := tlsConfigs[configKey]; !tlsExists { referenceGrants, err := client.GetReferenceGrants(certificateNamespace)
tlsConf, err := getTLS(client, certificateRef.Name, gateway.Namespace)
if err != nil { if err != nil {
// update "ResolvedRefs" status true with "InvalidCertificateRef" reason
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{
Type: string(gatev1.ListenerConditionResolvedRefs), Type: string(gatev1.ListenerConditionResolvedRefs),
Status: metav1.ConditionFalse, Status: metav1.ConditionFalse,
ObservedGeneration: gateway.Generation, ObservedGeneration: gateway.Generation,
LastTransitionTime: metav1.Now(), LastTransitionTime: metav1.Now(),
Reason: string(gatev1.ListenerReasonInvalidCertificateRef), Reason: string(gatev1.ListenerReasonRefNotPermitted),
Message: fmt.Sprintf("Error while retrieving certificate: %v", err), Message: fmt.Sprintf("Cannot find any ReferenceGrant: %v", err),
})
continue
}
referenceGrants = filterReferenceGrantsFrom(referenceGrants, "gateway.networking.k8s.io", "Gateway", gateway.Namespace)
referenceGrants = filterReferenceGrantsTo(referenceGrants, groupCore, "Secret", string(certificateRef.Name))
if len(referenceGrants) == 0 {
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{
Type: string(gatev1.ListenerConditionResolvedRefs),
Status: metav1.ConditionFalse,
ObservedGeneration: gateway.Generation,
LastTransitionTime: metav1.Now(),
Reason: string(gatev1.ListenerReasonRefNotPermitted),
Message: "Required ReferenceGrant for cross namespace secret reference is missing",
}) })
continue continue
} }
}
configKey := certificateNamespace + "/" + string(certificateRef.Name)
if _, tlsExists := tlsConfigs[configKey]; !tlsExists {
tlsConf, err := getTLS(client, certificateRef.Name, certificateNamespace)
if err != nil {
// update "ResolvedRefs" status false with "InvalidCertificateRef" reason
// update "Programmed" status false with "Invalid" reason
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions,
metav1.Condition{
Type: string(gatev1.ListenerConditionResolvedRefs),
Status: metav1.ConditionFalse,
ObservedGeneration: gateway.Generation,
LastTransitionTime: metav1.Now(),
Reason: string(gatev1.ListenerReasonInvalidCertificateRef),
Message: fmt.Sprintf("Error while retrieving certificate: %v", err),
},
metav1.Condition{
Type: string(gatev1.ListenerConditionProgrammed),
Status: metav1.ConditionFalse,
ObservedGeneration: gateway.Generation,
LastTransitionTime: metav1.Now(),
Reason: string(gatev1.ListenerReasonInvalid),
Message: fmt.Sprintf("Error while retrieving certificate: %v", err),
},
)
continue
}
tlsConfigs[configKey] = tlsConf tlsConfigs[configKey] = tlsConf
} }
} }
@ -548,15 +582,32 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listenerStatuses [
var result error var result error
for i, listener := range listenerStatuses { for i, listener := range listenerStatuses {
if len(listener.Conditions) == 0 { if len(listener.Conditions) == 0 {
// GatewayConditionReady "Ready", GatewayConditionReason "ListenerReady" listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions,
listenerStatuses[i].Conditions = append(listenerStatuses[i].Conditions, metav1.Condition{ metav1.Condition{
Type: string(gatev1.ListenerReasonAccepted), Type: string(gatev1.ListenerConditionAccepted),
Status: metav1.ConditionTrue, Status: metav1.ConditionTrue,
ObservedGeneration: gateway.Generation, ObservedGeneration: gateway.Generation,
LastTransitionTime: metav1.Now(), LastTransitionTime: metav1.Now(),
Reason: "ListenerReady", Reason: string(gatev1.ListenerReasonAccepted),
Message: "No error found", Message: "No error found",
}) },
metav1.Condition{
Type: string(gatev1.ListenerConditionResolvedRefs),
Status: metav1.ConditionTrue,
ObservedGeneration: gateway.Generation,
LastTransitionTime: metav1.Now(),
Reason: string(gatev1.ListenerReasonResolvedRefs),
Message: "No error found",
},
metav1.Condition{
Type: string(gatev1.ListenerConditionProgrammed),
Status: metav1.ConditionTrue,
ObservedGeneration: gateway.Generation,
LastTransitionTime: metav1.Now(),
Reason: string(gatev1.ListenerReasonProgrammed),
Message: "No error found",
},
)
continue continue
} }
@ -565,6 +616,7 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listenerStatuses [
result = multierror.Append(result, errors.New(condition.Message)) result = multierror.Append(result, errors.New(condition.Message))
} }
} }
gatewayStatus.Listeners = listenerStatuses
if result != nil { if result != nil {
// GatewayConditionReady "Ready", GatewayConditionReason "ListenersNotValid" // GatewayConditionReady "Ready", GatewayConditionReason "ListenersNotValid"
@ -580,8 +632,6 @@ func (p *Provider) makeGatewayStatus(gateway *gatev1.Gateway, listenerStatuses [
return gatewayStatus, result return gatewayStatus, result
} }
gatewayStatus.Listeners = listenerStatuses
gatewayStatus.Conditions = append(gatewayStatus.Conditions, gatewayStatus.Conditions = append(gatewayStatus.Conditions,
// update "Accepted" status with "Accepted" reason // update "Accepted" status with "Accepted" reason
metav1.Condition{ metav1.Condition{
@ -656,7 +706,7 @@ func getAllowedRouteKinds(gateway *gatev1.Gateway, listener gatev1.Listener, sup
} }
var ( var (
routeKinds []gatev1.RouteGroupKind routeKinds = []gatev1.RouteGroupKind{}
conditions []metav1.Condition conditions []metav1.Condition
) )
@ -672,12 +722,12 @@ func getAllowedRouteKinds(gateway *gatev1.Gateway, listener gatev1.Listener, sup
if !isSupported { if !isSupported {
conditions = append(conditions, metav1.Condition{ conditions = append(conditions, metav1.Condition{
Type: string(gatev1.ListenerConditionAccepted), Type: string(gatev1.ListenerConditionResolvedRefs),
Status: metav1.ConditionTrue, Status: metav1.ConditionFalse,
ObservedGeneration: gateway.Generation, ObservedGeneration: gateway.Generation,
LastTransitionTime: metav1.Now(), LastTransitionTime: metav1.Now(),
Reason: string(gatev1.ListenerReasonInvalidRouteKinds), Reason: string(gatev1.ListenerReasonInvalidRouteKinds),
Message: fmt.Sprintf("Listener protocol %q does not support RouteGroupKind %v/%s", listener.Protocol, routeKind.Group, routeKind.Kind), Message: fmt.Sprintf("Listener protocol %q does not support RouteGroupKind %s/%s", listener.Protocol, groupToString(routeKind.Group), routeKind.Kind),
}) })
continue continue
} }
@ -712,7 +762,7 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li
routes, err := client.GetHTTPRoutes(namespaces) routes, err := client.GetHTTPRoutes(namespaces)
if err != nil { if err != nil {
// update "ResolvedRefs" status true with "InvalidRoutesRef" reason // update "ResolvedRefs" status true with "RefNotPermitted" reason
return []metav1.Condition{{ return []metav1.Condition{{
Type: string(gatev1.ListenerConditionResolvedRefs), Type: string(gatev1.ListenerConditionResolvedRefs),
Status: metav1.ConditionFalse, Status: metav1.ConditionFalse,
@ -757,7 +807,7 @@ func (p *Provider) gatewayHTTPRouteToHTTPConf(ctx context.Context, ep string, li
for _, routeRule := range route.Spec.Rules { for _, routeRule := range route.Spec.Rules {
rule, err := extractRule(routeRule, hostRule) rule, err := extractRule(routeRule, hostRule)
if err != nil { if err != nil {
// update "ResolvedRefs" status true with "DroppedRoutes" reason // update "ResolvedRefs" status true with "UnsupportedPathOrHeaderType" reason
conditions = append(conditions, metav1.Condition{ conditions = append(conditions, metav1.Condition{
Type: string(gatev1.ListenerConditionResolvedRefs), Type: string(gatev1.ListenerConditionResolvedRefs),
Status: metav1.ConditionFalse, Status: metav1.ConditionFalse,
@ -1048,7 +1098,7 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1.Li
Type: string(gatev1.GatewayClassConditionStatusAccepted), Type: string(gatev1.GatewayClassConditionStatusAccepted),
Status: metav1.ConditionFalse, Status: metav1.ConditionFalse,
ObservedGeneration: gateway.Generation, ObservedGeneration: gateway.Generation,
Reason: string(gatev1.ListenerConditionConflicted), Reason: string(gatev1.ListenerReasonHostnameConflict),
Message: fmt.Sprintf("No hostname match between listener: %v and route: %v", listener.Hostname, route.Spec.Hostnames), Message: fmt.Sprintf("No hostname match between listener: %v and route: %v", listener.Hostname, route.Spec.Hostnames),
LastTransitionTime: metav1.Now(), LastTransitionTime: metav1.Now(),
}) })
@ -1059,7 +1109,7 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1.Li
rule, err := hostSNIRule(hostnames) rule, err := hostSNIRule(hostnames)
if err != nil { if err != nil {
// update "ResolvedRefs" status true with "DroppedRoutes" reason // update "ResolvedRefs" status true with "InvalidHostnames" reason
conditions = append(conditions, metav1.Condition{ conditions = append(conditions, metav1.Condition{
Type: string(gatev1.ListenerConditionResolvedRefs), Type: string(gatev1.ListenerConditionResolvedRefs),
Status: metav1.ConditionFalse, Status: metav1.ConditionFalse,
@ -1111,7 +1161,7 @@ func gatewayTLSRouteToTCPConf(ctx context.Context, ep string, listener gatev1.Li
wrrService, subServices, err := loadTCPServices(client, route.Namespace, routeRule.BackendRefs) wrrService, subServices, err := loadTCPServices(client, route.Namespace, routeRule.BackendRefs)
if err != nil { if err != nil {
// update "ResolvedRefs" status true with "DroppedRoutes" reason // update "ResolvedRefs" status true with "InvalidBackendRefs" reason
conditions = append(conditions, metav1.Condition{ conditions = append(conditions, metav1.Condition{
Type: string(gatev1.ListenerConditionResolvedRefs), Type: string(gatev1.ListenerConditionResolvedRefs),
Status: metav1.ConditionFalse, Status: metav1.ConditionFalse,
@ -1526,7 +1576,7 @@ func loadServices(client Client, namespace string, backendRefs []gatev1.HTTPBack
continue continue
} }
if *backendRef.Group != "" && *backendRef.Group != "core" && *backendRef.Kind != "Service" { if *backendRef.Group != "" && *backendRef.Group != groupCore && *backendRef.Kind != "Service" {
return nil, nil, fmt.Errorf("unsupported HTTPBackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name) return nil, nil, fmt.Errorf("unsupported HTTPBackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
} }
@ -1649,7 +1699,7 @@ func loadTCPServices(client Client, namespace string, backendRefs []gatev1.Backe
continue continue
} }
if *backendRef.Group != "" && *backendRef.Group != "core" && *backendRef.Kind != "Service" { if *backendRef.Group != "" && *backendRef.Group != groupCore && *backendRef.Kind != "Service" {
return nil, nil, fmt.Errorf("unsupported BackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name) return nil, nil, fmt.Errorf("unsupported BackendRef %s/%s/%s", *backendRef.Group, *backendRef.Kind, backendRef.Name)
} }
@ -1880,3 +1930,65 @@ func makeListenerKey(l gatev1.Listener) string {
return fmt.Sprintf("%s|%s|%d", l.Protocol, hostname, l.Port) return fmt.Sprintf("%s|%s|%d", l.Protocol, hostname, l.Port)
} }
func filterReferenceGrantsFrom(referenceGrants []*gatev1beta1.ReferenceGrant, group, kind, namespace string) []*gatev1beta1.ReferenceGrant {
var matchingReferenceGrants []*gatev1beta1.ReferenceGrant
for _, referenceGrant := range referenceGrants {
if referenceGrantMatchesFrom(referenceGrant, group, kind, namespace) {
matchingReferenceGrants = append(matchingReferenceGrants, referenceGrant)
}
}
return matchingReferenceGrants
}
func referenceGrantMatchesFrom(referenceGrant *gatev1beta1.ReferenceGrant, group, kind, namespace string) bool {
for _, from := range referenceGrant.Spec.From {
sanitizedGroup := string(from.Group)
if sanitizedGroup == "" {
sanitizedGroup = groupCore
}
if string(from.Namespace) != namespace || string(from.Kind) != kind || sanitizedGroup != group {
continue
}
return true
}
return false
}
func filterReferenceGrantsTo(referenceGrants []*gatev1beta1.ReferenceGrant, group, kind, name string) []*gatev1beta1.ReferenceGrant {
var matchingReferenceGrants []*gatev1beta1.ReferenceGrant
for _, referenceGrant := range referenceGrants {
if referenceGrantMatchesTo(referenceGrant, group, kind, name) {
matchingReferenceGrants = append(matchingReferenceGrants, referenceGrant)
}
}
return matchingReferenceGrants
}
func referenceGrantMatchesTo(referenceGrant *gatev1beta1.ReferenceGrant, group, kind, name string) bool {
for _, to := range referenceGrant.Spec.To {
sanitizedGroup := string(to.Group)
if sanitizedGroup == "" {
sanitizedGroup = groupCore
}
if string(to.Kind) != kind || sanitizedGroup != group || (to.Name != nil && string(*to.Name) != name) {
continue
}
return true
}
return false
}
func groupToString(p *gatev1.Group) string {
if p == nil {
return "<nil>"
}
return string(*p)
}
func kindToString(p *gatev1.Kind) string {
if p == nil {
return "<nil>"
}
return string(*p)
}

View file

@ -15,6 +15,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr" "k8s.io/utils/ptr"
gatev1 "sigs.k8s.io/gateway-api/apis/v1" gatev1 "sigs.k8s.io/gateway-api/apis/v1"
gatev1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
) )
var _ provider.Provider = (*Provider)(nil) var _ provider.Provider = (*Provider)(nil)
@ -4628,6 +4629,196 @@ func TestLoadMixedRoutes(t *testing.T) {
} }
} }
func TestLoadRoutesWithReferenceGrants(t *testing.T) {
testCases := []struct {
desc string
ingressClass string
paths []string
expected *dynamic.Configuration
entryPoints map[string]Entrypoint
}{
{
desc: "Empty",
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
ServersTransports: map[string]*dynamic.TCPServersTransport{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Empty because ReferenceGrant for Secret is missing",
paths: []string{"services.yml", "referencegrant/for_secret_missing.yml"},
entryPoints: map[string]Entrypoint{
"tls": {Address: ":9000"},
},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
ServersTransports: map[string]*dynamic.TCPServersTransport{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Empty because ReferenceGrant spec.from does not match",
paths: []string{"services.yml", "referencegrant/for_secret_not_matching_from.yml"},
entryPoints: map[string]Entrypoint{
"tls": {Address: ":9000"},
},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
ServersTransports: map[string]*dynamic.TCPServersTransport{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "Empty because ReferenceGrant spec.to does not match",
paths: []string{"services.yml", "referencegrant/for_secret_not_matching_to.yml"},
entryPoints: map[string]Entrypoint{
"tls": {Address: ":9000"},
},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{},
ServersTransports: map[string]*dynamic.TCPServersTransport{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TLS: &dynamic.TLSConfiguration{},
},
},
{
desc: "For Secret",
paths: []string{"services.yml", "referencegrant/for_secret.yml"},
entryPoints: map[string]Entrypoint{
"tls": {Address: ":9000"},
},
expected: &dynamic.Configuration{
UDP: &dynamic.UDPConfiguration{
Routers: map[string]*dynamic.UDPRouter{},
Services: map[string]*dynamic.UDPService{},
},
TCP: &dynamic.TCPConfiguration{
Routers: map[string]*dynamic.TCPRouter{
"default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb": {
EntryPoints: []string{"tls"},
Service: "default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb-wrr-0",
Rule: "HostSNI(`*`)",
RuleSyntax: "v3",
TLS: &dynamic.RouterTCPTLSConfig{},
},
},
Middlewares: map[string]*dynamic.TCPMiddleware{},
Services: map[string]*dynamic.TCPService{
"default-tcp-app-1-my-gateway-tls-e3b0c44298fc1c149afb-wrr-0": {
Weighted: &dynamic.TCPWeightedRoundRobin{
Services: []dynamic.TCPWRRService{{
Name: "default-whoamitcp-9000",
Weight: func(i int) *int { return &i }(1),
}},
},
},
"default-whoamitcp-9000": {
LoadBalancer: &dynamic.TCPServersLoadBalancer{
Servers: []dynamic.TCPServer{
{
Address: "10.10.0.9:9000",
},
{
Address: "10.10.0.10:9000",
},
},
},
},
},
ServersTransports: map[string]*dynamic.TCPServersTransport{},
},
HTTP: &dynamic.HTTPConfiguration{
Routers: map[string]*dynamic.Router{},
Middlewares: map[string]*dynamic.Middleware{},
Services: map[string]*dynamic.Service{},
ServersTransports: map[string]*dynamic.ServersTransport{},
},
TLS: &dynamic.TLSConfiguration{
Certificates: []*tls.CertAndStores{
{
Certificate: tls.Certificate{
CertFile: types.FileOrContent("-----BEGIN CERTIFICATE-----\n-----END CERTIFICATE-----"),
KeyFile: types.FileOrContent("-----BEGIN PRIVATE KEY-----\n-----END PRIVATE KEY-----"),
},
},
},
},
},
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
if test.expected == nil {
return
}
p := Provider{EntryPoints: test.entryPoints}
conf := p.loadConfigurationFromGateway(context.Background(), newClientMock(test.paths...))
assert.Equal(t, test.expected, conf)
})
}
}
func Test_hostRule(t *testing.T) { func Test_hostRule(t *testing.T) {
testCases := []struct { testCases := []struct {
desc string desc string
@ -5644,3 +5835,247 @@ func kindPtr(kind gatev1.Kind) *gatev1.Kind {
func pathMatchTypePtr(p gatev1.PathMatchType) *gatev1.PathMatchType { return &p } func pathMatchTypePtr(p gatev1.PathMatchType) *gatev1.PathMatchType { return &p }
func headerMatchTypePtr(h gatev1.HeaderMatchType) *gatev1.HeaderMatchType { return &h } func headerMatchTypePtr(h gatev1.HeaderMatchType) *gatev1.HeaderMatchType { return &h }
func Test_referenceGrantMatchesFrom(t *testing.T) {
testCases := []struct {
desc string
referenceGrant gatev1beta1.ReferenceGrant
group string
kind string
namespace string
expectedResult bool
}{
{
desc: "matches",
referenceGrant: gatev1beta1.ReferenceGrant{
Spec: gatev1beta1.ReferenceGrantSpec{
From: []gatev1beta1.ReferenceGrantFrom{
{
Group: "correct-group",
Kind: "correct-kind",
Namespace: "correct-namespace",
},
},
},
},
group: "correct-group",
kind: "correct-kind",
namespace: "correct-namespace",
expectedResult: true,
},
{
desc: "empty group matches core",
referenceGrant: gatev1beta1.ReferenceGrant{
Spec: gatev1beta1.ReferenceGrantSpec{
From: []gatev1beta1.ReferenceGrantFrom{
{
Group: "",
Kind: "correct-kind",
Namespace: "correct-namespace",
},
},
},
},
group: "core",
kind: "correct-kind",
namespace: "correct-namespace",
expectedResult: true,
},
{
desc: "wrong group",
referenceGrant: gatev1beta1.ReferenceGrant{
Spec: gatev1beta1.ReferenceGrantSpec{
From: []gatev1beta1.ReferenceGrantFrom{
{
Group: "wrong-group",
Kind: "correct-kind",
Namespace: "correct-namespace",
},
},
},
},
group: "correct-group",
kind: "correct-kind",
namespace: "correct-namespace",
expectedResult: false,
},
{
desc: "wrong kind",
referenceGrant: gatev1beta1.ReferenceGrant{
Spec: gatev1beta1.ReferenceGrantSpec{
From: []gatev1beta1.ReferenceGrantFrom{
{
Group: "correct-group",
Kind: "wrong-kind",
Namespace: "correct-namespace",
},
},
},
},
group: "correct-group",
kind: "correct-kind",
namespace: "correct-namespace",
expectedResult: false,
},
{
desc: "wrong namespace",
referenceGrant: gatev1beta1.ReferenceGrant{
Spec: gatev1beta1.ReferenceGrantSpec{
From: []gatev1beta1.ReferenceGrantFrom{
{
Group: "correct-group",
Kind: "correct-kind",
Namespace: "wrong-namespace",
},
},
},
},
group: "correct-group",
kind: "correct-kind",
namespace: "correct-namespace",
expectedResult: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
assert.Equal(t, test.expectedResult, referenceGrantMatchesFrom(&test.referenceGrant, test.group, test.kind, test.namespace))
})
}
}
func Test_referenceGrantMatchesTo(t *testing.T) {
testCases := []struct {
desc string
referenceGrant gatev1beta1.ReferenceGrant
group string
kind string
name string
expectedResult bool
}{
{
desc: "matches",
referenceGrant: gatev1beta1.ReferenceGrant{
Spec: gatev1beta1.ReferenceGrantSpec{
To: []gatev1beta1.ReferenceGrantTo{
{
Group: "correct-group",
Kind: "correct-kind",
Name: objectNamePtr("correct-name"),
},
},
},
},
group: "correct-group",
kind: "correct-kind",
name: "correct-name",
expectedResult: true,
},
{
desc: "matches without name",
referenceGrant: gatev1beta1.ReferenceGrant{
Spec: gatev1beta1.ReferenceGrantSpec{
To: []gatev1beta1.ReferenceGrantTo{
{
Group: "correct-group",
Kind: "correct-kind",
Name: nil,
},
},
},
},
group: "correct-group",
kind: "correct-kind",
name: "some-name",
expectedResult: true,
},
{
desc: "empty group matches core",
referenceGrant: gatev1beta1.ReferenceGrant{
Spec: gatev1beta1.ReferenceGrantSpec{
To: []gatev1beta1.ReferenceGrantTo{
{
Group: "",
Kind: "correct-kind",
Name: objectNamePtr("correct-name"),
},
},
},
},
group: "core",
kind: "correct-kind",
name: "correct-name",
expectedResult: true,
},
{
desc: "wrong group",
referenceGrant: gatev1beta1.ReferenceGrant{
Spec: gatev1beta1.ReferenceGrantSpec{
To: []gatev1beta1.ReferenceGrantTo{
{
Group: "wrong-group",
Kind: "correct-kind",
Name: objectNamePtr("correct-name"),
},
},
},
},
group: "correct-group",
kind: "correct-kind",
name: "correct-namespace",
expectedResult: false,
},
{
desc: "wrong kind",
referenceGrant: gatev1beta1.ReferenceGrant{
Spec: gatev1beta1.ReferenceGrantSpec{
To: []gatev1beta1.ReferenceGrantTo{
{
Group: "correct-group",
Kind: "wrong-kind",
Name: objectNamePtr("correct-name"),
},
},
},
},
group: "correct-group",
kind: "correct-kind",
name: "correct-name",
expectedResult: false,
},
{
desc: "wrong name",
referenceGrant: gatev1beta1.ReferenceGrant{
Spec: gatev1beta1.ReferenceGrantSpec{
To: []gatev1beta1.ReferenceGrantTo{
{
Group: "correct-group",
Kind: "correct-kind",
Name: objectNamePtr("wrong-name"),
},
},
},
},
group: "correct-group",
kind: "correct-kind",
name: "correct-name",
expectedResult: false,
},
}
for _, test := range testCases {
test := test
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
assert.Equal(t, test.expectedResult, referenceGrantMatchesTo(&test.referenceGrant, test.group, test.kind, test.name))
})
}
}
func objectNamePtr(objectName gatev1.ObjectName) *gatev1.ObjectName {
return &objectName
}

View file

@ -12,7 +12,7 @@ import (
// MustParseYaml parses a YAML to objects. // MustParseYaml parses a YAML to objects.
func MustParseYaml(content []byte) []runtime.Object { func MustParseYaml(content []byte) []runtime.Object {
acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|ServersTransportTCP|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute)$`) acceptedK8sTypes := regexp.MustCompile(`^(Namespace|Deployment|Endpoints|Service|Ingress|IngressRoute|IngressRouteTCP|IngressRouteUDP|Middleware|MiddlewareTCP|Secret|TLSOption|TLSStore|TraefikService|IngressClass|ServersTransport|ServersTransportTCP|GatewayClass|Gateway|HTTPRoute|TCPRoute|TLSRoute|ReferenceGrant)$`)
files := strings.Split(string(content), "---\n") files := strings.Split(string(content), "---\n")
retVal := make([]runtime.Object, 0, len(files)) retVal := make([]runtime.Object, 0, len(files))