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)
+                       }
+               })
+       }
+}

Reply via email to