@exabugs/dynamodb-client 1.16.0 → 1.17.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 +15 -0
- package/README.md +1 -0
- package/dist/notify/channels/slack.d.ts +8 -0
- package/dist/notify/channels/slack.d.ts.map +1 -1
- package/dist/notify/channels/slack.js +27 -19
- package/dist/notify/channels/slack.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/dist/system-changes/handler.d.ts +47 -0
- package/dist/system-changes/handler.d.ts.map +1 -0
- package/dist/system-changes/handler.js +74 -0
- package/dist/system-changes/handler.js.map +1 -0
- package/dist/system-changes/index.d.ts +15 -0
- package/dist/system-changes/index.d.ts.map +1 -0
- package/dist/system-changes/index.js +12 -0
- package/dist/system-changes/index.js.map +1 -0
- package/dist/system-changes/record.d.ts +92 -0
- package/dist/system-changes/record.d.ts.map +1 -0
- package/dist/system-changes/record.js +198 -0
- package/dist/system-changes/record.js.map +1 -0
- package/dist/system-changes/recorder.d.ts +104 -0
- package/dist/system-changes/recorder.d.ts.map +1 -0
- package/dist/system-changes/recorder.js +184 -0
- package/dist/system-changes/recorder.js.map +1 -0
- package/dist/system-changes/slack.d.ts +19 -0
- package/dist/system-changes/slack.d.ts.map +1 -0
- package/dist/system-changes/slack.js +124 -0
- package/dist/system-changes/slack.js.map +1 -0
- package/docs/RECORDS_CLIENT.md +2 -0
- package/docs/SYSTEM_CHANGES.md +117 -0
- package/package.json +7 -2
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* システム変更履歴(systemChanges)の入力の検証と、変更(run)の遷移の判定(ADR 0006 の (a)。純粋な関数だけ)。
|
|
3
|
+
*
|
|
4
|
+
* asanowa(functions/ops-notify/src/system-change-recorder.ts。ADR-0044)と astro(apps/server/src/system-changes/record.ts。
|
|
5
|
+
* ADR-0011)に複製されていたものを、1 つにした。入力の形と遷移の規則は docs/SYSTEM_CHANGES.md。
|
|
6
|
+
*/
|
|
7
|
+
import { cleanLine } from '../notify/policy.js';
|
|
8
|
+
/** リソース名(Records のコレクション名。管理画面の URL〔/systemChanges/{id}/show〕にも使う) */
|
|
9
|
+
export const SYSTEM_CHANGES_RESOURCE = 'systemChanges';
|
|
10
|
+
/** 種別(許可リスト)。管理画面のラベル・Slack の文言(slack.ts の KIND_LABEL)と揃える */
|
|
11
|
+
export const SYSTEM_CHANGE_KINDS = ['app-release', 'ota', 'infra', 'lambda-release'];
|
|
12
|
+
/** 結果(許可リスト) */
|
|
13
|
+
export const SYSTEM_CHANGE_RESULTS = ['started', 'succeeded', 'failed'];
|
|
14
|
+
/** 文字列の長さの上限(超えた分は切り、末尾に … を付ける) */
|
|
15
|
+
export const LIMITS = {
|
|
16
|
+
target: 200,
|
|
17
|
+
version: 100,
|
|
18
|
+
commit: 64,
|
|
19
|
+
actor: 200,
|
|
20
|
+
/** details を JSON にしたときのバイト数の上限(超えたら拒否する。構造を壊して切らない) */
|
|
21
|
+
detailsBytes: 4096,
|
|
22
|
+
};
|
|
23
|
+
/** 入力のキー(これ以外のキーは、綴りの誤りとして拒否する) */
|
|
24
|
+
const INPUT_KEYS = new Set([
|
|
25
|
+
'kind',
|
|
26
|
+
'result',
|
|
27
|
+
'target',
|
|
28
|
+
'version',
|
|
29
|
+
'commit',
|
|
30
|
+
'actor',
|
|
31
|
+
'details',
|
|
32
|
+
'occurredAt',
|
|
33
|
+
'notify',
|
|
34
|
+
'changeId',
|
|
35
|
+
]);
|
|
36
|
+
/** 変更 ID の形式(ULID: Crockford の base32 の 26 文字。Records が付ける id と同じ形) */
|
|
37
|
+
export const CHANGE_ID_PATTERN = /^[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
38
|
+
/** 入力の誤り(呼び出し元の誤り。再試行しても直らない) */
|
|
39
|
+
export class SystemChangeInputError extends Error {
|
|
40
|
+
constructor(message) {
|
|
41
|
+
super(message);
|
|
42
|
+
this.name = 'SystemChangeInputError';
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/** ISO 8601(日時・タイムゾーン付き)。日付だけ・タイムゾーンなしは、解釈がずれるので拒否する */
|
|
46
|
+
const ISO_8601 = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(:\d{2}(\.\d{1,9})?)?(Z|[+-]\d{2}:\d{2})$/;
|
|
47
|
+
export const isPlainObject = (v) => typeof v === 'object' &&
|
|
48
|
+
v !== null &&
|
|
49
|
+
!Array.isArray(v) &&
|
|
50
|
+
Object.getPrototypeOf(v) === Object.prototype;
|
|
51
|
+
/**
|
|
52
|
+
* 入力を検証し、保存する項目にする(純粋な関数)。誤りは SystemChangeInputError。
|
|
53
|
+
* 任意の文字列は、空(空白だけを含む)なら項目ごと省く(null は保存しない)
|
|
54
|
+
*/
|
|
55
|
+
export function toSystemChangeRecord(input, now = () => new Date()) {
|
|
56
|
+
return parseSystemChangeInput(input, now).record;
|
|
57
|
+
}
|
|
58
|
+
/** 入力を検証し、保存する項目と、変更 ID(あれば)にする(純粋な関数)。誤りは SystemChangeInputError */
|
|
59
|
+
export function parseSystemChangeInput(input, now = () => new Date()) {
|
|
60
|
+
if (!isPlainObject(input))
|
|
61
|
+
throw new SystemChangeInputError('入力はオブジェクトにしてください');
|
|
62
|
+
const unknownKeys = Object.keys(input).filter((k) => !INPUT_KEYS.has(k));
|
|
63
|
+
if (unknownKeys.length > 0) {
|
|
64
|
+
throw new SystemChangeInputError(`不明な項目です: ${unknownKeys.join(', ').slice(0, 200)}`);
|
|
65
|
+
}
|
|
66
|
+
if (input.notify !== undefined && typeof input.notify !== 'boolean') {
|
|
67
|
+
throw new SystemChangeInputError('notify は true / false にしてください');
|
|
68
|
+
}
|
|
69
|
+
const { kind, result } = input;
|
|
70
|
+
if (typeof kind !== 'string' || !SYSTEM_CHANGE_KINDS.includes(kind)) {
|
|
71
|
+
throw new SystemChangeInputError(`kind は ${SYSTEM_CHANGE_KINDS.join(' / ')} のどれかです`);
|
|
72
|
+
}
|
|
73
|
+
if (typeof result !== 'string' ||
|
|
74
|
+
!SYSTEM_CHANGE_RESULTS.includes(result)) {
|
|
75
|
+
throw new SystemChangeInputError(`result は ${SYSTEM_CHANGE_RESULTS.join(' / ')} のどれかです`);
|
|
76
|
+
}
|
|
77
|
+
if (typeof input.target !== 'string')
|
|
78
|
+
throw new SystemChangeInputError('target は必須です');
|
|
79
|
+
const target = cleanLine(input.target, LIMITS.target);
|
|
80
|
+
if (!target)
|
|
81
|
+
throw new SystemChangeInputError('target が空です');
|
|
82
|
+
const optional = (name) => {
|
|
83
|
+
const v = input[name];
|
|
84
|
+
if (v === undefined || v === null)
|
|
85
|
+
return undefined;
|
|
86
|
+
if (typeof v !== 'string')
|
|
87
|
+
throw new SystemChangeInputError(`${name} は文字列にしてください`);
|
|
88
|
+
return cleanLine(v, LIMITS[name]);
|
|
89
|
+
};
|
|
90
|
+
let changeId;
|
|
91
|
+
if (input.changeId !== undefined && input.changeId !== null) {
|
|
92
|
+
if (typeof input.changeId !== 'string' || !CHANGE_ID_PATTERN.test(input.changeId)) {
|
|
93
|
+
throw new SystemChangeInputError('changeId は ULID の形式(0-9 と A-Z〔I・L・O・U を除く〕の大文字 26 文字)にしてください');
|
|
94
|
+
}
|
|
95
|
+
changeId = input.changeId;
|
|
96
|
+
}
|
|
97
|
+
let occurredAt;
|
|
98
|
+
if (input.occurredAt === undefined || input.occurredAt === null) {
|
|
99
|
+
occurredAt = now().toISOString();
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
const t = input.occurredAt;
|
|
103
|
+
if (typeof t !== 'string' || !ISO_8601.test(t) || Number.isNaN(Date.parse(t))) {
|
|
104
|
+
throw new SystemChangeInputError('occurredAt は ISO 8601 の日時(例: 2026-10-16T09:00:00+09:00)にしてください');
|
|
105
|
+
}
|
|
106
|
+
// UTC の文字列にそろえる(並べ替えは文字列の順なので、タイムゾーンが混ざると順序がずれる)
|
|
107
|
+
occurredAt = new Date(t).toISOString();
|
|
108
|
+
}
|
|
109
|
+
let details;
|
|
110
|
+
if (input.details !== undefined && input.details !== null) {
|
|
111
|
+
if (!isPlainObject(input.details)) {
|
|
112
|
+
throw new SystemChangeInputError('details はオブジェクトにしてください');
|
|
113
|
+
}
|
|
114
|
+
const json = JSON.stringify(input.details);
|
|
115
|
+
if (Buffer.byteLength(json, 'utf8') > LIMITS.detailsBytes) {
|
|
116
|
+
throw new SystemChangeInputError(`details が大きすぎます(JSON で ${LIMITS.detailsBytes} バイトまで)`);
|
|
117
|
+
}
|
|
118
|
+
// JSON にできる値だけを残す(undefined・関数などを落とす)
|
|
119
|
+
details = JSON.parse(json);
|
|
120
|
+
}
|
|
121
|
+
const record = {
|
|
122
|
+
kind: kind,
|
|
123
|
+
result: result,
|
|
124
|
+
target,
|
|
125
|
+
occurredAt,
|
|
126
|
+
};
|
|
127
|
+
const version = optional('version');
|
|
128
|
+
const commit = optional('commit');
|
|
129
|
+
const actor = optional('actor');
|
|
130
|
+
if (version)
|
|
131
|
+
record.version = version;
|
|
132
|
+
if (commit)
|
|
133
|
+
record.commit = commit;
|
|
134
|
+
if (actor)
|
|
135
|
+
record.actor = actor;
|
|
136
|
+
if (details && Object.keys(details).length > 0)
|
|
137
|
+
record.details = details;
|
|
138
|
+
return changeId ? { record, changeId } : { record };
|
|
139
|
+
}
|
|
140
|
+
/** 終わった状態(succeeded / failed) */
|
|
141
|
+
export const isFinished = (r) => r !== 'started';
|
|
142
|
+
export function decideRunWrite(existing, record) {
|
|
143
|
+
if (!existing) {
|
|
144
|
+
const set = { ...record };
|
|
145
|
+
if (record.result === 'started')
|
|
146
|
+
set.startedAt = record.occurredAt;
|
|
147
|
+
else
|
|
148
|
+
set.finishedAt = record.occurredAt;
|
|
149
|
+
return { action: 'create', set };
|
|
150
|
+
}
|
|
151
|
+
if (isFinished(existing.result)) {
|
|
152
|
+
if (record.result === 'started')
|
|
153
|
+
return { action: 'ignore', reason: 'stale-start' };
|
|
154
|
+
if (record.result === existing.result)
|
|
155
|
+
return { action: 'ignore', reason: 'duplicate-finish' };
|
|
156
|
+
return { action: 'ignore', reason: 'conflict' };
|
|
157
|
+
}
|
|
158
|
+
if (record.result === 'started')
|
|
159
|
+
return { action: 'ignore', reason: 'duplicate-start' };
|
|
160
|
+
// 終了の時刻は、入力の時刻(無ければ、現在。toSystemChangeRecord が occurredAt に入れている)
|
|
161
|
+
const finishedAt = record.occurredAt;
|
|
162
|
+
const set = { result: record.result, finishedAt };
|
|
163
|
+
const startedAt = typeof existing.startedAt === 'string' ? existing.startedAt : undefined;
|
|
164
|
+
if (startedAt) {
|
|
165
|
+
const d = Date.parse(finishedAt) - Date.parse(startedAt);
|
|
166
|
+
// 時計のずれ・時刻の指定の誤りで負になったときは、所要時間を付けない
|
|
167
|
+
if (Number.isFinite(d) && d >= 0)
|
|
168
|
+
set.durationMs = d;
|
|
169
|
+
}
|
|
170
|
+
// 入力にある項目だけを $set する(details は Records が深くマージする。開始のキーは残る。
|
|
171
|
+
// ただし配列〔lambdaReleases など〕は、マージせず、入力の値で置き換わる。DynamoDB Local の統合テストで確認)
|
|
172
|
+
for (const k of ['target', 'version', 'commit', 'actor', 'details']) {
|
|
173
|
+
if (record[k] !== undefined)
|
|
174
|
+
set[k] = record[k];
|
|
175
|
+
}
|
|
176
|
+
const merged = {
|
|
177
|
+
kind: existing.kind,
|
|
178
|
+
result: record.result,
|
|
179
|
+
target: (set.target ?? existing.target),
|
|
180
|
+
occurredAt: existing.occurredAt,
|
|
181
|
+
finishedAt,
|
|
182
|
+
};
|
|
183
|
+
for (const k of ['version', 'commit', 'actor', 'startedAt']) {
|
|
184
|
+
const v = set[k] ?? existing[k];
|
|
185
|
+
if (typeof v === 'string')
|
|
186
|
+
merged[k] = v;
|
|
187
|
+
}
|
|
188
|
+
if (set.durationMs !== undefined)
|
|
189
|
+
merged.durationMs = set.durationMs;
|
|
190
|
+
const details = {
|
|
191
|
+
...(isPlainObject(existing.details) ? existing.details : {}),
|
|
192
|
+
...(record.details ?? {}),
|
|
193
|
+
};
|
|
194
|
+
if (Object.keys(details).length > 0)
|
|
195
|
+
merged.details = details;
|
|
196
|
+
return { action: 'finish', set, merged };
|
|
197
|
+
}
|
|
198
|
+
//# sourceMappingURL=record.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"record.js","sourceRoot":"","sources":["../../src/system-changes/record.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* システム変更履歴(systemChanges)の書き込みと Slack 通知(ADR 0006 の (a))。
|
|
3
|
+
*
|
|
4
|
+
* asanowa(ADR-0044)と astro(ADR-0011)に複製されていたロジックを、1 つにした。アプリ固有の差は注入する:
|
|
5
|
+
* - アプリ名(ログの `app`、Slack の見出し、失敗の案内の Records Lambda 名): `options.app`
|
|
6
|
+
* - 書き込み先: `SystemChangesCollection`(生のコレクションは `systemChangesCollectionOf` で包む。権限チェック層を
|
|
7
|
+
* 通すアプリは、自分の層で包んだものを渡す)
|
|
8
|
+
* - ログ: `options.log`(既定は notify の `logJson`)
|
|
9
|
+
* - Slack: `options.slack`(webhook の取得、環境名、管理画面の URL)
|
|
10
|
+
*
|
|
11
|
+
* テーブルには、Records(DynamoClient)経由でしか触れない(docs/DYNAMODB_ACCESS.md)。
|
|
12
|
+
*/
|
|
13
|
+
import type { RecordsCollection } from '../client/recordsClient.js';
|
|
14
|
+
import { type StoredSystemChange, type SystemChangeRecord } from './record.js';
|
|
15
|
+
/** 記録の HTTP の時間切れ(ミリ秒)。Lambda の時間切れ(両アプリの Terraform で 15 秒)より短くする */
|
|
16
|
+
export declare const RECORD_HTTP_TIMEOUT_MS = 5000;
|
|
17
|
+
/** Slack への送信の時間切れ(ミリ秒) */
|
|
18
|
+
export declare const SLACK_HTTP_TIMEOUT_MS = 3000;
|
|
19
|
+
/**
|
|
20
|
+
* 書き込み先(テストでは差し替える)。
|
|
21
|
+
* findOne・updateOne は changeId の記録だけが使う(無いと changeId の入力は書けない。insertOne だけの
|
|
22
|
+
* 差し替えも、そのまま使えるように任意にしている)
|
|
23
|
+
*/
|
|
24
|
+
export interface SystemChangesCollection {
|
|
25
|
+
/** changeId の無い記録: 新しい 1 件(id は Records が ULID で付ける) */
|
|
26
|
+
insertOne(document: SystemChangeRecord): Promise<{
|
|
27
|
+
insertedId: string;
|
|
28
|
+
}>;
|
|
29
|
+
/** id の 1 件。無ければ null */
|
|
30
|
+
findOne?(id: string): Promise<StoredSystemChange | null>;
|
|
31
|
+
/** id のレコードに $set する。upsert: true なら、無ければ作る(createdAt / updatedAt は Records が付ける) */
|
|
32
|
+
updateOne?(id: string, set: Partial<SystemChangeRecord>, options: {
|
|
33
|
+
upsert: boolean;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
}
|
|
36
|
+
export type SystemChangeLogLevel = 'info' | 'warn' | 'error';
|
|
37
|
+
/** 構造化ログの出力先。fields には app・target(systemChanges)が入る */
|
|
38
|
+
export type SystemChangeLog = (level: SystemChangeLogLevel, fields: Record<string, unknown>) => void;
|
|
39
|
+
export interface RecordSystemChangeOptions {
|
|
40
|
+
/** アプリ名(ログの app、Slack の見出し、失敗の案内の `<app>-<env>-records`) */
|
|
41
|
+
app: string;
|
|
42
|
+
/** 現在時刻(テスト用) */
|
|
43
|
+
now?: () => Date;
|
|
44
|
+
/** ログの出力先。既定は notify の logJson(JSON 1 行。Lambda の JSON 形式ではオブジェクト) */
|
|
45
|
+
log?: SystemChangeLog;
|
|
46
|
+
}
|
|
47
|
+
/** recordSystemChange の結果。ignored は、changeId の記録で、状態を変えずに無視したとき(書いていない) */
|
|
48
|
+
export interface RecordOutcome {
|
|
49
|
+
id: string;
|
|
50
|
+
occurredAt: string;
|
|
51
|
+
/** Slack に出す内容(changeId の更新では、既存と入力を合わせたもの) */
|
|
52
|
+
record: SystemChangeRecord;
|
|
53
|
+
ignored?: true;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 検証して書く。changeId が無ければ 1 件を insertOne、あれば、その ID のレコードを作成・更新する(decideRunWrite)。
|
|
57
|
+
* 失敗は構造化ログに出して、例外にする。
|
|
58
|
+
* 読み取り → 書き込みは非アトミック(Records に条件付き書き込みが無い)。同じ changeId の呼び出しが、同時に
|
|
59
|
+
* 走らない前提(ラッパーは、開始と終了を順に呼ぶ)
|
|
60
|
+
*/
|
|
61
|
+
export declare function recordSystemChange(collection: SystemChangesCollection, input: unknown, options: RecordSystemChangeOptions): Promise<RecordOutcome>;
|
|
62
|
+
/**
|
|
63
|
+
* Records のコレクション(DynamoClient)を、書き込み先にする(権限チェック層を持たないアプリの既定)。
|
|
64
|
+
* 1 件の読み取りは `find({ id }, { sort: { id: 'asc' }, limit: 1 })`。sort を id にすると、Records の ID の経路
|
|
65
|
+
* (PK/SK の Query 1 回。executeSpecificIdQuery)に入る(sort が無いと updatedAt の既定になり、全件の走査になる)。
|
|
66
|
+
* 無い ID は空配列で null になる(`findOne` は 404〔ITEM_NOT_FOUND〕を
|
|
67
|
+
* 返し、Records が warn のログを出して運用通知に届くため、使わない。astro で確認した動作)
|
|
68
|
+
*/
|
|
69
|
+
export declare function systemChangesCollectionOf(raw: RecordsCollection<StoredSystemChange>): Required<SystemChangesCollection>;
|
|
70
|
+
/** Slack 通知の設定。webhookParam が無ければ、送らない */
|
|
71
|
+
export interface SystemChangeSlackOptions {
|
|
72
|
+
/** webhook の URL の置き場所(SSM のパラメータ名など)。getWebhookUrl に渡す */
|
|
73
|
+
webhookParam?: string;
|
|
74
|
+
/** webhook の URL を取る(秘密。キャッシュしてよい) */
|
|
75
|
+
getWebhookUrl: (param: string) => Promise<string>;
|
|
76
|
+
/** 見出しの `[app/env]` の env */
|
|
77
|
+
env?: string;
|
|
78
|
+
/** 管理画面の URL(あれば、詳細 `/systemChanges/{id}/show` へのリンクを付ける) */
|
|
79
|
+
adminBaseUrl?: string;
|
|
80
|
+
/** 開始(started)も送る(既定 false) */
|
|
81
|
+
notifyStarted?: boolean;
|
|
82
|
+
/** Webhook の URL が、この文字列で始まること(既定 https://hooks.slack.com/。notify の createSlackChannel と同じ) */
|
|
83
|
+
urlPrefix?: string;
|
|
84
|
+
fetchImpl?: typeof fetch;
|
|
85
|
+
/** 既定 SLACK_HTTP_TIMEOUT_MS(3 秒) */
|
|
86
|
+
timeoutMs?: number;
|
|
87
|
+
}
|
|
88
|
+
export interface RecordAndNotifyOptions extends RecordSystemChangeOptions {
|
|
89
|
+
slack?: SystemChangeSlackOptions;
|
|
90
|
+
}
|
|
91
|
+
/** 記録 Lambda の応答 */
|
|
92
|
+
export interface SystemChangeResponse {
|
|
93
|
+
id: string;
|
|
94
|
+
occurredAt: string;
|
|
95
|
+
/** Slack に送ったか(送らない指定・開始・設定なし・無視・送信の失敗は false) */
|
|
96
|
+
notified: boolean;
|
|
97
|
+
/** changeId の記録で、状態を変えずに無視したときだけ付ける(そのときは、Slack に送らない) */
|
|
98
|
+
ignored?: true;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* 記録して、結果を Slack に送る。送信の失敗は、記録を失敗にしない(構造化ログ warn に出す。URL は出さない)
|
|
102
|
+
*/
|
|
103
|
+
export declare function recordAndNotify(collection: SystemChangesCollection, input: unknown, options: RecordAndNotifyOptions): Promise<SystemChangeResponse>;
|
|
104
|
+
//# sourceMappingURL=recorder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.d.ts","sourceRoot":"","sources":["../../src/system-changes/recorder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { postSlackText } from '../notify/channels/slack.js';
|
|
2
|
+
import { logJson } from '../notify/logger.js';
|
|
3
|
+
import { SYSTEM_CHANGES_RESOURCE, decideRunWrite, isPlainObject, parseSystemChangeInput, } from './record.js';
|
|
4
|
+
import { formatSystemChangeMessage, shouldNotify } from './slack.js';
|
|
5
|
+
/** 記録の HTTP の時間切れ(ミリ秒)。Lambda の時間切れ(両アプリの Terraform で 15 秒)より短くする */
|
|
6
|
+
export const RECORD_HTTP_TIMEOUT_MS = 5000;
|
|
7
|
+
/** Slack への送信の時間切れ(ミリ秒) */
|
|
8
|
+
export const SLACK_HTTP_TIMEOUT_MS = 3000;
|
|
9
|
+
/** 無視の理由ごとの説明(ログ用) */
|
|
10
|
+
const IGNORE_MESSAGE = {
|
|
11
|
+
'stale-start': '終わった変更に、遅れて開始が届いたので、無視しました(状態を戻さない)',
|
|
12
|
+
'duplicate-finish': '同じ終了が、もう記録されているので、無視しました(再送)',
|
|
13
|
+
'duplicate-start': '開始が、もう記録されているので、無視しました(再送)',
|
|
14
|
+
conflict: '終わった変更に、違う終了が届いたので、無視しました(最初の終了を優先)',
|
|
15
|
+
};
|
|
16
|
+
/** app と target を固定したログ */
|
|
17
|
+
function bindLog(app, log) {
|
|
18
|
+
const out = log ?? ((level, fields) => logJson(level, fields));
|
|
19
|
+
return (level, fields) => out(level, { app, target: SYSTEM_CHANGES_RESOURCE, ...fields });
|
|
20
|
+
}
|
|
21
|
+
/** changeId の記録: 読み取り → 判定 → 書き込み */
|
|
22
|
+
async function writeRun(log, collection, changeId, record, summary) {
|
|
23
|
+
if (!collection.findOne || !collection.updateOne) {
|
|
24
|
+
throw new Error('この書き込み先は、changeId の記録(findOne・updateOne)に対応していません');
|
|
25
|
+
}
|
|
26
|
+
const existing = await collection.findOne(changeId);
|
|
27
|
+
const decision = decideRunWrite(existing, record);
|
|
28
|
+
if (decision.action === 'ignore') {
|
|
29
|
+
const conflict = decision.reason === 'conflict';
|
|
30
|
+
log(conflict ? 'warn' : 'info', {
|
|
31
|
+
event: conflict ? 'SYSTEM_CHANGE_CONFLICT' : 'SYSTEM_CHANGE_IGNORED',
|
|
32
|
+
message: IGNORE_MESSAGE[decision.reason],
|
|
33
|
+
reason: decision.reason,
|
|
34
|
+
id: changeId,
|
|
35
|
+
existingResult: existing?.result,
|
|
36
|
+
...(conflict
|
|
37
|
+
? {
|
|
38
|
+
action: '同じ変更 ID に、成功と失敗の両方が届きました。呼び出し元(ラッパー・CI)を確認してください。記録は最初の終了のまま',
|
|
39
|
+
}
|
|
40
|
+
: {}),
|
|
41
|
+
...summary,
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
id: changeId,
|
|
45
|
+
occurredAt: existing?.occurredAt ?? record.occurredAt,
|
|
46
|
+
record: existing ?? record,
|
|
47
|
+
ignored: true,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const recorded = {
|
|
51
|
+
event: 'SYSTEM_CHANGE_RECORDED',
|
|
52
|
+
message: 'system change recorded',
|
|
53
|
+
id: changeId,
|
|
54
|
+
};
|
|
55
|
+
if (decision.action === 'create') {
|
|
56
|
+
await collection.updateOne(changeId, decision.set, { upsert: true });
|
|
57
|
+
log('info', { ...recorded, transition: 'created', ...summary });
|
|
58
|
+
return { id: changeId, occurredAt: record.occurredAt, record: decision.set };
|
|
59
|
+
}
|
|
60
|
+
await collection.updateOne(changeId, decision.set, { upsert: false });
|
|
61
|
+
log('info', {
|
|
62
|
+
...recorded,
|
|
63
|
+
transition: 'finished',
|
|
64
|
+
...(decision.set.durationMs !== undefined ? { durationMs: decision.set.durationMs } : {}),
|
|
65
|
+
...summary,
|
|
66
|
+
});
|
|
67
|
+
return { id: changeId, occurredAt: decision.merged.occurredAt, record: decision.merged };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* 検証して書く。changeId が無ければ 1 件を insertOne、あれば、その ID のレコードを作成・更新する(decideRunWrite)。
|
|
71
|
+
* 失敗は構造化ログに出して、例外にする。
|
|
72
|
+
* 読み取り → 書き込みは非アトミック(Records に条件付き書き込みが無い)。同じ changeId の呼び出しが、同時に
|
|
73
|
+
* 走らない前提(ラッパーは、開始と終了を順に呼ぶ)
|
|
74
|
+
*/
|
|
75
|
+
export async function recordSystemChange(collection, input, options) {
|
|
76
|
+
const log = bindLog(options.app, options.log);
|
|
77
|
+
let record;
|
|
78
|
+
let changeId;
|
|
79
|
+
try {
|
|
80
|
+
({ record, changeId } = parseSystemChangeInput(input, options.now));
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
84
|
+
log('warn', {
|
|
85
|
+
event: 'SYSTEM_CHANGE_INVALID_INPUT',
|
|
86
|
+
message: `システム変更履歴の入力が不正です: ${message}`.slice(0, 1000),
|
|
87
|
+
action: '呼び出し元(スクリプト・CI)の入力を確認してください。記録はされていません',
|
|
88
|
+
});
|
|
89
|
+
throw e;
|
|
90
|
+
}
|
|
91
|
+
// 記録の内容(ログで追えるように。details は大きくなりうるので、出さない)
|
|
92
|
+
const summary = {
|
|
93
|
+
kind: record.kind,
|
|
94
|
+
result: record.result,
|
|
95
|
+
changeTarget: record.target,
|
|
96
|
+
occurredAt: record.occurredAt,
|
|
97
|
+
...(changeId ? { changeId } : {}),
|
|
98
|
+
};
|
|
99
|
+
try {
|
|
100
|
+
if (changeId)
|
|
101
|
+
return await writeRun(log, collection, changeId, record, summary);
|
|
102
|
+
const r = await collection.insertOne(record);
|
|
103
|
+
log('info', {
|
|
104
|
+
event: 'SYSTEM_CHANGE_RECORDED',
|
|
105
|
+
message: 'system change recorded',
|
|
106
|
+
id: r.insertedId,
|
|
107
|
+
...summary,
|
|
108
|
+
});
|
|
109
|
+
return { id: r.insertedId, occurredAt: record.occurredAt, record };
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
113
|
+
log('error', {
|
|
114
|
+
event: 'SYSTEM_CHANGE_RECORD_FAILED',
|
|
115
|
+
message: `システム変更履歴の書き込みに失敗しました: ${message}`.slice(0, 1000),
|
|
116
|
+
action: `記録されていません。Records Lambda(${options.app}-<env>-records)のログを確認し、必要なら同じ入力で呼び直してください`,
|
|
117
|
+
...summary,
|
|
118
|
+
});
|
|
119
|
+
throw new Error(`システム変更履歴の書き込みに失敗しました: ${message}`, { cause: e });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Records のコレクション(DynamoClient)を、書き込み先にする(権限チェック層を持たないアプリの既定)。
|
|
124
|
+
* 1 件の読み取りは `find({ id }, { sort: { id: 'asc' }, limit: 1 })`。sort を id にすると、Records の ID の経路
|
|
125
|
+
* (PK/SK の Query 1 回。executeSpecificIdQuery)に入る(sort が無いと updatedAt の既定になり、全件の走査になる)。
|
|
126
|
+
* 無い ID は空配列で null になる(`findOne` は 404〔ITEM_NOT_FOUND〕を
|
|
127
|
+
* 返し、Records が warn のログを出して運用通知に届くため、使わない。astro で確認した動作)
|
|
128
|
+
*/
|
|
129
|
+
export function systemChangesCollectionOf(raw) {
|
|
130
|
+
return {
|
|
131
|
+
insertOne: async (doc) => ({ insertedId: (await raw.insertOne({ ...doc })).insertedId }),
|
|
132
|
+
findOne: async (id) => (await raw.find({ id }, { sort: { id: 'asc' }, limit: 1 }).toArray())[0] ?? null,
|
|
133
|
+
updateOne: async (id, set, { upsert }) => {
|
|
134
|
+
await raw.updateOne({ id }, { $set: { ...set } }, { upsert });
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
const errorName = (e) => (e instanceof Error ? e.name : 'unknown');
|
|
139
|
+
/**
|
|
140
|
+
* 記録して、結果を Slack に送る。送信の失敗は、記録を失敗にしない(構造化ログ warn に出す。URL は出さない)
|
|
141
|
+
*/
|
|
142
|
+
export async function recordAndNotify(collection, input, options) {
|
|
143
|
+
const { id, occurredAt, record, ignored } = await recordSystemChange(collection, input, options);
|
|
144
|
+
const done = (notified) => ignored ? { id, occurredAt, notified, ignored } : { id, occurredAt, notified };
|
|
145
|
+
const slack = options.slack;
|
|
146
|
+
const notifyFlag = isPlainObject(input) && typeof input.notify === 'boolean' ? input.notify : undefined;
|
|
147
|
+
if (ignored ||
|
|
148
|
+
!slack?.webhookParam ||
|
|
149
|
+
!shouldNotify(record.result, { notify: notifyFlag, notifyStarted: slack.notifyStarted })) {
|
|
150
|
+
return done(false);
|
|
151
|
+
}
|
|
152
|
+
const warn = (reason) => bindLog(options.app, options.log)('warn', {
|
|
153
|
+
event: 'SYSTEM_CHANGE_SLACK_FAILED',
|
|
154
|
+
message: `システム変更の Slack 通知に失敗しました(記録はされています): ${reason}`.slice(0, 500),
|
|
155
|
+
action: 'Slack の webhook(SSM)と、Slack 側の設定を確認してください。記録(systemChanges)は残っています',
|
|
156
|
+
id,
|
|
157
|
+
});
|
|
158
|
+
let url;
|
|
159
|
+
try {
|
|
160
|
+
url = await slack.getWebhookUrl(slack.webhookParam);
|
|
161
|
+
}
|
|
162
|
+
catch (e) {
|
|
163
|
+
// 取得のエラーは、名前だけを残す
|
|
164
|
+
warn(errorName(e));
|
|
165
|
+
return done(false);
|
|
166
|
+
}
|
|
167
|
+
try {
|
|
168
|
+
const text = formatSystemChangeMessage(options.app, slack.env, record, id, slack.adminBaseUrl);
|
|
169
|
+
await postSlackText(url, text, {
|
|
170
|
+
urlPrefix: slack.urlPrefix,
|
|
171
|
+
fetchImpl: slack.fetchImpl,
|
|
172
|
+
timeoutMs: slack.timeoutMs ?? SLACK_HTTP_TIMEOUT_MS,
|
|
173
|
+
});
|
|
174
|
+
return done(true);
|
|
175
|
+
}
|
|
176
|
+
catch (e) {
|
|
177
|
+
// postSlackText のエラーのメッセージは、URL を含まない(原因の種類と HTTP の状態だけ)。
|
|
178
|
+
// 「Slack への送信に失敗しました (X)」は、warn の本文と重ならないよう、X だけにする
|
|
179
|
+
const message = e instanceof Error ? e.message : 'unknown';
|
|
180
|
+
warn(/^Slack への送信に失敗しました \((.*)\)$/.exec(message)?.[1] ?? message);
|
|
181
|
+
return done(false);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
//# sourceMappingURL=recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recorder.js","sourceRoot":"","sources":["../../src/system-changes/recorder.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SystemChangeRecord, SystemChangeResult } from './record.js';
|
|
2
|
+
/**
|
|
3
|
+
* details.lambdaReleases([{ target, result, change }]。ラッパーが infra の終了に埋め込む)から、成功・失敗の関数名を取り出す。
|
|
4
|
+
* 形の合わない要素は飛ばす
|
|
5
|
+
*/
|
|
6
|
+
export declare function lambdaReleaseNames(value: unknown): {
|
|
7
|
+
succeeded: string[];
|
|
8
|
+
failed: string[];
|
|
9
|
+
};
|
|
10
|
+
/** 所要時間(ミリ秒)を「52 秒」「3 分 12 秒」「1 時間 5 分」の形にする(1 秒未満は「1 秒未満」) */
|
|
11
|
+
export declare function formatDuration(ms: number): string;
|
|
12
|
+
/** Slack に送るメッセージ(text)。管理画面の詳細へのリンクは、adminBaseUrl があるときだけ */
|
|
13
|
+
export declare function formatSystemChangeMessage(app: string, env: string | undefined, record: SystemChangeRecord, id: string, adminBaseUrl?: string): string;
|
|
14
|
+
/** 通知するか(結果は送る。開始は notifyStarted のときだけ。呼び出し側が notify=false を指定したら送らない) */
|
|
15
|
+
export declare function shouldNotify(result: SystemChangeResult, options: {
|
|
16
|
+
notify?: boolean;
|
|
17
|
+
notifyStarted?: boolean;
|
|
18
|
+
}): boolean;
|
|
19
|
+
//# sourceMappingURL=slack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/system-changes/slack.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAoB,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAuB5F;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG;IAAE,SAAS,EAAE,MAAM,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,EAAE,CAAA;CAAE,CAY5F;AA8BD,+DAA+D;AAC/D,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CASjD;AAED,8DAA8D;AAC9D,wBAAgB,yBAAyB,CACvC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,GAAG,SAAS,EACvB,MAAM,EAAE,kBAAkB,EAC1B,EAAE,EAAE,MAAM,EACV,YAAY,CAAC,EAAE,MAAM,GACpB,MAAM,CAiBR;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,CAC1B,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,aAAa,CAAC,EAAE,OAAO,CAAA;CAAE,GACrD,OAAO,CAIT"}
|