Skip to content

Commit 4270ade

Browse files
authored
feat: add webhook for ingressclass and gateway (#2572) (#275)
Signed-off-by: Ashing Zheng <[email protected]>
1 parent dc00a9e commit 4270ade

File tree

13 files changed

+554
-9
lines changed

13 files changed

+554
-9
lines changed

.github/workflows/apisix-e2e-test.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ jobs:
4242
cases_subset:
4343
- apisix.apache.org
4444
- networking.k8s.io
45+
- webhook
4546
fail-fast: false
4647
runs-on: ubuntu-latest
4748
steps:
@@ -115,4 +116,8 @@ jobs:
115116
TEST_LABEL: ${{ matrix.cases_subset }}
116117
TEST_ENV: CI
117118
run: |
118-
make ginkgo-e2e-test
119+
if [[ "${{ matrix.cases_subset }}" == "webhook" ]]; then
120+
E2E_NODES=1 make ginkgo-e2e-test
121+
else
122+
make ginkgo-e2e-test
123+
fi

.github/workflows/e2e-test.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ jobs:
3535
cases_subset:
3636
- apisix.apache.org
3737
- networking.k8s.io
38+
- webhook
3839
fail-fast: false
3940
runs-on: ubuntu-latest
4041
steps:

PROJECT

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,4 +78,21 @@ resources:
7878
webhooks:
7979
validation: true
8080
webhookVersion: v1
81+
- core: true
82+
domain: k8s.io
83+
group: networking
84+
kind: IngressClass
85+
path: k8s.io/api/networking/v1
86+
version: v1
87+
webhooks:
88+
validation: true
89+
webhookVersion: v1
90+
- external: true
91+
group: gateway.networking.k8s.io
92+
kind: Gateway
93+
path: sigs.k8s.io/gateway-api/apis/v1
94+
version: v1
95+
webhooks:
96+
validation: true
97+
webhookVersion: v1
8198
version: "3"

config/webhook/manifests.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,26 @@ kind: ValidatingWebhookConfiguration
44
metadata:
55
name: validating-webhook-configuration
66
webhooks:
7+
- admissionReviewVersions:
8+
- v1
9+
clientConfig:
10+
service:
11+
name: webhook-service
12+
namespace: system
13+
path: /validate-gateway-networking-k8s-io-v1-gateway
14+
failurePolicy: Fail
15+
name: vgateway-v1.kb.io
16+
rules:
17+
- apiGroups:
18+
- gateway.networking.k8s.io
19+
apiVersions:
20+
- v1
21+
operations:
22+
- CREATE
23+
- UPDATE
24+
resources:
25+
- gateways
26+
sideEffects: None
727
- admissionReviewVersions:
828
- v1
929
clientConfig:
@@ -24,3 +44,23 @@ webhooks:
2444
resources:
2545
- ingresses
2646
sideEffects: None
47+
- admissionReviewVersions:
48+
- v1
49+
clientConfig:
50+
service:
51+
name: webhook-service
52+
namespace: system
53+
path: /validate-networking-k8s-io-v1-ingressclass
54+
failurePolicy: Fail
55+
name: vingressclass-v1.kb.io
56+
rules:
57+
- apiGroups:
58+
- networking.k8s.io
59+
apiVersions:
60+
- v1
61+
operations:
62+
- CREATE
63+
- UPDATE
64+
resources:
65+
- ingressclasses
66+
sideEffects: None

internal/manager/webhooks.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,5 +29,11 @@ func setupWebhooks(_ context.Context, mgr manager.Manager) error {
2929
if err := webhookv1.SetupIngressWebhookWithManager(mgr); err != nil {
3030
return err
3131
}
32+
if err := webhookv1.SetupIngressClassWebhookWithManager(mgr); err != nil {
33+
return err
34+
}
35+
if err := webhookv1.SetupGatewayWebhookWithManager(mgr); err != nil {
36+
return err
37+
}
3238
return nil
3339
}

internal/provider/common/adcdebugserver.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,7 @@ func (asrv *ADCDebugProvider) showResourceDetail(w http.ResponseWriter, r *http.
262262
return
263263
}
264264

265-
var resource interface{}
265+
var resource any
266266
switch resourceType {
267267
case adctypes.TypeService:
268268
for _, svc := range resources.Services {
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
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+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
23+
"k8s.io/apimachinery/pkg/runtime"
24+
ctrl "sigs.k8s.io/controller-runtime"
25+
"sigs.k8s.io/controller-runtime/pkg/client"
26+
logf "sigs.k8s.io/controller-runtime/pkg/log"
27+
"sigs.k8s.io/controller-runtime/pkg/webhook"
28+
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
29+
gatewaynetworkingk8siov1 "sigs.k8s.io/gateway-api/apis/v1"
30+
31+
v1alpha1 "github.com/apache/apisix-ingress-controller/api/v1alpha1"
32+
"github.com/apache/apisix-ingress-controller/internal/controller/config"
33+
internaltypes "github.com/apache/apisix-ingress-controller/internal/types"
34+
)
35+
36+
// nolint:unused
37+
// log is for logging in this package.
38+
var gatewaylog = logf.Log.WithName("gateway-resource")
39+
40+
// SetupGatewayWebhookWithManager registers the webhook for Gateway in the manager.
41+
func SetupGatewayWebhookWithManager(mgr ctrl.Manager) error {
42+
return ctrl.NewWebhookManagedBy(mgr).For(&gatewaynetworkingk8siov1.Gateway{}).
43+
WithValidator(&GatewayCustomValidator{Client: mgr.GetClient()}).
44+
Complete()
45+
}
46+
47+
// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
48+
// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
49+
// +kubebuilder:webhook:path=/validate-gateway-networking-k8s-io-v1-gateway,mutating=false,failurePolicy=fail,sideEffects=None,groups=gateway.networking.k8s.io,resources=gateways,verbs=create;update,versions=v1,name=vgateway-v1.kb.io,admissionReviewVersions=v1
50+
51+
// GatewayCustomValidator struct is responsible for validating the Gateway resource
52+
// when it is created, updated, or deleted.
53+
//
54+
// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,
55+
// as this struct is used only for temporary operations and does not need to be deeply copied.
56+
type GatewayCustomValidator struct {
57+
Client client.Client
58+
}
59+
60+
var _ webhook.CustomValidator = &GatewayCustomValidator{}
61+
62+
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type Gateway.
63+
func (v *GatewayCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
64+
gateway, ok := obj.(*gatewaynetworkingk8siov1.Gateway)
65+
if !ok {
66+
return nil, fmt.Errorf("expected a Gateway object but got %T", obj)
67+
}
68+
gatewaylog.Info("Validation for Gateway upon creation", "name", gateway.GetName())
69+
70+
warnings := v.warnIfMissingGatewayProxyForGateway(ctx, gateway)
71+
72+
return warnings, nil
73+
}
74+
75+
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type Gateway.
76+
func (v *GatewayCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
77+
gateway, ok := newObj.(*gatewaynetworkingk8siov1.Gateway)
78+
if !ok {
79+
return nil, fmt.Errorf("expected a Gateway object for the newObj but got %T", newObj)
80+
}
81+
gatewaylog.Info("Validation for Gateway upon update", "name", gateway.GetName())
82+
83+
warnings := v.warnIfMissingGatewayProxyForGateway(ctx, gateway)
84+
85+
return warnings, nil
86+
}
87+
88+
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type Gateway.
89+
func (v *GatewayCustomValidator) ValidateDelete(_ context.Context, obj runtime.Object) (admission.Warnings, error) {
90+
return nil, nil
91+
}
92+
93+
func (v *GatewayCustomValidator) warnIfMissingGatewayProxyForGateway(ctx context.Context, gateway *gatewaynetworkingk8siov1.Gateway) admission.Warnings {
94+
var warnings admission.Warnings
95+
96+
// get gateway class
97+
gatewayClass := &gatewaynetworkingk8siov1.GatewayClass{}
98+
if err := v.Client.Get(ctx, client.ObjectKey{Name: string(gateway.Spec.GatewayClassName)}, gatewayClass); err != nil {
99+
gatewaylog.Error(err, "failed to get gateway class", "gateway", gateway.GetName(), "gatewayclass", gateway.Spec.GatewayClassName)
100+
return nil
101+
}
102+
// match controller
103+
if string(gatewayClass.Spec.ControllerName) != config.ControllerConfig.ControllerName {
104+
return nil
105+
}
106+
107+
infra := gateway.Spec.Infrastructure
108+
if infra == nil || infra.ParametersRef == nil {
109+
return nil
110+
}
111+
ref := infra.ParametersRef
112+
if string(ref.Group) != v1alpha1.GroupVersion.Group || string(ref.Kind) != internaltypes.KindGatewayProxy {
113+
return nil
114+
}
115+
116+
ns := gateway.GetNamespace()
117+
name := ref.Name
118+
119+
var gp v1alpha1.GatewayProxy
120+
if err := v.Client.Get(ctx, client.ObjectKey{Namespace: ns, Name: name}, &gp); err != nil {
121+
if k8serrors.IsNotFound(err) {
122+
msg := fmt.Sprintf("Referenced GatewayProxy '%s/%s' not found.", ns, name)
123+
warnings = append(warnings, msg)
124+
gatewaylog.Info("Gateway references missing GatewayProxy", "gateway", gateway.GetName(), "namespace", ns, "gatewayproxy", name)
125+
} else {
126+
gatewaylog.Error(err, "failed to resolve GatewayProxy for Gateway", "gateway", gateway.GetName(), "namespace", ns, "gatewayproxy", name)
127+
}
128+
}
129+
return warnings
130+
}
Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
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+
networkingv1 "k8s.io/api/networking/v1"
23+
k8serrors "k8s.io/apimachinery/pkg/api/errors"
24+
"k8s.io/apimachinery/pkg/runtime"
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+
31+
v1alpha1 "github.com/apache/apisix-ingress-controller/api/v1alpha1"
32+
"github.com/apache/apisix-ingress-controller/internal/controller/config"
33+
internaltypes "github.com/apache/apisix-ingress-controller/internal/types"
34+
)
35+
36+
// nolint:unused
37+
// log is for logging in this package.
38+
var ingressclasslog = logf.Log.WithName("ingressclass-resource")
39+
40+
// SetupIngressClassWebhookWithManager registers the webhook for IngressClass in the manager.
41+
func SetupIngressClassWebhookWithManager(mgr ctrl.Manager) error {
42+
return ctrl.NewWebhookManagedBy(mgr).For(&networkingv1.IngressClass{}).
43+
WithValidator(&IngressClassCustomValidator{Client: mgr.GetClient()}).
44+
Complete()
45+
}
46+
47+
// NOTE: The 'path' attribute must follow a specific pattern and should not be modified directly here.
48+
// Modifying the path for an invalid path can cause API server errors; failing to locate the webhook.
49+
// +kubebuilder:webhook:path=/validate-networking-k8s-io-v1-ingressclass,mutating=false,failurePolicy=fail,sideEffects=None,groups=networking.k8s.io,resources=ingressclasses,verbs=create;update,versions=v1,name=vingressclass-v1.kb.io,admissionReviewVersions=v1
50+
51+
// IngressClassCustomValidator struct is responsible for validating the IngressClass resource
52+
// when it is created, updated, or deleted.
53+
//
54+
// NOTE: The +kubebuilder:object:generate=false marker prevents controller-gen from generating DeepCopy methods,
55+
// as this struct is used only for temporary operations and does not need to be deeply copied.
56+
type IngressClassCustomValidator struct {
57+
Client client.Client
58+
}
59+
60+
var _ webhook.CustomValidator = &IngressClassCustomValidator{}
61+
62+
// ValidateCreate implements webhook.CustomValidator so a webhook will be registered for the type IngressClass.
63+
func (v *IngressClassCustomValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (admission.Warnings, error) {
64+
ingressclass, ok := obj.(*networkingv1.IngressClass)
65+
if !ok {
66+
return nil, fmt.Errorf("expected a IngressClass object but got %T", obj)
67+
}
68+
ingressclasslog.Info("Validation for IngressClass upon creation", "name", ingressclass.GetName())
69+
70+
warnings := v.warnIfMissingGatewayProxyForIngressClass(ctx, ingressclass)
71+
72+
return warnings, nil
73+
}
74+
75+
// ValidateUpdate implements webhook.CustomValidator so a webhook will be registered for the type IngressClass.
76+
func (v *IngressClassCustomValidator) ValidateUpdate(ctx context.Context, oldObj, newObj runtime.Object) (admission.Warnings, error) {
77+
ingressclass, ok := newObj.(*networkingv1.IngressClass)
78+
if !ok {
79+
return nil, fmt.Errorf("expected a IngressClass object for the newObj but got %T", newObj)
80+
}
81+
ingressclasslog.Info("Validation for IngressClass upon update", "name", ingressclass.GetName())
82+
83+
warnings := v.warnIfMissingGatewayProxyForIngressClass(ctx, ingressclass)
84+
85+
return warnings, nil
86+
}
87+
88+
// ValidateDelete implements webhook.CustomValidator so a webhook will be registered for the type IngressClass.
89+
func (v *IngressClassCustomValidator) ValidateDelete(_ context.Context, obj runtime.Object) (admission.Warnings, error) {
90+
return nil, nil
91+
}
92+
93+
func (v *IngressClassCustomValidator) warnIfMissingGatewayProxyForIngressClass(ctx context.Context, ingressClass *networkingv1.IngressClass) admission.Warnings {
94+
var warnings admission.Warnings
95+
96+
// match controller
97+
if ingressClass.Spec.Controller != config.ControllerConfig.ControllerName {
98+
return nil
99+
}
100+
101+
params := ingressClass.Spec.Parameters
102+
if params == nil || params.APIGroup == nil {
103+
return nil
104+
}
105+
if *params.APIGroup != v1alpha1.GroupVersion.Group || params.Kind != internaltypes.KindGatewayProxy {
106+
return nil
107+
}
108+
109+
ns := ingressClass.GetNamespace()
110+
if params.Namespace != nil && *params.Namespace != "" {
111+
ns = *params.Namespace
112+
}
113+
name := params.Name
114+
115+
var gp v1alpha1.GatewayProxy
116+
if err := v.Client.Get(ctx, client.ObjectKey{Namespace: ns, Name: name}, &gp); err != nil {
117+
if k8serrors.IsNotFound(err) {
118+
msg := fmt.Sprintf("Referenced GatewayProxy '%s/%s' not found.", ns, name)
119+
warnings = append(warnings, msg)
120+
ingressclasslog.Info("IngressClass references missing GatewayProxy", "ingressclass", ingressClass.GetName(), "namespace", ns, "gatewayproxy", name)
121+
} else {
122+
ingressclasslog.Error(err, "failed to resolve GatewayProxy for IngressClass", "ingressclass", ingressClass.GetName(), "namespace", ns, "gatewayproxy", name)
123+
}
124+
}
125+
return warnings
126+
}

0 commit comments

Comments
 (0)