AWS Cloudwatch Synthetics CanaryをTerraformで構築する際のポイント

# はじめに
AWS Cloudwatch Synthetics CanaryをTerraformで構築する機会があったのでハマった箇所、工夫した箇所を記載していく。

#  ハマった箇所(source codeの配置場所)
Scriptをzip化して、CanaryにUPしているのだが、Canaryは作成できたが以下エラーが出力され処理に失敗していた。
```terminal
2023-10-17T11:00:21.387Z ERROR: Canary error:
Error: Cannot find module '/opt/nodejs/node_modules/pageLoadBlueprint'Require stack:- /var/task/index.js
    - /var/runtime/index.mjs Stack: Error: Cannot find module '/opt/nodejs/node_modules/pageLoadBlueprint'Require stack:
    - /var/task/index.js
    - /var/runtime/index.mjs    at Module._resolveFilename 
(node:internal/modules/cjs/loader:1077:15)    at Module._load
(node:internal/modules/cjs/loader:922:27)    at Module.require
(node:internal/modules/cjs/loader:1143:19)    at require
(node:internal/modules/cjs/helpers:121:18)    at exports.handler
(/var/task/index.js:85:30)    at process.processTicksAndRejections
(node:internal/process/task_queues:95:5)
```

## 原因
ソースコードを格納するディレクトリがNGだった。
処理が失敗していたときは`canary_script/source/pageLoadBlueprint.js"`に配置していた。
`canary_script/source/nodejs/node_modules/pageLoadBlueprint.js"`に変えたところうまく処理が行えるようになった。

つまりsource codeの置き場所としては`nodejs/node_modules/`の配下でないとNG。

zip化するときの処理は以下でおこなっている。
```terraform:cloud_watch_canary.tf
data "archive_file" "canary_zip" {
  type        = "zip"
  source_dir  = "${path.module}/canary_script/source"
  output_path = "${path.module}/canary_script/synthetics.zip"
}
```


###  フォルダ構成
```terminal:処理が失敗していた時
.
├── canary_script
│   └── source
│       └── pageLoadBlueprint.js
├── cloud_watch_alarm.tf
├── cloud_watch_canary.tf
├── cloud_watch_canary_iam.tf
├── cloud_watch_canary_s3.tf
├── cloud_watch_log.tf
├── event_bridge.tf
├── sns.tf
└── variables.tf
```
```terminal:処理が成功していた時
.
├── canary_script
│   └── source
│       └── nodejs
│           └── node_modules
│               └── pageLoadBlueprint.js
├── cloud_watch_alarm.tf
├── cloud_watch_canary.tf
├── cloud_watch_canary_iam.tf
├── cloud_watch_canary_s3.tf
├── cloud_watch_log.tf
├── event_bridge.tf
├── sns.tf
└── variables.tf
```

# 工夫した箇所(source codeを修正したらcanaryを更新させる)
動いたは良いものの、今度はsource code内で記述が誤っていた箇所があったのでsource codeを修正して`terraform plan`を実行したところ、`no change`となってしまい、変更が検知されなかった。

## 原因
zip化の処理を行っているが、zip後のファイルが同じ名前であった為、`terraform plan`を実行しても変更差分として検出されなかった。

先に完成系のコード
## 対処
zip化した後のファイル名にhash値を入れることにより、検知させるようにする。

```terraform:cloud_watch_canary.tf
# source code zip
data "local_file" "canary_source" {
  filename = "${path.module}/canary_script/source/nodejs/node_modules/pageLoadBlueprint.js"
}

locals {
  source_hash = sha256(data.local_file.canary_source.content)
}

data "archive_file" "canary_zip" {
  type        = "zip"
  source_dir  = "${path.module}/canary_script/source"
  output_path = "${path.module}/canary_script/synthetics_${local.source_hash}.zip"
}

# canary
resource "aws_synthetics_canary" "synthetics" {
  name                 = "${var.name}-${var.env}-https"
  start_canary         = var.start_canary
  artifact_s3_location = "s3://${aws_s3_bucket.synthetics.bucket}/logs/"
  execution_role_arn   = aws_iam_role.synthetics.arn
  handler              = "pageLoadBlueprint.handler"
  zip_file             = data.archive_file.canary_zip.output_path
  runtime_version      = "syn-nodejs-puppeteer-6.0"
  schedule {
    expression = "rate(5 minutes)"
  }

  run_config {
    timeout_in_seconds = 60
  }

  lifecycle {
    create_before_destroy = true
  }
}


```
### コード解説
- まずはsource codeを指定する。
```terraform
data "local_file" "canary_source" {
  filename = "${path.module}/canary_script/source/nodejs/node_modules/pageLoadBlueprint.js"
}
```

- 次にsource codeのハッシュ値を取得する。
```terraform
locals {
  source_hash = sha256(data.local_file.canary_source.content)
}
```

- ハッシュ値を取得したら、zip化後のファイル名にhash値を入れるようにする。
```terraform
data "archive_file" "canary_zip" {
  type        = "zip"
  source_dir  = "${path.module}/canary_script/source"
  output_path = "${path.module}/canary_script/synthetics_${local.source_hash}.zip"
}
```

- 最後にcanaryの`zip_file`でzip化した後のファイル名を引っ張ってくるようにする。
```terraform
# canary
resource "aws_synthetics_canary" "synthetics" {
  name                 = "${var.name}-${var.env}-https"
  start_canary         = var.start_canary
  artifact_s3_location = "s3://${aws_s3_bucket.synthetics.bucket}/logs/"
  execution_role_arn   = aws_iam_role.synthetics.arn
  handler              = "pageLoadBlueprint.handler"
  zip_file             = data.archive_file.canary_zip.output_path
  runtime_version      = "syn-nodejs-puppeteer-6.0"
  schedule {
    expression = "rate(5 minutes)"
  }

  run_config {
    timeout_in_seconds = 60
  }

  lifecycle {
    create_before_destroy = true
  }
}
```

これでsource codeに変更があった場合はhash値も変わるので変更差分として検出できるようになる。