KubernetesでのJSONPathの書き方を基本から調べてみた

# はじめに
書式は[Cheat Sheet](https://kubernetes.io/docs/reference/kubectl/cheatsheet/)とか[JSONPath Support](https://kubernetes.io/docs/reference/kubectl/jsonpath/)に書いてあるのだが、解説をつけておくと（私のような）初心者には親切だと思われるので備忘録も兼ねて。

# 文字列の扱いと式の表現

- `-o jsonpath='<式>'`のように`' '`で囲んで記載するのが基本の書式。
- `{ }`で囲まれない場合は、単なる文字列として扱われる。

```shell-session
$ kubectl get pods -o jsonpath='aiueo\n'
aiueo\n
```

- 式(expression)は`{ }`で囲む。
- 式内部での文字列は`" "`で囲む。`" "`で囲まれていない場合は、ルートからの探索対象パスとして扱われる。
- 式として評価されると、`{"¥n"}`は改行になり、`{"¥t"}`はタブになる。
- 式は複数個並べることで連結される。つまり、`aaa:bbb（改行）ccc（改行）`を記載しようとする場合は、以下のような記法が考えられる。


```shell-session:例
$ kubectl get pods -o jsonpath='aaa:bbb{"\n"}ccc{"\n"}'
aaa:bbb
ccc

$ kubectl get pods -o jsonpath='{"aaa"}{" : " }{"bbb\n"}{"ccc\n"}'
aaa : bbb
ccc

```

#JSONPathの探索の基本

実際は、`kubectl get xxx`などの結果を元にした処理が求められる。

- JSONPathの探索は式（`{ }`で囲む)で行う。
- JSONPathでデータを抽出・整形する場合は、まずその構造がどうなってたどることができるか確認するのが最初にやるべきことである。よって、`kubectl get xxx -o yaml`のように出力した後、そのデータ構造を確認するとよい。
- ルート（一番上）は`$`で表すが、kubectlなどを実施する場合は特に何もしなくてもルートから始まることになっている。そのため、`{$.metadata.name}`は`{.metadata.name}`と書いても良い（kubectl実行時のサンプルでは、省略しているケースの方が多い気がする）。

```yaml
$ kubectl get pod busybox -o yaml
apiVersion: v1
kind: Pod
metadata:
  annotations:
    cni.projectcalico.org/podIP: 172.17.64.80/32
    cni.projectcalico.org/podIPs: 172.17.64.80/32
    k8s.v1.cni.cncf.io/networks-status: |-
      [{
          "name": "k8s-pod-network",
          "ips": [
              "172.17.64.80"
          ],
          "dns": {}
      }]
    openshift.io/scc: anyuid
  creationTimestamp: "2020-07-12T10:53:15Z"
  labels:
    app: newapp
    run: busybox
  name: busybox
  namespace: syasuda
  resourceVersion: "7232141"
  selfLink: /api/v1/namespaces/syasuda/pods/busybox
  uid: 08a975f5-c627-4565-a97d-5dc8ae731779
spec:
  containers:
  - image: busybox@sha256:2131f09e4044327fd101ca1fd4043e6f3ad921ae7ee901e9142e6e36b354a907
    imagePullPolicy: IfNotPresent
（以下略）
```


```shell-session
$ kubectl get pod busybox -o jsonpath='PodName : {$.metadata.name}{"\n"}NameSpace : {$.metadata.namespace}{"\n"}'
PodName : busybox
NameSpace : syasuda

$ kubectl get pod busybox -o jsonpath='PodName : {.metadata.name}{"\n"}NameSpace : {.metadata.namespace}{"\n"}'
PodName : busybox
NameSpace : syasuda
```

#配列

```shell-session
# kubectl get pod busybox -o yaml
（途中略）
spec
（途中略）
  tolerations:
  - effect: NoExecute
    key: node.kubernetes.io/not-ready
    operator: Exists
    tolerationSeconds: 300
  - effect: NoExecute
    key: node.kubernetes.io/unreachable
    operator: Exists
    tolerationSeconds: 300
（途中略）
```

上記は、`spec.tolerations`配下にObjectが2つ存在している。これはtoleration`s`のように複数形で書かれていることから次に配列が来ることが`暗示`されており、なおかつ実際に`-`で配列要素を列挙している。つまり、以下のような形になっている。

```yaml
spec
  tolerations
  -  <toleration1>
  -  <toleration2>
```

`'{@.spec.tolerations}'`だと配列そのものを返す。

```shell-session:[]で囲まれているため、2つの配列要素を内包する１つの配列として返ってきていることがわかる。
$ kubectl get pod busybox -o jsonpath='{@.spec.tolerations}'
[map[effect:NoExecute key:node.kubernetes.io/not-ready operator:Exists tolerationSeconds:300] map[effect:NoExecute key:node.kubernetes.io/unreachable operator:Exists tolerationSeconds:300]]
```

