Skip to content

Commit 0994aba

Browse files
authored
Merge aa0655e into 1da1ca0
2 parents 1da1ca0 + aa0655e commit 0994aba

File tree

6 files changed

+280
-11
lines changed

6 files changed

+280
-11
lines changed

docs/en/latest/concepts/apisix_route.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,46 @@ spec:
262262
secretRef: echo
263263
```
264264

265+
## Config with secretRef where the secret data contains path to a specific key that needs to be overridden in plugin config
266+
267+
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`.
268+
269+
```yaml
270+
apiVersion: v1
271+
kind: Secret
272+
metadata:
273+
#content is "v2"
274+
name: echo
275+
data:
276+
headers.X-Foo: djI=
277+
---
278+
apiVersion: apisix.apache.org/v2
279+
kind: ApisixRoute
280+
metadata:
281+
name: httpbin-route
282+
spec:
283+
http:
284+
- name: rule1
285+
match:
286+
hosts:
287+
- httpbin.org
288+
paths:
289+
- /ip
290+
backends:
291+
- serviceName: %s
292+
servicePort: %d
293+
weight: 10
294+
plugins:
295+
- name: echo
296+
enable: true
297+
config:
298+
before_body: "This is the preface"
299+
after_body: "This is the epilogue"
300+
headers:
301+
X-Foo: v1
302+
secretRef: echo
303+
```
304+
265305
## Websocket proxy
266306

267307
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:

pkg/providers/apisix/translation/apisix_pluginconfig.go

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
configv2 "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/apis/config/v2"
2222
"github.com/apache/apisix-ingress-controller/pkg/log"
2323
"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
24+
"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
2425
apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
2526
)
2627

