1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWS CodePipeline V2のトリガーフィルターをTerraformで扱う

Last updated at Posted at 2024-03-31

はじめに

2023年10月24日に発表されたAWS CodePipelineのV2タイプに対して2024年2月9日にもトリガーフィルターの新機能が追加された。これにより、GitHubやGitLab(セルフマネージド含む)のソースプロバイダに対して、

  • リポジトリへのPush契機だけではなく、プルリクエスト発行契機でも作成が可能になる
    ⇒ プルリク時にテストだけ流してパスしたことを確認してからマージすることが容易になる
  • ファイルパスで対象ファイルをフィルタすることが可能になる
    ⇒ モノレポで構成されるプロジェクトの特定領域の更新時だけビルドをすることが容易になる

といったことができるようになり、より柔軟にパイプラインが構成できるようになった。

TerraformのAWS Providerも追従してトリガーフィルターが利用可能になっているため、今回は、プルリクエストのフィルタをIaCで作成してみる。

本記事の前提となる知識は、主に以下だ。

  • TerraformでAWS CodePipelineを実装したことがある

なお、TerraformのAWS CodePipelineの実装サンプルそのものは過去の記事で書いているので、実装経験のない人は参考にしていただきたい。

トリガーフィルターは、AWS CodePipelineのソースプロバイダがAWS CodeCommitの場合は利用できない。AWS本家のマネージドサービスなのになぜ……という気はするが、ひとまず、外部のソースプロバイダを設定するにはAWS CodeStar Connections改めAWS CodeConnectionsを設定する必要がある。
AWS CodeConnectionを外部のプロバイダに設定する方法は、こちらの記事に記載しているので、この設定が済んでいる前提で以降の説明を記載する。

Terraformでのトリガーフィルター設定方法

パイプラインは以下のように設定をする。
なお、今回は、以下の設定を事前に行っている。

プロバイダ GitLabセルフマネージド
グループ example_group
リポジトリ example_repository
マージ先のブランチ main
resource "aws_codepipeline" "example" {
  name     = local.codepipeline_pipeline_name
  role_arn = aws_iam_role.codepipeline.arn

+  pipeline_type = "V2"

+  trigger {
+    provider_type = "CodeStarSourceConnection"
+    git_configuration {
+      source_action_name = "Source"
+      pull_request {
+        events = ["OPEN"]
+        branches {
+          includes = ["main"]
+        }
+      }
+    }
+  }

  artifact_store {
    type     = "S3"
    location = aws_s3_bucket.example.bucket
  }

  stage {
    name = "Source"

    action {
      run_order        = 1
      name             = "Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "CodeStarSourceConnection"
      version          = "1"
      output_artifacts = ["SourceArtifact"]
      namespace        = "SourceVariables"

      configuration = {
        ConnectionArn    = aws_codestarconnections_connection.example.arn
        FullRepositoryId = "example_group/example_repository"
        BranchName       = "main"
      }
    }
  }

  stage {
    name = "Build"

    action {
      run_order       = 2
      name            = "Build"
      category        = "Build"
      owner           = "AWS"
      provider        = "CodeBuild"
      version         = "1"
      input_artifacts = ["SourceArtifact"]

      configuration = {
        ProjectName = aws_codebuild_project.example.name
      }
    }
  }
}

詳細なトリガーフィルターの仕様は、AWS公式のAWS CodePipelineユーザーガイドTerraformドキュメントのtriggerブロックの項を参照。

プルリクエストは、events属性で指定し、OPEN, UPDATE, CLOSEの契機でパイプラインを起動することができる。配列なので、OPEN, UPDATE両方を指定しておき、プルリクエストで差し戻しになった後に更新した場合に、再度パイプラインをトリガすることも可能だ。

その他、Pushのフィルタについては、ファイルパス以外にtagsを指定することでタグでフィルタすることもできる。

