Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions internal/adc/translator/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/pluginconfig"
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/plugins"
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/regex"
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/servicenamespace"
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/upstream"
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations/websocket"
Expand All @@ -37,6 +38,7 @@ type IngressConfig struct {
EnableWebsocket bool
ServiceNamespace string
PluginConfigName string
UseRegex bool
}

var ingressAnnotationParsers = map[string]annotations.IngressAnnotationsParser{
Expand All @@ -45,6 +47,7 @@ var ingressAnnotationParsers = map[string]annotations.IngressAnnotationsParser{
"EnableWebsocket": websocket.NewParser(),
"PluginConfigName": pluginconfig.NewParser(),
"ServiceNamespace": servicenamespace.NewParser(),
"UseRegex": regex.NewParser(),
}

func (t *Translator) TranslateIngressAnnotations(anno map[string]string) *IngressConfig {
Expand Down
29 changes: 29 additions & 0 deletions internal/adc/translator/annotations/regex/regex.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// 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 regex

import (
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
)

type regex struct{}

func NewParser() annotations.IngressAnnotationsParser {
return &regex{}
}

func (r *regex) Parse(e annotations.Extractor) (any, error) {
return e.GetBoolAnnotation(annotations.AnnotationsUseRegex), nil
}
9 changes: 9 additions & 0 deletions internal/adc/translator/annotations_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,15 @@ func TestTranslateIngressAnnotations(t *testing.T) {
},
},
},
{
name: "regex",
anno: map[string]string{
annotations.AnnotationsUseRegex: "true",
},
expected: &IngressConfig{
UseRegex: true,
},
},
}

for _, tt := range tests {
Expand Down
20 changes: 19 additions & 1 deletion internal/adc/translator/ingress.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"k8s.io/utils/ptr"

adctypes "github.com/apache/apisix-ingress-controller/api/adc"
apiv2 "github.com/apache/apisix-ingress-controller/api/v2"
"github.com/apache/apisix-ingress-controller/internal/controller/label"
"github.com/apache/apisix-ingress-controller/internal/id"
"github.com/apache/apisix-ingress-controller/internal/provider"
Expand Down Expand Up @@ -281,7 +282,24 @@ func (t *Translator) buildRouteFromIngressPath(
prefix := strings.TrimSuffix(path.Path, "/") + "/*"
uris = append(uris, prefix)
case networkingv1.PathTypeImplementationSpecific:
uris = []string{"/*"}
if config.UseRegex {
uris = []string{"/*"}
vars := apiv2.ApisixRouteHTTPMatchExprs{
apiv2.ApisixRouteHTTPMatchExpr{
Subject: apiv2.ApisixRouteHTTPMatchExprSubject{
Scope: apiv2.ScopePath,
},
Op: apiv2.OpRegexMatch,
Value: &path.Path,
},
}
routeVars, err := vars.ToVars()
if err != nil {
t.Log.Error(err, "failed to convert regex match exprs to vars", "exprs", vars)
} else {
route.Vars = append(route.Vars, routeVars...)
}
}
}
}

Expand Down
40 changes: 2 additions & 38 deletions internal/webhook/v1/ingress_webhook.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ package v1
import (
"context"
"fmt"
"slices"

networkingv1 "k8s.io/api/networking/v1"
"k8s.io/apimachinery/pkg/runtime"
Expand All @@ -36,36 +35,6 @@ import (

var ingresslog = logf.Log.WithName("ingress-resource")

// unsupportedAnnotations contains all the APISIX Ingress annotations that are not supported in 2.0.0
// 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/auth-type",
}

// checkUnsupportedAnnotations checks if the Ingress contains any unsupported annotations
// and returns appropriate warnings
func checkUnsupportedAnnotations(ingress *networkingv1.Ingress) admission.Warnings {
var warnings admission.Warnings

if len(ingress.Annotations) == 0 {
return warnings
}

for annotation := range ingress.Annotations {
if slices.Contains(unsupportedAnnotations, annotation) {
warningMsg := fmt.Sprintf("Annotation '%s' is not supported in APISIX Ingress Controller 2.0.0.", annotation)
warnings = append(warnings, warningMsg)
ingresslog.Info("Detected unsupported annotation",
"ingress", ingress.GetName(),
"namespace", ingress.GetNamespace(),
"annotation", annotation)
}
}

return warnings
}

// SetupIngressWebhookWithManager registers the webhook for Ingress in the manager.
func SetupIngressWebhookWithManager(mgr ctrl.Manager) error {
return ctrl.NewWebhookManagedBy(mgr).For(&networkingv1.Ingress{}).
Expand Down Expand Up @@ -113,10 +82,7 @@ func (v *IngressCustomValidator) ValidateCreate(ctx context.Context, obj runtime
return nil, fmt.Errorf("%s", sslvalidator.FormatConflicts(conflicts))
}

// Check for unsupported annotations and generate warnings
warnings := checkUnsupportedAnnotations(ingress)
warnings = append(warnings, v.collectReferenceWarnings(ctx, ingress)...)

warnings := v.collectReferenceWarnings(ctx, ingress)
return warnings, nil
}

Expand All @@ -137,9 +103,7 @@ func (v *IngressCustomValidator) ValidateUpdate(ctx context.Context, oldObj, new
return nil, fmt.Errorf("%s", sslvalidator.FormatConflicts(conflicts))
}

// Check for unsupported annotations and generate warnings
warnings := checkUnsupportedAnnotations(ingress)
warnings = append(warnings, v.collectReferenceWarnings(ctx, ingress)...)
warnings := v.collectReferenceWarnings(ctx, ingress)
return warnings, nil
}

