Amplify Gen 2 における pipeline-deploy コマンドがどのようにして実行環境を CI か判別しているのか調べた
- Amplify Gen 2 には
pipeline-deploy
というデプロイ用のコマンドがある - ローカル環境では実行できないコマンドらしく、どのようにして実行環境がローカルか CI かを判別しているのか気になったので調べた
pipeline-deploy
コマンドとは?
CLI commands - React - AWS Amplify Gen 2 Documentation
https://docs.amplify.aws/react/reference/cli-commands/#npx-ampx-pipeline-deploy
npx ampx pipeline-deploy
Deploys the Amplify project in a CI/CD pipeline for a specified Amplify app and branch.
- Amplify Gen 2 が提供しているコマンドの 1つ
- Amplify Gen 2 で定義されたリソースを実際の AWS 環境にデプロイしてくれる
具体的に以下のような指定で使用する
npx ampx pipeline-deploy --branch $BRANCH_NAME --app-id $AWS_APP_ID
- 一方で、このコマンドをユーザーが直接ターミナルに打ち込む機会は恐らく無く、これは Amplify Hosting のビルドで使用されるコマンド
適当な例だが、 Amplify Gen 2 を使用した場合、 Amplify Hosting 側では以下のようなビルド設定が生成される
version: 1 applications: - backend: phases: build: commands: - npm ci --cache .npm --prefer-offline - npx ampx pipeline-deploy --branch $AWS_BRANCH --app-id $AWS_APP_ID
このように Amplify Hosting のビルド環境で実行され、実際のデプロイを行うというもの
ローカル環境で pipeline-deploy
コマンドを実行したらどうなるの?
気になったので実行してみた
% BRANCH_NAME=main # 実際に存在するブランチを指定
% AWS_APP_ID=hogehogehoge # 実際に作成した Amplify Hosting App ID を指定
% npx ampx pipeline-deploy --branch $BRANCH_NAME --app-id $AWS_APP_ID
ampx pipeline-deploy
Command to deploy backends in a custom CI/CD pipeline. This command is not inten
ded to be used locally.
オプション:
--debug Print debug logs to the console [真偽] [デフォルト: false]
--help ヘルプを表示 [真偽]
--branch Name of the git branch being deployed [文字列] [必須]
--app-id The app id of the target Amplify app [文字列] [必須]
--outputs-out-dir A path to directory where amplify_outputs is written. If no
t provided defaults to current process working directory.
[文字列]
--outputs-version Version of the configuration. Version 0 represents classic
amplify-cli config file amplify-configuration and 1 represe
nts newer config file amplify_outputs
[文字列] [選択してください: "0", "1", "1.1"] [デフォルト: "1.1"]
--outputs-format amplify_outputs file format
[文字列] [選択してください: "mjs", "json", "json-mobile", "ts", "dart"]
RunningPipelineDeployNotInCiError: It looks like this command is being run outside of a CI/CD workflow.
RunningPipelineDeployNotInCiError
が発生し、 CI/CD 環境で実行してねという感じでエラーが発生した- さて、どのようにしてコマンドの実行環境が CI/CD 環境であることを識別しているのだろうか?
実装コード
- Amplify Gen 2 の CLI は aws-amplify/amplify-backend というリポジトリでコードが管理されている
pipeline-deploy
コマンドの実装箇所はこの辺確認した時点での最新バージョン 1.2.5 のコードを確認する
-
import _isCI from 'is-ci'; ... /** * Creates top level entry point for deploy command. */ constructor( private readonly clientConfigGenerator: ClientConfigGeneratorAdapter, private readonly backendDeployer: BackendDeployer, private readonly isCiEnvironment: typeof _isCI = _isCI ) { this.command = 'pipeline-deploy'; this.describe = 'Command to deploy backends in a custom CI/CD pipeline. This command is not intended to be used locally.'; } ... handler = async ( args: ArgumentsCamelCase<PipelineDeployCommandOptions> ): Promise<void> => { if (!this.isCiEnvironment) { throw new AmplifyUserError('RunningPipelineDeployNotInCiError', { message: 'It looks like this command is being run outside of a CI/CD workflow.', resolution: `To deploy locally use ${format.normalizeAmpxCommand( 'sandbox' )} instead.`, }); }
-
!this.isCiEnvironment
というそれっぽいコードを発見具体的にどのような判定条件を持っているかは実装が見当たらず、そもそも
is-ci
というパッケージが提供している様子
is-ci
というパッケージは何なのか
- 普通に npm で公開されているパッケージ
- ソースコードも GitHub で公開されている
- その名の通り、実行環境が CI 環境かを判定するパッケージらしい
- 確認時点最新バージョンの v3.0.1 のコードを見てみる
is-ci/index.js at v3.0.1 · watson/is-ci
https://github.com/watson/is-ci/blob/v3.0.1/index.js
'use strict'
module.exports = require('ci-info').isCI
- うーん、シンプル!
- たらい回しっぽいが、
ci-info
というパッケージがあり、そこからisCI
という機能を持ってきてるっぽい is-ci
は利便性的にis-ci
というパッケージ名で提供したいから用意されたパッケージなのかな
ci-info
というパッケージは何なのか
- これも普通に npm で公開されているパッケージ
- ソースコードも GitHub で公開されている
- CI 環境の詳細を取得するパッケージ
早速最新版の v4.0.0 のコードを見てみる
https://github.com/watson/ci-info/blob/v4.0.0/index.js
const vendors = require('./vendors.json') const env = process.env ... vendors.forEach(function (vendor) { const envs = Array.isArray(vendor.env) ? vendor.env : [vendor.env] const isCI = envs.every(function (obj) { return checkEnv(obj) }) exports[vendor.constant] = isCI if (!isCI) { return } ... exports.isCI = !!( env.CI !== 'false' && // Bypass all checks if CI env is explicitly set to 'false' (env.BUILD_ID || // Jenkins, Cloudbees env.BUILD_NUMBER || // Jenkins, TeamCity env.CI || // Travis CI, CircleCI, Cirrus CI, Gitlab CI, Appveyor, CodeShip, dsari env.CI_APP_ID || // Appflow env.CI_BUILD_ID || // Appflow env.CI_BUILD_NUMBER || // Appflow env.CI_NAME || // Codeship and others env.CONTINUOUS_INTEGRATION || // Travis CI, Cirrus CI env.RUN_ID || // TaskCluster, dsari exports.name || false) ) ... function checkEnv (obj) { // "env": "CIRRUS" if (typeof obj === 'string') return !!env[obj] // "env": { "env": "NODE", "includes": "/app/.heroku/node/bin/node" } if ('env' in obj) { // Currently there are no other types, uncomment when there are // if ('includes' in obj) { return env[obj.env] && env[obj.env].includes(obj.includes) // } } if ('any' in obj) { return obj.any.some(function (k) { return !!env[k] }) } return Object.keys(obj).every(function (k) { return env[k] === obj[k] }) }
vendors.json に CI サービスで使用される環境変数データが定義されている
それらのベンダー環境変数ごとに
checkEnv
関数で検証している- 実行環境の環境変数にベンダー環境変数が含まれているか
- 含まれている場合の、更に細かい条件
- ベンダー環境変数のデータとして
includes
が定義されていたら、その値が設定されているか- 例:
{ "env": "NODE", "includes": "/app/.heroku/node/bin/node" }
- 例:
- ベンダー環境変数に
any
が定義されている場合any
のいずれかの値が設定されているか- 例:
{ "any": ["ghprbPullId", "CHANGE_ID"] }
- 例:
- ベンダー環境変数にその他のプロパティが定義されている場合、各キーの値が一致しているか
- 例:
{ "GITHUB_EVENT_NAME": "pull_request" }
- 例:
- ベンダー環境変数のデータとして
それとは別に、シンプルに所定の環境変数が存在しているかをチェックしている
CI
,BUILD_ID
,RUN_ID
など
自分でそれっぽい環境変数を設定して CI 判定を騙せるか
- とりあえず
CI
という環境変数が設定されていれば良さそう - ひとまず
is-ci
で試す - 環境変数 CI を設定して終了コードを見れば良い
これで良さそう
% npx is-ci
% echo $?
1
% CI=true npx is-ci
% echo $?
0
pipeline-deploy
コマンドを騙してみる% BRANCH_NAME=main # 実際に存在するブランチを指定 % AWS_APP_ID=hogehogehoge # 実際に作成した Amplify Hosting App ID を指定 % CI=true npx ampx pipeline-deploy --branch $BRANCH_NAME --app-id $AWS_APP_ID [Warning at /amplify-dj7ei5fv7s9yo-main-branch-3428837f4a/data/amplifyData/GraphQLAPI] @predictions is deprecated. This functionality will be removed in the next major release. ... ✨ Synthesis time: 0.03s amplify-dj7ei5fv7s9yo-main-branch-3428837f4a: start: Building 424267a4966cdd641ee920b25b5cdc78c95d4efb78a52ba6ad792da75ca746c7:current_account-current_region ... amplify-dj7ei5fv7s9yo-main-branch-3428837f4a: deploying... [1/1] amplify-dj7ei5fv7s9yo-main-branch-3428837f4a: creating CloudFormation changeset... amplify-dj7ei5fv7s9yo-main-branch-3428837f4a-auth179371D7-35XW14TVQV4P | 0/5 | 10:36:25 | UPDATE_IN_PROGRESS | AWS::CloudFormation::Stack | amplify-dj7ei5fv7s9yo-main-branch-3428837f4a-auth179371D7-35XW14TVQV4P User Initiated ... ✅ amplify-dj7ei5fv7s9yo-main-branch-3428837f4a ✨ Deployment time: 114.04s Outputs: amplify-dj7ei5fv7s9yo-main-branch-3428837f4a.allowUnauthenticatedIdentities = true ... ✨ Total time: 114.06s File written: amplify_outputs.json
CloudFormation のデプロイがキックされた!
CI=true
で無事騙すことが出来た
結論
pipeline-deploy
コマンドはis-ci
,ci-info
パッケージを使用して、実行環境が CI であるかを判定している- コマンド実行時に
CI=true
を指定することで、判定を騙してローカルで実行することも出来る
試した環境
% sw_vers
ProductName: macOS
ProductVersion: 14.6.1
BuildVersion: 23G93
% npx ampx --version
1.2.5