CircleCI Orbsのための共有スクリプトの作成
この記事は元々私のブログこちらで公開されました。
問題点
CircleCI Orbsは、include
というディレクティブを提供しており、コマンド設定にスクリプトを含める際に利用できます。残念ながら、このディレクティブでは、含まれるスクリプトの中で他の共有スクリプトを明確に参照する方法が提供されていません。
実は、include
ディレクティブは、参照されたスクリプトのテキスト本体をinclude
ディレクティブの部分に置き換えるマクロに過ぎません。つまり、共有スクリプティングモジュールを作成するのが難しくなります。
動機
最近、私は自分のプロジェクトのDevOpsツーリングをカスタムCircleCI Orbに集中させています。このorbは、私が現在および将来的に作成を計画している標準化されたリポジトリでの典型的なアクションを抽象化することを目的としています。
Nxプラグインとカスタムワークスペースプリセットを使用したモノリポジェネレータを作業中です。Codecov統合のテストカバレッジレポートのために、モノリポの中の各パッケージごとにカバレッジレポートを動的にアップロードし、それに合ったCodecovフラグを割り当てたいです。
残念ながら、公式のCodecov CircleCI Orbでは、このユースケースを単独では十分に対応できないので、私は彼らのスクリプトのいくつかを使って、Nxワークスペース設定を理解し、パッケージごとにセグメント化されたカバレッジのアップロードを自動的に行う何かを作り出しました。
その過程で、他のスクリプトで再利用できる共有スクリプトの関数を書く能力が欲しくなり、よりモジュール化されたテスト可能なスクリプトを書くため、そしてスクリプトをDRY(Don't Repeat Yourself)に保つためになりました。
解決策
幸いにも、include
ディレクティブはorbの設定の中で、特にstep
のcommand
プロパティ内に限らずどこにでも使用できます。
さらに、include
ディレクティブを使用して、共有スクリプトの本文を他のコマンドに環境変数として提供することもできます。
推奨されない: 共有スクリプトの本文をeval
する
この問題を解決する最初の試みは、共有スクリプトの本文を環境変数として提供し、その内容をeval
するというものでした。
明らかに、これは最も安全な方法ではありませんし、この方法でのeval
の使用は少し問題があります。しかし、問題は解決しました。
src/commands/upload-monorepo-coverage.yml
:
steps:
- run:
name: Upload Monorepo Coverage Results
command: << include(scripts/uploadMonorepoCoverageResults.sh) >>
environment:
PARSE_NX_PROJECTS_SCRIPT: << include(scripts/parseNxProjects.sh) >>
src/scripts/parseNxProjects.sh
:
#! /usr/bin/env bash
# 他のファイルで使いたい共通関数
parse_nx_projects() {
# ...
}
src/scripts/uploadMonorepoCoverageResults.sh
:
#! /usr/bin/env bash
eval "$PARSE_NX_PROJECTS_SCRIPT"
# `parseNxProjects.sh`からのこの共有関数が呼び出せるようになった
parse_nx_projects
# ...
やや良い: 共有スクリプトの内容をディスクに書き込む
上記のようなeval
を使用することは私の感覚に反していました。実際にそれが_実際に_より安全だとは確信していませんが、eval
アプローチに触発された異なる方法で結論付けました。それは、include
ディレクティブを使用して、共有スクリプトの内容をディスク上の予測可能な場所に書き込み、その共有スクリプトのパスをそれを消費する必要があるスクリプトに提供するというものです。
これにより、私の共有関数をsource
で読み込めるようになり、少し良い感じです。
この目的のために私は、write-shared-script
と呼ばれる特定のコマンドを書きました。
そのコマンドのソースは以下の通りです:
description: >
このコマンドは共有スクリプトをディスクに書き込んで、他のスクリプトによって消費されるようにします
parameters:
script-dir:
type: string
default: ~/@chiubaka/circleci-orb/scripts
description: 共有スクリプトを書き込むディレクトリへのパス。
script-name:
type: string
description: 書き込むスクリプトの名前
script:
type: string
description: 書き込むスクリプト。ここで`include`ディレクティブを使用して含めるべきです。
steps:
- run:
name: Write << parameters.script-name >> to disk
command: << include(scripts/writeSharedScript.sh) >>
environment:
SCRIPT: << parameters.script >>
SCRIPT_DIR: << parameters.script-dir >>
SCRIPT_NAME: << parameters.script-name >>
そしてwriteSharedScript.sh
は以下のようになります:
#! /usr/bin/env bash
SCRIPT_PATH="$SCRIPT_DIR/$SCRIPT_NAME"
mkdir -p "$SCRIPT_DIR"
echo "$SCRIPT" > "$SCRIPT_PATH"
chmod +x "$SCRIPT_PATH"
そして共有スクリプトが必要なコマンドのステップは以下のようになります:
- write-shared-script:
script-name: parseNxProjects.sh
script: << include(scripts/parseNxProjects.sh) >>
- run:
name: Upload Monorepo Coverage Results
command: << include(scripts/uploadMonorepoCoverageResults.sh) >>
environment:
PARSE_NX_PROJECTS_SCRIPT: ~/@chiubaka/circleci-orb/scripts/parseNxProjects.sh
最後に、uploadMonorepoCoverageResults.sh
スクリプトは以下のようになります:
#! /usr/bin/env bash
source "$PARSE_NX_PROJECTS_SCRIPT"
# `parseNxProjects.sh`からのこの共有関数が呼び出せるようになった
parse_nx_projects
# ...
セキュリティ上の配慮
おそらく、CircleCIの文脈でこれら二つのアプローチの違いは大きくありません。実際には、eval
への入力はCircleCIが正常に動作している限り、常に私のコントロール下にあります。もしCircleCIにセキュリティの侵害があって、攻撃者がこのeval
ステートメントの入力をコントロールできるようになる場合、ここではまったく異なる脅威モデルを扱って、より大きな問題があります。
技術的には、攻撃者がそのeval
ステートメントの入力をコントロールできるとしたら、同じ攻撃者はおそらくディスク上の共有スクリプトの内容をコントロールできることになり、これは同様の重大性の攻撃になるでしょう。
それでも、eval
を使用するという重大な罪を避ける方が良いと感じますし、少なくともこの方法では私のorbスクリプトは実際にディスクに共有スクリプトが書き込まれるため、本番環境でよりデバッグしやすくなります。
こちらの記事はdev.toの良い記事を日本人向けに翻訳しています。
https://dev.to/chiubaka/writing-shared-scripts-for-circleci-orbs-3c2c