CodePipeline V2の機能をCDK Pipelinesで対応させてみた

## はじめに
2024/02/09のアップデートで、CodePipelineで、追加のパイプライントリガーフィルターに対応しました。  
[CodePipeline が追加のトリガーフィルターと新しい実行モードのサポートを開始](https://aws.amazon.com/jp/about-aws/whats-new/2024/02/codepipeline-trigger-filters-execution-modes/)

本機能を利用することで、特定ブランチやリポジトリ内の特定ファイルなど、指定したイベントをトリガーとしてCodePipelineを起動できるようになりました。
ですが、CDK Pipelinesで作ったパイプラインにV2を利用しようとすると、工夫が必要になります。
そこで今回はCDK Pipelines＋CodePipeline V2を実現させ、その上で新機能を試してみたいと思います。

## 条件

- CodePipeline V2であること
- 以下のソースであること
    - GitHub.com
    - GitHub Enterprise Server
    - Bitbucket.com
    - GitLab.com
    - GitLab セルフマネージド

記載のように、AWS CodeCommitでは本機能が利用できません。  
現状、CodeCommit環境では簡単には実装できず、AWS公式ドキュメントにLambdaを使ったアーキテクチャが公開されておりました。

[Automatically detect changes and initiate different CodePipeline pipelines for a monorepo in CodeCommit](https://docs.aws.amazon.com/ja_jp/prescriptive-guidance/latest/patterns/automatically-detect-changes-and-initiate-different-codepipeline-pipelines-for-a-monorepo-in-codecommit.html)

今回はGitHubを利用して実装します。

## バージョン情報

aws-cdk: 2.143.0 で検証。

## 実装イメージ

![cdkpipelines_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989049/46fbbe9d-be9d-fec4-1952-355ea12adb9e.png)

## CodeStarConnectionの準備

CodePipelineの設定＞接続より、事前にGitHubとの接続を作成しておく必要があります。

![codestar.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989049/a45afd68-ce5d-8e86-ce1a-d4dd0cd3d4c2.png)

## CodePipeline V2対応

[CDK Pipelines](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines-readme.html)のデフォルトPropsで自動作成されるパイプラインではV2に対応しておらず、V1が作られてしまいます。  
ですが、CDK PipelinesのL3 Construct [CodePipeline](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.pipelines.CodePipeline.html)には、
`codePipeline`プロパティがあり、別途定義したCodePipelineを利用できます。

そのため、aws-codepipelineのL2 Construct [Pipeline](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_codepipeline.Pipeline.html)をV2で作成し、そのパイプラインをCDK Pipelines用のパイプラインとして利用することで間接的にV2を利用できます。

### サンプルコード

```ts
import * as codepipeline from "aws-cdk-lib/aws-codepipeline";
import {
  CodeBuildStep,
  CodePipeline,
  CodePipelineSource,
} from "aws-cdk-lib/pipelines";

export class PipelineStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props: PipelineProps) {
    super(scope, id, props);

// <中略>

  // GitHub Connection
  const source = CodePipelineSource.connection(
      "github_user_name/repository_name", // GitHubのリポジトリ名を設定
      "main",                             // ブランチ名を設定
      {
      connectionArn:
          "arn:aws:codestar-connections:ap-northeast-1:XXXXXXXXXXXX:connection/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", // CodeStarConnectionのARNを設定
      }
  );

  // aws-cdk-lib/aws-codepipeline のPipelineをV2で作成
  const codePipeline = new codepipeline.Pipeline(this, "CDKPipelineV2", {
      pipelineName: "CDKPipelineV2",
      pipelineType: codepipeline.PipelineType.V2,
      role: pipelineRole,
      crossAccountKeys: true,
      restartExecutionOnUpdate: false,
  });

  // aws-cdk-lib/pipelines" のPipelineを作成し、上で作成したcodePipelineを設定。
  const cdkPipeline = new CodePipeline(
      this,
      "Docs"
      {
      codePipeline: codePipeline,
      synth: new CodeBuildStep("BuildStep", {
          input: source,
          installCommands: getInstallCommands(),
          commands: ['npm ci','npm run build','mkdocs build','npx cdk synth'],    // ビルドコマンドを記入
      }),
      selfMutation: true,
      dockerEnabledForSynth: true,
      }
  )};
};
```

#### Deployステージの補足

今回の主題でないためカットしていますが、本来はパイプライン上にデプロイステージ等が必要です。
デプロイステージでは以下のような要素を含んだStackを投入しています。

- WAF
- CloudFront Distribution
- 公開用のS3
- s3deploy.BucketDeploymentによるアセットのS3デプロイ

### GitHub Connection

aws-cdk-lib/pipelinesの`CodePipelineSource.connection`を利用して、コネクションを設定します。

### aws-cdk-lib/aws-codepipeline のPipelineをV2で作成

`aws-cdk-lib/aws-codepipeline`で新しいCodePipelineを作成します。  
事前に作成することで、pipelineTypeをV2に設定できます。  
※サンプルコードでは中略していますが、pipelineRoleは必要なIAM Roleを定義しています。

### aws-cdk-lib/pipelines" のPipelineを作成し、上で作成したcodePipelineを設定

`aws-cdk-lib/pipelines`でCDK Pipelinesを作成します。  
codePipelineパラメータに、上で作ったV2パイプラインを指定することでV2対応できます。  
synth:CodeBuildStepのinputには、GitHub Connectionで作成したconnectionを設定します。

## トリガーの設定

2024/05/27時点、TriggerクラスでPush時にフィルタリングする設定が実装されておりませんでした。  
[Trigger](https://docs.aws.amazon.com/cdk/api/v2/docs/aws-cdk-lib.aws_codepipeline.Trigger.html)  
なので今回はaddPropertyOverrideを利用し、L2 Constructで提供されていない部分を直接編集します。

[コンストラクトライブラリからの AWS コンストラクトのカスタマイズ](https://docs.aws.amazon.com/ja_jp/cdk/v2/guide/cfn_layer.html#develop-customize-escape)

```ts
    
    // 上記サンプルコードの続き
    
    // パイプラインを構築（providerTypeで呼ぶため）
    cdkPipeline.buildPipeline();

    // SourceStageを取得
    const sourceStage = cdkPipeline.pipeline.stages.find(
      (stage) => stage.stageName === "Source"
    );

    const sourceAction = sourceStage!
      .actions[0] as codepipeline_actions.CodeStarConnectionsSourceAction;
    const sourceActionName = sourceAction.actionProperties.actionName;

    // トリガーを追加
    codePipeline.addTrigger({
      providerType: codepipeline.ProviderType.CODE_STAR_SOURCE_CONNECTION,

      // the properties below are optional
      gitConfiguration: {
        sourceAction: sourceAction,

        // pushFilterのBranchとFilepathはまだCDK未対応。tagsを仮設定（addPropertyOverrideで上書きする）
        pushFilter: [
          {
            tagsIncludes: ["tagsIncludes"], // 置換されるので適当な文字列を設定
          },
        ],
      },
    });

    // addPropertyOverrideでトリガー条件を上書きする
    const pushFilterJson = {
      Branches: {
        Includes: ["対象にしたいブランチ名"],
      },
      FilePaths: {
        Includes: ["対象にしたいフォルダパス"],
      },
    };

    // cfnPipelineを取得して、addPropertyOverrideを実施
    const cfnPipeline = cdkPipeline.pipeline.node
      .defaultChild as codepipeline.CfnPipeline;
    cfnPipeline.addPropertyOverride("Triggers", [
      {
        GitConfiguration: {
          Push: [pushFilterJson],
          SourceActionName: sourceActionName,
        },
        ProviderType: "CodeStarSourceConnection",
      },
    ]);
```

### パイプラインを構築（providerTypeで呼ぶため）

cdkPipeline.buildPipeline()を挿入することで、パイプラインがビルドされます。  
これを入れることで、後続の`sourceStage.actions[0] as codepipeline_actions.CodeStarConnectionsSourceAction;`部分が取得可能になります。

### トリガーを追加

`codePipeline.addTrigger`でトリガーを作成します。  
gitConfiguration.pushFilterは先述の通りブランチやファイルパスを設定する項目が現時点で存在しないため、`tagsIncludes`のみ設定し、適当な値を設定しておきます。(何も設定しないとエラーとなりました。)

### トリガー条件の設定

`pushFilterJson`という変数にフィルター条件を設定します。  
必要なブランチ名、フォルダパスを設定します。

#### addPropertyOverrideを実施

`cdkPipeline.pipeline.node.defaultChild as codepipeline.CfnPipeline`を使ってcfnPipelineを取得し、  
`cfnPipeline.addPropertyOverride`を利用してTriggersのプロパティを上書きます。

## デプロイ

上記設定でCDK Pipelinesをcdk deployします。

V2のパイプラインが構築されました！
![codepipeline_v2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989049/9f149575-675a-3052-52f6-bf6099735aa4.png)

トリガー上でブランチ・ファイルパスが導入されています。
![codepipeline_trigger.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989049/3dfdb022-3e08-93c3-45d8-c337fad4248a.png)

addPropertyOverrideで上書きしたことで、タグの条件は削除されています。

## 特定フォルダの更新テスト

my-projectフォルダを修正します。
今回のフォルダ配下にはMkDocsで作成したMarkdownファイルが格納されています。

「サイト更新テスト」という文章を追加し、GitHubのリモートリポジトリにPushします。
![my-project_update.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989049/6c6f6354-b2d7-d94d-e3e0-f1d7301f6c08.png)

パイプラインが起動しました！
![my-project-trigger-execution.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989049/901f9923-a328-06ec-7c4d-ff5547451288.png)

パイプラインが正常終了しました。サイトを見てみましょう。
![my-project-trigger-execution2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989049/0ebb6f87-1a5b-24ba-797a-2b64059e5602.png)

サイトが無事更新されました。(今回はMkdocsの初期レイアウトを配置しています。)
![mkdocs_site.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2989049/550dc87c-0a3d-a13e-bcb1-4afa7efba2fb.png)

## 終わりに

現時点の環境で実現可能な、CDK PipelinesとCodePipeline V2の融合、およびGitHubの特定ブランチや特定ファイルパスの更新をトリガーにする方法を解説しました。  
CodePipeline V2では、今回は紹介しませんでしたがQUEUEやParallel等の実行方法も可能になり、より柔軟な設定でパイプラインを実行できます。CDK Pipelinesでもぜひ導入したいところですね。  
料金面でもV1はパイプライン1本あたり$1/月が固定でかかる料金体系でしたが、V2では実行時間に対する従量課金制になり、0.002USD/実行時間（分）の料金体系となりました。短時間の実行しかされないような複数のパイプラインを持つ場合でも大幅にコストを抑えられる形となりました。  
V2はまだあまり活用できておりませんでしたが、多くの利点があるので今後はV2を軸にパイプライン活用を検討していこうと思います。

## 参考資料

- [AWS CodePipeline V2 Support · Issue #28694 · aws/aws-cdk](https://github.com/aws/aws-cdk/issues/28694)
- [CodePipeline が追加のトリガーフィルターと新しい実行モードのサポートを開始](https://aws.amazon.com/jp/about-aws/whats-new/2024/02/codepipeline-trigger-filters-execution-modes/)