GitHubActionsとCodePipelineを連携させてDeployする。

# やりたいこと
- ソース管理はGitHubを利用する。
- GitHub flow
- 最終的にはAWS上のEC2に対してDeployを行う。
- master push時には承認なしにDevelop環境にDeployしたい。
- tag push時にはStaging〜Production環境にDeployしたいが、各環境Deploy時に承認が必要。

# 前提
- 諸々の事情で、AWS側でGitHubをSourceArtifactsとして動作することができない。
- 承認は、これも諸事情で、AWS側で実施したい。
- 本来はProduction環境はAWS上別アカウントにした方がベターなのでは？とは思うが、小さいシステムなのと、まだ最終的なプロダクトではないということで、同一アカウントのまま。別アカウントの場合は、クロスアカウントの考慮が必要。

# 結論
- BuildはGitHub Actionsで実施する。
- Build後、Zip化してS3にPushし、そこからCodePipelineを動作させることとする。
- CodePipeline側でApproveを追加し、承認行為をさせる。そこからはCodeDeployでDeployする。

## 実際の内容。
- master pushとtag pushでGitHub Actionsのymlを分けてしまう。（場合わけ利用するのが面倒になった。）
- CodePipelineも二つに分離する。

とても安易なので、他でも参考になるかどうかは不明。

### GitHub Actionsのyml

PRの際にはBuildのみ動作するようにする。S3 Pushは今回手間を省くために以下を利用した。
https://github.com/opspresso/action-s3-sync

AdoptOpenJDKを利用したかったので、setup-java@v2を利用した。
https://github.com/actions/setup-java

zip化する時に、`zip -j`しないとCodeDeployが動作しないので注意が必要。
これはzip直下にAppSpec.ymlが必要なため
https://infraya.work/posts/codedeploy_appspec_not_found_error/

またAWSのKEY IDとかSECRETSは事前にGitHub側に登録が必要。当たり前だけど、yml内に直書きしちゃダメ。アタックされるよ。
https://docs.github.com/ja/actions/reference/encrypted-secrets#creating-encrypted-secrets-for-a-repository

```./.github/workflows/build.yml
name: Java CI with Maven

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up JDK 1.15
      uses: actions/setup-java@v2
      with:
        java-version: '15'
        distribution: 'adopt'
    - name: Build with Maven
      run: mvn -B verify --file pom.xml
      env:
        TZ: 'Asia/Tokyo'
    - name: App Packaging
      run: |
        mkdir ./publish
        zip -j ./publish/foo.zip ./target/product.jar ./script/stop.sh ./script/start.sh ./appspec.yml
    - name: Publish to AWS S3
      # この処理はMaster Push時のみに動かしたいので。
      if: github.ref == 'refs/heads/master'
      uses: opspresso/action-s3-sync@master
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        AWS_REGION: "ap-northeast-1"
        FROM_PATH: "./publish"
        DEST_PATH: "s3://artifact/"
```

tag pushの際には`Version_`から始まるtagのみを対象とする。本来はこの際にBuildさせるわけではなく、上記Master push時のartifactsを利用すべきだと思うが、面倒臭くなったので省略。

````./.github/workflows/tag-push.yml
name: Release Tag Push Event

on:
  push:
    tags: 
      - Version_*

jobs:
  build:

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Set up JDK 1.15
      uses: actions/setup-java@v2
      with:
        java-version: '15'
        distribution: 'adopt'
    - name: Build with Maven
      run: mvn -B verify --file pom.xml
      env:
        TZ: 'Asia/Tokyo'
    - name: App Packaging
      run: |
        mkdir ./publish
        zip -j ./publish/foo-prd.zip ./target/product.jar ./script/stop.sh ./script/start.sh ./appspec.yml
    - name: Publish to AWS S3
      uses: opspresso/action-s3-sync@master
      env:
        AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
        AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
        AWS_REGION: "ap-northeast-1"
        FROM_PATH: "./publish"
        DEST_PATH: "s3://artifact/"
```

### S3の状態
こうすると、S3には以下のような状態になる。

```
s3 - artifact - foo.zip
              - foo-prd.zip
```
それぞれのzipファイル格納時に反映させるように、CodePipelineを組むことにする。なお、S3はVersioning有効化しておく必要があるので、注意が必要。
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/tutorials-simple-s3.html

### Develop環境用CodePipeline

Terraformで作る。
IAMとかS3とかも色々あるんだけど、細かい箇所は省略。
本来はSource変更検知はEventBridgeでちゃんと作った方がいいのだが、`PollForSourceChanges`でやってしまう。そんなにDeploy頻度が多くなかったので。
https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/update-change-detection.html

```codepipeline.tf

