CodeBuildで別アカウントのCodeCommitからGitメタデータを取得する

こんにちは。システム開発一部の吉田です。

先月自作PCを組み立てました。ファンがピカピカに光ります。

グラフィックボートを買わなかったのでPCケース内のスペースが余りまして。そこにブロリーのフィギュアを置いてディスプレイ代わりにしていたらメモリがぶっ壊れました。流石は伝説の超サイヤ人。

さて、今回はCodePipelineとCodeBuildとCodeCommitのCode兄弟についてのお話です。主にCodeBuildについて。

これらの組み合わせて使うとCI/CDパイプラインを比較的簡単に作成できるのでとても便利です。皆さんもう使っているでしょうか?

特にAWSリソース操作の権限管理をIAMロールで完結できるのが個人的には気に入っています。

そんな便利な3兄弟ですが、利用すると気づく独特な癖があります。

タイトルにもある通り、CodeBuildでGitメタデータを利用する際に少しだけハマったので解決策を書いていこうと思います。

先ずは結論

CodePipeline + CodeBuildで別アカウントにあるCodeCommitのGitメタデータを利用するにはビルド処理の中でgit cloneする必要がある。

ざっくりとしたやり方

  • CodeCommitがあるアカウントをアカウントA
  • CodePipeline, CodeBuildがあるアカウントをアカウントB

として、既にCodePipeline, CodeBuildを組み合わせたパイプラインがアカウントBに存在する前提で進めていきます。

事前準備

  • (アカウントA) CodeCommitをcloneできるIAMロールを用意し、アカウントBのCodeBuildのIAMロールからのスイッチロールが出来るようにプリンシパルを設定する。
  • (アカウントB) CodeBuildのIAMロールに↑のロールの対してのAssumeRoleを許可するポリシーを付与する。

buildspecでの手順

  1. (アカウントB) アカウントAのロールにCLIを使ってスイッチロールする。
  2. (アカウントB) Git認証情報のヘルパーにCLIを使うように設定する。
  3. (アカウントB) CodeCommitのリポジトリ名を指定してgit cloneを実行する。
  4. (アカウントB) スイッチロールを解除する。

そもそもGitメタデータとは

ざっくり言うとプロジェクトにある.gitディレクトリの中身のことです。.gitディレクトリはgit initとかgit cloneすると勝手に出来ます。

そのプロジェクトのgitに関する情報が全てそこ入っているので、gitを使いたい場合はGitメタデータが必須となります。

今回のビルドプロジェクトではファイルごとの差分を取る(git diff)ためにメタデータが必要でした。

CodePipelineだとGitメタデータがとれない?

CodePipelineはリポジトリのソースをS3のアーティファクトバケットに保存して後続のステージに渡します。ここにメタデータが含まれません。

いやクローンするんだからメタデータついてくると普通思いますよね?でも実際はクローンはせずにzipでダウンロードしているようです。コンソールからパイプラインを作成するときに作成画面よく見ると書いてあります。Cloudformationで作っていた私はこれに気づけませんでした。

ソースがCodeCommitの場合、完全クローンという機能を利用すればGitメタデータの取得はできます。ただし、この機能は同一アカウントのCodeCommitリポジトリ限定という悲しみがあります。

(↓AWS公式ドキュメントの前提条件に書いてある)

docs.aws.amazon.com

私たちのチームでリポジトリを置くアカウントは別にするという戦略をとっていたため、完全クローンの機能は使えませんでした。

どうする?

メタデータを取るにはCodeBuild内の処理でgit cloneするしかないだろうなと思い、ググったらそれっぽい記事を見つけたのでそれを参考にしながら実装しました。それっぽい記事は↓

itnext.io

中にあるシェルスクリプトにリポジトリ名を引数として渡して実行すると、クローンしてGitメタデータを取得してくれます。こちらを流用させていただきます。

ただ、こちらの記事は完全クローンが無かった時代に同一アカウントのCodeCommitのメタデータを取得するの記事のようで、私たちのプロジェクトに当てはめるには少し工夫が必要でした。

本題

スクリプトを実行する前に事前準備が必要です。まずはCodeBuild内でスイッチロールします。何故かというとCodeCommitのソースはクロスアカウントでクローン出来ないためです。

スイッチロール先(アカウントA)のIAMロールはCodeCommit関連のポリシーとスイッチロールを許可するプリンシパルが必要です。cloudformationでの例を以下に記載します。

CodeCommitShareRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: codecommit-share-role
      Path: /
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: "Allow"
            Principal:
              AWS: "arn:aws:iam::{Account A}:role/CodeBuildServiceRole"
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: AllowCodeCommitPull
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "codecommit:GetBranch"
                  - "codecommit:GetCommit"
                  - "codecommit:GitPull"
                Resource: "*"

CodeBuildにアタッチするロール(アカウントB)の例。スイッチロールの権限以外省略しているためビルドに必要な権限を適宜付け足してください。

  CodeBuildServiceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: codebuild-service-role
      Path: /
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service: "codebuild.amazonaws.com"
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: AllowCodeCommitAssumeRole
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - sts:AssumeRole
                Resource: "arn:aws:iam::{Account B}:role/CodeCommitShareRole"

実際のbuildspecはこうなります。ロールのARN, リポジトリURL, ブランチは環境変数にして渡しています。後続でcliを使うのでメタデータを取得した後はスイッチロールを解除するようにしました。

  pre_build:
    commands:
      - aws sts get-caller-identity
      - credentials=$(aws sts assume-role --role-arn ${ASSUME_ROLE_ARN} --role-session-name "RoleSessionFromCodeBuild" | jq .Credentials)
      - export AWS_ACCESS_KEY_ID=$(echo ${credentials} | jq -r .AccessKeyId)
      - export AWS_SECRET_ACCESS_KEY=$(echo ${credentials} | jq -r .SecretAccessKey)
      - export AWS_SESSION_TOKEN=$(echo ${credentials} | jq -r .SessionToken)
      - aws sts get-caller-identity
      - git config --global credential.helper '!aws --region ap-northeast-1 codecommit credential-helper $@'
      - git config --global credential.UseHttpPath true
      - curl https://raw.githubusercontent.com/TimothyJones/codepipeline-git-metadata-example/master/scripts/codebuild-git-wrapper.sh -o codebuild-git-wrapper.sh
      - ./codebuild-git-wrapper.sh $REPO_URL $REPO_BRANCH
      - export -n AWS_SESSION_TOKEN AWS_SECRET_ACCESS_KEY AWS_ACCESS_KEY_ID
      - aws sts get-caller-identity

これで後続のフェーズでGitメタデータを使うことが出来ます。

余談

  • 私は特に理由もなくgitのcredentialヘルパーを手動設定していますが、参考にした記事の通りenvに記載しても問題ないと思います。
  • リンク先のシェルスクリプトが置き換わってしまう可能性があるのでbuildspecでcurlするのではなくCodeBuild用のイメージに含めるのを推奨します。CodeBuildの公式イメージはめちゃくちゃでかいので注意。