@exabugs/dynamodb-client 1.20.0 → 1.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +1 -1
- package/dist/notify/ops/index.d.ts +10 -0
- package/dist/notify/ops/index.d.ts.map +1 -0
- package/dist/notify/ops/index.js +9 -0
- package/dist/notify/ops/index.js.map +1 -0
- package/dist/notify/ops-notify.d.ts +106 -0
- package/dist/notify/ops-notify.d.ts.map +1 -0
- package/dist/notify/ops-notify.js +205 -0
- package/dist/notify/ops-notify.js.map +1 -0
- package/dist/notify/store.d.ts +5 -0
- package/dist/notify/store.d.ts.map +1 -1
- package/dist/notify/store.js +9 -2
- package/dist/notify/store.js.map +1 -1
- package/dist/server/handler.cjs +3 -3
- package/dist/server/media-handler.cjs +2 -2
- package/dist/server/media-process-handler-dlq.cjs +2 -2
- package/dist/server/media-process-handler.cjs +2 -2
- package/docs/DYNAMODB_ACCESS.md +1 -1
- package/docs/NOTIFY.md +53 -2
- package/docs/SYSTEM_CHANGES.md +1 -1
- package/package.json +6 -1
- package/terraform/README.md +9 -0
- package/terraform/alerts-cleanup/README.md +65 -0
- package/terraform/alerts-cleanup/main.tf +111 -0
- package/terraform/alerts-cleanup/outputs.tf +14 -0
- package/terraform/alerts-cleanup/variables.tf +42 -0
- package/terraform/alerts-cleanup/versions.tf +16 -0
- package/terraform/ops-notify/README.md +109 -0
- package/terraform/ops-notify/main.tf +295 -0
- package/terraform/ops-notify/outputs.tf +19 -0
- package/terraform/ops-notify/us-east-1.tf +68 -0
- package/terraform/ops-notify/variables.tf +105 -0
- package/terraform/ops-notify/versions.tf +18 -0
- package/terraform/system-change-recorder/README.md +71 -0
- package/terraform/system-change-recorder/main.tf +122 -0
- package/terraform/system-change-recorder/outputs.tf +14 -0
- package/terraform/system-change-recorder/variables.tf +53 -0
- package/terraform/system-change-recorder/versions.tf +16 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# 運用通知(ADR 0003・ADR 0006 の (b))
|
|
2
|
+
#
|
|
3
|
+
# ログ(warn 以上)・CloudWatch アラームの状態変化・週 1 回の生存確認 → 通知 Lambda
|
|
4
|
+
# → 重複抑制・流量制限 → Slack とメール(管理者全員)。
|
|
5
|
+
# Lambda の成果物はアプリが作り、source_file で渡す(実装は notify の部品。docs/NOTIFY.md)。
|
|
6
|
+
# リソースのアドレス・名前は、asanowa / astro の旧モジュール(infra/modules/ops-notify)と同じ。
|
|
7
|
+
#
|
|
8
|
+
# 通知 Lambda 自身のログは購読しない(無限ループ)。失敗は Lambda の失敗先(SNS)→ 運用者のメールへ届ける。
|
|
9
|
+
|
|
10
|
+
data "aws_caller_identity" "current" {}
|
|
11
|
+
data "aws_region" "current" {}
|
|
12
|
+
|
|
13
|
+
locals {
|
|
14
|
+
prefix = "${var.project_name}-${var.environment}"
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# 重複抑制・流量制限の記録(パーティションキー pk、TTL 属性 ttl)
|
|
18
|
+
resource "aws_dynamodb_table" "notices" {
|
|
19
|
+
name = "${local.prefix}-notices"
|
|
20
|
+
billing_mode = "PAY_PER_REQUEST"
|
|
21
|
+
hash_key = "pk"
|
|
22
|
+
|
|
23
|
+
attribute {
|
|
24
|
+
name = "pk"
|
|
25
|
+
type = "S"
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
ttl {
|
|
29
|
+
attribute_name = "ttl"
|
|
30
|
+
enabled = true
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
server_side_encryption {
|
|
34
|
+
enabled = true
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# 通知 Lambda が失敗したとき(Slack もメールも届けられないとき)の届け先
|
|
39
|
+
resource "aws_sns_topic" "failure" {
|
|
40
|
+
name = "${local.prefix}-notify-failure"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
resource "aws_sns_topic_policy" "failure" {
|
|
44
|
+
arn = aws_sns_topic.failure.arn
|
|
45
|
+
|
|
46
|
+
policy = jsonencode({
|
|
47
|
+
Version = "2012-10-17"
|
|
48
|
+
Statement = [
|
|
49
|
+
{
|
|
50
|
+
Sid = "AllowLambdaDestination"
|
|
51
|
+
Effect = "Allow"
|
|
52
|
+
Principal = { Service = "lambda.amazonaws.com" }
|
|
53
|
+
Action = "sns:Publish"
|
|
54
|
+
Resource = aws_sns_topic.failure.arn
|
|
55
|
+
Condition = {
|
|
56
|
+
StringEquals = { "aws:SourceAccount" = data.aws_caller_identity.current.account_id }
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
Sid = "DenyInsecureTransport"
|
|
61
|
+
Effect = "Deny"
|
|
62
|
+
Principal = "*"
|
|
63
|
+
Action = "sns:Publish"
|
|
64
|
+
Resource = aws_sns_topic.failure.arn
|
|
65
|
+
Condition = {
|
|
66
|
+
Bool = { "aws:SecureTransport" = "false" }
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
]
|
|
70
|
+
})
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
resource "aws_sns_topic_subscription" "operator" {
|
|
74
|
+
topic_arn = aws_sns_topic.failure.arn
|
|
75
|
+
protocol = "email"
|
|
76
|
+
endpoint = var.operator_email
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
data "archive_file" "notify" {
|
|
80
|
+
type = "zip"
|
|
81
|
+
source_file = var.source_file
|
|
82
|
+
output_path = "${path.root}/.terraform/archives/notify.zip"
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
resource "aws_cloudwatch_log_group" "notify" {
|
|
86
|
+
name = "/aws/lambda/${local.prefix}-notify"
|
|
87
|
+
retention_in_days = var.log_retention_days
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
resource "aws_iam_role" "notify" {
|
|
91
|
+
name = "${local.prefix}-notify-role"
|
|
92
|
+
|
|
93
|
+
assume_role_policy = jsonencode({
|
|
94
|
+
Version = "2012-10-17"
|
|
95
|
+
Statement = [{
|
|
96
|
+
Action = "sts:AssumeRole"
|
|
97
|
+
Effect = "Allow"
|
|
98
|
+
Principal = { Service = "lambda.amazonaws.com" }
|
|
99
|
+
}]
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
resource "aws_iam_role_policy_attachment" "notify_logs" {
|
|
104
|
+
role = aws_iam_role.notify.name
|
|
105
|
+
policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# 最小権限: 記録のテーブル(更新だけ。alerts の集計の行も UpdateItem だけで足りる)、Records Lambda(管理者の一覧を読む)、Webhook のパラメータ、
|
|
109
|
+
# SES(送信元アドレスとドメインに絞る)、失敗先の SNS。alerts の書き込みも Records の IAM URL(既存の権限)
|
|
110
|
+
resource "aws_iam_role_policy" "notify" {
|
|
111
|
+
name = "${local.prefix}-notify"
|
|
112
|
+
role = aws_iam_role.notify.id
|
|
113
|
+
|
|
114
|
+
policy = jsonencode({
|
|
115
|
+
Version = "2012-10-17"
|
|
116
|
+
Statement = concat(
|
|
117
|
+
[
|
|
118
|
+
{
|
|
119
|
+
Effect = "Allow"
|
|
120
|
+
Action = ["dynamodb:UpdateItem"]
|
|
121
|
+
Resource = [aws_dynamodb_table.notices.arn]
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
Effect = "Allow"
|
|
125
|
+
Action = ["lambda:InvokeFunctionUrl"]
|
|
126
|
+
Resource = [var.records_iam_alias_arn]
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
Effect = "Allow"
|
|
130
|
+
Action = ["ssm:GetParameter"]
|
|
131
|
+
Resource = ["arn:aws:ssm:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:parameter${var.slack_webhook_param_name}"]
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
Effect = "Allow"
|
|
135
|
+
Action = ["sns:Publish"]
|
|
136
|
+
Resource = [aws_sns_topic.failure.arn]
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
var.mail_from == "" ? [] : [
|
|
140
|
+
{
|
|
141
|
+
Effect = "Allow"
|
|
142
|
+
Action = ["ses:SendEmail"]
|
|
143
|
+
Resource = ["arn:aws:ses:${var.ses_region}:${data.aws_caller_identity.current.account_id}:identity/${var.ses_identity_domain}"]
|
|
144
|
+
Condition = {
|
|
145
|
+
StringEquals = { "ses:FromAddress" = var.mail_from }
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
)
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
resource "aws_lambda_function" "notify" {
|
|
154
|
+
function_name = "${local.prefix}-notify"
|
|
155
|
+
role = aws_iam_role.notify.arn
|
|
156
|
+
filename = data.archive_file.notify.output_path
|
|
157
|
+
source_code_hash = data.archive_file.notify.output_base64sha256
|
|
158
|
+
handler = var.handler
|
|
159
|
+
runtime = "nodejs22.x"
|
|
160
|
+
architectures = ["arm64"]
|
|
161
|
+
timeout = 60
|
|
162
|
+
memory_size = 256
|
|
163
|
+
|
|
164
|
+
# 既定 null(予約しない。従来どおり)。alerts の記録を有効にするときだけ 1(variables.tf)
|
|
165
|
+
reserved_concurrent_executions = var.reserved_concurrent_executions
|
|
166
|
+
|
|
167
|
+
environment {
|
|
168
|
+
# PROJECT_NAME・ENV_NAME・RECORDS_IAM_URL・NOTICE_TABLE は、通知のエントリが必須にすることがある(asanowa)ので常に入れる。
|
|
169
|
+
# alerts(ADMIN_BASE_URL・SKIP_RECORD_LOG_GROUPS)は、空なら環境変数に入れない(記録しない。asanowa の形)
|
|
170
|
+
variables = merge(
|
|
171
|
+
{
|
|
172
|
+
PROJECT_NAME = var.project_name
|
|
173
|
+
ENV_NAME = var.environment
|
|
174
|
+
RECORDS_IAM_URL = var.records_iam_function_url
|
|
175
|
+
NOTICE_TABLE = aws_dynamodb_table.notices.name
|
|
176
|
+
SLACK_WEBHOOK_PARAM = var.slack_webhook_param_name
|
|
177
|
+
MAIL_FROM = var.mail_from
|
|
178
|
+
SES_REGION = var.ses_region
|
|
179
|
+
},
|
|
180
|
+
var.admin_base_url == "" ? {} : { ADMIN_BASE_URL = var.admin_base_url },
|
|
181
|
+
length(var.skip_record_log_groups) == 0 ? {} : { SKIP_RECORD_LOG_GROUPS = join(",", var.skip_record_log_groups) }
|
|
182
|
+
)
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
logging_config {
|
|
186
|
+
log_format = "JSON"
|
|
187
|
+
log_group = aws_cloudwatch_log_group.notify.name
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
depends_on = [aws_iam_role_policy_attachment.notify_logs]
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
# 再試行は 0(重複を避ける)。失敗は SNS(運用者のメール)へ
|
|
194
|
+
resource "aws_lambda_function_event_invoke_config" "notify" {
|
|
195
|
+
function_name = aws_lambda_function.notify.function_name
|
|
196
|
+
maximum_retry_attempts = 0
|
|
197
|
+
|
|
198
|
+
destination_config {
|
|
199
|
+
on_failure {
|
|
200
|
+
destination = aws_sns_topic.failure.arn
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
depends_on = [aws_iam_role_policy.notify, aws_sns_topic_policy.failure]
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# --- 入口 1: ログの購読(広めの語句。通知するかはハンドラが厳密に判定する)---
|
|
208
|
+
|
|
209
|
+
resource "aws_lambda_permission" "logs" {
|
|
210
|
+
for_each = toset(var.watched_log_group_names)
|
|
211
|
+
|
|
212
|
+
statement_id = "AllowLogs-${replace(each.value, "/", "_")}"
|
|
213
|
+
action = "lambda:InvokeFunction"
|
|
214
|
+
function_name = aws_lambda_function.notify.function_name
|
|
215
|
+
principal = "logs.amazonaws.com"
|
|
216
|
+
source_arn = "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:${each.value}:*"
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
resource "aws_cloudwatch_log_subscription_filter" "notify" {
|
|
220
|
+
for_each = toset(var.watched_log_group_names)
|
|
221
|
+
|
|
222
|
+
name = "${local.prefix}-notify"
|
|
223
|
+
log_group_name = each.value
|
|
224
|
+
filter_pattern = "?warn ?error ?fatal ?WARN ?ERROR ?FATAL ?\"Task timed out\" ?\"Runtime exited\" ?Unhandled ?Uncaught ?\"Invoke Error\" ?\"Status: timeout\" ?\"Status: error\" ?timeout"
|
|
225
|
+
destination_arn = aws_lambda_function.notify.arn
|
|
226
|
+
|
|
227
|
+
depends_on = [aws_lambda_permission.logs]
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
# --- 入口 2: CloudWatch アラームの状態変化(このプロジェクト・環境のアラームだけ)---
|
|
231
|
+
|
|
232
|
+
resource "aws_cloudwatch_event_rule" "alarms" {
|
|
233
|
+
name = "${local.prefix}-notify-alarms"
|
|
234
|
+
description = "CloudWatch アラームの状態変化を、運用通知へ"
|
|
235
|
+
|
|
236
|
+
event_pattern = jsonencode({
|
|
237
|
+
source = ["aws.cloudwatch"]
|
|
238
|
+
"detail-type" = ["CloudWatch Alarm State Change"]
|
|
239
|
+
detail = {
|
|
240
|
+
alarmName = [{ prefix = "${local.prefix}-" }]
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
resource "aws_cloudwatch_event_target" "alarms" {
|
|
246
|
+
rule = aws_cloudwatch_event_rule.alarms.name
|
|
247
|
+
arn = aws_lambda_function.notify.arn
|
|
248
|
+
|
|
249
|
+
retry_policy {
|
|
250
|
+
maximum_retry_attempts = 0
|
|
251
|
+
maximum_event_age_in_seconds = 3600
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
resource "aws_lambda_permission" "alarms" {
|
|
256
|
+
statement_id = "AllowEventBridgeAlarms"
|
|
257
|
+
action = "lambda:InvokeFunction"
|
|
258
|
+
function_name = aws_lambda_function.notify.function_name
|
|
259
|
+
principal = "events.amazonaws.com"
|
|
260
|
+
source_arn = aws_cloudwatch_event_rule.alarms.arn
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
# --- 入口 3: 生存確認(通知の経路が壊れて無音になることを防ぐ)---
|
|
264
|
+
|
|
265
|
+
resource "aws_cloudwatch_event_rule" "heartbeat" {
|
|
266
|
+
name = "${local.prefix}-notify-heartbeat"
|
|
267
|
+
description = "通知の経路が生きていることの確認(Slack に投稿)"
|
|
268
|
+
schedule_expression = var.heartbeat_schedule
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
resource "aws_cloudwatch_event_target" "heartbeat" {
|
|
272
|
+
rule = aws_cloudwatch_event_rule.heartbeat.name
|
|
273
|
+
arn = aws_lambda_function.notify.arn
|
|
274
|
+
|
|
275
|
+
input = jsonencode({
|
|
276
|
+
notice = {
|
|
277
|
+
level = "ok"
|
|
278
|
+
event = "HEARTBEAT"
|
|
279
|
+
message = "通知の経路は正常です(週 1 回の生存確認)"
|
|
280
|
+
}
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
retry_policy {
|
|
284
|
+
maximum_retry_attempts = 0
|
|
285
|
+
maximum_event_age_in_seconds = 3600
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
resource "aws_lambda_permission" "heartbeat" {
|
|
290
|
+
statement_id = "AllowEventBridgeHeartbeat"
|
|
291
|
+
action = "lambda:InvokeFunction"
|
|
292
|
+
function_name = aws_lambda_function.notify.function_name
|
|
293
|
+
principal = "events.amazonaws.com"
|
|
294
|
+
source_arn = aws_cloudwatch_event_rule.heartbeat.arn
|
|
295
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
output "function_name" {
|
|
2
|
+
description = "通知 Lambda の関数名"
|
|
3
|
+
value = aws_lambda_function.notify.function_name
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
output "failure_topic_arn" {
|
|
7
|
+
description = "通知 Lambda の失敗先の SNS トピックの ARN(運用者のメールに届く)"
|
|
8
|
+
value = aws_sns_topic.failure.arn
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
output "notice_table_name" {
|
|
12
|
+
description = "重複抑制・流量制限の記録のテーブル名"
|
|
13
|
+
value = aws_dynamodb_table.notices.name
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
output "function_arn" {
|
|
17
|
+
description = "通知 Lambda の ARN"
|
|
18
|
+
value = aws_lambda_function.notify.arn
|
|
19
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# us-east-1 のアラームの状態変化を、ホームのリージョン(例: Tokyo)の default バスへ転送する(asanowa の ADR-0046 から移した)
|
|
2
|
+
#
|
|
3
|
+
# CloudFront の指標は us-east-1 にしか出ないので、そのアラームも us-east-1 に作る。EventBridge は
|
|
4
|
+
# アラームと同じリージョンのバスにしか届かないため、us-east-1 のルールで Tokyo の default バスへ転送する。
|
|
5
|
+
# Tokyo の既存のルール(alarms)が、転送されたイベント(source・detail-type・detail は保たれる)を拾う。
|
|
6
|
+
#
|
|
7
|
+
# forward_us_east_1_alarms が true のときだけ作る(既定 false)。provider の aws.us_east_1 は、呼び出し側が常に渡す
|
|
8
|
+
# (versions.tf の configuration_aliases。false でも要る)。リソースのアドレス・名前は asanowa の旧モジュールと同じ。
|
|
9
|
+
|
|
10
|
+
locals {
|
|
11
|
+
forward_count = var.forward_us_east_1_alarms ? 1 : 0
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
# 転送先は、Tokyo の default バス
|
|
15
|
+
resource "aws_iam_role" "forward_alarms" {
|
|
16
|
+
count = local.forward_count
|
|
17
|
+
|
|
18
|
+
name = "${local.prefix}-notify-forward-alarms"
|
|
19
|
+
|
|
20
|
+
assume_role_policy = jsonencode({
|
|
21
|
+
Version = "2012-10-17"
|
|
22
|
+
Statement = [{
|
|
23
|
+
Action = "sts:AssumeRole"
|
|
24
|
+
Effect = "Allow"
|
|
25
|
+
Principal = { Service = "events.amazonaws.com" }
|
|
26
|
+
}]
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
resource "aws_iam_role_policy" "forward_alarms" {
|
|
31
|
+
count = local.forward_count
|
|
32
|
+
|
|
33
|
+
name = "${local.prefix}-notify-forward-alarms"
|
|
34
|
+
role = aws_iam_role.forward_alarms[0].id
|
|
35
|
+
|
|
36
|
+
policy = jsonencode({
|
|
37
|
+
Version = "2012-10-17"
|
|
38
|
+
Statement = [{
|
|
39
|
+
Effect = "Allow"
|
|
40
|
+
Action = ["events:PutEvents"]
|
|
41
|
+
Resource = [local.home_default_bus_arn]
|
|
42
|
+
}]
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
locals {
|
|
47
|
+
home_default_bus_arn = "arn:aws:events:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:event-bus/default"
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
resource "aws_cloudwatch_event_rule" "alarms_us_east_1" {
|
|
51
|
+
provider = aws.us_east_1
|
|
52
|
+
count = local.forward_count
|
|
53
|
+
|
|
54
|
+
name = "${local.prefix}-notify-alarms-forward"
|
|
55
|
+
description = "us-east-1 の CloudWatch アラームの状態変化を、Tokyo の運用通知へ転送"
|
|
56
|
+
|
|
57
|
+
# Tokyo のルール(alarms)と同じパターン
|
|
58
|
+
event_pattern = aws_cloudwatch_event_rule.alarms.event_pattern
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
resource "aws_cloudwatch_event_target" "alarms_us_east_1" {
|
|
62
|
+
provider = aws.us_east_1
|
|
63
|
+
count = local.forward_count
|
|
64
|
+
|
|
65
|
+
rule = aws_cloudwatch_event_rule.alarms_us_east_1[0].name
|
|
66
|
+
arn = local.home_default_bus_arn
|
|
67
|
+
role_arn = aws_iam_role.forward_alarms[0].arn
|
|
68
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
variable "project_name" {
|
|
2
|
+
description = "プロジェクト名"
|
|
3
|
+
type = string
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
variable "environment" {
|
|
7
|
+
description = "環境識別子(dev, prd)"
|
|
8
|
+
type = string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
variable "records_iam_function_url" {
|
|
12
|
+
description = "Records Lambda の AWS_IAM 認証版 Function URL(管理者の一覧を読む)"
|
|
13
|
+
type = string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
variable "records_iam_alias_arn" {
|
|
17
|
+
description = "Records Lambda の IAM 認証用エイリアスの ARN(lambda:InvokeFunctionUrl の Resource)"
|
|
18
|
+
type = string
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
variable "watched_log_group_names" {
|
|
22
|
+
description = "warn 以上のログを通知する Lambda のロググループ名。通知 Lambda 自身のロググループは入れない(自分のログで無限ループになる)"
|
|
23
|
+
type = list(string)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
variable "slack_webhook_param_name" {
|
|
27
|
+
description = "Slack の Webhook の URL を置いた SSM パラメータ(SecureString)の名前。値は Terraform で扱わない(状態に残るため)。アプリの手順で置く"
|
|
28
|
+
type = string
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
variable "mail_from" {
|
|
32
|
+
description = "メールの送信元(SES の検証済みドメインのアドレス)。空ならメールは送らない"
|
|
33
|
+
type = string
|
|
34
|
+
default = ""
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
variable "ses_region" {
|
|
38
|
+
description = "SES のリージョン(本番アクセス・ドメイン検証が済んだもの)"
|
|
39
|
+
type = string
|
|
40
|
+
default = "us-east-1"
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
variable "ses_identity_domain" {
|
|
44
|
+
description = "SES の検証済みドメイン(送信権限の対象)"
|
|
45
|
+
type = string
|
|
46
|
+
default = ""
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
variable "operator_email" {
|
|
50
|
+
description = "通知 Lambda 自身が失敗したときの届け先(運用者。admins に依存しない最後の砦)。SNS の確認メールが届く"
|
|
51
|
+
type = string
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
variable "heartbeat_schedule" {
|
|
55
|
+
description = "通知の経路が生きていることの確認(Slack に投稿)の間隔"
|
|
56
|
+
type = string
|
|
57
|
+
default = "rate(7 days)"
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
variable "log_retention_days" {
|
|
61
|
+
description = "CloudWatch Logsの保持期間(日数)"
|
|
62
|
+
type = number
|
|
63
|
+
default = 7
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
# --- alerts(通知を管理画面のレコードとして残す。ADR 0003)---
|
|
67
|
+
|
|
68
|
+
variable "admin_base_url" {
|
|
69
|
+
description = "管理画面のベース URL。空でなければ alerts に記録し、通知に詳細の URL(<admin_base_url>/alerts/<id>/show)を載せる。空なら記録しない(従来どおり)"
|
|
70
|
+
type = string
|
|
71
|
+
default = ""
|
|
72
|
+
|
|
73
|
+
validation {
|
|
74
|
+
condition = var.admin_base_url == "" || can(regex("^https?://[^/]+", var.admin_base_url))
|
|
75
|
+
error_message = "admin_base_url は空か、http(s):// で始まる URL にしてください。"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
variable "skip_record_log_groups" {
|
|
80
|
+
description = "alerts に記録しないロググループ(完全一致、または末尾 * の前方一致)。Records Lambda のロググループを必ず入れる(記録の失敗 → Records のエラーログ → 通知 → 記録、のループを防ぐ)。通知は送る。空なら環境変数に入れない"
|
|
81
|
+
type = list(string)
|
|
82
|
+
default = []
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
variable "reserved_concurrent_executions" {
|
|
86
|
+
description = "通知 Lambda の予約同時実行数。既定 null(予約しない。従来どおり)。**1 にするのは alerts の記録を有効にする(admin_base_url を設定する)ときだけ**(同じ alerts レコードへの同時の書き込みを避ける。入れると、記録なしでも通知の遅れ方が変わる。直列化は努力目標で、強制はされない)"
|
|
87
|
+
type = number
|
|
88
|
+
default = null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
variable "source_file" {
|
|
92
|
+
description = "Lambda の成果物(バンドル済みの 1 ファイル。例: apps/server/dist/notify.cjs)。zip にして使う"
|
|
93
|
+
type = string
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
variable "handler" {
|
|
97
|
+
description = "Lambda のハンドラ(例: notify.handler)"
|
|
98
|
+
type = string
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
variable "forward_us_east_1_alarms" {
|
|
102
|
+
description = "us-east-1 の CloudWatch アラーム(CloudFront など。指標が us-east-1 にしか出ないもの)の状態変化を、ホームのリージョンの default バスへ転送するか。転送先のルール(alarms)が拾う。true なら providers の aws.us_east_1 が us-east-1 であること"
|
|
103
|
+
type = bool
|
|
104
|
+
default = false
|
|
105
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# provider の版: astro(5.x)と asanowa(6.x)の両方で使う。
|
|
2
|
+
# 下限は Lambda の logging_config(5.x の途中で追加)。上限は data.aws_region の name(6.x で非推奨)が消える 7.x の前まで
|
|
3
|
+
terraform {
|
|
4
|
+
required_version = ">= 1.5"
|
|
5
|
+
|
|
6
|
+
required_providers {
|
|
7
|
+
aws = {
|
|
8
|
+
source = "hashicorp/aws"
|
|
9
|
+
version = ">= 5.30, < 7.0"
|
|
10
|
+
# us-east-1 のアラームの転送(us-east-1.tf)。呼び出し側は providers = { aws.us_east_1 = aws.us_east_1 } を常に渡す
|
|
11
|
+
configuration_aliases = [aws.us_east_1]
|
|
12
|
+
}
|
|
13
|
+
archive = {
|
|
14
|
+
source = "hashicorp/archive"
|
|
15
|
+
version = ">= 2.0"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# システム変更履歴の記録 Lambda Terraformモジュール
|
|
2
|
+
|
|
3
|
+
ADR 0006 の (b)(1.21.0)。asanowa・astro の `infra/modules/system-change-recorder` を 1 つにしたもの。使い方の詳細は [docs/SYSTEM_CHANGES.md](../../docs/SYSTEM_CHANGES.md)。
|
|
4
|
+
|
|
5
|
+
## 作るもの
|
|
6
|
+
|
|
7
|
+
- Lambda `<project>-<env>-system-change-recorder`(Node.js 22、arm64、15 秒、256 MB。構造化ログ JSON)と、そのロググループ・実行ロール
|
|
8
|
+
- 権限: Records の IAM 版 URL の呼び出し(`lambda:InvokeFunctionUrl`)、Slack の webhook のパラメータの読み取り(`slack_webhook_param_name` があるときだけ)。DynamoDB には直接触れない
|
|
9
|
+
- 非同期の呼び出しの再試行 0(同じ入力の再試行は、同じ変更が 2 件になる)
|
|
10
|
+
- 作らないもの: Function URL・EventBridge・`aws_lambda_permission`(呼び出しは人・CI の AWS の認証。`dynamodb-system-change` の CLI)
|
|
11
|
+
|
|
12
|
+
## 前提
|
|
13
|
+
|
|
14
|
+
- 参照は `node_modules` のパス(版は npm の版で固定される。git の `ref` ではない)。例: アプリの infra が `infra/` なら `source = "../node_modules/@exabugs/dynamodb-client/terraform/system-change-recorder"`、`infra/apps/<app>/` なら `"../../../node_modules/..."`
|
|
15
|
+
- **Lambda の成果物はアプリが作り、`source_file`(バンドル済みの 1 ファイル)と `handler` で渡す**。エントリはアプリの数行(`createSystemChangeHandler`。アプリ名や、権限チェック層を通すアプリの包み方を渡す)なので、ライブラリの dist は使わない
|
|
16
|
+
- zip は `${path.root}/.terraform/archives/system-change-recorder.zip` に作る(`path.module` を使わない。pnpm の版つきのパスが state に入ると、版を上げるたびに差分が出るため)
|
|
17
|
+
- pnpm では、版を上げた後に `.terraform/modules` を消してから `terraform init` する([../README.md](../README.md) の注意)
|
|
18
|
+
- モジュール全体の `depends_on` は使わない(依存先に変更があると、中のデータソースが apply 時の読み取りになり、既存のリソースが作り直しになる)。依存は、出力の値(`log_group_name` など)で渡す
|
|
19
|
+
|
|
20
|
+
## 使い方
|
|
21
|
+
|
|
22
|
+
```hcl
|
|
23
|
+
module "system_change_recorder" {
|
|
24
|
+
source = "../node_modules/@exabugs/dynamodb-client/terraform/system-change-recorder"
|
|
25
|
+
|
|
26
|
+
project_name = var.project_name
|
|
27
|
+
environment = var.environment
|
|
28
|
+
records_iam_function_url = module.records.iam_function_url
|
|
29
|
+
records_iam_alias_arn = module.records.iam_alias_arn
|
|
30
|
+
log_retention_days = var.log_retention_days
|
|
31
|
+
|
|
32
|
+
# アプリのバンドル(エントリは createSystemChangeHandler の数行)
|
|
33
|
+
source_file = "${path.root}/../apps/server/dist/system-change-recorder.cjs"
|
|
34
|
+
handler = "system-change-recorder.handler"
|
|
35
|
+
|
|
36
|
+
slack_webhook_param_name = "/${var.project_name}/${var.environment}/ops/slack-webhook"
|
|
37
|
+
admin_base_url = var.ops_admin_base_url
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 変数
|
|
42
|
+
|
|
43
|
+
| 変数 | 既定 | 説明 |
|
|
44
|
+
| ---------------------------------------------------- | ------- | ----------------------------------------------------------------------------- |
|
|
45
|
+
| `project_name` / `environment` | 必須 | 名前の接頭辞 `<project>-<env>`。環境変数 `PROJECT_NAME` / `ENV_NAME` |
|
|
46
|
+
| `records_iam_function_url` / `records_iam_alias_arn` | 必須 | Records の IAM 版 Function URL と、そのエイリアスの ARN(呼び出し権限の対象) |
|
|
47
|
+
| `source_file` / `handler` | 必須 | アプリのバンドル(1 ファイル)と、ハンドラ |
|
|
48
|
+
| `log_retention_days` | 7 | ロググループの保持日数 |
|
|
49
|
+
| `slack_webhook_param_name` | `""` | Slack の webhook の SSM パラメータ名(SecureString)。空なら Slack に送らない |
|
|
50
|
+
| `admin_base_url` | `""` | 管理画面の URL(Slack の通知に詳細のリンクを付ける) |
|
|
51
|
+
| `notify_started` | `false` | 「開始」も Slack に送るか |
|
|
52
|
+
|
|
53
|
+
## 出力
|
|
54
|
+
|
|
55
|
+
`function_name`(CLI の `--function` の既定と同じ名前)、`function_arn`、`log_group_name`(ops-notify に購読させる)
|
|
56
|
+
|
|
57
|
+
## アプリに残るもの
|
|
58
|
+
|
|
59
|
+
- 上の module ブロック(`source`・`source_file`・`handler` と設定の値)
|
|
60
|
+
- Lambda のエントリ(`createSystemChangeHandler` を呼ぶ数行)とバンドルの作成(esbuild など)
|
|
61
|
+
- 秘密(webhook の URL など)の SSM パラメータの作成
|
|
62
|
+
|
|
63
|
+
## アプリのモジュールからの移行(置き換えを起こさない)
|
|
64
|
+
|
|
65
|
+
リソースのアドレス・名前は、asanowa / astro の旧モジュール(`infra/modules/system-change-recorder`)と同じ。**module ブロックの名前(`module "system_change_recorder"`)を変えずに `source` だけを差し替え**、`source_file`・`handler` を足す。アドレスが変わらないので `moved` は要らない(ラッパーのモジュールで包むと、アドレスが変わり `moved` が要る)。
|
|
66
|
+
|
|
67
|
+
1. `terraform init`(モジュールの取り直し)→ `terraform plan`
|
|
68
|
+
2. **0 to add / 0 to destroy、`must be replaced` が無い**ことを確かめる。出てよいのは in-place の更新だけ(文言の差。asanowa / astro では Lambda の description の ADR の番号が消える)
|
|
69
|
+
3. 確認のうえ apply。旧モジュールのディレクトリを消す
|
|
70
|
+
|
|
71
|
+
詳細と実機の確認は [ADR 0006](../../docs/adr/0006-library-owns-table-operations.md) の段階 6。
|