Repository navigation
257 lines (224 loc) · 10.8 KB
/
Copy pathdeploy-function.yml
File metadata and controls
257 lines (224 loc) · 10.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
# ============================================================================
# SocraMetry — enebular クラウド実行環境への ZIP デプロイ
#
# 現状: v0.1 のバックエンド API とフロントエンドは実装済みです。
# トリガーは workflow_dispatch のみに絞っています。
# 自動デプロイにするなら、staging で通しの確認が済んだあとに
# 下の push トリガーを有効化してください(意図しないデプロイを避けるため)。
#
# デプロイ先は GitHub Environment で切り替えます。認証情報(Secrets)と
# enebular の ID 群(Variables)は Environment ごとに持ちます。
#
# | Environment | 用途 | 状態 |
# |---|---|---|
# | staging | 通しの動作確認・デモ | 設定済み |
# | development | 開発中の動作確認 | 設定済み |
# | production | 本番 | **未作成**(選ぶと設定不足で止まります) |
#
# 仕様: docs/deployment.md
# ============================================================================
name: Deploy function to enebular
on:
workflow_dispatch:
inputs:
environment:
description: "デプロイ先の GitHub Environment"
required: true
default: "staging"
type: choice
options:
- staging
- development
- production
version_name:
description: "enebular に記録するバージョン名"
required: true
default: "manual"
type: string
version_comment:
description: "バージョンコメント"
required: true
default: "Deployed from GitHub Actions"
type: string
# 実装完了後に有効化する:
# push:
# branches: [develop] # → staging
# tags: ["v*"] # → production
concurrency:
# 同一環境への同時デプロイを防ぐ(enebular 側の状態が競合するため)
group: deploy-function-${{ github.event.inputs.environment || 'staging' }}
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ github.event.inputs.environment || 'staging' }}
permissions:
contents: read
env:
ENEBULAR_ACCESS_KEY: ${{ secrets.ENEBULAR_ACCESS_KEY }}
ENEBULAR_SECRET_KEY: ${{ secrets.ENEBULAR_SECRET_KEY }}
PROJECT_ID: ${{ vars.ENEBULAR_PROJECT_ID }}
CLOUD_ID: ${{ vars.ENEBULAR_CLOUD_ID }}
FILE_ASSET_ID: ${{ vars.ENEBULAR_FILE_ASSET_ID }}
ZIP_PATH: apps/function/socrametry-function.zip
steps:
- name: Checkout
uses: actions/checkout@v7
- name: Set up pnpm
uses: pnpm/action-setup@v6
- name: Set up Node.js
uses: actions/setup-node@v7
with:
node-version: "22"
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
# ── 品質ゲート: 壊れたものをデプロイしない ─────────────────
- name: Typecheck and test
run: |
set -euo pipefail
pnpm typecheck
pnpm test
# ── ZIP をビルド(esbuild で単一 CJS にバンドル: ADR-008)──
- name: Build ZIP
run: pnpm --filter @socrametry/function build:zip
# ── enebular の ZIP 要件を満たしているか機械的に検証 ────────
# 要件は docs/deployment.md §6 のチェックリスト 1〜4 に対応
- name: Verify ZIP layout
run: |
set -euo pipefail
if [ ! -f "$ZIP_PATH" ]; then
echo "::error::ZIP が生成されていません: $ZIP_PATH"
exit 1
fi
echo "--- ZIP contents ---"
unzip -l "$ZIP_PATH"
# (1) ルート直下に index.js と package.json があるか
entries="$(unzip -Z1 "$ZIP_PATH")"
for required in index.js package.json; do
if ! grep -qx "$required" <<< "$entries"; then
echo "::error::ZIP のルート直下に $required がありません(親フォルダで包まれている可能性)"
exit 1
fi
done
# (2) package.json に "type": "module" が含まれていないか
unzip -p "$ZIP_PATH" package.json > /tmp/zip-package.json
if node -e "process.exit(JSON.parse(require('fs').readFileSync('/tmp/zip-package.json','utf8')).type === 'module' ? 1 : 0)"; then
echo 'package.json に "type": "module" がないことを確認しました'
else
echo '::error::ZIP 内の package.json に "type": "module" が含まれています(Lambda は CommonJS 必須)'
exit 1
fi
# (3) index.js が CommonJS として handler を公開しているか
# 文字列の grep はしない。esbuild の CJS 出力は __toCommonJS 経由で
# module.exports を組み立てるため、"exports.handler" という字面は現れない。
# 実際に require して型を見るのが、契約そのものの検証になる。
verify_dir="$(mktemp -d)"
unzip -q "$ZIP_PATH" -d "$verify_dir"
if ! node -e "
const m = require('$verify_dir/index.js');
if (typeof m.handler !== 'function') {
console.error('handler が関数として公開されていません:', typeof m.handler);
process.exit(1);
}
"; then
echo "::error::index.js が index.handler を公開していません"
exit 1
fi
# (4) サイズが 250MB 以下か
size_bytes="$(stat -c%s "$ZIP_PATH")"
echo "ZIP size: $((size_bytes / 1024 / 1024)) MB"
if [ "$size_bytes" -gt $((250 * 1024 * 1024)) ]; then
echo "::error::ZIP が 250MB を超えています"
exit 1
fi
- name: Install enebular CLI
run: npm install -g @uhuru/enebular-cli@1.0.0
- name: Validate credentials and settings
run: |
set -euo pipefail
# 公式ブログの注記: 値の前後に空白や改行があると認証エラーになる
if [ -z "${ENEBULAR_ACCESS_KEY}" ] || [ -z "${ENEBULAR_SECRET_KEY}" ]; then
echo "::error::GitHub Secrets の ENEBULAR_ACCESS_KEY と ENEBULAR_SECRET_KEY が必要です"
exit 1
fi
for name in PROJECT_ID CLOUD_ID FILE_ASSET_ID; do
if [ -z "${!name}" ]; then
echo "::error::GitHub Variables の ENEBULAR_${name} が設定されていません"
exit 1
fi
done
# ── ファイルアセットの中身を新しい ZIP に差し替える ──────────
- name: Update file asset
run: |
set -euo pipefail
enebular update file \
--project-id "$PROJECT_ID" \
--asset-id "$FILE_ASSET_ID" \
--file "$ZIP_PATH" \
--json
# ── クラウド実行環境へデプロイ ────────────────────────────
# ZIP デプロイなので --asset-type は flow ではなく file
- name: Deploy to cloud execution environment
run: |
set -euo pipefail
enebular deploy cloud \
--project-id "$PROJECT_ID" \
--cloud-id "$CLOUD_ID" \
--asset-id "$FILE_ASSET_ID" \
--asset-type file \
--json
# ── ロールバック用にバージョンを記録 ──────────────────────
- name: Create file version
env:
VERSION_NAME: ${{ github.event.inputs.version_name || github.ref_name }}
VERSION_COMMENT: ${{ github.event.inputs.version_comment || github.sha }}
run: |
set -euo pipefail
# enebular の制約: --name は 1〜30 文字の英数字・アンダースコア・ハイフンのみ。
# ブランチ名(feat/xxx)やタグ(v1.0.0)はそのままでは通らないため、
# **入力を弾くのではなく整形する。** ここで失敗させると、ZIP のデプロイは
# 済んでいるのにバージョン記録とスモークテストだけが実行されない状態になり、
# 「デプロイは成功したのか」が分かりにくい壊れ方をする。
safe_name="$(printf '%s' "$VERSION_NAME" \
| tr -c 'A-Za-z0-9_-' '-' \
| sed 's/^-*//; s/-*$//' \
| cut -c1-30)"
# 記号だけの入力は整形後に空になるため、既定値へ落とす
[ -n "$safe_name" ] || safe_name="manual"
if [ "$safe_name" != "$VERSION_NAME" ]; then
echo "バージョン名を enebular の制約に合わせて整形しました: $VERSION_NAME → $safe_name"
fi
enebular add file-version \
--project-id "$PROJECT_ID" \
--asset-id "$FILE_ASSET_ID" \
--name "$safe_name" \
--comment "$VERSION_COMMENT ($GITHUB_SHA)" \
--json
# ── デプロイ後の疎通確認 ─────────────────────────────────
- name: Smoke test
if: vars.HTTP_TRIGGER_URL != ''
run: |
set -euo pipefail
# 反映まで少し待つ
sleep 15
status="$(curl -sS -o /tmp/health.json -w '%{http_code}' "${{ vars.HTTP_TRIGGER_URL }}/v1/health")"
cat /tmp/health.json
echo
if [ "$status" != "200" ]; then
echo "::error::ヘルスチェックが $status を返しました"
exit 1
fi
# デプロイしたコミットが実際に動いているか(ZIP の差し替え漏れの検出)
deployed="$(node -p "JSON.parse(require('fs').readFileSync('/tmp/health.json','utf8')).commit")"
if [ "$deployed" != "$GITHUB_SHA" ]; then
echo "::error::動いているのは $deployed で、デプロイしたはずの $GITHUB_SHA ではありません"
exit 1
fi
# 環境変数の設定漏れ(apps/function/src/config.ts)
# キー名は公開しないため、不足時はクラウド実行環境のログを見る
if ! node -e "process.exit(JSON.parse(require('fs').readFileSync('/tmp/health.json','utf8')).configOk ? 0 : 1)"; then
missing="$(node -p "JSON.parse(require('fs').readFileSync('/tmp/health.json','utf8')).configMissing")"
echo "::error::環境変数が ${missing} 件不足しています。不足しているキー名はクラウド実行環境のログ(config.incomplete)に出ています"
exit 1
fi