This is an automated email from the ASF dual-hosted git repository.
alinsran pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix-ingress-controller.git
The following commit(s) were added to refs/heads/master by this push:
new 37a0b075 chore: add annotations extractor interface (#2610)
37a0b075 is described below
commit 37a0b0756a017aae65a68e85877a126a213d5260
Author: AlinsRan <[email protected]>
AuthorDate: Wed Oct 22 09:53:42 2025 +0800
chore: add annotations extractor interface (#2610)
---
internal/adc/translator/annotations.go | 61 ++++++++++++
internal/adc/translator/annotations/types.go | 142 +++++++++++++++++++++++++++
internal/adc/translator/annotations_test.go | 87 ++++++++++++++++
3 files changed, 290 insertions(+)
diff --git a/internal/adc/translator/annotations.go
b/internal/adc/translator/annotations.go
new file mode 100644
index 00000000..509a093d
--- /dev/null
+++ b/internal/adc/translator/annotations.go
@@ -0,0 +1,61 @@
+// 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 translator
+
+import (
+ "errors"
+ "fmt"
+
+ "github.com/imdario/mergo"
+
+
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
+)
+
+// Structure extracted by Ingress Resource
+type Ingress struct{}
+
+// parsers registered for ingress annotations
+var ingressAnnotationParsers =
map[string]annotations.IngressAnnotationsParser{}
+
+func (t *Translator) TranslateIngressAnnotations(anno map[string]string)
*Ingress {
+ ing := &Ingress{}
+ if err := translateAnnotations(anno, ing); err != nil {
+ t.Log.Error(err, "failed to translate ingress annotations",
"annotations", anno)
+ }
+ return ing
+}
+
+func translateAnnotations(anno map[string]string, dst any) error {
+ extractor := annotations.NewExtractor(anno)
+ data := make(map[string]any)
+ var errs []error
+
+ for name, parser := range ingressAnnotationParsers {
+ out, err := parser.Parse(extractor)
+ if err != nil {
+ errs = append(errs, fmt.Errorf("parse %s: %w", name,
err))
+ continue
+ }
+ if out != nil {
+ data[name] = out
+ }
+ }
+
+ if err := mergo.MapWithOverwrite(dst, data); err != nil {
+ errs = append(errs, fmt.Errorf("merge: %w", err))
+ }
+ return errors.Join(errs...)
+}
diff --git a/internal/adc/translator/annotations/types.go
b/internal/adc/translator/annotations/types.go
new file mode 100644
index 00000000..1ce19783
--- /dev/null
+++ b/internal/adc/translator/annotations/types.go
@@ -0,0 +1,142 @@
+// 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 annotations
+
+import (
+ "strings"
+)
+
+const (
+ // AnnotationsPrefix is the apisix annotation prefix
+ AnnotationsPrefix = "k8s.apisix.apache.org/"
+
+ // Supported annotations
+ AnnotationsUseRegex = AnnotationsPrefix + "use-regex"
+ AnnotationsEnableWebSocket = AnnotationsPrefix + "enable-websocket"
+ AnnotationsPluginConfigName = AnnotationsPrefix + "plugin-config-name"
+ AnnotationsUpstreamScheme = AnnotationsPrefix + "upstream-scheme"
+
+ // Support retries and timeouts on upstream
+ AnnotationsUpstreamRetry = AnnotationsPrefix +
"upstream-retries"
+ AnnotationsUpstreamTimeoutConnect = AnnotationsPrefix +
"upstream-connect-timeout"
+ AnnotationsUpstreamTimeoutRead = AnnotationsPrefix +
"upstream-read-timeout"
+ AnnotationsUpstreamTimeoutSend = AnnotationsPrefix +
"upstream-send-timeout"
+)
+
+const (
+ // Supported the annotations of the APISIX plugins
+
+ // cors plugin
+ AnnotationsEnableCors = AnnotationsPrefix + "enable-cors"
+ AnnotationsCorsAllowOrigin = AnnotationsPrefix + "cors-allow-origin"
+ AnnotationsCorsAllowHeaders = AnnotationsPrefix + "cors-allow-headers"
+ AnnotationsCorsAllowMethods = AnnotationsPrefix + "cors-allow-methods"
+
+ // csrf plugin
+ AnnotationsEnableCsrf = AnnotationsPrefix + "enable-csrf"
+ AnnotationsCsrfKey = AnnotationsPrefix + "csrf-key"
+
+ // redirect plugin
+ AnnotationsHttpToHttps = AnnotationsPrefix + "http-to-https"
+ AnnotationsHttpRedirect = AnnotationsPrefix + "http-redirect"
+ AnnotationsHttpRedirectCode = AnnotationsPrefix + "http-redirect-code"
+
+ // rewrite plugin
+ AnnotationsRewriteTarget = AnnotationsPrefix +
"rewrite-target"
+ AnnotationsRewriteTargetRegex = AnnotationsPrefix +
"rewrite-target-regex"
+ AnnotationsRewriteTargetRegexTemplate = AnnotationsPrefix +
"rewrite-target-regex-template"
+
+ // response-rewrite plugin
+ AnnotationsEnableResponseRewrite = AnnotationsPrefix +
"enable-response-rewrite"
+ AnnotationsResponseRewriteStatusCode = AnnotationsPrefix +
"response-rewrite-status-code"
+ AnnotationsResponseRewriteBody = AnnotationsPrefix +
"response-rewrite-body"
+ AnnotationsResponseRewriteBodyBase64 = AnnotationsPrefix +
"response-rewrite-body-base64"
+ AnnotationsResponseRewriteHeaderAdd = AnnotationsPrefix +
"response-rewrite-add-header"
+ AnnotationsResponseRewriteHeaderSet = AnnotationsPrefix +
"response-rewrite-set-header"
+ AnnotationsResponseRewriteHeaderRemove = AnnotationsPrefix +
"response-rewrite-remove-header"
+
+ // forward-auth plugin
+ AnnotationsForwardAuthURI = AnnotationsPrefix + "auth-uri"
+ AnnotationsForwardAuthSSLVerify = AnnotationsPrefix +
"auth-ssl-verify"
+ AnnotationsForwardAuthRequestHeaders = AnnotationsPrefix +
"auth-request-headers"
+ AnnotationsForwardAuthUpstreamHeaders = AnnotationsPrefix +
"auth-upstream-headers"
+ AnnotationsForwardAuthClientHeaders = AnnotationsPrefix +
"auth-client-headers"
+
+ // ip-restriction plugin
+ AnnotationsAllowlistSourceRange = AnnotationsPrefix +
"allowlist-source-range"
+ AnnotationsBlocklistSourceRange = AnnotationsPrefix +
"blocklist-source-range"
+
+ // http-method plugin
+ AnnotationsHttpAllowMethods = AnnotationsPrefix + "http-allow-methods"
+ AnnotationsHttpBlockMethods = AnnotationsPrefix + "http-block-methods"
+
+ // key-auth plugin and basic-auth plugin
+ // auth-type: keyAuth | basicAuth
+ AnnotationsAuthType = AnnotationsPrefix + "auth-type"
+
+ // support backend service cross namespace
+ AnnotationsSvcNamespace = AnnotationsPrefix + "svc-namespace"
+)
+
+// Handler abstracts the behavior so that the apisix-ingress-controller knows
+type IngressAnnotationsParser interface {
+ // Handle parses the target annotation and converts it to the
type-agnostic structure.
+ // The return value might be nil since some features have an explicit
switch, users should
+ // judge whether Handle is failed by the second error value.
+ Parse(Extractor) (any, error)
+}
+
+// Extractor encapsulates some auxiliary methods to extract annotations.
+type Extractor interface {
+ // GetStringAnnotation returns the string value of the target
annotation.
+ // When the target annoatation is missing, empty string will be given.
+ GetStringAnnotation(string) string
+ // GetStringsAnnotation returns a string slice which splits the value
of target
+ // annotation by the comma symbol. When the target annotation is
missing, a nil
+ // slice will be given.
+ GetStringsAnnotation(string) []string
+ // GetBoolAnnotation returns a boolean value from the given annotation.
+ // When value is "true", true will be given, other values will be
treated as
+ // false.
+ GetBoolAnnotation(string) bool
+}
+
+type extractor struct {
+ annotations map[string]string
+}
+
+func (e *extractor) GetStringAnnotation(name string) string {
+ return e.annotations[name]
+}
+
+func (e *extractor) GetStringsAnnotation(name string) []string {
+ value := e.GetStringAnnotation(name)
+ if value == "" {
+ return nil
+ }
+ return strings.Split(value, ",")
+}
+
+func (e *extractor) GetBoolAnnotation(name string) bool {
+ return e.annotations[name] == "true"
+}
+
+// NewExtractor creates an annotation extractor.
+func NewExtractor(annotations map[string]string) Extractor {
+ return &extractor{
+ annotations: annotations,
+ }
+}
diff --git a/internal/adc/translator/annotations_test.go
b/internal/adc/translator/annotations_test.go
new file mode 100644
index 00000000..d23a2474
--- /dev/null
+++ b/internal/adc/translator/annotations_test.go
@@ -0,0 +1,87 @@
+// 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 translator
+
+import (
+ "errors"
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+
+
"github.com/apache/apisix-ingress-controller/internal/adc/translator/annotations"
+)
+
+type mockParser struct {
+ output any
+ err error
+}
+
+func (m *mockParser) Parse(extractor annotations.Extractor) (any, error) {
+ return m.output, m.err
+}
+
+func TestTranslateAnnotations(t *testing.T) {
+ tests := []struct {
+ name string
+ anno map[string]string
+ parsers map[string]annotations.IngressAnnotationsParser
+ expected any
+ expectErr bool
+ }{
+ {
+ name: "successful parsing",
+ anno: map[string]string{"key1": "value1"},
+ parsers:
map[string]annotations.IngressAnnotationsParser{
+ "key1": &mockParser{output: "parsedValue1",
err: nil},
+ },
+ expected: map[string]any{"key1": "parsedValue1"},
+ expectErr: false,
+ },
+ {
+ name: "parsing with error",
+ anno: map[string]string{"key1": "value1"},
+ parsers:
map[string]annotations.IngressAnnotationsParser{
+ "key1": &mockParser{output: nil, err:
errors.New("parse error")},
+ },
+ expected: map[string]any{},
+ expectErr: true,
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ // Set up mock parsers
+ for key, parser := range tt.parsers {
+ ingressAnnotationParsers[key] = parser
+ }
+
+ dst := make(map[string]any)
+ err := translateAnnotations(tt.anno, &dst)
+
+ if tt.expectErr {
+ assert.Error(t, err)
+ } else {
+ assert.NoError(t, err)
+ }
+ assert.Equal(t, tt.expected, dst)
+
+ // Clean up mock parsers
+ for key := range tt.parsers {
+ delete(ingressAnnotationParsers, key)
+ }
+ })
+ }
+}