メモ：CodePipelineでDockerImageを作る

CI/CDツールはAWSのCodePipelineを軽く使った事があるので、一応備忘録として書いておく。
（jenkisは使った事がないが、別途見ておきたい）

# CodePipeline
基本的には

* コード管理であるgit (CodeCommitやGitHub)
* ビルド、テストを行う(CodeBuildもしくはJenkis。ECRにPushする）
* デプロイはCodeDeployでECSでリリースなど

のセットをpipelineで作成する。
起動トリガーはgitへのPushやS3へのファイルアップロード等が選択可能
途中で停止させて、ボタンを押さないと進まないようにすることも可能

# 設定方法

terraformで作成する場合を想定する。Formationでも大差はない、はず？
少し前のバージョンで書いたコードなので今のterraformで使う場合は修正が必要か。

まずはaws_codebuild_projectでビルドプロジェクトを作成する。
var.やlocal.は変数設定している箇所。
ECRやCodeCommitのリポジトリは既に存在しているという前提。
(大量に作るのでない限り、リポジトリはわざわざIaCで管理する必要なさそうな気がする）
[AWS公式](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/sample-docker.html)も参照


```terraform
resource "aws_codebuild_project" "codebuild_project" {
  name = local.CodeBuildName
  service_role = "arn:aws:iam::${local.account}:role/CodeBuildServiceRole"

  artifacts {
    type = "NO_ARTIFACTS"
  }

  # ビルド環境の設定
  environment {
    compute_type                = "BUILD_GENERAL1_SMALL"
    image                       = "aws/codebuild/standard:2.0"
    type                        = "LINUX_CONTAINER"
    image_pull_credentials_type = "CODEBUILD"

	# ここで設定した値をbuildspeck.ymlで使える
    environment_variable {
      name  = "AWS_DEFAULT_REGION"
      value = "ap-northeast-1"
    }

    environment_variable {
      name  = "AWS_ACCOUNT_ID"
      value = local.account
    }

    # ECRは事前に作成
    environment_variable {
      name  = "ECR_REPOSITORY_NAME"
      value = local.ECRName
    }

    environment_variable {
      name  = "SYSTEM_ID"
      value = var.SystemID
    }

    environment_variable {
      name  = "CONTAINER_NAME"
      value = local.ContainerName
    }

    privileged_mode = "true"
  }

  # クラウドウォッチにログ出力する場合
  logs_config {
    cloudwatch_logs {
      group_name = local.CloudWatchLogName
      status = "ENABLED"
      stream_name = var.SystemID
    }
  }

  // gitHubの場合は別途参照
  source {
    type            = "CODECOMMIT"
    location        = "https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/${var.GitReposName}"
  }
}
```

ビルドするマシンは[公式](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-compute-types.html)から選択
イメージも[公式](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-available.html)を見ると良い。
ここを見るとaws/codebuild/standard:2.0は時代遅れっぽい。


CodeCommitをソースにしているが、GitHubの場合はパラメータストアにトークン保存した上で以下のようにする。
CodeCommitも悪くはないのだが、本番とテストでアカウントが分かる事を考えるとgitHubが使えるならそちらで良い気はする。
（専用線経由でアクセスできるからCodeCommitを使いたい、というような要望はあるかもしれないけど）

```terraform
source {
    type                = "GITHUB"
    location            = var.github_repository_location
    git_clone_depth     = 1
    report_build_status = true # 結果をリポジトリに通知する場合はtrueを指定
  }
```

そして、このビルド設定をCodePipelineに適用する。

```terraform
resource "aws_codepipeline" "codepipeline" {
  name = local.CodePipelineName

  # ロールは事前に作成されているとする
  role_arn = "arn:aws:iam::${local.account}:role/CodePipelineServiceRole"

  # S3の名前は自分の所有しているものにする
  artifact_store {
    location = "s3_codepipeline"
    type     = "S3"
  }

  # Source,Build,Deployの順番で実行されるので、設定していく。
  stage {
    name = "Source"
    action {
      name             = "Source"
      category         = "Source"
      owner            = "AWS"
      provider         = "CodeCommit"
      version          = "1"
      output_artifacts = ["source_output"]

      configuration = {
        RepositoryName   = var.GitReposName
        BranchName = "master"
		PollForSourceChanges = "false" # CloudWatchで検出するのでfalseにする
      }
    }
  }

  stage {
    name = "Build"
    action {
      name             = "Build"
      category         = "Build"
      owner            = "AWS"
      provider         = "CodeBuild"
      input_artifacts  = ["source_output"]
      output_artifacts = ["build_output"]
      version          = "1"

      configuration = {
        ProjectName =   local.CodeBuildName

      }
    }
  }

  # ECSにデプロイ
  stage {
    name = "Deploy"
    action {
      name             = "Deploy"
      category         = "Deploy"
      owner            = "AWS"
      provider         = "ECS"
      input_artifacts  = ["build_output"]
      version          = "1"

      configuration = {
        ClusterName = local.ECSClusterName
        ServiceName = local.ECSServiceName
        FileName    = "imagedefinitions.json"

      }
    }
  }
}
```

ソースコードの検知はCloudWatchを使う

```terraform
resource "aws_cloudwatch_event_rule" "event_rule" {
  name        = local.CloudWatchEventName
  description = "Amazon CloudWatch Events rule to automatically start your pipeline when a change occurs in the AWSCodeCommit."

  event_pattern = <<-JSON
  {
    "source": [
      "aws.codecommit"
    ],
    "detail-type": [
      "CodeCommit Repository State Change"
    ],
    "resources": [
      "arn:aws:codecommit:ap-northeast-1:${var.git_account}:${var.GitReposName}"
    ],
    "detail": {
      "event": [
        "referenceCreated",
        "referenceUpdated"
      ],
      "referenceType": [
        "branch"
      ],
      "referenceName": [
        "master"
      ]
    }
  }
  JSON

  depends_on = ["aws_codepipeline.codepipeline"]
}

resource "aws_cloudwatch_event_target" "event_target" {
  rule      = aws_cloudwatch_event_rule.event_rule.name
  target_id = aws_cloudwatch_event_rule.event_rule.name
  arn       = aws_codepipeline.codepipeline.arn
  role_arn  = "arn:aws:iam::${local.account}:role/StartPipelineExecutionRole"
}

```

これでgit上のファイルが変わった場合に動くように出来る。
特定のファイルが変わった場合のみビルド場合は.shでコントロールするといった方法があった。
機能追加されてフィルタをかけられるようになっている、というような情報は知らない。

S3へのファイルをアップロードをトリガーとする場合は以下のようにソースを設定する。
こちらはkeyでファイル名を指定できるので、例えばxyz.warがアップロードされるとpipelineが走る、というように出来る。

```terraform
resource "aws_cloudwatch_event_rule" "event_rule" {
  name        = local.CloudWatchEventName
  description = "Amazon CloudWatch Events rule to automatically start your pipeline when a change occurs in the AWSCodeCommit."

  event_pattern = <<-JSON
  {
    "source": [ "aws.s3" ],
    "detail-type": [ "AWS API Call via CloudTrail" ],
    "detail": { 
      "eventSource": [ "s3.amazonaws.com" ],
      "eventName": [ "PutObject", "CompleteMultipartUpload", "CopyObject" ],
      "requestParameters": {
        "bucketName": [ "s3-codepipeline" ],
        "key": [ "${local.CodePipelineName}/${var.warfile}" ]
      }
    }
  }
  JSON

  depends_on = ["aws_codepipeline.codepipeline"]
}
```

これらのAWS上の設定をした上で、実際にビルドするには設定ファイルbuildspec.ymlがgit上に必要となる。


```yml
version: 0.2

env:
  # Dockerhubへのアクセストークンをパラメーターストアから読み込んでおく
  parameter-store:
    DOCKER_USER: docker-hub-user
    DOCKER_TOKEN: docker-hub-token
    
phases:
  # 自動テストを行う場合はinstallセクションのcommandsでテストツールインストール
  install:
    runtime-versions:
      docker: 18
  # ログイン等、ビルド前の準備
  # AWS_ACCOUNT_ID等はterraform側で設定した値
  pre_build:
    commands:
      - echo Logging in to Amazon ECR ...
      - $(aws ecr get-login --no-include-email --region $AWS_DEFAULT_REGION)
      - REPOSITORY_URI=$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$ECR_REPOSITORY_NAME
      - IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - echo $DOCKER_TOKEN | docker login -u $DOCKER_USER --password-stdin
  build:
    # ここではS3から.warをコピーしてdockerImageを作成する想定
    commands:
      - aws s3 cp s3://s3-codepipeline/$PIPELINE_NAME/$SYSTEM_WAR ./$SYSTEM_WAR
      - echo Build started on `date`
      - echo Building the Docker image...          
      - docker build -t $REPOSITORY_URI:latest .
      - echo docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    # イメージをECRにプッシュする
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker image...
      - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - printf '[{"name":"%s","imageUri":"%s"}]' $CONTAINER_NAME $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$ECR_REPOSITORY_NAME:$IMAGE_TAG > imagedefinitions.json
artifacts:
  files: imagedefinitions.json
```

これでビルドが動くがDockerイメージを作成しているので、Dockerfileが必要なので
例えばtomcat9+jdk8のイメージにwarファイルを追加するようなものを用意して配置しておく。

```dockerfile
#
# VERSION 0.0.1
#


FROM tomcat:9.0.41-jdk8-corretto
LABEL maintainer="test@test.com"
ARG MEM_MX="2048m"
ARG MEM_MS="64m"
ARG APP_WARFILE="./xyz.war"


RUN echo $CATALINA_HOME
RUN echo $CATALINA_HOME/webapps/

ADD ./app/conf/server.xml $CATALINA_HOME/conf/server.xml

ENV CATALINA_OPTS "-server -Xmx$MEM_MX -Xms$MEM_MS 


# check Web application
ADD $APP_WARFILE $CATALINA_HOME/webapps

WORKDIR $CATALINA_HOME/bin

```

これで一連の動きは出来る。
テストレポート等を作る場合は[ここ](https://dev.classmethod.jp/articles/codebuild-test-reporting-ga/)とか[公式](https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/test-report-pytest.html)を参考に。
buildでテストを実行するコマンドを書いた上でreportsセクションを追加してやればよい。

途中で承認処理を付ける場合
[公式](https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/approvals.html)

おわり