`'{@.spec.tolerations[*]}'`だと配列を構成するオブジェクト（今回は2つ）を返す。

```shell-session:2つの配列要素が返ってきている。
$ kubectl get pod busybox -o jsonpath='{@.spec.tolerations[*]}'
map[effect:NoExecute key:node.kubernetes.io/not-ready operator:Exists tolerationSeconds:300] map[effect:NoExecute key:node.kubernetes.io/unreachable operator:Exists tolerationSeconds:300]
```

`'{@.spec.tolerations[]}'`と`'{@.spec.tolerations[0]}'`は同じ意味っぽい。

```shell-session:どちらも１つめの配列要素が返ってきている
$ kubectl get pod busybox -o jsonpath='{@.spec.tolerations[]}'
map[effect:NoExecute key:node.kubernetes.io/not-ready operator:Exists tolerationSeconds:300]

$ kubectl get pod busybox -o jsonpath='{@.spec.tolerations[0]}'
map[effect:NoExecute key:node.kubernetes.io/not-ready operator:Exists tolerationSeconds:300]
```


# ループ処理

一般的には`kubectl get pods`のように複数のオブジェクトを対象とした操作が多いと思われるが、例えばこちらは以下のようなデータ構造になっている。 こちらも`items`配下に配列があることを暗示するためにitem`s`と複数形になっており、なおかつ配列を表す`-`という記号から始まっている。


```yaml
items
  - <Pod1>
  - <Pod2>
  - <Pod3>
```

この構造を元に、Pod名と取得するために`'{.items[*].metadata.name}'`と記述すると、以下のようにJSONPathで表されたPod名一覧が順に並んで表示される。

```shell-session
$ kubectl get pods -o jsonpath='{.items[*].metadata.name}'
busybox busybox2 hello-world-2-build hello-world-2-deploy hello-world-2-jl5r8 hello-world-2-qzb6s hello-world-2-rjjcw hello-world-2-zdgqd hello-world-2-zp7lb
```

もし対応するNameSpaceも取得しようして`'{.items[*].metadata.name}, {.items[*].metadata.name}'`と記載すると


```shell-session
$ kubectl get pods -o jsonpath='{.items[*].metadata.name}{.items[*].metadata.namespace}'
busybox busybox2 hello-world-2-build hello-world-2-deploy hello-world-2-jl5r8 hello-world-2-qzb6s hello-world-2-rjjcw hello-world-2-zdgqd hello-world-2-zp7lbsyasuda syasuda syasuda syasuda syasuda syasuda syasuda syasuda syasuda
```

のように`Pod名一覧`+`NameSpace一覧`のように出力されてしまう。つまり、配列要素（今回はPod）ごとにPod名やNameSpaceを出力しようとすると、こういうラフな書き方では役に立たない。代わりに、配列要素ごとに処理を行うループ処理が必要になる。items配下の配列要素ごとに改行などを入れるというループ作業を行いたい場合は`{range} ~ {end}`を利用する。

```shell-session
for x in myArrays
   x.metadata.name + " : " + x.metadata.namespace + "\n"
endfor
```

を書くつもりで、以下のような感じで（.items[*]は配列を構成するオブジェクト全体に相当。各配列要素を表す変数`x`に相当するものは`@`にて表現可能。

```shell-session
range .items[*]
   @.metadata.name + " : " + @.metadata.namespace + "\n"
end
```

のように書くつもりで、`{range .items[*]}{@.metadata.name}{" : "}{@.metadata.namespace}{"\n"}{end}`と表現する。


```shell-session
$ kubectl get pods -o jsonpath='{range .items[*]}{@.metadata.name} : {@.metadata.namespace}{"\n"}{end}'
busybox : syasuda
busybox2 : syasuda
hello-world-2-build : syasuda
hello-world-2-deploy : syasuda
hello-world-2-jl5r8 : syasuda
hello-world-2-qzb6s : syasuda
hello-world-2-rjjcw : syasuda
hello-world-2-stcwr : syasuda
hello-world-2-tc79g : syasuda
hello-world-2-zdgqd : syasuda
hello-world-2-zp7lb : syasuda
```

ただし、`@`は付けなくても、自動的にそれぞれの配列要素をトップディレクトリとして扱ってくれているみたいである。

```shell-session
$ kubectl get pods -o jsonpath='{range .items[*]}{.metadata.name} : {.metadata.namespace}{"\n"}{end}'
busybox : syasuda
busybox2 : syasuda
hello-world-2-build : syasuda
hello-world-2-deploy : syasuda
hello-world-2-jl5r8 : syasuda
hello-world-2-qzb6s : syasuda
hello-world-2-rjjcw : syasuda
hello-world-2-stcwr : syasuda
hello-world-2-tc79g : syasuda
hello-world-2-zdgqd : syasuda
hello-world-2-zp7lb : syasuda
```