@@ -33,14 +34,6 @@ func (t *translator) TranslatePluginConfigV2(config *configv2.ApisixPluginConfig
3334
continue
3435
}
3536
if plugin.Config != nil {
36-
// Here, it will override same key.
37-
if t, ok := pluginMap[plugin.Name]; ok {
38-
log.Infow("TranslatePluginConfigV2 override same plugin key",
39-
zap.String("key", plugin.Name),
40-
zap.Any("old", t),
41-
zap.Any("new", plugin.Config),
42-
)
43-
}
4437
if plugin.SecretRef != "" {
4538
sec, err := t.SecretLister.Secrets(config.Namespace).Get(plugin.SecretRef)
4639
if err != nil {
@@ -52,8 +45,9 @@ func (t *translator) TranslatePluginConfigV2(config *configv2.ApisixPluginConfig
5245
log.Debugw("Add new items, then override items with the same plugin key",
5346
zap.Any("plugin", plugin.Name),
5447
zap.String("secretRef", plugin.SecretRef))
48+
5549
for key, value := range sec.Data {
56-
plugin.Config[key] = string(value)
50+
utils.InsertKeyInMap(key, string(value), plugin.Config)
5751
}
5852
}
5953
pluginMap[plugin.Name] = plugin.Config

pkg/providers/apisix/translation/apisix_route.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
_const "github.com/apache/apisix-ingress-controller/pkg/kube/apisix/const"
3232
"github.com/apache/apisix-ingress-controller/pkg/log"
3333
"github.com/apache/apisix-ingress-controller/pkg/providers/translation"
34+
"github.com/apache/apisix-ingress-controller/pkg/providers/utils"
3435
apisixv1 "github.com/apache/apisix-ingress-controller/pkg/types/apisix/v1"
3536
)
3637

@@ -101,8 +102,9 @@ func (t *translator) translateHTTPRouteV2(ctx *translation.TranslateContext, ar
101102
log.Debugw("Add new items, then override items with the same plugin key",
102103
zap.Any("plugin", plugin.Name),
103104
zap.String("secretRef", plugin.SecretRef))
105+
104106
for key, value := range sec.Data {
105-
plugin.Config[key] = string(value)
107+
utils.InsertKeyInMap(key, string(value), plugin.Config)
106108
}
107109
}
108110
pluginMap[plugin.Name] = plugin.Config
@@ -536,7 +538,7 @@ func (t *translator) translateStreamRouteV2(ctx *translation.TranslateContext, a
536538
zap.Any("plugin", plugin.Name),
537539
zap.String("secretRef", plugin.SecretRef))
538540
for key, value := range sec.Data {
539-
plugin.Config[key] = string(value)
541+
utils.InsertKeyInMap(key, string(value), plugin.Config)
540542
}
541543
}
542544
pluginMap[plugin.Name] = plugin.Config

pkg/providers/utils/insert_map.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one or more
2+
// contributor license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright ownership.
4+
// The ASF licenses this file to You under the Apache License, Version 2.0
5+
// (the "License"); you may not use this file except in compliance with
6+
// the License. You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
package utils
16+
17+
import (
18+
"strings"
19+
)
20+
21+
// InsertKeyInMap takes a dot separated string and recursively goes inside the destination
22+
// to fill the value
23+
func InsertKeyInMap(key string, value interface{}, dest map[string]interface{}) {
24+
if key == "" {
25+
return
26+
}
27+
keys := strings.SplitN(key, ".", 2)
28+
//base condition. the length of keys will be atleast 1
29+
if len(keys) < 2 {
30+
dest[keys[0]] = value
31+
return
32+
}
33+
34+
ikey := keys[0]
35+
restKey := keys[1]
36+
if dest[ikey] == nil {
37+
dest[ikey] = make(map[string]interface{})
38+
}
39+
newDest, ok := dest[ikey].(map[string]interface{})
40+
if !ok {
41+
newDest = make(map[string]interface{})
42+
dest[ikey] = newDest
43+
}
44+
InsertKeyInMap(restKey, value, newDest)
45+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one or more
2+
// contributor license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright ownership.
4+
// The ASF licenses this file to You under the Apache License, Version 2.0
5+
// (the "License"); you may not use this file except in compliance with
6+
// the License. You may obtain a copy of the License at
7+
//
8+
// http://www.apache.org/licenses/LICENSE-2.0
9+
//
10+
// Unless required by applicable law or agreed to in writing, software
11+
// distributed under the License is distributed on an "AS IS" BASIS,
12+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
// See the License for the specific language governing permissions and
14+
// limitations under the License.
15+
package utils
16+
17+
import (
18+
"encoding/json"
19+
"fmt"
20+
"testing"
21+
)
22+
23+
func TestInsertKeyInMap(t *testing.T) {
24+
type testCase struct {
25+
key string
26+
value interface{}
27+
dest string
28+
merged string
29+
}
30+
testCases := []testCase{{
31+
dest: `{
32+
"a":1,
33+
"b":{
34+
"c":{
35+
"d":"e"
36+
},
37+
"f":"g"
38+
}
39+
}`,
40+
key: `b.c`,
41+
value: 2,
42+
merged: `{
43+
"a":1,
44+
"b":{
45+
"c":2,
46+
"f":"g"
47+
}
48+
}`,
49+
}, {
50+
dest: `{
51+
"a":1,
52+
"b":{
53+
"c": 2,
54+
"f":"g"
55+
}
56+
}`,
57+
key: `b.c`,
58+
value: map[string]string{
59+
"d": "e",
60+
},
61+
merged: `{
62+
"a":1,
63+
"b":{
64+
"c":{
65+
"d":"e"
66+
},
67+
"f":"g"
68+
}
69+
}`,
70+
}, {
71+
dest: `{
72+
"a":1,
73+
"b":{
74+
"c": 2,
75+
"f":"g"
76+
}
77+
}`,
78+
key: `b.c.d`,
79+
value: map[string]string{
80+
"x": "y",
81+
},
82+
merged: `{
83+
"a":1,
84+
"b":{
85+
"c":{
86+
"d":{
87+
"x":"y"
88+
}
89+
},
90+
"f":"g"
91+
}
92+
}`,
93+
}, {
94+
dest: `{
95+
"a":1,
96+
"b":"old"
97+
}
98+
`,
99+
key: "b",
100+
value: "new",
101+
merged: `{
102+
"a":1,
103+
"b":"new"
104+
}`,
105+
}}
106+
107+
for _, t0 := range testCases {
108+
destMap := make(map[string]interface{})
109+
err := json.Unmarshal([]byte(t0.dest), &destMap)
110+
if err != nil {
111+
t.Fatal(err)
112+
}
113+
out := make(map[string]interface{})
114+
err = json.Unmarshal([]byte(t0.merged), &out)
115+
if err != nil {
116+
t.Fatal(err)
117+
}
118+
outB, err := json.MarshalIndent(out, " ", "")
119+
if err != nil {
120+
t.Fatal(err)
121+
}
122+
123+
InsertKeyInMap(t0.key, t0.value, destMap)
124+
fmt.Println(destMap)
125+
merged, err := json.MarshalIndent(destMap, " ", "")
126+
if err != nil {
127+
t.Fatal(err)
128+
}
129+
if string(outB) != string(merged) {
130+
t.Errorf("Expected \n%s\n but got \n%s\n", string(outB), string(merged))
131+
}
132+
}
133+
}

test/e2e/suite-plugins/suite-plugins-general/secret_ref.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,62 @@ spec:
8787
resp.Body().Contains("This is the epilogue")
8888
resp.Body().Contains("my custom body")
8989
})
90+
91+
ginkgo.It("suite-plugins-general: nested plugin config with secretRef", func() {
92+
backendSvc, backendPorts := s.DefaultHTTPBackend()
93+
secret := `
94+
apiVersion: v1
95+
kind: Secret
96+
metadata:
97+
name: echo
98+
data:
99+
headers.X-Foo: djI=
100+
# content is "my custom body"
101+
body: Im15IGN1c3RvbSBib2R5Ig==
102+
`
103+
assert.Nil(ginkgo.GinkgoT(), s.CreateResourceFromString(secret), "creating echo secret for ApisixRoute")
104+
ar := fmt.Sprintf(`
105+
apiVersion: apisix.apache.org/v2
106+
kind: ApisixRoute
107+
metadata:
108+
name: httpbin-route
109+
spec:
110+
http:
111+
- name: rule1
112+
match:
113+
hosts:
114+
- httpbin.org
115+
paths:
116+
- /ip
117+
backends:
118+
- serviceName: %s
119+
servicePort: %d
120+
weight: 10
121+
plugins:
122+
- name: echo
123+
enable: true
124+
config:
125+
before_body: "This is the preface"
126+
after_body: "This is the epilogue"
127+
headers:
128+
X-Foo: v1
129+
secretRef: echo
130+
131+
`, backendSvc, backendPorts[0])
132+
133+
assert.Nil(ginkgo.GinkgoT(), s.CreateVersionedApisixResource(ar))
134+
135+
err := s.EnsureNumApisixUpstreamsCreated(1)
136+
assert.Nil(ginkgo.GinkgoT(), err, "Checking number of upstreams")
137+
err = s.EnsureNumApisixRoutesCreated(1)
138+
assert.Nil(ginkgo.GinkgoT(), err, "Checking number of routes")
139+
140+
resp := s.NewAPISIXClient().GET("/ip").WithHeader("Host", "httpbin.org").Expect()
141+
resp.Status(http.StatusOK)
142+
resp.Header("X-Foo").Equal("v2")
143+
})
90144
}
145+
91146
ginkgo.Describe("suite-plugins-general: scaffold v2", func() {
92147
suites(scaffold.NewDefaultV2Scaffold)
93148
})

0 commit comments

Comments
 (0)