YAMLのValidationにJSON Schemaを利用する際の技術的選択肢

#vscode
#githubactions

背景

アプリケーションの設定ファイルなどでYAMLを記述する際、JSON Schemaでフォーマットを機械的にチェックしたい。特にチーム開発では各自の環境で同一Schemaによる検証が自動で走るようにしたいし、CI上でも同様の検証を統一的に行いたい。

JSON Schemaにまつわるツールは https://json-schema.org/tools にある通り豊富だが、何かオフィシャルなものがあるわけではなく選択肢ごとにPros/Consがある。そのため統一的な検証を行うための現実的な方法を調べた。

結論から

以下が最も現実的に感じられた。

  • エディタではLSP (yaml-language-server) でSchemaによる検証が自動で効くようにする

  • CIではJSON Schemaで検証を行う自前スクリプトを用意し実行する

  • エディタとCI間での厳密な検証ロジック共通化は諦める(必ず同一Schemaによる検証が走るようスクリプトで担保する)

開発エディタでJSON Schemaをサポートする

エディタで利用できるLSPには redhat-developer/yaml-language-server がある。これはYAMLのsyntaxをサポートするのと同時にJSON Schemaによる検証にも対応している。以下のようにファイル上でSchemaファイルのパスを指定しておけば自動でsyntax検証できるので複数人での開発に便利。

# yaml-language-server: $schema=./config-schema.json rpc: address: 127.0.0.1 ...

VSCodeの場合は内部的にyaml-language-serverを利用している YAML Extension をインストールするだけでよく、IntelliJでも最近このSchema指定のコメント記法がサポートされている(issue)。その他のエディタに関しても、例えばEmacsの LSP Mode のようにLSPでyaml-language-serverのnpm packageを利用できたりするので、基本的にはどの環境でも挙動が揃えられる。

CIでエディタ同等の検証を実行する

各自の手元でSchema違反に気づけるようにしつつ、ブランチを本流にmergeした際も同じ処理を走らせたい。また検証を実行する実装が異なるとサポートされるSchema Versionも異なる可能性があるので、なるべく同じものを利用したい。

しかし結論としてはCIとエディタで完全に実装・挙動を揃えるのは現実的ではなく、Pythonで自前の検証スクリプトを用意するのが現実的という考えに至った。以下にCIで検証を行うための有力な選択肢をリストする。

ツールProsCons採用
yaml-language-server各エディタ上でも使うもので、そのままCIでも利用すれば環境を完全に揃えられるLanguage Serverを叩く場合エディタClientの仕組みを詳しく理解する必要があり実装の難易度が高く(実装サンプル)、持続的なメンテナンスが大変
ajv利用事例も豊富で企業によるスポンサーやContributionもあり直近1年はissue/PRがかなり滞留していてメンテナンス状況が怪しい
python-jsonschemaREADMEの通り利用が簡単。Pythonで実装が必要とはいえ言語知識はほぼ必要なく、他言語の実装と比べて特にメンテナンスが活発に見える実行環境にPythonランタイム等が必要で、プロジェクトによっては新規のセットアップコスト・認知負荷がかかる

Python実装は自前で用意するコストが発生するとはいえ、用意するべきものが非常に少ない。検証するファイル内の `# yaml-language-server: ...` コメントが効かない問題もあるが、CI上ではファイルに対応するSchemaファイルを固定するよう実装しておけば問題ないだろう。CLIに関してはいくつか見てみたが直近のメンテ状況が怪しかったり、サポートされているSchema Versionが古いなど様々だった。

以下にGitHub Actionsで検証を行うサンプルを載せる。まずJSON Schemaによる検証を実行するPythonスクリプトは非常にシンプル。

# validate_with_schema.py # JSON SchemaでYamlの設定ファイルを検証する例 import argparse import json from jsonschema import validate import yaml yaml.SafeLoader.add_constructor('!vault', lambda loader, node: loader.construct_scalar(node)) # src_yml を読み込み dest_json に JSON Schema を出力する def convert(target_yaml, schema_json): with open(schema_json, 'r') as schema_json_file: schema_data = json.load(schema_json_file) with open(target_yaml, 'r') as target_yaml_file: yaml_data = yaml.safe_load(target_yaml_file) validate(instance=yaml_data, schema=schema_data) print('✅ Validated successfully.') if __name__ == '__main__': parser = argparse.ArgumentParser(description='Validate config yaml with JSON Schema.') parser.add_argument('target_yaml', type=str, help='Path to yaml file to validate.') parser.add_argument('schema_json', type=str, help='Path to json schema for validating yaml.') args = parser.parse_args() convert(args.target_yaml, args.schema_json)

これをGitHub Actions workflowから実行する。

name: Validate Config with JSON Schema on: pull_request: paths: - "path/to/config/**/*.yaml" jobs: set-matrix: runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: read outputs: files: ${{ steps.set-matrix.outputs.target_files }} steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set matrix id: set-matrix run: | # Validate対象のファイル一覧を取ってくる target_files="$(echo -n "$(ls path/to/config/*.yaml)" | tr '\n' ' ' | jq -R -s -c 'split(" ")')" echo "target_files=${target_files}" >> "${GITHUB_OUTPUT}" main: name: Validate runs-on: ubuntu-latest timeout-minutes: 10 permissions: contents: read needs: - set-matrix if: ${{ needs.set-matrix.outputs.files != '[]' }} strategy: fail-fast: false matrix: file: ${{ fromJson(needs.set-matrix.outputs.files) }} steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Python uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0 with: python-version: "3.10" cache: "pip" - name: Run validation script working-directory: .github/scripts/json-schema-tools run: | echo "Validating ${{ matrix.file }}" pip install -r requirements.txt # Schemaを指定して検証を実行する python validate_with_schema.py ../../../${{ matrix.file }} ../../../path/to/schema/config_schema.json

まとめ

JSON Schemaでの検証を開発環境やCIで行う際の選択肢をまとめた。

所感としては、JSON Schema自体は目新しいものではなく長く使われているものだと思うけど、こういったツール周りのエコシステムはこれを使っとけば一発といったものはまだ無く、やや悩まされるものだったなと思う。

特にCLIでの検証についてはより標準的な仕組みで行えると嬉しい。こうしたファイル内容に対して検証をする際はconftestを使うケースが多く、JSON Schemaに対応する issue も上がっているので、これが実現すれば自前実装から解放されてメンテナンスが楽になりそうだ。

Tweet