AWS CDKでCodePipelineを構築しました。

お疲れさまです。@naokiurです。
2020年、今年も宜しくお願い致します。

[AWS CDK](https://aws.amazon.com/jp/cdk/)を知ってから、
「これは、AWS CDKだとどうやって作成するんだろう…？」
みたいな考えになることが多いです。

今回は、CI/CD環境の簡単なものを、
AWS CDK（Java）で構築しました。

# 環境
* MacBook Pro (Retina 13-inch、Early 2015)
* macOS Mojave 10.14.6
* Java 1.8.0_191
* IntelliJ IDEA CE 2019.2.4

# CodePipelineの構築
## 構成図
<img width="516" alt="スクリーンショット 2020-01-05 21.01.32.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129313/b8b73a4d-7d8e-046f-0bb4-9b0c05596aa6.png">


ソースを取得して、
ZIP化、S3に格納するだけですが、
業務ではGithubを用いているため、
CodeCommitではなく、
Githubをソース取得元にしました。

## CodePipeline構築
CodePipelineを構築するためには、
大きくわけて、以下を作成することで、構築することができました。

* Pipelineクラス
* Stagesクラス
* Actionクラス
* Artifactクラス

### Pipelineクラス
文字通り、CodePipelineを表現するクラスです。
このクラスを `build()`し、
`cdk deploy`することで、
AWS上にCodePipelineを構築することができました。

```Java
final Pipeline saveToS3Pipeline = Pipeline.Builder
        .create(this, "saveSourceToS3")
        .pipelineName("saveSourceToS3")
        .stages(new ArrayList<>(Arrays.asList(source, build, deploy)))
        .build();
```

これに限らず、
AWSリソースを生成するためには、
`hoge.Builder.create(Stackクラス, id).~中略~.build()`
で構築できるようになっていることが多いようなので、
わかりやすいなと感じております。

CodePipelineは `Stage`を最低2つ必要とするため、
`stages()`には少なくとも2つ要素を持つListを指定する必要があります。
指定していない場合、 エラーとなりました。

### Stageクラス
CodePipelineに設定するStageのクラスです。
今回は（中身があまりないながら）
Source, Build, Deployの3つのステージをすべて作成しました。

```Java
final StageProps source = StageProps.builder()
        .stageName("DownloadSourceFromGithub")
        .actions(new ArrayList<>(Arrays.asList(github)))
        .build();

final StageProps build = StageProps.builder()
        .stageName("BuildSource")
        .actions(new ArrayList<>(Arrays.asList(codeBuild)))
        .build();

final StageProps deploy = StageProps.builder()
        .stageName("SaveSourceToS3")
        .actions(new ArrayList<>(Arrays.asList(s3)))
        .build();
```
早速 `hoge.Builder.create(Stackクラス, id).~中略~.build()`という書き方ではないですね。
Stageは、AWSサービスではなく、CodePipelineの一要素であり、
CloudFormationのスタックの `リソース`として出てくるものではないためです。
（※個人の意見です）

Stageには、複数のActionを持つことができるようです。

### Actionクラス
Stageで実行するActionのクラスです。
それぞれのStageに対応した、Actionを作成します。
今回は（中身があまりないので）
各Stageにつき、1つずつです。


```Java
final Action github = GitHubSourceAction.Builder
        .create()
        .actionName("DownloadFromGithub")
        .oauthToken(githubToken)
        .branch(branchName)
        .repo(repoName)
        .owner(ownerName)
        .output(sourceArtifact)
        .build();

final Action codeBuild = CodeBuildAction.Builder
        .create()
        .actionName("BuildSource")
        .project(codeBuildProject)
        .input(sourceArtifact)
        .outputs(new ArrayList<>(Arrays.asList(buildArtifact)))
        .build();

final Action s3 = S3DeployAction.Builder
        .create()
        .bucket(deployBucket)
        .actionName("DeploySourceToS3")
        .input(buildArtifact)
        .build();
```
`codepipeline.actions`パッケージ内に、
実行するActionに応じたクラスがあるので、
必要なクラスを生成します。
（現在、CodePipelineでできることすべてではないようです…）

#### GitHubSourceActionクラス
GithubをSourceとするためのActionです。
わかりやすいですね。
リポジトリやブランチを設定します。

接続するためのOAuthTokenを指定することもでき、
SecretValueクラスを使用します。

##### SecretValueクラス
シークレットな情報を取得するためのクラスです。
System ManagerのパラメータストアのSecretから情報を取得したり、
Secret Managerから情報することができるようです。

今回、Secret Managerに予めGithubのTokenを設定しておき、
それを取得するように致しました。

```Java
final SecretsManagerSecretOptions secretOptions = SecretsManagerSecretOptions.builder()
        .jsonField("github-token")
        .build();
final SecretValue githubToken = SecretValue.secretsManager(
        "naokiur-secret",
        secretOptions
);
```

#### CodeBuildActionクラス
CodePipeline用のCodeBuildを構築するクラスです。
今回はリポジトリ内にbuildspec.ymlを作成しました。

#### S3DeployActionクラス
S3にデプロイするためのクラスです。

### Artifactクラス
Actionクラスに出てきたのですが、
CodePipelineでもある、
各ActionのInput/Outputに指定するクラスです。
これで、受け渡しをするイメージを持っています。

以下の2つを作成し、
それぞれのActionのInput/Outputに指定しました。

```Java
final Artifact sourceArtifact = Artifact.artifact("Source");
final Artifact buildArtifact = Artifact.artifact("Build");

```

これで、一通りの構築をすることができました！！
<img width="1017" alt="スクリーンショット 2020-01-08 13.30.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/129313/c6c0b306-6f1b-f932-02d4-1b9fa4881700.png">


# ハマったところ
* System ManagerのパラメータストアのSecretは、CodePipelineに設定することができない
    * 自分が知らなかっただけなのですが、サポートされていない、とのことでした…。
    * 最初、SecretManagerではなく、パラメータストアのSecretでやろうとしていたのですが、以下エラーメッセージが出てしまいました…
        * SSM Secure reference is not supported in [AWS::CodePipeline::Pipeline/Properties/Stages,AWS::CodePipeline::Webhook/Properties/AuthenticationConfiguration/SecretToken]
* GithubのWebhook時にInvalid credentialsが出る
    * SecretValueクラスを生成する際です。
    * これも自分が知らなかっただけなのですが、最初、SecretValue生成時は以下でした。
        * `final SecretValue githubToken = SecretValue.secretsManager("naokiur-secret");`
    * これだと接続ができず、Tokenはあっているはずなのに、なぜだろう…とハマってしまいました。
    * 結局、キーの指定方法が誤っていて、取得できておらず、Tokenが空でアクセスしていたようです。
        * CodePipeline実行時にエラーとなるまで気づかなかった…
    * CloudFormationのファイルを生成できるコマンド、 `cdk synthesize`をしたところ、当該項目が以下となっていました。
        * `SecretToken: "{{resolve:secretsmanager:naokiur-secret:SecretString:::}}"`
        * これで、パラメータ不足に気づきました…。

# 参考にさせて頂きました
* https://dev.classmethod.jp/server-side/serverless/aws-cdk-cicd/
* https://github.com/aws/aws-cdk/issues/3515
* https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/GitHub-rotate-personal-token-CLI.html