また、ループは複数組み合わせることが可能であり、入れ子にすることが可能。

```shell-session
$ kubectl get pods -o jsonpath='{range .items[*]}{@.metadata.name} : {@.metadata.namespace}{"\n"}{range @.spec.containers[*]}{@.image}{"\n"}{end}{end}'
busybox : syasuda
busybox@sha256:2131f09e4044327fd101ca1fd4043e6f3ad921ae7ee901e9142e6e36b354a907
busybox2 : syasuda
busybox:1.32.0
hello-world-2-build : syasuda
quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:ade7f38cacf5f8abfbbfc6e484af0a3b4716a6911723a919b48790bd40b2ba89
hello-world-2-deploy : syasuda
quay.io/openshift-release-dev/ocp-v4.0-art-dev@sha256:100b0636c019975cdb1dee15b501428404785dfd5f259973062c510226554a66
hello-world-2-jl5r8 : syasuda
image-registry.openshift-image-registry.svc:5000/syasuda/hello-world@sha256:6275d631745919926f3fa0669d800a64ab10045a05c161f09e561d3d4c55275a
hello-world-2-qzb6s : syasuda
image-registry.openshift-image-registry.svc:5000/syasuda/hello-world@sha256:6275d631745919926f3fa0669d800a64ab10045a05c161f09e561d3d4c55275a
hello-world-2-rjjcw : syasuda
image-registry.openshift-image-registry.svc:5000/syasuda/hello-world@sha256:6275d631745919926f3fa0669d800a64ab10045a05c161f09e561d3d4c55275a
hello-world-2-stcwr : syasuda
image-registry.openshift-image-registry.svc:5000/syasuda/hello-world@sha256:6275d631745919926f3fa0669d800a64ab10045a05c161f09e561d3d4c55275a
hello-world-2-tc79g : syasuda
image-registry.openshift-image-registry.svc:5000/syasuda/hello-world@sha256:6275d631745919926f3fa0669d800a64ab10045a05c161f09e561d3d4c55275a
hello-world-2-zdgqd : syasuda
image-registry.openshift-image-registry.svc:5000/syasuda/hello-world@sha256:6275d631745919926f3fa0669d800a64ab10045a05c161f09e561d3d4c55275a
hello-world-2-zp7lb : syasuda
image-registry.openshift-image-registry.svc:5000/syasuda/hello-world@sha256:6275d631745919926f3fa0669d800a64ab10045a05c161f09e561d3d4c55275a
```


#フィルタリング

node情報で`type=ExternalIP`のIPアドレスを取得したい場合は、以下のように記載をする。

```shell-session
$ kubectl get nodes -o jsonpath='{.items[*].status.addresses[?(@.type=="ExternalIP")].address}'
10.240.0.10 10.240.0.11 10.240.128.10 10.240.128.11 10.240.64.11 10.240.64.12
```

これは何をやっているのかをどうやっているのかを探ってみる。
まず、`kubectl get nodes -o yaml`の結果は以下の通り。

```yaml
$ kubectl get nodes -o yaml
items
 - xxxx
（省略）
  status:
    addresses:
    - address: 10.240.64.12
      type: InternalIP
    - address: 10.240.64.12
      type: ExternalIP
    - address: 10.240.64.12
      type: Hostname
（省略）
```

このことから以下のことがわかる。

 - `items[*]`となっているので、特定の配列要素だけでなく、全ての配列要素が対象となる。
 - `.items[*].status.addresses`となっており、再び配列が存在している。この配列addresses[0],addresses[1],addresses[2]......の中から、addresses["特定の条件"]という配列要素を`addresses[?(@.type=="ExternalIP")]`の箇所で指定している。
 - `?`はifを表す。つまり、`if(条件式)`のような感じで書いている。
 - `@`は現在の配列要素を表す。
 - よって、`.status.addresses[?(@.type=="ExternalIP")]`によって、"ExternalIP"を含む配列要素のみが選択される。この配列要素の`address`が`.status.addresses[?(@.type=="ExternalIP")].address`の対象になる。


# .と..は何が違うか？
https://goessner.net/articles/JsonPath/

- .は単なる直下のchild
- ..は直下だけでなく、その下の方まで再帰的に探す。

```shell-session
$ kubectl get pods -o jsonpath='{.items[*].metadata.name}'
```

は（他に`.metadata.name`が存在しないのであれば）以下のように書くこともできるかもしれない。

```
$ kubectl get pods -o jsonpath='{..metadata.name}'
```


#ソート

```shell-session
$ kubectl get services --sort-by=.metadata.name
```

#Custom-Columns

```shell-session
$ kubectl get pods -A -o=custom-columns='DATA:spec.containers[*].image'
$ kubectl get pods -A -o=custom-columns='DATA:spec.containers[?(@.image!="k8s.gcr.io/coredns:1.6.2")].image'
```