deckの一部のファイル操作(patch、add-plugins等)では対象エンティティの選択にJSON Path形式を利用する。(deckのpatchやadd-pluginsの説明には書いていないが、Lintingのページに記載がある)
JSON Pathの書き方はRFC-9535で規定されていて、基本的にはここで定義された書き方で書けるが、例外もある(後述)。
ここではdeckで使えるJSON Pathの書き方をdeck file add-pluginsコマンドを使って検証する。
検証では以下のファイルを用いた。
_format_version: "3.0"
services:
- enabled: true
host: httpbin.org
name: httpbin-service-1
path: /
port: 443
protocol: https
tags:
- httpbin-1
routes:
- name: httpbin-route-1
paths:
- /httpbin-1
protocols:
- https
- enabled: true
host: httpbin.konghq.com
name: httpbin-service-2
path: /
port: 443
protocol: https
tags:
- httpbin-2
routes:
- name: httpbin-route-2
headers:
foo:
- bar
paths:
- /httpbin-2
protocols:
- https
add-pluginsで使うYAMLは以下をベースとする。
_format_version: "1.0"
add-plugins:
- overwrite: false
plugins:
- name: key-auth
selectors:
- $
このselectorsでJSONPath形式でクエリが書けるので、これを色々変えてbase.yamlのどこにkey-auth.yamlで定義したプラグインを差し込めるかを検証する。
Global Scopeで追加
まず、元のkey-auth.yamlのselectorsであるが、$がルートを意味するため、Global Scopeで適用される。
適用した結果はこちら。
$ deck file add-plugins -s base.yaml key-auth-global.yaml
_format_version: "3.0"
plugins:
- name: key-auth
services:
- enabled: true
:(省略)
Service等、他のエンティティには適用されず最上位に挿入される。
Serviceへの追加
全Serviceを対象
全てのServiceに適用する場合は以下のような感じ。
selectors:
- $.service[*]
.でファイル内の子要素を参照する。[]で配列のアクセスとなり、*を指定することで全ての配列の要素を参照する感じになる。
実行した結果は以下となる。
_format_version: "3.0"
services:
- enabled: true
host: httpbin.org
name: httpbin-service-1
path: /
plugins:
- name: key-auth
port: 443
:(省略)
- enabled: true
host: httpbin.konghq.com
name:
path: /
plugins:
- name: key-auth
port: 443
:(省略)
名前が一致するServiceに適用
Service名を指定する場合は以下。
- selectors:
- $.services[?(@.name=='httpbin-service-2')]
?()でフィルタを指定する。
フィルタ内では@で現在の要素を指定、その中の.nameが'httpbin-service-2'と一致するものを絞って選択している。
結果は以下。
services:
- enabled: true
host: httpbin.org
name: httpbin-service-1
:(省略)
- enabled: true
host: httpbin.konghq.com
name: httpbin-service-2
path: /
plugins:
- name: key-auth
port: 443
name ==で選択したhttpbin-service-2のみに適用された。
なお、selectors:は複数書けるようになっているが、複数書いた場合はOR条件になる。
なので、複数のService名を指定したい場合は以下のように書く。
- selectors:
- $.services[?(@.name=='httpbin-service-1')]
- $.services[?(@.name=='httpbin-service-2')]
タグが一致するServiceに適用
deck gateway dumpした後に、特定のタグのものにPluginを追加したいようなケースとかで実行する。
以下のような感じで書く。
- selectors:
- $.services[?(@.tags[?(@ == 'httpbin-1')])]
先程のnameは単純な一致だったが、tagsは配列で複数書けるので、tags[]の配列のキーを比較する形で書く。
名前とタグが一致するServiceに適用
フィルタ内にAND条件を追加する。AND条件は&&で書けて、名前一致とタグ一致はこれまで検証した式がそのまま使えるので、&&で結合する。
- selectors:
- $.services[?(@.name == 'httpbin-1' && @.tags[?(@ == 'httpbin-1')])]
Routeへの追加
全Routeを対象
- selectors:
- $..routes[*]
$.services[*].routes[*] でもいいが、..だとどこにroutesがいても設定してくれる。Serviceに紐づかないようなRouteがいるようなケースでも使える。
逆にServiceに紐づくRouteのみ指定したい場合は$.services[*].routes[*] の方がいいと思う。
パスが一致するRouteに適用
いちいちRouteの名前なんて覚えていないが、パス名は分かるからそこにプラグイン差し込みたい、というような人向け。
- selectors:
- $.services[*].routes[?(@.paths[?(@ == '/httpbin-1')])]
$..routes[?(@.paths[?(@ == '/httpbin-1')])]で行けそうなものだが、deckの不具合(正確には使っているライブラリの不具合)で適用できない。
なので、回避策として..を使わずに指定する。
ヘッダが一致するRouteに適用
ヘッダはdeckだとheaders.key[].valueのような構造になっているため、以下のような形になる。
- selectors:
- $.services[*].routes[?(@.headers.foo[?(@ == "bar")])]
パスとヘッダが一致するRouteに適用
同じパスにアクセスさせるのだけど、ヘッダが違う時だけ振る舞いを変えるようなケース。
- selectors:
- $.services[*].routes[?(@.paths[?(@ == '/httpbin-2')] && @.headers.foo[0] == 'bar']
これもバグがあり、$.services[*].routes[?(@.paths[?(@ == '/httpbin-2')] && @.headers.foo[?(@ == "bar")])]だと上手く行かない(JSONPath Online Evaluatorでは通る)。
どうもフィルタを複数通したあとに&&を使って更にフィルタを使うと構文エラーとされてしまう模様。
なので苦肉の策としてヘッダ側は配列決め打ちでフィルタを使わないようにする。
結論
JSONPathで柔軟にKongのエンティティを選択してプラグインの追加等が出来ることが確認できた。
検証ではService、Routeのみ対象としたが、Consumer等も名前が変わるだけで同じやり方で操作できるので、JSONPathを使ったselectorを活用することでdeck利用時に柔軟にYAMLをカスタマイズ出来るかと思う。
一方で、ライブラリのバグで上手く動かないケースもそこそこあることが確認できた。
使っているライブラリがBroadcomに買収されたVMware製であることを考えるとバグ修正も簡単には行かなさそうな気がするので、利用する場合(特に複雑な式を書く場合)はバグありきで事前検証をしっかりした方が良さそうだ。