jqで入れ子の配列を展開して複数のオブジェクトにする

jqで以下のことをやりたくて、ちょっと悩んだ末にできたのでメモ。

## やりたかったこと

以下のような配列を含む1つのオブジェクトを、

```json:sample.json（配列を含む1つのオブジェクト）
{
  "pod": {
    "name": "mypod"
  },
  "containers": [
    {
      "name": "mycontainer1",
      "usage": {
            "cpu": "50"
          }
    },
    {
      "name": "mycontainer2",
      "usage": {
            "cpu": "100"
          }
    }
  ]
}
```

以下のような複数のオブジェクトにしたい。

```json:（複数のオブジェクト）
{
  "pod_name": "mypod",
  "container_name": "mycontainer1",
  "container_cpu": "50"
}
{
  "pod_name": "mypod",
  "container_name": "mycontainer2",
  "container_cpu": "100"
}
```

## やり方

[jq Manual](https://stedolan.github.io/jq/manual/)のObject Constructionのところに解説があるが、新たなオブジェクトを作り、このときに`containers`配列の皮むき（`[]`）を行うことで、別々のオブジェクトに展開できる。

### 入れ子の配列の展開

```shell:コマンド
cat sample.json | \
  jq '{
        pod,
        container: .containers[]
      }'
```

```json:実行結果（複数のオブジェクト）
{
  "pod": {
    "name": "mypod"
  },
  "container": {
    "name": "mycontainer1",
    "usage": {
      "cpu": "50"
    }
  }
}
{
  "pod": {
    "name": "mypod"
  },
  "container": {
    "name": "mycontainer2",
    "usage": {
      "cpu": "100"
    }
  }
}
```

（補足）`pod`のところは省略した形式で、元のフィールドをそのまま渡しているが、ちゃんと書くこともできる。

```shell
cat sample.json | \
  jq '{
        pod: { name: .pod.name },
        container: .containers[]
      }'
```

### オブジェクトの生成

展開した結果から欲しい情報をピックアップして新たなオブジェクトにする。

```shell:コマンド
cat sample.json | \
  jq '{
        pod,
        container: .containers[]
      } |
      {
        pod_name: .pod.name,
        container_name: .container.name,
        container_cpu: .container.usage.cpu
      }'
```

```json:実行結果（複数のオブジェクト）
{
  "pod_name": "mypod",
  "container_name": "mycontainer1",
  "container_cpu": "50"
}
{
  "pod_name": "mypod",
  "container_name": "mycontainer2",
  "container_cpu": "100"
}
```

## 注意点

中間オブジェクトを経由せずにオブジェクトをつくると結果がかけ算になりオブジェクトの数が多くなってしまう。

```shell:コマンド
cat sample.json | \
  jq '{
        pod_name: .pod.name,
        container_name: .containers[].name,
        container_cpu: .containers[].usage.cpu
       }'
```

```json:実行結果
{
  "pod_name": "mypod",
  "container_name": "mycontainer1",
  "container_cpu": "50"
}
{
  "pod_name": "mypod",
  "container_name": "mycontainer1",
  "container_cpu": "100"
}
{
  "pod_name": "mypod",
  "container_name": "mycontainer2",
  "container_cpu": "50"
}
{
  "pod_name": "mypod",
  "container_name": "mycontainer2",
  "container_cpu": "100"
}
```

中間オブジェクトを経由せずに配列をつくると元々の配列の要素がそのまま並んでしまう。

```shell:コマンド
cat sample.json | \
  jq '[
        .pod.name,
        .containers[].name,
        .containers[].usage.cpu
       ]'
```

```json:実行結果
[
  "mypod",
  "mycontainer1",
  "mycontainer2",
  "50",
  "100"
]
```

## おまけ

もともとやりたかったことは、KubernetesのMetric APIから以下のようなjsonが返ってくるので、これをcsvにすること。以下はIBM Cloud Private v3.1.2の場合。

```json
{
  "kind": "PodMetricsList",
  "apiVersion": "metrics.k8s.io/v1beta1",
  "metadata": {
    "selfLink": "/apis/metrics.k8s.io/v1beta1/pods"
  },
  "items": [
    {
      "metadata": {
        "name": "audit-logging-fluentd-ds-pgp88",
        "namespace": "kube-system",
        "selfLink": "/apis/metrics.k8s.io/v1beta1/namespaces/kube-system/pods/audit-logging-fluentd-ds-pgp88",
        "creationTimestamp": "2019-04-10T01:22:42Z"
      },
      "timestamp": "2019-04-10T01:22:17Z",
      "window": "30s",
      "containers": [
        {
          "name": "fluentd",
          "usage": {
            "cpu": "2513099n",
            "memory": "62276Ki"
          }
        }
      ]
    },

（以下略）
```

### トークンの取得

`cloudctl login`した後、`cloudclt tokens`でトークンを取得する。

```shell
ID_TOKEN=$(LANG=C cloudctl tokens | grep "ID token:" | awk '{print $3}')
```

### Metric APIからのデータの取得

curlでjsonデータを取得して変数に入れる。

```shell
CLUSTER="mycluster.icp"
JSON=$(curl -s -k -H "Authorization: Bearer ${ID_TOKEN}" \
  "https://${CLUSTER}:8001/apis/metrics.k8s.io/v1beta1/pods" \
  | jq -c '.')
```

### jqを使ってjsonをcsvに変換

jqを使って配列を複数のオブジェクトに展開した中間データを作成し、そこから欲しいデータをピックアップしてそれぞれを配列にし、`@csv`を使ってcsvにする。ついでに`rtrimstr(str)`関数を使って単位を除く。

```shell:コマンド
echo $JSON | \
  jq -r '.items[] |
          {
            metadata,
            timestamp,
            container: .containers[]
          } |
          [
            .timestamp,
            .metadata.namespace,
            .metadata.name,
            .container.name,
            ( .container.usage.cpu | rtrimstr("n") ),
            ( .container.usage.memory | rtrimstr("Ki") )
          ] | @csv'
```

```csv:実行結果
"2019-04-10T02:35:25Z","kube-system","monitoring-prometheus-collectdexporter-57b8c6ff5c-dclw4","collectd-exporter","1488183","11416"
"2019-04-10T02:35:25Z","kube-system","monitoring-prometheus-collectdexporter-57b8c6ff5c-dclw4","router","54112","4532"
"2019-04-10T02:35:09Z","kube-system","logging-elk-filebeat-ds-2x49r","filebeat","4745057","28692"
"2019-04-10T02:35:20Z","kube-system","monitoring-prometheus-elasticsearchexporter-7f5967f56-d68sd","router","12534","8312"
"2019-04-10T02:35:20Z","kube-system","monitoring-prometheus-elasticsearchexporter-7f5967f56-d68sd","elasticsearchexporter","3207930","13132"
"2019-04-10T02:35:10Z","kube-system","k8s-etcd-9.188.124.130","etcd","50751515","261432"

（以下略）
```