シンプルな構成のCodePipelineでPHPの静的解析を動かしてみよう
こんにちは。深緑です。
はじめに
PHPでのシステム開発においてPHPStanによる静的解析を回しています。
以前は解析を手動で実行してましたが、最近は CodeComit + CodePipeline を用いて、
コミットしたら静的解析を実行するようにしました。
CodePipelineの構築はどうせならということでCloudFormationを使いましたが、
AWSのドキュメントにしっくり来るものがなく、作成にちょっと苦労したので共有します。
構築したCodePipelineの動作
CodePipelineでCodeCommitを監視。
masterブランチにコミットすると反応して、CodeBuildを起動。
CodeBuildはPHPStanを実行します。
この構成をCloudFormationで作成しています。
静的解析でNGが見つかった場合、ビルド失敗→CodePipeline失敗となります。
本記事ではまだやってませんが、SNSを繋げれば失敗時にメールを飛ばしたりできるはずです。
成功するとこのようになります。
テンプレートファイルはこちらです。
CodePipelineとCodeBuild、及び実行に必要なバケット・ロールを作っています。
このテンプレートを作る時AWSのドキュメントでサンプルを探したのですが、
CodePipeline+CodeDeployはあるのですがCodeBuildはなかったので少々苦労しました💦
プッシュイベントを使用するようにパイプラインを編集する - AWS公式
テンプレートリファレンス AWS::CodePipeline::Pipeline - AWS公式
CloudFormationテンプレートの使い方
用意したテンプレートは使い回しが効くように以下のパラメータを設けています。
ApplicationName
アプリケーションの名前です。
各リソースの名前の一部として使います。
!Sub ${ApplicationName}-codepipeline
!Subが何を意味するかは以前の記事で触れていますのでご覧ください。
RepositoryName
CodePipelineが監視するCodeCommitのリポジトリ名です。
BranchName
CodePipelineが監視するリポジトリのブランチ名です。
CodeBuild
PHPStanを動かすのに、アプリのソースと同じバージョンのPHPがCodeBuildで動かせる必要があります。
使用可能なランタイムでイメージを変える必要があるのでパラメータ化しています。
Available runtimes - AWS公式
CodeBuild に用意されている Docker イメージ - AWS公式
BuildSpecFile
buildspec.ymlのパスを指定します。
ビルドは環境や状況に応じて動きを変えたい時があります。
そういう場合にbuildspecファイルを切り替えることで動作の分岐を実現できるようファイルパスで対象を指定可能にしています。
OutputArtifactName
CodeBuildが作成するアーティファクトの名前を指定します。
CodeBuildのアーティファクトは私の認識ではCodeDeployで使うものなので、
本当はこの記事のテンプレートではあんまり意味がないパラメータです。
テンプレートのCodeBuildの部分の説明
CodePiplineのアクションにCodeBuildを追加
- Name: Build
Actions:
- Name: !Sub ${ApplicationName}-codebuild
ActionTypeId:
Category: Build
Owner: AWS
Provider: CodeBuild
Version: '1'
RunOrder: 1
Configuration:
# BatchEnabled: 'true'
# CombineArtifacts: 'true'
ProjectName: !Ref AppPackageBuild
PrimarySource: SourceOutput
# EnvironmentVariables: '[{"name":"TEST_VARIABLE","value":"TEST_VALUE","type":"PLAINTEXT"},{"name":"ParamStoreTest","value":"PARAMETER_NAME","type":"PARAMETER_STORE"}]'
OutputArtifacts:
- Name: !Ref OutputArtifactName
InputArtifacts:
- Name: SourceOutput
CodePipelineにCodeBuildを追加してるのはここになります。
最も大事なのはProjectName: !Ref AppPackageBuild
の部分です。
AppPackageBuild
というのがこの少し上になります。
AppPackageBuild:
Type: 'AWS::CodeBuild::Project'
Properties:
Artifacts:
Type: CODEPIPELINE
Environment:
ComputeType: BUILD_GENERAL1_SMALL
Image: !Ref CodeBuildImage
Type: LINUX_CONTAINER
# PrivilegedMode: True
Name: !Sub '${ApplicationName}-codebuild'
ServiceRole: !GetAtt
- CodeBuildServiceRole
- Arn
Source:
Type: CODEPIPELINE
BuildSpec: !Ref BuildSpecFile
AWS::CodeBuild::Project - AWS公式
ここがCodeBuildの設定を行っている部分です。
ComputeType
はCodeBuildのコンピューティングタイプです。
とりあえず最小にしてますが、タイプによって金額が違うので適切なものを選ぶのがベターです。
ビルド環境のコンピューティングタイプ - AWS公式
Image
はDockerコンテナイメージを指定します。
パラメータで入力したイメージ識別子を渡しています。
BuildSpec
もパラメータのbuildspec.ymlのパスを渡しています。
権限の設定
CodePipelineServiceRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- codepipeline.amazonaws.com
Action: 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: !Sub ${ApplicationName}-policy-codepipeline
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'codecommit:CancelUploadArchive'
- 'codecommit:GetBranch'
- 'codecommit:GetCommit'
- 'codecommit:GetUploadArchiveStatus'
- 'codecommit:UploadArchive'
Resource: '*'
- Effect: Allow
Action:
- 'codebuild:BatchGetBuilds'
- 'codebuild:StartBuild'
Resource: '*'
- Effect: Allow
Action:
- 'cloudwatch:*'
- 's3:*'
Resource: '*'
CodePipelineのロールにCodeBuildを起動するためのポリシーを追加しています。
CodeBuildServiceRole:
Type: 'AWS::IAM::Role'
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Principal:
Service:
- codebuild.amazonaws.com
Action: 'sts:AssumeRole'
Path: /
Policies:
- PolicyName: !Sub ${ApplicationName}-policy-codebuild
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- 'codebuild:BatchGetBuilds'
- 'codebuild:StartBuild'
Resource: '*'
- Effect: Allow
Action:
- 'cloudwatch:*'
- 'logs:*'
- 's3:*'
Resource: '*'
CodeBuildのロールにはログ出力のためのCloudWatchのポリシーと、アーティファクトを格納するためのS3のポリシーを追加しています。
codebuild:BatchGetBuilds
とcodebuild:StartBuild
はなんで必要なのかよくわからないのですが私の環境だとないとエラーになります。
原因がわかったら追記します。
テストで使用したPHPソースの説明
このフォルダをCodeCommitのリポジトリにアップしています。
フォルダ内のbuildspec.yml
をパラメータで指定。
これによりCodeBuildがbuildspec.yml
に基づきビルドを実行しています。
version: 0.2
phases:
install:
runtime-versions:
php: 8.1
commands:
- composer install --working-dir=./
pre_build:
commands:
- composer stan --working-dir=./
#build:
# commands:
# - sample
アプリのソースがPHP8.1ベースなのでビルド時のランタイムもPHP8.1にします。
なので先述のCodeBuildのImage
もPHP8.1が使えるものを指定します。
ビルド前にインストールコマンドを実行できるのでcomposerにインストールコマンドを実行させています。
composer.jsonでPHPStanを指定してるので、
これでPHPStanがインストールされて静的解析の準備が整います。
インストールの事前に実行するコマンド(pre_build)で、PHPStanを実行しています。
これで静的解析でNGが見つかった場合、実際のビルドに入る前にキャンセルすることができます。
まとめ
CodePipelineでの静的解析は途中までやること自体が目的だったのですが、
回し始めてみると意外と仕事をしてくれて最近ではソースに対して少し安心感が増しました(笑)
このままユニットテストとかも組み込んで行きたいと思っています。
なお、GitHub Actionsという選択肢があるのは理解していますが、
しばらくはこのままCodePipelineで行くと思います。
本記事がどこかの開発現場のお役に立てれば幸いです。
補足
composer stan
というコマンドはありません。
composer.jsonでstan
というショートカットを作ってるのでこのような指定ができます。
PHPStanは意外とメモリを喰うので、私はオプションでメモリを指定するようにしています。
また、他のオプションをつけることもちょいちょいあるので都度指定しなくて良いようにショートカットにしています。
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi"
],
"stan": [
"phpstan analyze --memory-limit=1G"
]
},
Discussion