conftestで複数ファイルを横断してチェックする
記事一覧はこちら
背景・モチベーション
以前にconftestの概要をざっくり知って試してみたという記事を出しました。
実際に実務で利用してみると、1ファイル内だけのテストだけでなく、複数のファイルを横断的に見てチェックを行いたいという需要がありました。
例えば、Serviceで指定しているラベルがDeploymentでのpodのlabelに含まれるかといった検査です。
--combine
オプションを利用する
contestのv0.24.0で試しています
--combine
オプションとは
上記のドキュメントから確認できるように、--combine
というオプションをconftest
実行時に付けてあげると複数ファイルを一度にロードしてくれます。しかしながら、単一のファイル向けに宣言していたRuleは正常に動かなくなるため、このオプションを利用する場合には書き直す必要がありそうです。
--combine
と毎回宣言するのも手間なので、自分はconftest
を実行するディレクトリに、conftest.toml
を配置して設定を行っています。
combine = true
--combine
オプションを利用した時のinputの内容が変わる
このcombine
オプションを利用すると、input
に入ってくるドキュメントの内容が変わってきます。input
の中身を確かめるだけのpolicyを書いて実行してみます。
# policy/combine.rego package main deny[msg] { msg = json.marshal(input) }
入力となるマニフェストは下記です。
# manifests.yaml kind: Service metadata: name: hello-kubernetes spec: type: LoadBalancer ports: - port: 80 targetPort: 8080 selector: app: goodbye-kubernetes --- apiVersion: apps/v1 kind: Deployment metadata: name: hello-kubernetes spec: replicas: 3 selector: matchLabels: app: hello-kubernetes
実行すると下記のような結果になります。
$ conftest test manifests.yaml --combine FAIL - Combined - main - [{"contents":{"apiVersion":"v1","kind":"Service","metadata":{"name":"hello-kubernetes"},"spec":{"ports":[{"port":80,"targetPort":8080}],"selector":{"app":"goodbye-kubernetes"},"type":"LoadBalancer"}},"path":"manifests.yaml"},{"contents":{"apiVersion":"apps/v1","kind":"Deployment","metadata":{"name":"hello-kubernetes"},"spec":{"replicas":3,"selector":{"matchLabels":{"app":"hello-kubernetes"}}}},"path":"manifests.yaml"}]
出力結果のjsonを整形してあげると下記のようになります。
それぞれのResourceが配列となっています。ドキュメントにあるようにpath
キーとcontents
キーをそれぞれのアイテムが持っています。path
キーはファイル名でcontents
は実際のドキュメントの中身です。
[{ "contents": { "apiVersion": "v1", "kind": "Service", "metadata": { "name": "hello-kubernetes" }, "spec": { "ports": [{ "port": 80, "targetPort": 8080 }], "selector": { "app": "goodbye-kubernetes" }, "type": "LoadBalancer" } }, "path": "manifests.yaml" }, { "contents": { "apiVersion": "apps/v1", "kind": "Deployment", "metadata": { "name": "hello-kubernetes" }, "spec": { "replicas": 3, "selector": { "matchLabels": { "app": "hello-kubernetes" } } } }, "path": "manifests.yaml" }]
複数ドキュメントを横断してチェックしてみる
inputのファイルなどはまとめてこちらのリポジトリに置いてあります。
最初の方でも言及したようにService
リソースの .spec.selector
が Deploymentの.spec.template.labels
に含まれるかをチェックしてみたいと思います。
※ ここではlabelのkeyをappとします
package main deny[msg] { input[deploy].contents.kind == "Deployment" deployment := input[deploy].contents input[svc].contents.kind == "Service" service := input[svc].contents service.spec.selector.app != deployment.spec.template.metadata.labels.app msg := sprintf("Labels are different! Deployment %v has 'app: %v', Service %v has 'app: %v'", [deployment.metadata.name, deployment.spec.template.metadata.labels.app, service.metadata.name, service.spec.selector.app]) }
ちなみにconftestのドキュメントのExampleにあったように実行すると、下記のようなエラーが出てしまいました。同名の変数ではまずいのではと思い変更して実行したところ上手くいきました。
※ Issue上げたところ対応してもらったので現在はドキュメント上直っていると思います。
Error: running test: load: loading policies: get compiler: 1 error occurred: policy/combine.rego:5: rego_compile_error: var deployment referenced above
これで実行すると下記のようになります。
期待したチェックが機能しています。
$ conftest test manifests.yaml FAIL - Combined - main - Labels are different! Deployment hello-kubernetes has 'app: hello-kubernetes', Service hello-kubernetes has 'app: goodbye-kubernetes'
Rego言語は今まで学習してきた言語とだいぶ違うので感覚を掴みづらいですね…!ただめっちゃ便利なので今後も上手くつかって円滑にチェックしていこうと思います。