AWS CodePipeline + CodeBuild で Submodule 付きのリポジトリをビルドする

弊社では一部のサービスのビルドとデプロイにAWS CodePipeline, CodeBuild を使用しています。インフラの作成には terraform を使用しています。
ソースは Github で管理しているのですが、ビルドしたいリポジトリが別リポジトリをサブモジュールとして参照している場合に、サブモジュールのソースを取得できませんでした。
その時の対処法を記しておきます。

# CodeBuild でサブモジュール使えなかったっけ？
使えます。ただし CodePipeline と組み合わせなければ。
terraform のリファレンスを見てみましょう。
https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/codebuild_project

> source: git_submodules_config
> 
> This block is only valid when the type is CODECOMMIT, GITHUB, GITHUB_ENTERPRISE, GITLAB, or GITLAB_SELF_MANAGED.
> 
> fetch_submodules - (Required) Whether to fetch Git submodules for the AWS CodeBuild build project.

CodePipeline と組み合わせているので type は `CODEPIPELINE` になっていました。ということで属性をちょっといじるだけでは解決しません。

# 対処法
パイプラインのソースステージでプライマリとサブモジュールそれぞれ別々に取得するよう action を作成します。ビルドステージではどれがプライマリなのかを示してやります。

## terraform
まずは CodePipeline の定義
```terraform
resource "aws_codepipeline" "pipeline" {
  # nameなどを省略
  
  stage {
    name = "Source"

    # プライマリリポジトリ用
    action {
      name             = "SourcePrimary"
      category         = "Source"
      namespace        = "SourceVariablesPrimary"
      owner            = "AWS"
      provider         = "CodeStarSourceConnection"
      version          = "1"
      output_artifacts = ["primary_output"]
        
      configuration = {
        ConnectionArn    = data.aws_codestarconnections_connection.github.arn
        FullRepositoryId = var.github_repository
        BranchName       = var.target_branch
      }
    }

    # サブモジュールリポジトリ用
    action {
      name             = "SourceSubModule"
      category         = "Source"
      namespace        = "SourceVariablesSubModule"
      owner            = "AWS"
      provider         = "CodeStarSourceConnection"
      version          = "1"
      output_artifacts = ["submodule_output"]
        
      configuration = {
        ConnectionArn    = data.aws_codestarconnections_connection.github.arn
        FullRepositoryId = var.github_repository_submodule
        BranchName       = var.target_branch_submodule
      }
    }
  }

  stage {
    name = "Build"

    action {
      name             = "Build"
      category         = "Build"
      owner            = "AWS"
      provider         = "CodeBuild"
      input_artifacts  = ["primary_output", "submodule_output"]
      output_artifacts = ["build_output"]
      version          = "1"

      configuration = {
        ProjectName   = aws_codebuild_project.buildstage.name
        # 2つ以上のソースがある時はどちらがプライマリか指定する必要がある
        PrimarySource = "primary_output"
      }
    }
  }
}
```
次に CodeBuild の定義です。
```terraform
resource "aws_codebuild_project" "buildstage" {
  # nameなどを省略
  
  source {
    type      = "CODEPIPELINE"
    buildspec = "buildspec-build.yaml"
  }
}
```

## buildspec.yaml
プライマリのリポジトリは自動でカレントディレクトリに展開されます。
一方、サブモジュールは環境変数 `${CODEBUILD_SRC_DIR_xxxxxxx}` の示すディレクトリに展開されます。ここで `xxxxxxx` の部分はソースステージの action で指定した `output_artifacts` の値です。

https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-env-vars.html
> セカンダリソースの場合、ディレクトリパスの環境変数は「CODEBUILD_SRC_DIR_<sourceIdentifier>」です。「<sourceIdentifier>」は作成するソース識別子です。

あとはサブモジュールのソースを任意のディレクトリに移動するなりしましょう。

```yaml
version: 0.2

phases:
  pre_build:
    commands:
      # ソースステージの action で `output_artifacts = ["submodule_output"]` と指定したので
      # サブモジュールは ${CODEBUILD_SRC_DIR_submodule_output} に展開される
      cp -r ${CODEBUILD_SRC_DIR_submodule_output}/* ./submodule_directory/

```

# まとめ
CodePipeline + CodeBuild でサブモジュールを含むソースをビルドする場合はこうするとよいです。

+ プライマリとサブモジュールをそれぞれソースのactionで別々に取得する
+ buildspec でサブモジュールを任意のディレクトリに移動する