Skip to content

Commit e699a73

Browse files
authored
feat: support udproute webhook (#2588) (#339)
1 parent 7f3e3fb commit e699a73

File tree

10 files changed

+651
-84
lines changed

10 files changed

+651
-84
lines changed

config/webhook/manifests.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,3 +224,23 @@ webhooks:
224224
resources:
225225
- tcproutes
226226
sideEffects: None
227+
- admissionReviewVersions:
228+
- v1
229+
clientConfig:
230+
service:
231+
name: webhook-service
232+
namespace: system
233+
path: /validate-gateway-networking-k8s-io-v1alpha2-udproute
234+
failurePolicy: Fail
235+
name: vudproute-v1alpha2.kb.io
236+
rules:
237+
- apiGroups:
238+
- gateway.networking.k8s.io
239+
apiVersions:
240+
- v1alpha2
241+
operations:
242+
- CREATE
243+
- UPDATE
244+
resources:
245+
- udproutes
246+
sideEffects: None

internal/manager/webhooks.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,9 @@ func setupWebhooks(_ context.Context, mgr manager.Manager) error {
4747
if err := webhookv1.SetupTCPRouteWebhookWithManager(mgr); err != nil {
4848
return err
4949
}
50+
if err := webhookv1.SetupUDPRouteWebhookWithManager(mgr); err != nil {
51+
return err
52+
}
5053
if err := webhookv1.SetupApisixConsumerWebhookWithManager(mgr); err != nil {
5154
return err
5255
}

internal/webhook/v1/ownership.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ func isTCPRouteManaged(ctx context.Context, c client.Client, route *gatewayv1alp
6868
return routeReferencesManagedGateway(ctx, c, route.Spec.ParentRefs, route.Namespace)
6969
}
7070

71+
func isUDPRouteManaged(ctx context.Context, c client.Client, route *gatewayv1alpha2.UDPRoute) (bool, error) {
72+
if route == nil {
73+
return false, nil
74+
}
75+
return routeReferencesManagedGateway(ctx, c, route.Spec.ParentRefs, route.Namespace)
76+
}
77+
7178
func routeReferencesManagedGateway(ctx context.Context, c client.Client, parents []gatewayv1.ParentReference, defaultNamespace string) (bool, error) {
7279
for _, parent := range parents {
7380
if parent.Name == "" {
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one or more
2+
// contributor license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright ownership.
4+
// The ASF licenses this file to You under the Apache License, Version 2.0
5+
// (the "License"); you may not use this file except in compliance with
6+
// the License. You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
16+
package v1
17+
18+
import (
19+
"context"
20+
"fmt"
21+
22+
corev1 "k8s.io/api/core/v1"
23+
"k8s.io/apimachinery/pkg/runtime"
24+
"k8s.io/apimachinery/pkg/types"
25+
ctrl "sigs.k8s.io/controller-runtime"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
27+
logf "sigs.k8s.io/controller-runtime/pkg/log"
28+
"sigs.k8s.io/controller-runtime/pkg/webhook"
29+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
30+
gatewayv1alpha2 "sigs.k8s.io/gateway-api/apis/v1alpha2"
31+
32+
internaltypes "github.com/apache/apisix-ingress-controller/internal/types"
33+
"github.com/apache/apisix-ingress-controller/internal/webhook/v1/reference"
34+
)
35+
36+
var udpRouteLog = logf.Log.WithName("udproute-resource")
37+
38+
func SetupUDPRouteWebhookWithManager(mgr ctrl.Manager) error {
39+
return ctrl.NewWebhookManagedBy(mgr).
40+
For(&gatewayv1alpha2.UDPRoute{}).
41+
WithValidator(NewUDPRouteCustomValidator(mgr.GetClient())).
42+
Complete()
43+
}
44+
45+
// +kubebuilder:webhook:path=/validate-gateway-networking-k8s-io-v1alpha2-udproute,mutating=false,failurePolicy=fail,sideEffects=None,groups=gateway.networking.k8s.io,resources=udproutes,verbs=create;update,versions=v1alpha2,name=vudproute-v1alpha2.kb.io,admissionReviewVersions=v1
46+
47+
type UDPRouteCustomValidator struct {
48+
Client client.Client
49+
checker reference.Checker
50+
}
51+
52+
var _ webhook.CustomValidator = &UDPRouteCustomValidator{}
53+
54+
func NewUDPRouteCustomValidator(c client.Client) *UDPRouteCustomValidator {
55+
return &UDPRouteCustomValidator{
56+
Client: c,
57+
checker: reference.NewChecker(c, udpRouteLog),
58+
}
59+
}
60+
61+
func (v *UDPRouteCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
62+
route, ok := obj.(*gatewayv1alpha2.UDPRoute)
63+
if !ok {
64+
return nil, fmt.Errorf("expected a UDPRoute object but got %T", obj)
65+
}
66+
udpRouteLog.Info("Validation for UDPRoute upon creation", "name", route.GetName(), "namespace", route.GetNamespace())
67+
managed, err := isUDPRouteManaged(ctx, v.Client, route)
68+
if err != nil {
69+
udpRouteLog.Error(err, "failed to decide controller ownership", "name", route.GetName(), "namespace", route.GetNamespace())
70+
return nil, nil
71+
}
72+
if !managed {
73+
return nil, nil
74+
}
75+
76+
return v.collectWarnings(ctx, route), nil
77+
}
78+
79+
func (v *UDPRouteCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
80+
route, ok := newObj.(*gatewayv1alpha2.UDPRoute)
81+
if !ok {
82+
return nil, fmt.Errorf("expected a UDPRoute object for the newObj but got %T", newObj)
83+
}
84+
udpRouteLog.Info("Validation for UDPRoute upon update", "name", route.GetName(), "namespace", route.GetNamespace())
85+
managed, err := isUDPRouteManaged(ctx, v.Client, route)
86+
if err != nil {
87+
udpRouteLog.Error(err, "failed to decide controller ownership", "name", route.GetName(), "namespace", route.GetNamespace())
88+
return nil, nil
89+
}
90+
if !managed {
91+
return nil, nil
92+
}
93+
94+
return v.collectWarnings(ctx, route), nil
95+
}
96+
97+
func (*UDPRouteCustomValidator) ValidateDelete(context.Context, runtime.Object) (admission.Warnings, error) {
98+
return nil, nil
99+
}
100+
101+
func (v *UDPRouteCustomValidator) collectWarnings(ctx context.Context, route *gatewayv1alpha2.UDPRoute) admission.Warnings {
102+
serviceVisited := make(map[types.NamespacedName]struct{})
103+
namespace := route.GetNamespace()
104+
105+
var warnings admission.Warnings
106+
107+
addServiceWarning := func(nn types.NamespacedName) {
108+
if nn.Name == "" || nn.Namespace == "" {
109+
return
110+
}
111+
if _, seen := serviceVisited[nn]; seen {
112+
return
113+
}
114+
serviceVisited[nn] = struct{}{}
115+
warnings = append(warnings, v.checker.Service(ctx, reference.ServiceRef{
116+
Object: route,
117+
NamespacedName: nn,
118+
})...)
119+
}
120+
121+
addBackendRef := func(ns, name string, group *gatewayv1alpha2.Group, kind *gatewayv1alpha2.Kind) {
122+
if name == "" {
123+
return
124+
}
125+
if group != nil && string(*group) != corev1.GroupName {
126+
return
127+
}
128+
if kind != nil && *kind != internaltypes.KindService {
129+
return
130+
}
131+
nn := types.NamespacedName{Namespace: ns, Name: name}
132+
addServiceWarning(nn)
133+
}
134+
135+
for _, rule := range route.Spec.Rules {
136+
for _, backend := range rule.BackendRefs {
137+
targetNamespace := namespace
138+
if backend.Namespace != nil && *backend.Namespace != "" {
139+
targetNamespace = string(*backend.Namespace)
140+
}
141+
addBackendRef(targetNamespace, string(backend.Name), backend.Group, backend.Kind)
142+
}
143+
}
144+
145+
return warnings
146+
}

0 commit comments

Comments
 (0)