This is an automated email from the ASF dual-hosted git repository.
ashishtiwari 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 a759ae5c feat: Allow merging nested values in plugin config secretRef
(#2096)
a759ae5c is described below
commit a759ae5c37404e0e43a5188281cb04bd617318f1
Author: Ashish Tiwari <[email protected]>
AuthorDate: Fri Dec 15 16:00:58 2023 +0530
feat: Allow merging nested values in plugin config secretRef (#2096)
* feat: Allow merging nested values in plugin config secretRef
---------
Signed-off-by: Ashish Tiwari <[email protected]>
---
docs/en/latest/concepts/apisix_route.md | 40 +++++++
.../apisix/translation/apisix_pluginconfig.go | 12 +-
pkg/providers/apisix/translation/apisix_route.go | 6 +-
pkg/providers/utils/insert_map.go | 45 +++++++
pkg/providers/utils/insert_map_test.go | 133 +++++++++++++++++++++
.../suite-plugins-general/secret_ref.go | 55 +++++++++
6 files changed, 280 insertions(+), 11 deletions(-)
diff --git a/docs/en/latest/concepts/apisix_route.md
b/docs/en/latest/concepts/apisix_route.md
index 5da39829..c40937fb 100644
--- a/docs/en/latest/concepts/apisix_route.md
+++ b/docs/en/latest/concepts/apisix_route.md
@@ -262,6 +262,46 @@ spec:
secretRef: echo
```
+## Config with secretRef where the secret data contains path to a specific key
that needs to be overridden in plugin config
+
+You can also configure specific fields in the plugin configuration that are
deeply nested by passing the path to that field. The path is dot-separated keys
that lead to that field. The below example overrides the `X-Foo` header field
in the plugin configuration from `v1` to `v2`.
+
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+ #content is "v2"
+ name: echo
+data:
+ headers.X-Foo: djI=
+---
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - httpbin.org
+ paths:
+ - /ip
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ weight: 10
+ plugins:
+ - name: echo
+ enable: true
+ config:
+ before_body: "This is the preface"
+ after_body: "This is the epilogue"
+ headers:
+ X-Foo: v1
+ secretRef: echo
+```
+
## Websocket proxy
You can route requests to
[WebSocket](https://en.wikipedia.org/wiki/WebSocket#:~:text=WebSocket%20is%20a%20computer%20communications,WebSocket%20is%20distinct%20from%20HTTP.)
services by setting the `websocket` attribute to `true` as shown below:
diff --git a/pkg/providers/apisix/translation/apisix_pluginconfig.go
b/pkg/providers/apisix/translation/apisix_pluginconfig.go
index f8b32217..5ee0e2a4 100644
--- a/pkg/providers/apisix/translation/apisix_pluginconfig.go
+++ b/pkg/providers/apisix/translation/apisix_pluginconfig.go
@@ -21,6 +21,7 @@ import (
configv2
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
"github.com/apache/apisix-ingress-controller/pkg/log"
"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+ "github.com/apache/apisix-ingress-controller/pkg/providers/utils"
apisixv1
"github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)
@@ -33,14 +34,6 @@ func (t *translator) TranslatePluginConfigV2(config
*configv2.ApisixPluginConfig
continue
}
if plugin.Config != nil {
- // Here, it will override same key.
- if t, ok := pluginMap[plugin.Name]; ok {
- log.Infow("TranslatePluginConfigV2
override same plugin key",
- zap.String("key", plugin.Name),
- zap.Any("old", t),
- zap.Any("new", plugin.Config),
- )
- }
if plugin.SecretRef != "" {
sec, err :=
t.SecretLister.Secrets(config.Namespace).Get(plugin.SecretRef)
if err != nil {
@@ -52,8 +45,9 @@ func (t *translator) TranslatePluginConfigV2(config
*configv2.ApisixPluginConfig
log.Debugw("Add new items, then
override items with the same plugin key",
zap.Any("plugin", plugin.Name),
zap.String("secretRef",
plugin.SecretRef))
+
for key, value := range sec.Data {
- plugin.Config[key] =
string(value)
+ utils.InsertKeyInMap(key,
string(value), plugin.Config)
}
}
pluginMap[plugin.Name] = plugin.Config
diff --git a/pkg/providers/apisix/translation/apisix_route.go
b/pkg/providers/apisix/translation/apisix_route.go
index 50d8b652..856709e6 100644
--- a/pkg/providers/apisix/translation/apisix_route.go
+++ b/pkg/providers/apisix/translation/apisix_route.go
@@ -31,6 +31,7 @@ import (
_const
"github.com/apache/apisix-ingress-controller/pkg/kube/apisix/const"
"github.com/apache/apisix-ingress-controller/pkg/log"
"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
+ "github.com/apache/apisix-ingress-controller/pkg/providers/utils"
apisixv1
"github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
)
@@ -101,8 +102,9 @@ func (t *translator) translateHTTPRouteV2(ctx
*translation.TranslateContext, ar
log.Debugw("Add new items, then
override items with the same plugin key",
zap.Any("plugin", plugin.Name),
zap.String("secretRef",
plugin.SecretRef))
+
for key, value := range sec.Data {
- plugin.Config[key] =
string(value)
+ utils.InsertKeyInMap(key,
string(value), plugin.Config)
}
}
pluginMap[plugin.Name] = plugin.Config
@@ -536,7 +538,7 @@ func (t *translator) translateStreamRouteV2(ctx
*translation.TranslateContext, a
zap.Any("plugin", plugin.Name),
zap.String("secretRef",
plugin.SecretRef))
for key, value := range sec.Data {
- plugin.Config[key] =
string(value)
+ utils.InsertKeyInMap(key,
string(value), plugin.Config)
}
}
pluginMap[plugin.Name] = plugin.Config
diff --git a/pkg/providers/utils/insert_map.go
b/pkg/providers/utils/insert_map.go
new file mode 100644
index 00000000..75056447
--- /dev/null
+++ b/pkg/providers/utils/insert_map.go
@@ -0,0 +1,45 @@
+// 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 utils
+
+import (
+ "strings"
+)
+
+// InsertKeyInMap takes a dot separated string and recursively goes inside the
destination
+// to fill the value
+func InsertKeyInMap(key string, value interface{}, dest
map[string]interface{}) {
+ if key == "" {
+ return
+ }
+ keys := strings.SplitN(key, ".", 2)
+ //base condition. the length of keys will be atleast 1
+ if len(keys) < 2 {
+ dest[keys[0]] = value
+ return
+ }
+
+ ikey := keys[0]
+ restKey := keys[1]
+ if dest[ikey] == nil {
+ dest[ikey] = make(map[string]interface{})
+ }
+ newDest, ok := dest[ikey].(map[string]interface{})
+ if !ok {
+ newDest = make(map[string]interface{})
+ dest[ikey] = newDest
+ }
+ InsertKeyInMap(restKey, value, newDest)
+}
diff --git a/pkg/providers/utils/insert_map_test.go
b/pkg/providers/utils/insert_map_test.go
new file mode 100644
index 00000000..b97bc6dd
--- /dev/null
+++ b/pkg/providers/utils/insert_map_test.go
@@ -0,0 +1,133 @@
+// 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 utils
+
+import (
+ "encoding/json"
+ "fmt"
+ "testing"
+)
+
+func TestInsertKeyInMap(t *testing.T) {
+ type testCase struct {
+ key string
+ value interface{}
+ dest string
+ merged string
+ }
+ testCases := []testCase{{
+ dest: `{
+ "a":1,
+ "b":{
+ "c":{
+ "d":"e"
+ },
+ "f":"g"
+ }
+ }`,
+ key: `b.c`,
+ value: 2,
+ merged: `{
+ "a":1,
+ "b":{
+ "c":2,
+ "f":"g"
+ }
+ }`,
+ }, {
+ dest: `{
+ "a":1,
+ "b":{
+ "c": 2,
+ "f":"g"
+ }
+ }`,
+ key: `b.c`,
+ value: map[string]string{
+ "d": "e",
+ },
+ merged: `{
+ "a":1,
+ "b":{
+ "c":{
+ "d":"e"
+ },
+ "f":"g"
+ }
+ }`,
+ }, {
+ dest: `{
+ "a":1,
+ "b":{
+ "c": 2,
+ "f":"g"
+ }
+ }`,
+ key: `b.c.d`,
+ value: map[string]string{
+ "x": "y",
+ },
+ merged: `{
+ "a":1,
+ "b":{
+ "c":{
+ "d":{
+ "x":"y"
+ }
+ },
+ "f":"g"
+ }
+ }`,
+ }, {
+ dest: `{
+ "a":1,
+ "b":"old"
+ }
+ `,
+ key: "b",
+ value: "new",
+ merged: `{
+ "a":1,
+ "b":"new"
+ }`,
+ }}
+
+ for _, t0 := range testCases {
+ destMap := make(map[string]interface{})
+ err := json.Unmarshal([]byte(t0.dest), &destMap)
+ if err != nil {
+ t.Fatal(err)
+ }
+ out := make(map[string]interface{})
+ err = json.Unmarshal([]byte(t0.merged), &out)
+ if err != nil {
+ t.Fatal(err)
+ }
+ outB, err := json.MarshalIndent(out, " ", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ InsertKeyInMap(t0.key, t0.value, destMap)
+ fmt.Println(destMap)
+ merged, err := json.MarshalIndent(destMap, " ", "")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if string(outB) != string(merged) {
+ t.Errorf("Expected \n%s\n but got \n%s\n",
string(outB), string(merged))
+ }
+ }
+}
diff --git a/test/e2e/suite-plugins/suite-plugins-general/secret_ref.go
b/test/e2e/suite-plugins/suite-plugins-general/secret_ref.go
index c8123890..733d3238 100644
--- a/test/e2e/suite-plugins/suite-plugins-general/secret_ref.go
+++ b/test/e2e/suite-plugins/suite-plugins-general/secret_ref.go
@@ -87,7 +87,62 @@ spec:
resp.Body().Contains("This is the epilogue")
resp.Body().Contains("my custom body")
})
+
+ ginkgo.It("suite-plugins-general: nested plugin config with
secretRef", func() {
+ backendSvc, backendPorts := s.DefaultHTTPBackend()
+ secret := `
+apiVersion: v1
+kind: Secret
+metadata:
+ name: echo
+data:
+ headers.X-Foo: djI=
+ # content is "my custom body"
+ body: Im15IGN1c3RvbSBib2R5Ig==
+`
+ assert.Nil(ginkgo.GinkgoT(),
s.CreateResourceFromString(secret), "creating echo secret for ApisixRoute")
+ ar := fmt.Sprintf(`
+apiVersion: apisix.apache.org/v2
+kind: ApisixRoute
+metadata:
+ name: httpbin-route
+spec:
+ http:
+ - name: rule1
+ match:
+ hosts:
+ - httpbin.org
+ paths:
+ - /ip
+ backends:
+ - serviceName: %s
+ servicePort: %d
+ weight: 10
+ plugins:
+ - name: echo
+ enable: true
+ config:
+ before_body: "This is the preface"
+ after_body: "This is the epilogue"
+ headers:
+ X-Foo: v1
+ secretRef: echo
+
+`, backendSvc, backendPorts[0])
+
+ assert.Nil(ginkgo.GinkgoT(),
s.CreateVersionedApisixResource(ar))
+
+ err := s.EnsureNumApisixUpstreamsCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
upstreams")
+ err = s.EnsureNumApisixRoutesCreated(1)
+ assert.Nil(ginkgo.GinkgoT(), err, "Checking number of
routes")
+
+ resp :=
s.NewAPISIXClient().GET("/ip").WithHeader("Host", "httpbin.org").Expect()
+ resp.Status(http.StatusOK)
+ resp.Header("X-Foo").Equal("v2")
+ })
}
+
ginkgo.Describe("suite-plugins-general: scaffold v2", func() {
suites(scaffold.NewDefaultV2Scaffold)
})