ファイルパス、タグ、ブランチ名にはワイルドカードも使えるので、例えば、ブランチ指定でFeature/*とすることで、Featureブランチすべてを対象にパイプラインを実行することも可能になる。

AWS CodeBuildでGitLabのメタ情報を参照する方法

さて、AWS CodeBuildでは、予約された環境変数を使うことで様々なメタ情報を取得することができる。
せっかくGitLabのプルリクエストが扱えるようになったのだから、GitLabのプルリクエスト関連のAPIを実行して、GitLab側にビルド結果を書き込むことで、マージまでの流れにおける開発者体験が向上するだろう。

ということで、環境変数にどのような情報が設定されるか、以下のようにBuildspecを作成して確認してみよう。

buildspec.yml
version: 0.2

phases:
  build:
    commands:
      - env | grep CODEBUILD_

このBuildspecで実行したAWS Codebuildのログの抜粋は以下。

[Container] 2024/03/31 11:55:22.932147 Running command env | grep CODEBUILD_
CODEBUILD_INITIATOR=codepipeline/example-pipeline
CODEBUILD_SRC_DIR=/codebuild/output/srcXXXXXXXXXX/src
CODEBUILD_BUILD_IMAGE=aws/codebuild/amazonlinux2-x86_64-standard:5.0
CODEBUILD_KMS_KEY_ID=arn:aws:kms:ap-northeast-1:XXXXXXXXXXXX:alias/aws/s3
CODEBUILD_GOPATH=/codebuild/output/srcXXXXXXXXXX
CODEBUILD_ACTION_RUNNER_URL=https://codefactory-ap-northeast-1-prod-default-build-agent-executor.s3.ap-northeast-1.amazonaws.com/cawsrunner.zip
CODEBUILD_PROJECT_UUID=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
CODEBUILD_BUILD_ID=example-buildproject:fcd5d542-XXXX-XXXX-XXXX-XXXXXXXXXXXX
CODEBUILD_CI=true
CODEBUILD_RESOLVED_SOURCE_VERSION=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
CODEBUILD_AGENT_ENDPOINT=http://127.0.0.1:7831
CODEBUILD_LAST_EXIT=0
CODEBUILD_BUILD_ARN=arn:aws:codebuild:ap-northeast-1:XXXXXXXXXXXX:build/example-buildproject:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
CODEBUILD_BUILD_NUMBER=6
CODEBUILD_LOG_PATH=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
CODEBUILD_EXECUTION_ROLE_BUILD=true
CODEBUILD_SOURCE_VERSION=arn:aws:s3:::example-bucket/example-codepipelin/SourceArti/XXXXXXX
CODEBUILD_BUILD_URL=https://ap-northeast-1.console.aws.amazon.com/codebuild/home?region=ap-northeast-1#/builds/example-buildproject:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/view/new
CODEBUILD_FE_REPORT_ENDPOINT=https://codebuild.ap-northeast-1.amazonaws.com/
CODEBUILD_BUILD_SUCCEEDING=1
CODEBUILD_BMR_URL=https://CODEBUILD_AGENT:3000
CODEBUILD_AUTH_TOKEN=XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
CODEBUILD_CONTAINER_NAME=default
CODEBUILD_START_TIME=1711886110515

……なんと、欲しい情報が無い!これではやりたいことが実現できないではないか!

と思ってマネコンを探して、

キャプチャ1.png

ここから……

image.png

見つけた!

ソースステージの出力に入っているということは、AWS CodePipelineの変数を使えばパイプラインの他のステージでも取得ができる!

ということで、以下のようにTerraformのコードを見直そう。あるステージの出力を後方のステージで参照する場合、namespaceの設定をしないと取得できないということに気を付けよう。

################################################################################
# CodePipeline                                                                 #
################################################################################
resource "aws_codepipeline" "example" {
  name     = local.codepipeline_pipeline_name
  role_arn = aws_iam_role.codepipeline.arn

  pipeline_type = "V2"

  trigger {
    provider_type = "CodeStarSourceConnection"
    git_configuration {
      source_action_name = "Source"
      pull_request {
        events = ["OPEN"]
        branches {
          includes = ["main"]
        }
      }
    }
  }

  artifact_store {
    type     = "S3"
    location = aws_s3_bucket.example.bucket
  }

  stage {
    name = "Source"

    action {
      run_order        = 1
      name             = "Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "CodeStarSourceConnection"
      version          = "1"
      output_artifacts = ["SourceArtifact"]
+     namespace        = "SourceVariables"

      configuration = {
        ConnectionArn    = aws_codestarconnections_connection.example.arn
        FullRepositoryId = "root/test"
        BranchName       = "main"
      }
    }
  }

  stage {
    name = "Build"

    action {
      run_order       = 2
      name            = "Build"
      category        = "Build"
      owner           = "AWS"
      provider        = "CodeBuild"
      version         = "1"
      input_artifacts = ["SourceArtifact"]

      configuration = {
        ProjectName = aws_codebuild_project.example.name
+       EnvironmentVariables =  jsonencode([
+         {
+           type  = "PLAINTEXT"
+           name  = "CODEPIPELINE_SRCVAR_COMMITID"
+           value = "#{SourceVariables.CommitId}"
+         },
+         {
+           type  = "PLAINTEXT"
+           name  = "CODEPIPELINE_SRCVAR_FULLREPOSITORYNAME"
+           value = "#{SourceVariables.FullRepositoryName}"
+         },
+         {
+           type  = "PLAINTEXT"
+           name  = "CODEPIPELINE_SRCVAR_PULLREQUESTID"
+           value = "#{SourceVariables.PullRequestId}"
+         },
+         {
+           type  = "PLAINTEXT"
+           name  = "CODEPIPELINE_SRCVAR_PULLREQUESTTITLE"
+           value = "#{SourceVariables.PullRequestTitle}"
+         },
+       ])
      }
    }
  }
}

これで、Buildspecを以下のように見直す。

buildspec.yml
version: 0.2

phases:
  build:
    commands:
-      - env | grep CODEBUILD_
+      - env | grep CODEPIPELINE_SRCVAR

AWS CodeBuildのログを取得すると、

[Container] 2024/03/31 12:25:27.225154 Running command env | grep CODEPIPELINE_SRCVAR
CODEPIPELINE_SRCVAR_PULLREQUESTID=12
CODEPIPELINE_SRCVAR_PULLREQUESTTITLE=Feature/test3
CODEPIPELINE_SRCVAR_COMMITID=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
CODEPIPELINE_SRCVAR_FULLREPOSITORYNAME=example_group/example_repository

取れた!これで、AWS CodeBuildからもGitLabのAPIを実行して情報を書きこむことができるようになった!

(おまけ)GitLabのマージリクエストに状態を書き込む

AWS CodeBuildからGitLabのAPIを実行して、ビルド状況をリアルタイムにマージリクエストのNotesに反映してくようにしてみよう。

GitLabのAPIを実行するスクリプト

GitLabのマージリクエストAPIのドキュメントを見ながら作っていく。

GitLabのAPIは、Notesへの初回書き込みはPOSTリクエスト、更新はPUTリクエストなので、NotesのIDが渡された場合で動作を変更している。

NoteIdの持ち回りはスクリプトの終了コードを使用しているので、NoteIdが128を超えるとうまく動かなくなる。
実運用で128を超えることが想定される場合は、ファイル出力をする等、別の方法を検討しよう。

エラーハンドリングはしていないので、実際に利用する場合は適宜追記をしていただきたい。

import json
import requests
import sys

args = sys.argv

print(args)

access_token = args[1]
baseurl      = args[2].replace('https://', '')
reponame     = args[3].replace('/', '%2F') # リポジトリ名はスラッシュが入り得るので、URLエンコードで```%2F```に置換する
mrid         = args[4]

if(len(args) == 6): 
  body = args[5]

  access_url = 'https://' + access_token + '@' + baseurl + '/api/v4/projects/' + reponame + '/merge_requests/' + mrid + '/notes'

  response = requests.post(
    access_url,
    verify=False,
    headers = {
      'Authorization': 'Bearer ' + access_token
    },
    data = {
      'body': body
    },
  )
else:
  noteid = args[5]
  body   = args[6]
 
  access_url = 'https://' + access_token + '@' + baseurl + '/api/v4/projects/' + reponame + '/merge_requests/' + mrid + '/notes/' + noteid

  response = requests.put(
    access_url,
    verify=False,
    headers = {
      'Authorization': 'Bearer ' + access_token
    },
    data = {
      'body': body
    },
  )

response_json = json.loads(response.text)

sys.exit(response_json['id']) # 初回書き込み後は同じNotesを更新していきたいので、終了コードにIDを設定して持ち回れるようにする

CodeBuildからスクリプトを取得できるようにする準備

今回はAmazon S3にスクリプトを入れて置き、AWS CodeBuildからaws s3 cpすることを想定した作りにする。
なお、CodeBuildに該当バケットへのアクセス権はつけておこう。

resource "aws_s3_bucket" "example" {
  bucket = local.s3_bucket_name
}

resource "aws_s3_bucket_ownership_controls" "example" {
  bucket = aws_s3_bucket.example.id

  rule {
    object_ownership = "BucketOwnerEnforced"
  }
}

resource "aws_s3_bucket_public_access_block" "example" {
  bucket = aws_s3_bucket.example.id

  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

resource "aws_s3_object" "example" {
  bucket = aws_s3_bucket.example.id

  source       = "スクリプトへのパス"
  key          = "スクリプト名"

  etag = filemd5("スクリプトへのパス")
}

CodePipelineの準備

CodePipelineでは以下を追加する。
また、GitLabのトークンはシークレット情報なので、AWS Secrets Managerに格納しておこう。

今回は紹介しないが、さらに後続のステージでGitLabのNotesを更新できるように、namespaceを設定しておくと良い。

resource "aws_secretsmanager_secret" "example" {
  name = local.secrets_name
}

resource "aws_secretsmanager_secret_version" "example" {
  secret_id     = aws_secretsmanager_secret.example.id
  secret_string = "GitLabで払い出したトークン"
}

resource "aws_codepipeline" "example" {
  # (中略)
  stage {
    name = "Build"

    action {
      run_order       = 2
      name            = "Build"
      category        = "Build"
      owner           = "AWS"
      provider        = "CodeBuild"
      version         = "1"
      input_artifacts = ["SourceArtifact"]
+     namespace       = "BuildVariables"

      configuration = {
        ProjectName = aws_codebuild_project.example.name
        EnvironmentVariables =  jsonencode([
+         {
+           type  = "PLAINTEXT"
+           name  = "CODEPIPELINE_S3_BUCKET_NAME"
+           value = aws_s3_bucket.example.id
+         },
+         {
+           type  = "PLAINTEXT"
+           name  = "CODEPIPELINE_GITLAB_ENDPOINT_URL"
+           value = aws_codestarconnections_host.example.provider_endpoint
+         },
+         {
+           type  = "SECRETS_MANAGER"
+           name  = "CODEPIPELINE_GITLAB_TOKEN"
+           value = aws_secretsmanager_secret.example.name
+         },
          {
            type  = "PLAINTEXT"
            name  = "CODEPIPELINE_SRCVAR_COMMITID"
            value = "#{SourceVariables.CommitId}"
          },
          {
            type  = "PLAINTEXT"
            name  = "CODEPIPELINE_SRCVAR_FULLREPOSITORYNAME"
            value = "#{SourceVariables.FullRepositoryName}"
          },
          {
            type  = "PLAINTEXT"
            name  = "CODEPIPELINE_SRCVAR_PULLREQUESTID"
            value = "#{SourceVariables.PullRequestId}"
          },
          {
            type  = "PLAINTEXT"
            name  = "CODEPIPELINE_SRCVAR_PULLREQUESTTITLE"
            value = "#{SourceVariables.PullRequestTitle}"
          },
        ])
      }
    }
  }
  # (後略)
}

Buildspecの例

AWS CodeBuildのBuildspecは以下のように作成すると良い。
今回のスクリプトは、内部でrequestsモジュールを使用しているため、installフェーズでpip installするのを忘れないようにしよう。
echo testしている行で、実際にテストをするためのコマンド(npm test等)を実行することを想定している。

env.exported-variablesは、ここに書いた変数名でBuildステージの出力をすることができる。
上記の設定とpost_buildフェーズで値を設定することで持ち回りが可能だ。

NOTEID=$?は、コマンド行を分けず一息で書く必要がある。CodeBuildは、コマンドの終了コードが0以外の場合にビルドが停止してしまうため、終了コードを退避することと、これにより終了コードを0で上書きする二重の効果を期待する。

version: 0.2

env:
  exported-variables:
    - CodeBuildExportNoteId
phases:
  install:
    commands:
      - aws s3 cp s3://${CODEPIPELINE_S3_BUCKET_NAME}/スクリプト名 ./
      - chmod 775 ./スクリプト名
      - pip install requests
  build:
    commands:
      - |
        python ./スクリプト名 ${CODEPIPELINE_GITLAB_TOKEN} ${CODEPIPELINE_GITLAB_ENDPOINT_URL} ${CODEPIPELINE_SRCVAR_FULLREPOSITORYNAME} ${CODEPIPELINE_SRCVAR_PULLREQUESTID} '
        | Phase | Status |
        | --- | --- |
        | test1 | ➖ |
        | test2 | ➖ |
        | Build | ➖ |'; NOTEID=$?
      - echo test1
      - sleep 5
      - |
        python ./スクリプト名 ${CODEPIPELINE_GITLAB_TOKEN} ${CODEPIPELINE_GITLAB_ENDPOINT_URL} ${CODEPIPELINE_SRCVAR_FULLREPOSITORYNAME} ${CODEPIPELINE_SRCVAR_PULLREQUESTID} ${NOTEID} '
        | Phase | Status |
        | --- | --- |
        | test1 | ⭕ |
        | test2 | ➖ |
        | Build | ➖ |'; NOTEID=$?
      - echo test2
      - sleep 5
      - |
        python ./スクリプト名 ${CODEPIPELINE_GITLAB_TOKEN} ${CODEPIPELINE_GITLAB_ENDPOINT_URL} ${CODEPIPELINE_SRCVAR_FULLREPOSITORYNAME} ${CODEPIPELINE_SRCVAR_PULLREQUESTID} ${NOTEID} '
        | Phase | Status |
        | --- | --- |
        | test1 | ⭕ |
        | test2 | ⭕ |
        | Build | ➖ |'; NOTEID=$?
      - echo build
      - sleep 5
      - |
        python ./スクリプト名 ${CODEPIPELINE_GITLAB_TOKEN} ${CODEPIPELINE_GITLAB_ENDPOINT_URL} ${CODEPIPELINE_SRCVAR_FULLREPOSITORYNAME} ${CODEPIPELINE_SRCVAR_PULLREQUESTID} ${NOTEID} '
        | Phase | Status |
        | --- | --- |
        | test1 | ⭕ |
        | test2 | ⭕ |
        | Build | ⭕ |'; NOTEID=$?
  post_build:
    commands:
      - export CodeBuildExportNoteId=${NOTEID}

これでマージリクエストを走らせると、Notesに以下のようにビルド結果が表示される。
実際は、更新を書けているので、段々と⭕が増えていくのが見える。

キャプチャ3.png

これで、マージリクエストの結果でいちいちCodeBuildの様子を見に行かなくても状況を把握できるようになる!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?