Expand Down
48 changes: 0 additions & 48 deletions internal/webhook/v1/ingress_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,54 +63,6 @@ func buildIngressValidator(t *testing.T, objects ...runtime.Object) *IngressCust
return NewIngressCustomValidator(builder.Build())
}

func TestIngressCustomValidator_ValidateCreate_SupportedAnnotations(t *testing.T) {
validator := buildIngressValidator(t)
obj := &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ingress",
Namespace: "default",
Annotations: map[string]string{
"ingressclass.kubernetes.io/is-default-class": "true",
},
},
}

warnings, err := validator.ValidateCreate(context.TODO(), obj)
assert.NoError(t, err)
assert.Empty(t, warnings)
}

func TestIngressCustomValidator_ValidateDelete_NoWarnings(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",
},
},
}

warnings, err := validator.ValidateDelete(context.TODO(), obj)
assert.NoError(t, err)
assert.Empty(t, warnings)
}

func TestIngressCustomValidator_ValidateCreate_NoAnnotations(t *testing.T) {
validator := buildIngressValidator(t)
obj := &networkingv1.Ingress{
ObjectMeta: metav1.ObjectMeta{
Name: "test-ingress",
Namespace: "default",
},
}

warnings, err := validator.ValidateCreate(context.TODO(), obj)
assert.NoError(t, err)
assert.Empty(t, warnings)
}

func TestIngressCustomValidator_WarnsForMissingServiceAndSecret(t *testing.T) {
validator := buildIngressValidator(t)
obj := &networkingv1.Ingress{
Expand Down
81 changes: 81 additions & 0 deletions test/e2e/ingress/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -1144,4 +1144,85 @@ spec:
})
})
})

Context("Route", func() {
var (
ingressRegex = `
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: regex
annotations:
k8s.apisix.apache.org/use-regex: "true"
spec:
ingressClassName: %s
rules:
- host: httpbin.example
http:
paths:
- path: /anything/.*/ok
pathType: ImplementationSpecific
backend:
service:
name: httpbin-service-e2e-test
port:
number: 80
`
)
BeforeEach(func() {
By("create GatewayProxy")
Expect(s.CreateResourceFromString(s.GetGatewayProxySpec())).NotTo(HaveOccurred(), "creating GatewayProxy")

By("create IngressClass")
err := s.CreateResourceFromStringWithNamespace(s.GetIngressClassYaml(), "")
Expect(err).NotTo(HaveOccurred(), "creating IngressClass")
time.Sleep(5 * time.Second)
})
It("regex match", func() {
Expect(s.CreateResourceFromString(fmt.Sprintf(ingressRegex, s.Namespace()))).ShouldNot(HaveOccurred(), "creating Ingress")

tests := []*scaffold.RequestAssert{
{
Method: "GET",
Path: "/anything/test/ok",
Host: "httpbin.example",
Check: scaffold.WithExpectedStatus(http.StatusOK),
},
{
Method: "GET",
Path: "/anything/ip/ok",
Host: "httpbin.example",
Check: scaffold.WithExpectedStatus(http.StatusOK),
},
{
Method: "GET",
Path: "/test/notok",
Host: "httpbin.example",
Check: scaffold.WithExpectedStatus(http.StatusNotFound),
},

{
Method: "GET",
Path: "/anything",
Host: "httpbin.example",
Check: scaffold.WithExpectedStatus(http.StatusNotFound),
},
{
Method: "GET",
Path: "/anything/test/notok",
Host: "httpbin.example",
Check: scaffold.WithExpectedStatus(http.StatusNotFound),
},
{
Method: "GET",
Path: "/anything/ok",
Host: "httpbin.example",
Check: scaffold.WithExpectedStatus(http.StatusNotFound),
},
}
for _, test := range tests {
s.RequestAssert(test)
}
})
})
})
Loading
Loading