AWS

CodePipelineにテストユニットを設定する

スポンサーリンク
CodePipelineにテストユニットを設定する AWS
スポンサーリンク
スポンサーリンク

CodePipelineにテストユニットを設定する

CodePipelineにテストユニットを追加することができます。
テストユニットはCodeBuildで作成できます。

今回はDockerイメージを作成し、ECRリポジトリにプッシュするパイプラインをCodePipelineで構築します。
その過程にテストユニットを追加することを目的とします。

構築する環境

Diagram of setting up Test Units in CodePipeline

CodePipelineを構成し、3つのリソースを連携させます。

1つ目はCodeCommitです。
CodeCommitはCodePipelineのソースステージを担当します。
Gitリポジトリとして使用します。

2つ目はCodeBuildです。
こちらのCodeBuildはCodePipelineのテストステージを担当します。
今回のテスト対象はPythonスクリプトとし、テストで使用するツールはpytestとします。

3つ目もCodeBuildです。
こちらのCodeBuildはCodePipelineのビルドステージを担当します。
CodeCommitにプッシュされたコードから、Dockerイメージをビルドします。
ビルドしたイメージをECRにプッシュします。

SSMパラメータストアにDockerHubアカウント情報を保存します。
DockerBuildでイメージを生成する際に、DockerHubにサインインした上でベースイメージをプルするために、これらを使用します。

CodePipelineが開始されるきっかけですが、CodeCommitへのプッシュを条件とします。
具体的には、EventBridgeに上記を満たすルールを用意します。

CloudFormationテンプレートファイル

上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。

awstut-fa/081 at main · awstut-an-r/awstut-fa
Contribute to awstut-an-r/awstut-fa development by creating an account on GitHub.

テンプレートファイルのポイント解説

本ページはCodePipelineで作成したパイプライン内に、テストユニットを追加することを目的とします。

CodePipelineに関する基本的な事項については、以下のページをご確認ください。

CloudFormationカスタムリソースを使用して、CloudFormationスタック削除時に、自動的にS3バケット内のオブジェクトと、ECRリポジトリ内のイメージを削除します。
詳細につきましては、以下のページをご確認ください。

ReportGroup

Resources: CodeBuildReportGroup: Type: AWS::CodeBuild::ReportGroup Properties: DeleteReports: true ExportConfig: ExportConfigType: NO_EXPORT Name: !Sub "${CodeBuildProject1}-${ReportName}" Type: TEST
Code language: YAML (yaml)

CodeBuildでテストユニットを実行するためには、レポートグループを作成します。

Typeプロパティでレポートグループのタイプを設定します。
「CODE_COVERAGE」を指定すると、コードカバレッジ、つまりコード網羅率のレポートを作成します。
「TEST」を指定すると、通常のテストレポートを作成します。
今回は後者を選択します。

DeleteReportsプロパティはレポートの削除に関するパラメータです。
これを有効化すると、グループ内部にレポートが残っていたとしても、グループとレポートをまとめて削除できるようになります。

ExportConfigはレポートグループのエクスポートに関するパラメータです。
生のレポートデータをS3バケットに出力することができます。
今回はエクスポートしません。

CodeBuild

Resources: CodeBuildProject1: Type: AWS::CodeBuild::Project Properties: Artifacts: Type: CODEPIPELINE Cache: Type: NO_CACHE Environment: ComputeType: !Ref ProjectEnvironmentComputeType EnvironmentVariables: - Name: REPORT_NAME Type: PLAINTEXT Value: !Ref ReportName Image: !Ref ProjectEnvironmentImage ImagePullCredentialsType: CODEBUILD Type: !Ref ProjectEnvironmentType PrivilegedMode: true LogsConfig: CloudWatchLogs: GroupName: !Ref LogGroup Status: ENABLED S3Logs: Status: DISABLED Name: !Sub "${Prefix}-project-01" ServiceRole: !GetAtt CodeBuildRole.Arn Source: Type: CODEPIPELINE BuildSpec: !Sub | version: 0.2 phases: install: runtime-versions: python: 3.7 commands: - pip3 install pytest - pip3 install bottle build: commands: - python -m pytest --junitxml=reports/pytest_reports.xml reports: $REPORT_NAME: files: - pytest_reports.xml base-directory: reports file-format: JUNITXML Visibility: PRIVATE
Code language: YAML (yaml)

Environmentプロパティでテストを実行する環境を設定します。

ComputeTypeプロパティでビルド環境のスペックを指定します。
今回は最小構成(メモリ4GB、2vCPU、ストレージ」50GB)の「BUILD_GENERAL1_SMALL」を指定します。

ビルド環境のコンピューティングタイプ - AWS CodeBuild
AWS CodeBuild は、以下のビルド環境に使用可能なメモリ、vCPU、およびディスクスペースを提供します。

EnvironmentVariablesプロパティでビルド環境で使用できる環境変数を設定できます。
今回は先述のレポートグループの名前を設定します。

