From 9b3687e189857b7a5220bbeba93f16f7786117ce Mon Sep 17 00:00:00 2001 From: Haitao Pan Date: Thu, 25 Jun 2026 11:48:47 +0800 Subject: [PATCH] =?UTF-8?q?fix(ci):=20=E6=B6=88=E9=99=A4=20workflow=20?= =?UTF-8?q?=E6=89=80=E6=9C=89=20heredoc=EF=BC=8C=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E5=A4=96=E7=BD=AE=E8=84=9A=E6=9C=AC=E8=B0=83=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 Configure remote backend 步骤的 shell heredoc(导致 YAML L191 语法错误) - 新增 scripts/render_backend_tf.py 外置脚本,接受 TF_STATE_ENDPOINT env 渲染 backend.tf - provision job 新增 Checkout xworkspace-console 步骤,确保 scripts/ 在 runner 可用 - 新增 CLAUDE.md,明确禁止 workflow 内嵌 heredoc(shell/python),要求外置脚本 Co-Authored-By: Claude Sonnet 4.6 --- .../workflows/deploy-ai-workspace-iac.yaml | 23 +++------ CLAUDE.md | 49 +++++++++++++++++++ scripts/render_backend_tf.py | 38 ++++++++++++++ 3 files changed, 93 insertions(+), 17 deletions(-) create mode 100644 CLAUDE.md create mode 100644 scripts/render_backend_tf.py diff --git a/.github/workflows/deploy-ai-workspace-iac.yaml b/.github/workflows/deploy-ai-workspace-iac.yaml index 73ed635..dc4f611 100644 --- a/.github/workflows/deploy-ai-workspace-iac.yaml +++ b/.github/workflows/deploy-ai-workspace-iac.yaml @@ -153,6 +153,11 @@ jobs: fi [ "$missing" -eq 0 ] || { echo "::error::必需机密缺失,终止 provision"; exit 1; } + - name: Checkout xworkspace-console (scripts) + uses: actions/checkout@v4 + with: + path: xw-console + - name: Checkout iac_modules uses: actions/checkout@v4 with: @@ -183,23 +188,7 @@ jobs: working-directory: ${{ env.ENV_DIR }} env: TF_STATE_ENDPOINT: ${{ steps.vault.outputs.TF_STATE_ENDPOINT }} - run: | - set -euo pipefail - # endpoints 块只能在 HCL 里配置,无法通过 -backend-config flag 传递。 - # 用非引号 heredoc 将 endpoint URL 展开写入 backend.tf。 - cat > backend.tf << TFEOF - terraform { - backend "s3" { - endpoints = { s3 = "${TF_STATE_ENDPOINT}" } - skip_credentials_validation = true - skip_region_validation = true - skip_requesting_account_id = true - skip_metadata_api_check = true - skip_s3_checksum = true - use_path_style = true - } - } - TFEOF + run: python3 $GITHUB_WORKSPACE/xw-console/scripts/render_backend_tf.py backend.tf - name: generate.py render (YAML -> 显式 HCL + tfvars) working-directory: ${{ env.VPS_ROOT }} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b38a4cb --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,49 @@ +# xworkspace-console — Agent 规范 + +## GitHub Actions Workflow 编写规则 + +### 禁止内嵌脚本(shell heredoc / python heredoc) + +**严禁**在 `.github/workflows/` 的 `run:` 块里使用任何内嵌 heredoc: + +```yaml +# ❌ 禁止 — shell heredoc +run: | + cat > file.tf << EOF + content + EOF + +# ❌ 禁止 — python 内联 heredoc +run: | + python3 - <<'PYEOF' + import os + ... + PYEOF +``` + +**原因:** +- Shell heredoc 内容从列 1 开始,超出 YAML literal block 缩进范围,导致整个 workflow 文件 YAML 解析失败,GitHub 丢失 `on:` 触发器。 +- Python 内联 heredoc 同理,且难以维护和测试。 + +**正确做法:外置脚本,workflow 只做调用。** + +```yaml +# ✅ 正确 — 外置 Python 脚本 +- name: Checkout xworkspace-console (scripts) + uses: actions/checkout@v4 + with: + path: xw-console + +- name: Configure remote backend + env: + TF_STATE_ENDPOINT: ${{ steps.vault.outputs.TF_STATE_ENDPOINT }} + run: python3 $GITHUB_WORKSPACE/xw-console/scripts/render_backend_tf.py backend.tf +``` + +脚本存放在 `scripts/` 目录,命名规范 `动词_名词.py` 或 `动词-名词.sh`。 + +### 其他规范 + +- workflow 使用的外置脚本必须在 `scripts/` 目录下,不得内嵌在 `run:` 块里。 +- workflow 文件修改后必须用 `python3 -c "import yaml; yaml.safe_load(open(...))"` 验证 YAML 语法再提交。 +- 不使用 GitHub Actions Secrets,所有机密统一从 Vault (https://vault.svc.plus) OIDC 读取。 diff --git a/scripts/render_backend_tf.py b/scripts/render_backend_tf.py new file mode 100644 index 0000000..84c228a --- /dev/null +++ b/scripts/render_backend_tf.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +""" +渲染 Terraform S3 backend 配置文件(backend.tf)。 + +用法: + TF_STATE_ENDPOINT=https://... python3 render_backend_tf.py [output_path] + +默认输出到当前目录的 backend.tf(terraform init 的 working-directory 里执行)。 +""" +import os +import sys + +endpoint = os.environ.get("TF_STATE_ENDPOINT", "") +if not endpoint: + print("ERROR: TF_STATE_ENDPOINT is not set", file=sys.stderr) + sys.exit(1) + +output = sys.argv[1] if len(sys.argv) > 1 else "backend.tf" + +content = f"""\ +terraform {{ + backend "s3" {{ + endpoints = {{ s3 = "{endpoint}" }} + skip_credentials_validation = true + skip_region_validation = true + skip_requesting_account_id = true + skip_metadata_api_check = true + skip_s3_checksum = true + use_path_style = true + }} +}} +""" + +with open(output, "w") as f: + f.write(content) + +print(f"backend.tf written to {output}") +print(f" endpoint = {endpoint[:40]}...")