-
Notifications
You must be signed in to change notification settings - Fork 0
735 lines (675 loc) · 33.9 KB
/
Copy pathtapdata-deploy.yml
File metadata and controls
735 lines (675 loc) · 33.9 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
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
# TapData Deploy (Matrix) —— worker 端可复用 workflow,隐藏"自动跳过"的部署步骤
# ============================================================================
# 与 tapdata-deploy.yml 并存,不改动原文件,不影响现有 deploy 流程。
#
# 与原版的唯一核心差异:
# 原版:connections / FDM / MDM / APIs 各自是独立 deploy job,用 preview 的
# has_changes + if: 判断是否运行。无变更的资源 job 被 skip,但仍以灰色
# "已跳过"出现在运行图里,容易让人误解。
# 本版:preview-all 之后构建"只含有变更资源"的动态 matrix,单个 deploy job 用
# strategy.matrix 展开。无变更的资源根本不会生成 matrix 腿 → 运行图里
# 看不到它,不再显示灰色"已跳过"。
#
# 代价(已确认接受):4 个资源合并为 1 个 deploy job → 部署前人工审批从最多 4 次
# 合并为 1 次。全部资源都无变更时会有 1 个 "Deploy (已跳过)" 灰节点(表示无需
# 部署),这是 GitHub Actions 机制下无法消除的,但与"部分跳过"的混乱不同。
#
# 触发方式:仅 workflow_call(多租户)。实际运行 actions 的是租户仓库,由租户的
# caller workflow(tenant-template/.github/workflows/tapdata-deploy-matrix.yml)
# 通过 uses: 调用本文件。worker 仓库自身不会触发它,零干扰。
# ============================================================================
name: TapData Deploy (Matrix)
# run-name 打头是本次部署对应的 PR 标题(放最前是因为 Actions 列表从右侧截断,放末尾会先被切掉):
# dev 部署由 PR 合入 main 触发,push 事件里没有 pull_request 字段,但 squash 合并的 commit 标题就是
# 「PR 标题 (#N)」、merge commit 则是「Merge pull request #N from …」,两种合并方式都足以定位 PR。
# 真·pull_request 事件(若租户日后接上)优先取 pull_request.title。tag / 手动触发没有 PR,回落到
# 字面量 Deploy——🚀 本身已经是 deploy 的标识(rollback 用 ⏪),不靠这个词区分。
run-name: "🚀 ${{ (github.event_name != 'workflow_dispatch' && github.ref_type != 'tag' && (github.event.pull_request.title || github.event.head_commit.message)) || 'Deploy' }} · ${{ inputs.project || vars.PROJECT_NAME || github.event.repository.name }} → ${{ inputs.target_env || (github.ref_type == 'tag' && 'sit' || 'dev') }} · ${{ github.event_name == 'workflow_dispatch' && format('manual by @{0}', github.actor) || (github.ref_type == 'tag' && format('tag {0}', github.ref_name) || format('branch {0}', github.ref_name)) }}"
# 并发守卫(worker 兜底):同 project + 同 env 的部署串行化,防止并发导入同一 TapData 项目组。
# 独立前缀 tapdata-deploy-worker- 与租户 caller 的并发组区分,避免同一次 run 的 caller/worker 互相阻塞;
# caller 已配守卫时本组通常不触发,仅作租户漏配 caller 守卫时的最后防线。
# 不同 project / 不同 env 仍可并行(TapData 保证跨 group 并发安全)。cancel-in-progress: false → 排队不打断。
concurrency:
group: tapdata-deploy-worker-${{ inputs.project }}-${{ inputs.target_env || (startsWith(inputs.caller_ref, 'refs/tags/') && 'sit') || 'dev' }}
cancel-in-progress: false
on:
# 多租户模式:被租户仓库通过 workflow_call 调用
workflow_call:
inputs:
project:
description: 'Project name (租户仓库中 {project}_tapdata_export/ 目录的前缀)'
required: true
type: string
target_env:
description: 'Target environment (workflow_dispatch 手动触发时由租户传入;push/tag 时留空,由 caller_event/caller_ref 自动推导)'
required: false
type: string
default: ''
caller_repo:
description: 'Caller tenant repo full name, e.g. your-org/your-tenant-repo'
required: true
type: string
caller_sha:
description: '租户仓库的 commit SHA,用于精确 checkout'
required: true
type: string
caller_event:
description: '租户仓库的原始事件名(push/workflow_dispatch),用于推导 TARGET_ENV'
required: true
type: string
caller_ref:
description: '租户仓库的原始 ref(refs/heads/main 或 refs/tags/xxx),用于判断是否为 tag'
required: true
type: string
worker_repo:
description: 'Worker repo full name, e.g. your-org/tapdata-cicd-worker (used to checkout worker scripts)'
required: true
type: string
secrets:
GH_DEPLOY_TOKEN:
required: true
env:
DEPLOY_DIR: /tmp/tapdata-deploy-${{ github.run_id }}
# 多租户模式:workflow_call 时使用租户的 caller_sha
DEPLOY_REF: ${{ inputs.caller_repo != '' && inputs.caller_sha || github.ref_name }}
# TARGET_ENV 推导逻辑:
# workflow_call + target_env 已传入 → 使用 inputs.target_env
# workflow_call + caller_event=push + 非 tag ref → dev
# workflow_call + caller_event=push + tag ref → sit
TARGET_ENV: >-
${{
(inputs.caller_repo != '' && inputs.target_env != '') && inputs.target_env
|| (inputs.caller_repo != '' && inputs.caller_event == 'push' && !startsWith(inputs.caller_ref, 'refs/tags/')) && 'dev'
|| (inputs.caller_repo != '' && inputs.caller_event == 'push' && startsWith(inputs.caller_ref, 'refs/tags/')) && 'sit'
|| inputs.target_env
}}
jobs:
# ============================================================
# Job 1: Preparation Phase
# ============================================================
preparation:
name: Preparation Phase
runs-on: [self-hosted, tapdata]
timeout-minutes: 5
environment: >-
${{
(inputs.caller_repo != '' && inputs.target_env != '') && inputs.target_env
|| (inputs.caller_repo != '' && inputs.caller_event == 'push' && !startsWith(inputs.caller_ref, 'refs/tags/')) && 'dev'
|| (inputs.caller_repo != '' && inputs.caller_event == 'push' && startsWith(inputs.caller_ref, 'refs/tags/')) && 'sit'
|| inputs.target_env
}}
outputs:
tapdata_token: ${{ steps.get_token.outputs.tapdata_token }}
target_env: ${{ env.TARGET_ENV }}
tapdata_url: ${{ steps.resolve_config.outputs.tapdata_url }}
project: ${{ steps.detect_project.outputs.project }}
vault_transport: ${{ steps.resolve_transport.outputs.vault_transport }}
prep_runner: ${{ steps.resolve_transport.outputs.prep_runner }}
steps:
# Step 1: checkout 租户仓库(含 JSON 文件)到 workspace 根
- name: Checkout Data Repository
uses: actions/checkout@v4
with:
repository: ${{ inputs.caller_repo != '' && inputs.caller_repo || github.repository }}
ref: ${{ inputs.caller_repo != '' && inputs.caller_sha || env.DEPLOY_REF }}
token: ${{ secrets.GH_DEPLOY_TOKEN }}
fetch-depth: 2
# Step 2: 设置 SCRIPT_ROOT、REPO_ROOT 和 WORKER_REPO
- name: Set Runtime Paths
run: |
if [[ -n "${{ inputs.caller_repo }}" ]]; then
echo "WORKER_REPO=${{ inputs.worker_repo }}" >> "$GITHUB_ENV"
echo "SCRIPT_ROOT=_worker" >> "$GITHUB_ENV"
echo "REPO_ROOT=${{ github.workspace }}" >> "$GITHUB_ENV"
else
echo "SCRIPT_ROOT=." >> "$GITHUB_ENV"
echo "REPO_ROOT=$(pwd)" >> "$GITHUB_ENV"
fi
# Step 3: 仅 workflow_call 时,额外 checkout worker scripts 到 _worker/
- name: Checkout Worker Scripts
if: inputs.caller_repo != ''
uses: actions/checkout@v4
with:
repository: ${{ env.WORKER_REPO }}
ref: main
token: ${{ secrets.GH_DEPLOY_TOKEN }}
path: _worker
- name: Detect Project
id: detect_project
run: bash ${{ env.SCRIPT_ROOT }}/scripts/tapdata-deploy/detect-project.sh
env:
# workflow_call 时:project 由租户程序化传入,直接使用;
# 单仓库 push / 手动 dispatch 时 INPUT_PROJECT 为空 → detect-project.sh 自动检测(一仓一项目)
INPUT_PROJECT: ${{ inputs.project }}
PROJECT_NAME: ${{ vars.PROJECT_NAME }}
- name: Check tapdata_export Directory
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/check-export-dir.sh
env:
EXPORT_DIR: ${{ env.REPO_ROOT }}/${{ env.PROJECT }}_tapdata_export
- name: Validate Input Parameters
run: bash ${{ env.SCRIPT_ROOT }}/scripts/tapdata-deploy/validate-inputs.sh
env:
REPO_ROOT: ${{ env.REPO_ROOT }}
DEPLOYMENT_REF: ${{ env.DEPLOY_REF }}
TARGET_ENV: ${{ env.TARGET_ENV }}
PROJECT: ${{ env.PROJECT }}
- name: Resolve TapData Connection Config
id: resolve_config
run: |
case "${TARGET_ENV}" in
dev) URL="${DEV_TAPDATA_URL:-${TAPDATA_URL}}" ; CODE="${DEV_TAPDATA_ACCESS_CODE:-${TAPDATA_ACCESS_CODE}}" ;;
sit) URL="${SIT_TAPDATA_URL:-${TAPDATA_URL}}" ; CODE="${SIT_TAPDATA_ACCESS_CODE:-${TAPDATA_ACCESS_CODE}}" ;;
lpt) URL="${LPT_TAPDATA_URL:-${TAPDATA_URL}}" ; CODE="${LPT_TAPDATA_ACCESS_CODE:-${TAPDATA_ACCESS_CODE}}" ;;
aat) URL="${AAT_TAPDATA_URL:-${TAPDATA_URL}}" ; CODE="${AAT_TAPDATA_ACCESS_CODE:-${TAPDATA_ACCESS_CODE}}" ;;
prod) URL="${PROD_TAPDATA_URL:-${TAPDATA_URL}}" ; CODE="${PROD_TAPDATA_ACCESS_CODE:-${TAPDATA_ACCESS_CODE}}" ;;
*) URL="${TAPDATA_URL}" ; CODE="${TAPDATA_ACCESS_CODE}" ;;
esac
echo "tapdata_url=${URL}" >> "${GITHUB_OUTPUT}"
echo "::add-mask::${CODE}"
echo "tapdata_accesscode=${CODE}" >> "${GITHUB_OUTPUT}"
env:
TARGET_ENV: ${{ env.TARGET_ENV }}
TAPDATA_URL: ${{ vars.TAPDATA_URL }}
DEV_TAPDATA_URL: ${{ vars.DEV_TAPDATA_URL }}
SIT_TAPDATA_URL: ${{ vars.SIT_TAPDATA_URL }}
LPT_TAPDATA_URL: ${{ vars.LPT_TAPDATA_URL }}
AAT_TAPDATA_URL: ${{ vars.AAT_TAPDATA_URL }}
PROD_TAPDATA_URL: ${{ vars.PROD_TAPDATA_URL }}
TAPDATA_ACCESS_CODE: ${{ secrets.TAPDATA_ACCESS_CODE }}
DEV_TAPDATA_ACCESS_CODE: ${{ secrets.DEV_TAPDATA_ACCESS_CODE }}
SIT_TAPDATA_ACCESS_CODE: ${{ secrets.SIT_TAPDATA_ACCESS_CODE }}
LPT_TAPDATA_ACCESS_CODE: ${{ secrets.LPT_TAPDATA_ACCESS_CODE }}
AAT_TAPDATA_ACCESS_CODE: ${{ secrets.AAT_TAPDATA_ACCESS_CODE }}
PROD_TAPDATA_ACCESS_CODE: ${{ secrets.PROD_TAPDATA_ACCESS_CODE }}
- name: Get TapData Token
id: get_token
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/get-token.sh
env:
TARGET_ENV: ${{ env.TARGET_ENV }}
TAPDATA_URL: ${{ steps.resolve_config.outputs.tapdata_url }}
TAPDATA_ACCESSCODE: ${{ steps.resolve_config.outputs.tapdata_accesscode }}
- name: Generate vault.json with Connection Secrets
run: bash ${{ env.SCRIPT_ROOT }}/scripts/tapdata-deploy/generate-vault.sh
env:
REPO_ROOT: ${{ env.REPO_ROOT }}
PROJECT: ${{ env.PROJECT }}
ALL_SECRETS: ${{ toJSON(secrets) }}
ALL_VARS: ${{ toJSON(vars) }}
- name: Encrypt vault.json
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/vault-crypto.sh
env:
VAULT_FILE: ${{ env.PROJECT }}_tapdata_export/vault.json
VAULT_ENCRYPTION_KEY: ${{ secrets.VAULT_ENCRYPTION_KEY }}
CRYPTO_ACTION: encrypt
- name: Upload vault (artifact v4)
id: upload_vault_v4
if: vars.VAULT_TRANSPORT != 'local'
continue-on-error: true
uses: actions/upload-artifact@v4
with:
name: vault-and-export
path: |
${{ env.PROJECT }}_tapdata_export/vault.json
retention-days: 1
- name: Resolve vault transport
id: resolve_transport
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/resolve-vault-transport.sh
env:
FORCED_TRANSPORT: ${{ vars.VAULT_TRANSPORT }}
UPLOAD_V4_OUTCOME: ${{ steps.upload_vault_v4.outcome }}
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
VAULT_FILE: ${{ env.PROJECT }}_tapdata_export/vault.json
RUNNER_NAME: ${{ runner.name }}
# ============================================================
# Job 2: Deploy Users (no approval required)
# ============================================================
deploy-users:
name: Deploy Users
runs-on: [self-hosted, tapdata]
timeout-minutes: 15
needs: [preparation]
env:
PROJECT: ${{ needs.preparation.outputs.project }}
steps:
- name: Checkout Data Repository
uses: actions/checkout@v4
with:
repository: ${{ inputs.caller_repo != '' && inputs.caller_repo || github.repository }}
ref: ${{ inputs.caller_repo != '' && inputs.caller_sha || env.DEPLOY_REF }}
token: ${{ secrets.GH_DEPLOY_TOKEN }}
- name: Set Runtime Paths
run: |
if [[ -n "${{ inputs.caller_repo }}" ]]; then
echo "WORKER_REPO=${{ inputs.worker_repo }}" >> "$GITHUB_ENV"
echo "SCRIPT_ROOT=_worker" >> "$GITHUB_ENV"
echo "REPO_ROOT=${{ github.workspace }}" >> "$GITHUB_ENV"
else
echo "SCRIPT_ROOT=." >> "$GITHUB_ENV"
echo "REPO_ROOT=$(pwd)" >> "$GITHUB_ENV"
fi
- name: Checkout Worker Scripts
if: inputs.caller_repo != ''
uses: actions/checkout@v4
with:
repository: ${{ env.WORKER_REPO }}
ref: main
token: ${{ secrets.GH_DEPLOY_TOKEN }}
path: _worker
- name: Download vault (artifact v4)
if: needs.preparation.outputs.vault_transport == 'artifact-v4'
uses: actions/download-artifact@v4
with:
name: vault-and-export
path: ${{ env.PROJECT }}_tapdata_export
- name: Download vault (local file)
if: needs.preparation.outputs.vault_transport == 'local'
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/vault-transport.sh pull
env:
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
VAULT_FILE: ${{ env.PROJECT }}_tapdata_export/vault.json
PREP_RUNNER: ${{ needs.preparation.outputs.prep_runner }}
- name: Decrypt vault.json
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/vault-crypto.sh
env:
VAULT_FILE: ${{ env.PROJECT }}_tapdata_export/vault.json
VAULT_ENCRYPTION_KEY: ${{ secrets.VAULT_ENCRYPTION_KEY }}
CRYPTO_ACTION: decrypt
- name: Compress Export Files
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/compress-files.sh
env:
REPO_ROOT: ${{ env.REPO_ROOT }}
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
ARCHIVE_NAME: users-export.tar
PROJECT: ${{ env.PROJECT }}
- name: Import Users via TapData API
id: import_users
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/import-resource.sh users
env:
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
ARCHIVE_NAME: users-export.tar
TAPDATA_TOKEN: ${{ needs.preparation.outputs.tapdata_token }}
TAPDATA_URL: ${{ needs.preparation.outputs.tapdata_url }}
TARGET_ENV: ${{ env.TARGET_ENV }}
# ============================================================
# Job 3: Preview All Resources(并构建动态 deploy matrix)
# ============================================================
preview-all:
name: Preview All Resources
runs-on: [self-hosted, tapdata]
timeout-minutes: 10
needs: [preparation, deploy-users]
env:
PROJECT: ${{ needs.preparation.outputs.project }}
outputs:
# 仅供参考/调试:各资源是否有变更
connections_has_changes: ${{ steps.preview_connections.outputs.has_changes }}
migrate_tasks_has_changes: ${{ steps.preview_migrate_tasks.outputs.has_changes }}
sync_tasks_has_changes: ${{ steps.preview_sync_tasks.outputs.has_changes }}
apis_has_changes: ${{ steps.preview_apis.outputs.has_changes }}
indexes_has_changes: ${{ steps.preview_indexes.outputs.has_changes }}
# 核心:动态 matrix(只含有变更的资源)+ 是否存在任何变更
deploy_matrix: ${{ steps.build_matrix.outputs.deploy_matrix }}
has_any_changes: ${{ steps.build_matrix.outputs.has_any_changes }}
steps:
- name: Checkout Data Repository
uses: actions/checkout@v4
with:
repository: ${{ inputs.caller_repo != '' && inputs.caller_repo || github.repository }}
ref: ${{ inputs.caller_repo != '' && inputs.caller_sha || env.DEPLOY_REF }}
token: ${{ secrets.GH_DEPLOY_TOKEN }}
- name: Set Runtime Paths
run: |
if [[ -n "${{ inputs.caller_repo }}" ]]; then
echo "WORKER_REPO=${{ inputs.worker_repo }}" >> "$GITHUB_ENV"
echo "SCRIPT_ROOT=_worker" >> "$GITHUB_ENV"
echo "REPO_ROOT=${{ github.workspace }}" >> "$GITHUB_ENV"
else
echo "SCRIPT_ROOT=." >> "$GITHUB_ENV"
echo "REPO_ROOT=$(pwd)" >> "$GITHUB_ENV"
fi
- name: Checkout Worker Scripts
if: inputs.caller_repo != ''
uses: actions/checkout@v4
with:
repository: ${{ env.WORKER_REPO }}
ref: main
token: ${{ secrets.GH_DEPLOY_TOKEN }}
path: _worker
- name: Download vault (artifact v4)
if: needs.preparation.outputs.vault_transport == 'artifact-v4'
uses: actions/download-artifact@v4
with:
name: vault-and-export
path: ${{ env.PROJECT }}_tapdata_export
- name: Download vault (local file)
if: needs.preparation.outputs.vault_transport == 'local'
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/vault-transport.sh pull
env:
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
VAULT_FILE: ${{ env.PROJECT }}_tapdata_export/vault.json
PREP_RUNNER: ${{ needs.preparation.outputs.prep_runner }}
- name: Decrypt vault.json
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/vault-crypto.sh
env:
VAULT_FILE: ${{ env.PROJECT }}_tapdata_export/vault.json
VAULT_ENCRYPTION_KEY: ${{ secrets.VAULT_ENCRYPTION_KEY }}
CRYPTO_ACTION: decrypt
- name: Compress Connections Export Files
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/compress-files.sh
env:
REPO_ROOT: ${{ env.REPO_ROOT }}
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
ARCHIVE_NAME: connections-export.tar
PROJECT: ${{ env.PROJECT }}
- name: Preview Connections
id: preview_connections
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/preview-resource.sh connections
env:
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
ARCHIVE_NAME: connections-export.tar
TAPDATA_TOKEN: ${{ needs.preparation.outputs.tapdata_token }}
TAPDATA_URL: ${{ needs.preparation.outputs.tapdata_url }}
TARGET_ENV: ${{ env.TARGET_ENV }}
- name: Compress Tasks Export Files
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/compress-files.sh
env:
REPO_ROOT: ${{ env.REPO_ROOT }}
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
ARCHIVE_NAME: tasks-export.tar
PROJECT: ${{ env.PROJECT }}
- name: Preview Data Replication
id: preview_migrate_tasks
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/preview-resource.sh migrate/tasks
env:
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
ARCHIVE_NAME: tasks-export.tar
TAPDATA_TOKEN: ${{ needs.preparation.outputs.tapdata_token }}
TAPDATA_URL: ${{ needs.preparation.outputs.tapdata_url }}
TARGET_ENV: ${{ env.TARGET_ENV }}
- name: Preview Data Transformation
id: preview_sync_tasks
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/preview-resource.sh sync/tasks
env:
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
ARCHIVE_NAME: tasks-export.tar
TAPDATA_TOKEN: ${{ needs.preparation.outputs.tapdata_token }}
TAPDATA_URL: ${{ needs.preparation.outputs.tapdata_url }}
TARGET_ENV: ${{ env.TARGET_ENV }}
- name: Compress APIs Export Files
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/compress-files.sh
env:
REPO_ROOT: ${{ env.REPO_ROOT }}
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
ARCHIVE_NAME: apis-export.tar
PROJECT: ${{ env.PROJECT }}
- name: Preview APIs
id: preview_apis
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/preview-resource.sh apis
env:
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
ARCHIVE_NAME: apis-export.tar
TAPDATA_TOKEN: ${{ needs.preparation.outputs.tapdata_token }}
TAPDATA_URL: ${{ needs.preparation.outputs.tapdata_url }}
TARGET_ENV: ${{ env.TARGET_ENV }}
# TAP-12057: index declarations ride inside the API package, so this reuses
# apis-export.tar (compress tars the whole export dir; no extra archive needed).
# Dry-run only -- it reads the target database and lists what would be created.
- name: Preview Serving Indexes
id: preview_indexes
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/preview-resource.sh indexes
env:
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
ARCHIVE_NAME: apis-export.tar
TAPDATA_TOKEN: ${{ needs.preparation.outputs.tapdata_token }}
TAPDATA_URL: ${{ needs.preparation.outputs.tapdata_url }}
TARGET_ENV: ${{ env.TARGET_ENV }}
# 核心新增步骤:根据各 preview 的 has_changes,按固定部署顺序
# (connections → FDM → MDM → APIs → Serving Indexes)拼出只含"有变更资源"的 matrix。
# 输出:
# deploy_matrix = {"include":[ {key,label,archive,import_arg}, ... ]}
# has_any_changes = true | false
# 空数组也输出为合法 JSON,配合下游 deploy job 的 if 守卫,避免空 matrix 报错。
- name: Build Deploy Matrix
id: build_matrix
run: |
set -euo pipefail
INCLUDE='[]'
N=0
# order 字段:按 add 调用顺序(= 真实执行顺序)递增编号。仅用于 GitHub 运行图
# 的显示排序——GitHub 把 matrix 腿按 job 名称字母序排列,给名字加数字前缀后,
# 字母序即等于 max-parallel:1 的执行序(connections→FDM→MDM→APIs)。
add() {
N=$((N+1))
INCLUDE=$(jq -c --argjson e "$1" --arg o "$N" '. + [($e + {order: $o})]' <<<"$INCLUDE")
}
if [[ "${{ steps.preview_connections.outputs.has_changes }}" == "true" ]]; then
add '{"key":"connections","label":"Connections","archive":"connections-export.tar","import_arg":"connections"}'
fi
if [[ "${{ steps.preview_migrate_tasks.outputs.has_changes }}" == "true" ]]; then
add '{"key":"migrate-tasks","label":"Data Replication","archive":"tasks-export.tar","import_arg":"migrate/tasks"}'
fi
if [[ "${{ steps.preview_sync_tasks.outputs.has_changes }}" == "true" ]]; then
add '{"key":"sync-tasks","label":"Data Transformation","archive":"tasks-export.tar","import_arg":"sync/tasks"}'
fi
if [[ "${{ steps.preview_apis.outputs.has_changes }}" == "true" ]]; then
add '{"key":"apis","label":"APIs","archive":"apis-export.tar","import_arg":"apis"}'
fi
# TAP-12057: 索引腿排在 apis 之后——声明随 Module 走,Module 必须先落地。
# 独立成腿而非塞进 apis:建索引是整条流水线里唯一昂贵、且在已有数据的集合上
# 带风险的操作,必须在部署计划表里显式可见、可单独审阅。
if [[ "${{ steps.preview_indexes.outputs.has_changes }}" == "true" ]]; then
add '{"key":"indexes","label":"Serving Indexes","archive":"apis-export.tar","import_arg":"indexes"}'
fi
COUNT=$(jq 'length' <<<"$INCLUDE")
MATRIX=$(jq -c -n --argjson inc "$INCLUDE" '{include: $inc}')
if [[ "$COUNT" -eq 0 ]]; then
echo "has_any_changes=false" >> "$GITHUB_OUTPUT"
else
echo "has_any_changes=true" >> "$GITHUB_OUTPUT"
fi
echo "deploy_matrix=${MATRIX}" >> "$GITHUB_OUTPUT"
echo "=== Build Deploy Matrix ==="
echo "Changed resources: ${COUNT}"
echo "deploy_matrix=${MATRIX}"
# Write the "will deploy / skip" list for this run to the run's Summary page to further reduce confusion.
- name: Write Deploy Plan Summary
run: |
{
echo "## Deploy Plan (what this run will actually execute)"
echo ""
echo "| Resource | Status |"
echo "| --- | --- |"
echo "| Connections | $([[ '${{ steps.preview_connections.outputs.has_changes }}' == 'true' ]] && echo '✅ Will deploy' || echo '— No changes, skipped') |"
echo "| Data Replication | $([[ '${{ steps.preview_migrate_tasks.outputs.has_changes }}' == 'true' ]] && echo '✅ Will deploy' || echo '— No changes, skipped') |"
echo "| Data Transformation | $([[ '${{ steps.preview_sync_tasks.outputs.has_changes }}' == 'true' ]] && echo '✅ Will deploy' || echo '— No changes, skipped') |"
# TAP-12057: a serving-index declaration lives inside the Module document, so ticking an
# index checkbox shows up here as an API change and must still be imported. Without the
# note the approver reads "APIs — Will deploy" as "the API itself changed". The note is
# computed in preview-resource.sh (all-vs-partial split lives there, with tests).
echo "| APIs | $([[ '${{ steps.preview_apis.outputs.has_changes }}' == 'true' ]] && echo '✅ Will deploy ${{ steps.preview_apis.outputs.index_only_note }}' || echo '— No changes, skipped') |"
echo "| Serving Indexes | $([[ '${{ steps.preview_indexes.outputs.has_changes }}' == 'true' ]] && echo '✅ Will deploy' || echo '— No changes, skipped') |"
echo ""
echo "> Only resources marked \"Will deploy\" appear in the run graph; resources with no changes are no longer shown as a greyed-out \"Skipped\" node."
} >> "$GITHUB_STEP_SUMMARY"
# ============================================================
# Job 4: Deploy(统一动态 matrix,替代原 connections/FDM/MDM/APIs 四个 job)
# - environment: deploy → 单次人工审批覆盖所有 matrix 腿
# - max-parallel: 1 + 按 connections→FDM→MDM→APIs 顺序 → 严格串行
# - fail-fast: true → 某腿失败即取消后续腿(等价于原依赖链"出错即停")
# - 只有有变更的资源才会成为 matrix 腿 → 运行图里看不到"已跳过"节点
# ============================================================
deploy:
name: Deploy ${{ matrix.order }}. ${{ matrix.label }}
runs-on: [self-hosted, tapdata]
timeout-minutes: 15
environment: deploy
needs: [preparation, preview-all]
if: needs.preview-all.outputs.has_any_changes == 'true'
strategy:
max-parallel: 1
fail-fast: true
matrix: ${{ fromJSON(needs.preview-all.outputs.deploy_matrix) }}
env:
PROJECT: ${{ needs.preparation.outputs.project }}
steps:
- name: Checkout Data Repository
uses: actions/checkout@v4
with:
repository: ${{ inputs.caller_repo != '' && inputs.caller_repo || github.repository }}
ref: ${{ inputs.caller_repo != '' && inputs.caller_sha || env.DEPLOY_REF }}
token: ${{ secrets.GH_DEPLOY_TOKEN }}
- name: Set Runtime Paths
run: |
if [[ -n "${{ inputs.caller_repo }}" ]]; then
echo "WORKER_REPO=${{ inputs.worker_repo }}" >> "$GITHUB_ENV"
echo "SCRIPT_ROOT=_worker" >> "$GITHUB_ENV"
echo "REPO_ROOT=${{ github.workspace }}" >> "$GITHUB_ENV"
else
echo "SCRIPT_ROOT=." >> "$GITHUB_ENV"
echo "REPO_ROOT=$(pwd)" >> "$GITHUB_ENV"
fi
- name: Checkout Worker Scripts
if: inputs.caller_repo != ''
uses: actions/checkout@v4
with:
repository: ${{ env.WORKER_REPO }}
ref: main
token: ${{ secrets.GH_DEPLOY_TOKEN }}
path: _worker
- name: Download vault (artifact v4)
if: needs.preparation.outputs.vault_transport == 'artifact-v4'
uses: actions/download-artifact@v4
with:
name: vault-and-export
path: ${{ env.PROJECT }}_tapdata_export
- name: Download vault (local file)
if: needs.preparation.outputs.vault_transport == 'local'
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/vault-transport.sh pull
env:
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
VAULT_FILE: ${{ env.PROJECT }}_tapdata_export/vault.json
PREP_RUNNER: ${{ needs.preparation.outputs.prep_runner }}
- name: Decrypt vault.json
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/vault-crypto.sh
env:
VAULT_FILE: ${{ env.PROJECT }}_tapdata_export/vault.json
VAULT_ENCRYPTION_KEY: ${{ secrets.VAULT_ENCRYPTION_KEY }}
CRYPTO_ACTION: decrypt
- name: Compress Export Files
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/compress-files.sh
env:
REPO_ROOT: ${{ env.REPO_ROOT }}
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
ARCHIVE_NAME: ${{ matrix.archive }}
PROJECT: ${{ env.PROJECT }}
- name: Import ${{ matrix.label }} via TapData API
id: import_resource
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/import-resource.sh ${{ matrix.import_arg }}
env:
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
ARCHIVE_NAME: ${{ matrix.archive }}
TAPDATA_TOKEN: ${{ needs.preparation.outputs.tapdata_token }}
TAPDATA_URL: ${{ needs.preparation.outputs.tapdata_url }}
TARGET_ENV: ${{ env.TARGET_ENV }}
# ============================================================
# Job 5: Deploy Group Info(始终收尾)
# ============================================================
deploy-group-info:
name: Deploy Group Info
runs-on: [self-hosted, tapdata]
timeout-minutes: 15
needs: [preparation, preview-all, deploy]
env:
PROJECT: ${{ needs.preparation.outputs.project }}
if: >-
!cancelled()
&& needs.preparation.result == 'success'
&& needs.preview-all.result == 'success'
&& (needs.deploy.result == 'success' || needs.deploy.result == 'skipped' || needs.deploy.result == 'cancelled')
outputs:
changed_groupInfo: ${{ steps.import_group_info.outputs.changed_groupInfo }}
steps:
- name: Checkout Data Repository
uses: actions/checkout@v4
with:
repository: ${{ inputs.caller_repo != '' && inputs.caller_repo || github.repository }}
ref: ${{ inputs.caller_repo != '' && inputs.caller_sha || env.DEPLOY_REF }}
token: ${{ secrets.GH_DEPLOY_TOKEN }}
- name: Set Runtime Paths
run: |
if [[ -n "${{ inputs.caller_repo }}" ]]; then
echo "WORKER_REPO=${{ inputs.worker_repo }}" >> "$GITHUB_ENV"
echo "SCRIPT_ROOT=_worker" >> "$GITHUB_ENV"
echo "REPO_ROOT=${{ github.workspace }}" >> "$GITHUB_ENV"
else
echo "SCRIPT_ROOT=." >> "$GITHUB_ENV"
echo "REPO_ROOT=$(pwd)" >> "$GITHUB_ENV"
fi
- name: Checkout Worker Scripts
if: inputs.caller_repo != ''
uses: actions/checkout@v4
with:
repository: ${{ env.WORKER_REPO }}
ref: main
token: ${{ secrets.GH_DEPLOY_TOKEN }}
path: _worker
- name: Download vault (artifact v4)
if: needs.preparation.outputs.vault_transport == 'artifact-v4'
uses: actions/download-artifact@v4
with:
name: vault-and-export
path: ${{ env.PROJECT }}_tapdata_export
- name: Download vault (local file)
if: needs.preparation.outputs.vault_transport == 'local'
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/vault-transport.sh pull
env:
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
VAULT_FILE: ${{ env.PROJECT }}_tapdata_export/vault.json
PREP_RUNNER: ${{ needs.preparation.outputs.prep_runner }}
- name: Decrypt vault.json
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/vault-crypto.sh
env:
VAULT_FILE: ${{ env.PROJECT }}_tapdata_export/vault.json
VAULT_ENCRYPTION_KEY: ${{ secrets.VAULT_ENCRYPTION_KEY }}
CRYPTO_ACTION: decrypt
- name: Compress Export Files
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/compress-files.sh
env:
REPO_ROOT: ${{ env.REPO_ROOT }}
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
ARCHIVE_NAME: groupInfo-export.tar
PROJECT: ${{ env.PROJECT }}
- name: Import Group Info via TapData API
id: import_group_info
run: bash ${{ env.SCRIPT_ROOT }}/scripts/common/import-resource.sh groupInfo
env:
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}
ARCHIVE_NAME: groupInfo-export.tar
TAPDATA_TOKEN: ${{ needs.preparation.outputs.tapdata_token }}
TAPDATA_URL: ${{ needs.preparation.outputs.tapdata_url }}
TARGET_ENV: ${{ env.TARGET_ENV }}
# ============================================================
# Job 6: Cleanup (always runs — removes vault credentials from runner /tmp)
# ============================================================
cleanup:
name: Cleanup
runs-on: [self-hosted, tapdata]
timeout-minutes: 5
needs:
- preparation
- deploy-users
- preview-all
- deploy
- deploy-group-info
if: always()
steps:
- name: Remove deploy temp dir (credentials)
run: |
if [[ -n "${DEPLOY_DIR:-}" ]]; then
rm -rf "${DEPLOY_DIR}"
echo "Removed ${DEPLOY_DIR}"
else
echo "DEPLOY_DIR empty, nothing to clean"
fi
env:
DEPLOY_DIR: ${{ env.DEPLOY_DIR }}