diff --git a/internal/adc/translator/annotations.go b/internal/adc/translator/annotations.go index 3f3942160c..9f92d43f7a 100644 --- a/internal/adc/translator/annotations.go +++ b/internal/adc/translator/annotations.go @@ -25,17 +25,20 @@ import ( "github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations" "github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/plugins" "github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/upstream" + "github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/websocket" ) // Structure extracted by Ingress Resource type IngressConfig struct { - Upstream upstream.Upstream - Plugins adctypes.Plugins + Upstream upstream.Upstream + Plugins adctypes.Plugins + EnableWebsocket bool } var ingressAnnotationParsers = map[string]annotations.IngressAnnotationsParser{ - "upstream": upstream.NewParser(), - "plugins": plugins.NewParser(), + "upstream": upstream.NewParser(), + "plugins": plugins.NewParser(), + "EnableWebsocket": websocket.NewParser(), } func (t *Translator) TranslateIngressAnnotations(anno map[string]string) *IngressConfig { diff --git a/internal/adc/translator/annotations/websocket/websocket.go b/internal/adc/translator/annotations/websocket/websocket.go new file mode 100644 index 0000000000..61474b91c3 --- /dev/null +++ b/internal/adc/translator/annotations/websocket/websocket.go @@ -0,0 +1,30 @@ +// 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 websocket + +import ( + "github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations" +) + +type WebSocket struct{} + +func NewParser() annotations.IngressAnnotationsParser { + return &WebSocket{} +} + +func (w *WebSocket) Parse(e annotations.Extractor) (any, error) { + return e.GetBoolAnnotation(annotations.AnnotationsEnableWebSocket), nil +} diff --git a/internal/adc/translator/annotations_test.go b/internal/adc/translator/annotations_test.go index 279d7beb3a..4ff6bfe807 100644 --- a/internal/adc/translator/annotations_test.go +++ b/internal/adc/translator/annotations_test.go @@ -207,6 +207,15 @@ func TestTranslateIngressAnnotations(t *testing.T) { }, }, }, + { + name: "enable websocket", + anno: map[string]string{ + annotations.AnnotationsEnableWebSocket: "true", + }, + expected: &IngressConfig{ + EnableWebsocket: true, + }, + }, } for _, tt := range tests { diff --git a/internal/adc/translator/ingress.go b/internal/adc/translator/ingress.go index bf57073822..5332662d68 100644 --- a/internal/adc/translator/ingress.go +++ b/internal/adc/translator/ingress.go @@ -162,7 +162,10 @@ func (t *Translator) buildServiceFromIngressPath( service.Upstream = upstream route := buildRouteFromIngressPath(obj, path, config, index, labels) - if protocol == internaltypes.AppProtocolWS || protocol == internaltypes.AppProtocolWSS { + // Check if websocket is enabled via annotation first, then fall back to appProtocol detection + if config != nil && config.EnableWebsocket { + route.EnableWebsocket = ptr.To(true) + } else if protocol == internaltypes.AppProtocolWS || protocol == internaltypes.AppProtocolWSS { route.EnableWebsocket = ptr.To(true) } service.Routes = []*adctypes.Route{route} diff --git a/internal/webhook/v1/ingress_webhook.go b/internal/webhook/v1/ingress_webhook.go index 4f940a2d75..590abf0443 100644 --- a/internal/webhook/v1/ingress_webhook.go +++ b/internal/webhook/v1/ingress_webhook.go @@ -40,7 +40,6 @@ var ingresslog = logf.Log.WithName("ingress-resource") // ref: https://apisix.apache.org/docs/ingress-controller/upgrade-guide/#limited-support-for-ingress-annotations var unsupportedAnnotations = []string{ "k8s.apisix.apache.org/use-regex", - "k8s.apisix.apache.org/enable-websocket", "k8s.apisix.apache.org/plugin-config-name", "k8s.apisix.apache.org/enable-csrf", "k8s.apisix.apache.org/csrf-key", diff --git a/internal/webhook/v1/ingress_webhook_test.go b/internal/webhook/v1/ingress_webhook_test.go index b775a81927..7394ef4b21 100644 --- a/internal/webhook/v1/ingress_webhook_test.go +++ b/internal/webhook/v1/ingress_webhook_test.go @@ -17,7 +17,6 @@ package v1 import ( "context" - "strings" "testing" "github.com/stretchr/testify/assert" @@ -64,29 +63,6 @@ func buildIngressValidator(t *testing.T, objects ...runtime.Object) *IngressCust return NewIngressCustomValidator(builder.Build()) } -func TestIngressCustomValidator_ValidateCreate_UnsupportedAnnotations(t *testing.T) { - validator := buildIngressValidator(t) - obj := &networkingv1.Ingress{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-ingress", - Namespace: "default", - Annotations: map[string]string{ - "k8s.apisix.apache.org/use-regex": "true", - "k8s.apisix.apache.org/enable-websocket": "true", - }, - }, - } - - warnings, err := validator.ValidateCreate(context.TODO(), obj) - assert.NoError(t, err) - assert.Len(t, warnings, 2) - - // Check that warnings contain the expected unsupported annotations - warningsStr := strings.Join(warnings, " ") - assert.Contains(t, warningsStr, "k8s.apisix.apache.org/use-regex") - assert.Contains(t, warningsStr, "k8s.apisix.apache.org/enable-websocket") -} - func TestIngressCustomValidator_ValidateCreate_SupportedAnnotations(t *testing.T) { validator := buildIngressValidator(t) obj := &networkingv1.Ingress{ diff --git a/test/e2e/ingress/annotations.go b/test/e2e/ingress/annotations.go index 57e9b8ad15..3128cb0456 100644 --- a/test/e2e/ingress/annotations.go +++ b/test/e2e/ingress/annotations.go @@ -128,6 +128,28 @@ spec: port: number: 80 ` + + ingressWebSocket = ` +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: websocket + annotations: + k8s.apisix.apache.org/enable-websocket: "true" +spec: + ingressClassName: %s + rules: + - host: nginx.example + http: + paths: + - path: /ws + pathType: Exact + backend: + service: + name: nginx + port: + number: 80 +` ) BeforeEach(func() { s.DeployNginx(framework.NginxOptions{ @@ -240,6 +262,15 @@ spec: Expect(corsConfig["allow_methods"]).To(Equal("GET,POST"), "checking cors allow methods") Expect(corsConfig["allow_headers"]).To(Equal("Origin,Authorization"), "checking cors allow headers") }) + + It("websocket", func() { + Expect(s.CreateResourceFromString(fmt.Sprintf(ingressWebSocket, s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress") + + routes, err := s.DefaultDataplaneResource().Route().List(context.Background()) + Expect(err).NotTo(HaveOccurred(), "listing Route") + Expect(routes).To(HaveLen(1), "checking Route length") + Expect(routes[0].EnableWebsocket).To(Equal(ptr.To(true)), "checking Route EnableWebsocket") + }) }) Context("Plugins", func() { diff --git a/test/e2e/webhook/ingress.go b/test/e2e/webhook/ingress.go index 69a7f3ef77..cf3e519981 100644 --- a/test/e2e/webhook/ingress.go +++ b/test/e2e/webhook/ingress.go @@ -58,7 +58,6 @@ metadata: namespace: %s annotations: k8s.apisix.apache.org/use-regex: "true" - k8s.apisix.apache.org/enable-websocket: "true" spec: ingressClassName: %s rules: @@ -76,7 +75,6 @@ spec: output, err := s.CreateResourceFromStringAndGetOutput(ingressYAML) Expect(err).ShouldNot(HaveOccurred()) - Expect(output).To(ContainSubstring(`Warning: Annotation 'k8s.apisix.apache.org/enable-websocket' is not supported`)) Expect(output).To(ContainSubstring(`Warning: Annotation 'k8s.apisix.apache.org/use-regex' is not supported`)) s.RequestAssert(&scaffold.RequestAssert{