Imageプロパティはビルド環境を作成するためのDockerイメージを指定します。
今回はARM版のAmazon Linux 2である「aws/codebuild/amazonlinux2-aarch64-standard:2.0」を指定します。

CodeBuild に用意されている Docker イメージ - AWS CodeBuild
AWS CodeBuild で管理される以下の Docker イメージは、CodeBuild コンソールおよび AWS CodePipeline コンソールで使用できます。

ImagePullCredentialsTypeプロパティはイメージをプルするための権限を設定します。
「CODEBUILD」を設定すると、独自のクリデンシャルを使用することになります。

Typeプロパティでビルド環境のタイプを設定します。
今回はARM環境を意味する「ARM_CONTAINER」を設定します。

PrivilegedModeプロパティはDockerデーモンの実行に関するパラメータです。
以下の引用の通り、今回は有効化します。

Enables running the Docker daemon inside a Docker container. Set to true only if the build project is used to build Docker images. Otherwise, a build that attempts to interact with the Docker daemon fails.

AWS::CodeBuild::Project Environment

Sourceプロパティでbuildspec.ymlの内容を定義します。
pytestを使用して、Pythonスクリプトをテストします。
今回は以下のページを参考に設定を行いました。

pytest によるテストレポートのセットアップ - AWS CodeBuild
pytest テスト フレームワークを使用して、CodeBuild でテストレポートをセットアップします。

以下がCodeBuild用のIAMロールです。

Resources: CodeBuildRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - codebuild.amazonaws.com Action: - sts:AssumeRole ManagedPolicyArns: - arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser Policies: - PolicyName: PipelineExecutionPolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - ssm:GetParameters Resource: - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${SSMParameterDockerHubPassword}" - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/${SSMParameterDockerHubUsername}" - Effect: Allow Action: - s3:PutObject - s3:GetObject - s3:GetObjectVersion - s3:GetBucketAcl - s3:GetBucketLocation Resource: - !Sub "arn:aws:s3:::${BucketName}" - !Sub "arn:aws:s3:::${BucketName}/*" - Effect: Allow Action: - codebuild:CreateReportGroup - codebuild:CreateReport - codebuild:UpdateReport - codebuild:BatchPutTestCases - codebuild:BatchPutCodeCoverages Resource: - !Sub "arn:aws:codebuild:${AWS::Region}:${AWS::AccountId}:report-group/*" - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: - !GetAtt LogGroup.Arn - !Sub - "${LogGroupArn}:log-stream:*" - LogGroupArn: !GetAtt LogGroup.Arn
Code language: YAML (yaml)

レポートグループに関する権限を付与します。

CodePipeline

Resources: Pipeline: Type: AWS::CodePipeline::Pipeline Properties: ArtifactStore: Location: !Ref BucketName Type: S3 Name: !Ref Prefix RoleArn: !GetAtt CodePipelineRole.Arn Stages: - Actions: - ActionTypeId: Category: Source Owner: AWS Provider: CodeCommit Version: 1 Configuration: BranchName: !Ref BranchName OutputArtifactFormat: CODE_ZIP PollForSourceChanges: false RepositoryName: !GetAtt CodeCommitRepository.Name Name: Source OutputArtifacts: - Name: !Ref PipelineSourceArtifact Region: !Ref AWS::Region RunOrder: 1 Name: Source - Actions: - ActionTypeId: Category: Test Owner: AWS Provider: CodeBuild Version: 1 Configuration: ProjectName: !Ref CodeBuildProject1 InputArtifacts: - Name: !Ref PipelineSourceArtifact Name: Test OutputArtifacts: [] Region: !Ref AWS::Region RunOrder: 1 Name: Test - Actions: - ActionTypeId: Category: Build Owner: AWS Provider: CodeBuild Version: 1 Configuration: ProjectName: !Ref CodeBuildProject2 InputArtifacts: - Name: !Ref PipelineSourceArtifact Name: Build OutputArtifacts: - Name: !Ref PipelineBuildArtifact Region: !Ref AWS::Region RunOrder: 1 Name: Build
Code language: YAML (yaml)

Stagesプロパティにテストステージを定義します。
Configurationプロパティ内で、テストステージの詳細設定を行います。
ProjectNameプロパティで、先述のCodeBuildを指定します。
InputArtifactsプロパティで、テスト対象のスクリプト等を指定します。今回はSourceステージのアーティファクトを指定します。

(参考)アプリコンテナ用ファイル

Dockerfile

FROM amazonlinux RUN yum update -y && yum install python3 python3-pip -y RUN pip3 install bottle COPY main.py ./ CMD ["python3", "main.py"] EXPOSE 8080
Code language: Dockerfile (dockerfile)

アプリコンテナ用のイメージは、Amazon Linux 2をベースとして作成します。

Python製WebフレームワークのBottleを使用します。
ですからPythonおよびpipをインストール後、これをインストールします。

アプリロジックを記載したPythonスクリプト(main.py)をコピーし、これを実行するように設定します。

先述の通り、アプリは8080/tcpでHTTPリクエストを待ち受けますので、このポートを公開します。