################################################################################
# CodePipeline for Build Account (Develop)                                     #
################################################################################
resource "aws_codepipeline" "cop-dev" {

  name     = "develop-pipeline"
  role_arn = "${aws_iam_role.iam-cop.arn}"

  artifact_store {
    type     = "S3"
    location = "${aws_s3_bucket.s3-artifact.bucket}"
  }

  stage {
    name = "Source"

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

      configuration = {
        S3Bucket       = "${aws_s3_bucket.s3-artifact.bucket}"
        S3ObjectKey    = "foo.zip"
        PollForSourceChanges = true
        
      }
    }
  }

  stage {
    name = "Deploy"

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

      configuration = {
        ApplicationName = "ec2-deploy"
        DeploymentGroupName = "ec2-deploy-group"
      }
    }
  }
}
```

### Production用Pipeline
一連の動作となるように、Staging承認〜Staging Deploy〜Production承認〜ProductionDeployとする。
StageのNameは同名は不可。なので例えばApproveで言うと、StagingApproveとProductionApproveと分けて作成している。また空白は許容されていないので`Staging Approve`みたいなNameはつけられない。

```codepipeline.tf
################################################################################
# CodePipeline for Build Account (Production)                                  #
################################################################################
resource "aws_codepipeline" "cop-prd" {

  name     = "production-pipeline"
  role_arn = "${aws_iam_role.iam-cop.arn}"

  artifact_store {
    type     = "S3"
    location = "${aws_s3_bucket.s3-artifact.bucket}"
  }

  stage {
    name = "Source"

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

      configuration = {
        S3Bucket       = "${aws_s3_bucket.s3-artifact.bucket}"
        S3ObjectKey    = "foo-prd.zip"
        PollForSourceChanges = true
        
      }
    }
  }
    stage {
    name = "StagingApprove"

    action {
      run_order        = 2
      name             = "Approval"
      category         = "Approval"
      owner            = "AWS"
      provider         = "Manual"
      version          = "1"

      configuration = {
        CustomData = "StagingReleaseの承認をお願いします"
      }
    }
  }

  stage {
    name = "StagingDeploy"

    action {
      run_order        = 3
      name             = "Deploy"
      category         = "Deploy"
      owner            = "AWS"
      provider         = "CodeDeploy"
      version          = "1"
      input_artifacts  = ["SourceArtifact"]

      configuration = {
        ApplicationName = "ec2-deploy-stg"
        DeploymentGroupName = "ec2-deploy-stg-group"
      }
    }
  }
  
  stage {
    name = "ProductionApprove"

    action {
      run_order        = 4
      name             = "Approval"
      category         = "Approval"
      owner            = "AWS"
      provider         = "Manual"
      version          = "1"

      configuration = {
        CustomData = "ProductionReleaseの承認をお願いします"
      }
    }
  }

  stage {
    name = "ProductionDeploy"

    action {
      run_order        = 5
      name             = "Deploy"
      category         = "Deploy"
      owner            = "AWS"
      provider         = "CodeDeploy"
      version          = "1"
      input_artifacts  = ["SourceArtifact"]

      configuration = {
        ApplicationName = "ec2-deploy-prd"
        DeploymentGroupName = "ec2-deploy-prd-group"
      }
    }
  }
}
```

### CodeDeploy

単純なEC2に対するIn-place Deploy
Fargate起動やBlue/Green Deployとかは考えてない。

また、これはDevelop環境用なので、Staging用、Production用はこれをコピーして作っている。本来は、Module化して、必要な箇所だけ変数化するべきだが、3つだし、今後増える可能性もあまりなかったので、コピー。こんなのが多すぎるな。

```codedeploy.tf
#--------------------------------------------------
# デプロイプロジェクト
#--------------------------------------------------
resource "aws_codedeploy_app" "cod-app" {
    compute_platform = "Server"
    name = "ec2-deploy"
}
# リリース時に参照するconfiguration
# 設定項目はリリースの際にどの程度のホストが正常起動している必要があるかのみ
resource "aws_codedeploy_deployment_config" "cod-config" {
  deployment_config_name = "ec2-deploy-config"

  minimum_healthy_hosts {
    type  = "HOST_COUNT"
    value = 0
  }
}

# リリース対象のサーバを識別するための設定
# `"application":"deploy"` というタグがついたEC2インスタンスを対象としている
resource "aws_codedeploy_deployment_group" "cod-group" {
  app_name               = "${aws_codedeploy_app.cod-app.name}"
  deployment_group_name  = "ec2-deploy-group"
  service_role_arn       = "${aws_iam_role.iam-cod.arn}"
  deployment_config_name = "${aws_codedeploy_deployment_config.cod-config.id}"

  ec2_tag_set {
    ec2_tag_filter {
      type  = "KEY_AND_VALUE"
      key   = "application"
      value = "deploy"
    }
  }
  
  auto_rollback_configuration {
    enabled = true
    events  = ["DEPLOYMENT_FAILURE"]
  }
}
```