Skip to content

Commit 749fbdb

Browse files
authored
feat: support kubernetes.io/ingress.class annotations (#313)
Signed-off-by: Ashing Zheng <[email protected]>
1 parent a77a861 commit 749fbdb

File tree

6 files changed

+152
-10
lines changed

6 files changed

+152
-10
lines changed

internal/controller/indexer/indexer.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -444,10 +444,11 @@ func IngressClassIndexFunc(rawObj client.Object) []string {
444444

445445
func IngressClassRefIndexFunc(rawObj client.Object) []string {
446446
ingress := rawObj.(*networkingv1.Ingress)
447-
if ingress.Spec.IngressClassName == nil {
447+
ingressClassName := internaltypes.GetEffectiveIngressClassName(ingress)
448+
if ingressClassName == "" {
448449
return nil
449450
}
450-
return []string{*ingress.Spec.IngressClassName}
451+
return []string{ingressClassName}
451452
}
452453

453454
func IngressServiceIndexFunc(rawObj client.Object) []string {

internal/controller/ingress_controller.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -254,13 +254,14 @@ func (r *IngressReconciler) listIngressForIngressClass(ctx context.Context, obj
254254
}
255255

256256
requests := make([]reconcile.Request, 0, len(ingressList.Items))
257-
for _, ingress := range ingressList.Items {
258-
if ingress.Spec.IngressClassName == nil || *ingress.Spec.IngressClassName == "" ||
259-
*ingress.Spec.IngressClassName == ingressClass.GetName() {
257+
for i := range ingressList.Items {
258+
ingress := &ingressList.Items[i]
259+
effectiveClassName := internaltypes.GetEffectiveIngressClassName(ingress)
260+
if effectiveClassName == "" || effectiveClassName == ingressClass.GetName() {
260261
requests = append(requests, reconcile.Request{
261262
NamespacedName: client.ObjectKey{
262-
Namespace: ingress.Namespace,
263-
Name: ingress.Name,
263+
Namespace: ingress.GetNamespace(),
264+
Name: ingress.GetName(),
264265
},
265266
})
266267
}

internal/controller/utils.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@ import (
4141
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
4242
"k8s.io/apimachinery/pkg/labels"
4343
k8stypes "k8s.io/apimachinery/pkg/types"
44-
"k8s.io/utils/ptr"
4544
ctrl "sigs.k8s.io/controller-runtime"
4645
"sigs.k8s.io/controller-runtime/pkg/client"
4746
"sigs.k8s.io/controller-runtime/pkg/event"
@@ -1718,7 +1717,7 @@ func MatchesIngressClass(c client.Client, log logr.Logger, obj client.Object, ap
17181717
func ExtractIngressClass(obj client.Object) string {
17191718
switch v := obj.(type) {
17201719
case *networkingv1.Ingress:
1721-
return ptr.Deref(v.Spec.IngressClassName, "")
1720+
return types.GetEffectiveIngressClassName(v)
17221721
case *apiv2.ApisixConsumer:
17231722
return v.Spec.IngressClassName
17241723
case *apiv2.ApisixRoute:

internal/types/k8s.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
netv1beta1 "k8s.io/api/networking/v1beta1"
2424
"k8s.io/apimachinery/pkg/runtime/schema"
2525
kschema "k8s.io/client-go/kubernetes/scheme"
26+
"k8s.io/utils/ptr"
2627
"sigs.k8s.io/controller-runtime/pkg/client"
2728
gatewayv1 "sigs.k8s.io/gateway-api/apis/v1"
2829
gatewayv1beta1 "sigs.k8s.io/gateway-api/apis/v1beta1"
@@ -31,7 +32,10 @@ import (
3132
v2 "github.com/apache/apisix-ingress-controller/api/v2"
3233
)
3334

34-
const DefaultIngressClassAnnotation = "ingressclass.kubernetes.io/is-default-class"
35+
const (
36+
DefaultIngressClassAnnotation = "ingressclass.kubernetes.io/is-default-class"
37+
IngressClassNameAnnotation = "kubernetes.io/ingress.class"
38+
)
3539

3640
const (
3741
KindGateway = "Gateway"
@@ -204,3 +208,10 @@ func GvkOf(obj any) schema.GroupVersionKind {
204208
return schema.GroupVersionKind{}
205209
}
206210
}
211+
212+
func GetEffectiveIngressClassName(ingress *netv1.Ingress) string {
213+
if cls := ptr.Deref(ingress.Spec.IngressClassName, ""); cls != "" {
214+
return cls
215+
}
216+
return ingress.GetAnnotations()[IngressClassNameAnnotation]
217+
}

internal/types/k8s_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package types
19+
20+
import (
21+
"testing"
22+
23+
networkingv1 "k8s.io/api/networking/v1"
24+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
25+
"k8s.io/utils/ptr"
26+
)
27+
28+
func TestGetEffectiveIngressClassName(t *testing.T) {
29+
tests := []struct {
30+
name string
31+
ingress *networkingv1.Ingress
32+
want string
33+
}{
34+
{
35+
name: "spec-class",
36+
ingress: &networkingv1.Ingress{
37+
Spec: networkingv1.IngressSpec{
38+
IngressClassName: ptr.To("spec-class"),
39+
},
40+
},
41+
want: "spec-class",
42+
},
43+
{
44+
name: "annotation-class",
45+
ingress: &networkingv1.Ingress{
46+
ObjectMeta: metav1.ObjectMeta{
47+
Annotations: map[string]string{
48+
"kubernetes.io/ingress.class": "annotation-class",
49+
},
50+
},
51+
},
52+
want: "annotation-class",
53+
},
54+
{
55+
name: "spec-class-and-annotation-class",
56+
ingress: &networkingv1.Ingress{
57+
Spec: networkingv1.IngressSpec{
58+
IngressClassName: ptr.To("spec-class"),
59+
},
60+
ObjectMeta: metav1.ObjectMeta{
61+
Annotations: map[string]string{
62+
IngressClassNameAnnotation: "annotation-class",
63+
},
64+
},
65+
},
66+
want: "spec-class",
67+
},
68+
{
69+
name: "empty-ingress",
70+
ingress: &networkingv1.Ingress{},
71+
want: "",
72+
},
73+
}
74+
for _, tt := range tests {
75+
t.Run(tt.name, func(t *testing.T) {
76+
got := GetEffectiveIngressClassName(tt.ingress)
77+
if got != tt.want {
78+
t.Errorf("GetEffectiveIngressClassName() = %v, want %v", got, tt.want)
79+
}
80+
})
81+
}
82+
}

test/e2e/ingress/ingress.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,54 @@ spec:
380380
})
381381
})
382382

383+
Context("Ingress Annotation", func() {
384+
var ingressSpecWithAnnotation = `
385+
apiVersion: networking.k8s.io/v1
386+
kind: Ingress
387+
metadata:
388+
name: %s
389+
annotations:
390+
kubernetes.io/ingress.class: %s
391+
spec:
392+
rules:
393+
- host: annotation.example.com
394+
http:
395+
paths:
396+
- path: /
397+
pathType: Prefix
398+
backend:
399+
service:
400+
name: httpbin-service-e2e-test
401+
port:
402+
number: 80
403+
`
404+
405+
It("kubernetes.io/ingress.class", func() {
406+
By("create GatewayProxy")
407+
err := s.CreateResourceFromString(s.GetGatewayProxySpec())
408+
Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy")
409+
time.Sleep(5 * time.Second)
410+
411+
By("create IngressClass")
412+
err = s.CreateResourceFromStringWithNamespace(s.GetIngressClassYaml(), s.Namespace())
413+
Expect(err).NotTo(HaveOccurred(), "creating GatewayProxy")
414+
time.Sleep(5 * time.Second)
415+
416+
By("create Ingress with annotation-defined class")
417+
ingressName := s.Namespace() + "-annotation"
418+
err = s.CreateResourceFromString(fmt.Sprintf(ingressSpecWithAnnotation, ingressName, s.Namespace()))
419+
Expect(err).NotTo(HaveOccurred(), "creating Ingress with kubernetes.io/ingress.class annotation")
420+
421+
By("verify ingress served through annotated class")
422+
s.RequestAssert(&scaffold.RequestAssert{
423+
Method: "GET",
424+
Path: "/get",
425+
Host: "annotation.example.com",
426+
Check: scaffold.WithExpectedStatus(http.StatusOK),
427+
})
428+
})
429+
})
430+
383431
// Tests concerning the default ingress class need to be run serially
384432
Context("IngressClass with GatewayProxy", Serial, func() {
385433
gatewayProxyYaml := `

0 commit comments

Comments
 (0)