main.py

from bottle import route, run @route('/') def hello(): return 'Hello CodePipeline.' if __name__ == '__main__': run(host='0.0.0.0', port=8080)
Code language: Python (python)

Bottleを使用して、簡易的なWebサーバを構築します。
8080/tcpでHTTPリクエストを待ち受け、「Hello CodePipeline.」を返すという単純な構成です。

test_main.py

import pytest from main import hello @pytest.mark.parametrize(('expected',), [ ('Hello CodePipeline.',), ]) def test_hello(expected): assert hello() == expected
Code language: Python (python)

pytestでmain.py内に定義したhello関数をテストするためのスクリプトです。
hello関数を実行し、「Hello CodePipeline.」の文字列が返ってきたら成功という内容です。

環境構築

CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。

CloudFormationスタックを作成し、スタック内のリソースを確認する

CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。

各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。

  • ECR:fa-081
  • CodeCommit:fa-081
  • CodeBuild1:fa-081-project-01
  • CodeBuild2:fa-081-project-02
  • CodePipeline:fa-081

AWS Management Consoleから各リソースを確認します。
CodePipelineを確認します。

Detail of CodePipeline 1.

パイプラインの実行に失敗しています。
これはCloudFormationスタック作成時に、CodeCommitが作成されたことがきっかけとして、パイプラインが実行されたためです。
現時点ではCodeCommitにコードをプッシュしていないため、パイプライン実行の過程でエラーが発生しました。

作成されているステージに注目します。
Testという名前で、テストユニット用のステージが作成されています。
CodeCommitにコードがプッシュされると、テストユニットが実行され、正常な動作が確認できれば、Dockerイメージをビルドし、ECRリポジトリにプッシュするという流れになります。

動作確認

テスト成功時

準備が整いましたので、CodeCommitにコードをプッシュします。

まずCodeCommitをプルします。

$ git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/fa-081 Cloning into 'fa-081'... warning: You appear to have cloned an empty repository.
Code language: Bash (bash)

空のリポジトリがプルされました。

リポジトリに3つのファイル(Dockerfile、main.py、test_main.py)を加えます。

$ ls -al total 12 drwxrwxr-x 3 ec2-user ec2-user 71 Aug 21 11:24 . drwxrwxr-x 6 ec2-user ec2-user 125 Aug 21 11:24 .. -rw-rw-r-- 1 ec2-user ec2-user 187 Aug 12 11:01 Dockerfile drwxrwxr-x 7 ec2-user ec2-user 119 Aug 21 11:24 .git -rw-rw-r-- 1 ec2-user ec2-user 681 Aug 21 08:07 main.py -rw-rw-r-- 1 ec2-user ec2-user 233 Aug 21 08:34 test_main.py
Code language: Bash (bash)

3ファイルをCodeCommitにプッシュします。

$ git add . $ git commit -m 'first commit' ... 3 files changed, 51 insertions(+) create mode 100644 Dockerfile create mode 100644 main.py create mode 100644 test_main.py $ git push ... * [new branch] master -> master
Code language: Bash (bash)

正常にプッシュできました。

しばらく待機した後、改めてCodePipelineを確認します。

Detail of CodePipeline 2.

パイプラインが開始されました。
Sourceステージは正常に完了し、テスト(Test)ステージに到達しました。
現在は「In progress」とあり、テストユニットを実行中であることがわかります。
しばらく待機します。

Detail of CodePipeline 3.

テストステージが正常に完了後、ビルドステージも正常に完了しました。

テストのレポートグループを確認します。

Detail of CodeBuild 1.

レポートグループが作成されています。
テストユニット実行結果の概要が確認できます。

今回実行したテストの詳細を確認します。

Detail of CodeBuild 2.

グラフや表でテスト結果が表示できます。

Detail of CodeBuild 3.

テスト時のフェーズ移行の過程も確認できます。
今回は全て正常に完了しました。

Detail of CodeBuild 4.

テスト実行時の詳細なログも確認することができます。

ECRリポジトリを確認します。

Detail of ECR Repository 1.

イメージがプッシュされています。テストが正常に完了しましたので、ビルドステージに移行し、Dockerイメージが生成されて、リポジトリにプッシュされたということです。

テスト失敗時

参考にテスト失敗時の挙動を確認します。
関数が出力する文字列を修正し、敢えてテストに失敗させます。

Detail of CodePipeline 4.

CodePipeline上で、テストに失敗したことがわかります。

Detail of CodeBuild 5.

レポートグループを確認すると、失敗したテストの概要がわかります。

続いて詳細を確認します。

Detail of CodeBuild 6.

ビルドフェーズで失敗していることがわかります。

Detail of CodeBuild 7.

ログを見ると、失敗した原因が確認できます。
返ってくる文字列が期待通りのものではなかったという内容です。

Detail of CodeBuild 8.

テストケースの詳細ページからも、同様の内容が確認できます。

まとめ

CodePipeline上にテストステージを追加する方法をご紹介しました。

タイトルとURLをコピーしました