返回 CodeWhale
sync-cnb.yml
根目录 / .github / workflows / sync-cnb.yml
1 name: Sync to CNB
2
3 # Mirror commits and release tags to cnb.cool/codewhale.net/codewhale
4 # so users behind GitHub-blocking networks can fetch the source and tagged
5 # releases from the Tencent-hosted mirror.
6 #
7 # Triggers:
8 # * push to main → mirrors that commit to CNB main
9 # * successful Release job → mirrors the published tag to CNB
10 # * release work branches → mirrors release-candidate refs for CNB preflight
11 # * fix/rebrand branches → mirrors first-party heavy Linux CI refs
12 # * Tencent release branches → mirrors Feishu/Lighthouse setup branches
13 # * workflow_dispatch → manual fallback if any of the above fails
14 #
15 # Why the rewrite (v0.8.31):
16 # The previous implementation used the opaque tencentcom/git-sync Docker
17 # action, which discovered every local ref via fetch-depth: 0 and tried
18 # to push them all — including dependabot/* branches that GitHub had
19 # locally. Those follow-on pushes ran without the configured credential
20 # helper in scope and failed with
21 # `fatal: could not read Username for 'https://cnb.cool'`
22 # No concurrency block meant the main-push and tag-push workflow runs
23 # that auto-tag.yml fires within seconds of each other raced. About
24 # half of recent runs failed for those two reasons combined.
25
26 on:
27 push:
28 branches:
29 - main
30 - 'work/v*'
31 - 'fix/*'
32 - 'rebrand/*'
33 - 'work/v*-feishu-*'
34 - 'work/v*-lighthouse*'
35 workflow_dispatch: {}
36 workflow_call:
37 inputs:
38 release_tag:
39 required: true
40 type: string
41 source_sha:
42 required: true
43 type: string
44 secrets:
45 CNB_GIT_TOKEN:
46 required: true
47
48 # Serialize runs per ref. One global group was wrong: with
49 # cancel-in-progress: false GitHub still keeps only ONE pending run per group
50 # and cancels it when a newer one queues, so a release tag sync waiting behind
51 # a main push was dropped by the next branch push. Keyed by ref, each tag gets
52 # its own group (never superseded), and a pending branch sync is only ever
53 # replaced by a newer push of the same branch, whose force-push supersedes it.
54 # The main and tag pushes from auto-tag.yml write different CNB refs, so
55 # running them concurrently cannot conflict.
56 concurrency:
57 group: cnb-sync-${{ github.ref }}
58 cancel-in-progress: false
59
60 permissions:
61 contents: read
62
63 jobs:
64 sync:
65 runs-on: ubuntu-latest
66 timeout-minutes: 15
67 steps:
68 - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
69 with:
70 fetch-depth: 0
71 ref: ${{ inputs.source_sha || github.sha }}
72
73 - name: Push triggering ref to CNB
74 env:
75 CNB_TOKEN: ${{ secrets.CNB_GIT_TOKEN }}
76 GH_TOKEN: ${{ github.token }}
77 RELEASE_TAG: ${{ inputs.release_tag }}
78 SOURCE_SHA: ${{ inputs.source_sha || github.sha }}
79 shell: bash
80 run: |
81 set -euo pipefail
82
83 if [ -z "${CNB_TOKEN:-}" ]; then
84 echo "::error::CNB_GIT_TOKEN secret is not set; cannot push to CNB." >&2
85 exit 1
86 fi
87
88 # URL-encode any '%' in the token so basic-auth doesn't break on
89 # special characters. CNB tokens are typically alphanumeric so
90 # this is belt-and-suspenders.
91 ENCODED_TOKEN="$(printf '%s' "${CNB_TOKEN}" | python3 -c 'import sys, urllib.parse; print(urllib.parse.quote(sys.stdin.read(), safe=""))')"
92 REMOTE_URL="https://cnb:${ENCODED_TOKEN}@cnb.cool/codewhale.net/codewhale.git"
93 # Use a masked alias so the token never appears in log lines.
94 git remote add cnb "${REMOTE_URL}"
95
96 # Push with retry on transient failures (CNB rate-limits, DNS
97 # blips, etc.). Args after `kind` are forwarded to `git push`
98 # so callers can pass `--force-with-lease`, multiple refspecs,
99 # etc. without quoting them into one string.
100 push_with_retry() {
101 local kind="$1"
102 shift
103 local attempt
104 for attempt in 1 2 3; do
105 echo "Attempt ${attempt}: pushing ${kind} to CNB"
106 if git push cnb "$@" 2>&1; then
107 echo "Successfully pushed ${kind} to CNB"
108 return 0
109 fi
110 if [ "${attempt}" -lt 3 ]; then
111 sleep $((attempt * 5))
112 fi
113 done
114 echo "::error::Failed to push ${kind} to CNB after 3 attempts" >&2
115 return 1
116 }
117
118 if [[ -n "${RELEASE_TAG}" || "${GITHUB_REF}" == refs/tags/* ]]; then
119 TAG="${RELEASE_TAG:-${GITHUB_REF#refs/tags/}}"
120 # The tag starts CNB's public release pipeline. Require the
121 # canonical release even for a manual recovery dispatch.
122 if ! [[ "${TAG}" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] ||
123 [[ "$(git rev-parse 'HEAD^{commit}')" != "${SOURCE_SHA}" ]]; then
124 echo "::error::CNB release source does not match the frozen tag input." >&2
125 exit 1
126 fi
127 ./scripts/release/verify-remote-tag.sh \
128 "https://github.com/${GITHUB_REPOSITORY}.git" "${TAG}" "${SOURCE_SHA}"
129 node scripts/release/verify-release-inventory.js --manifest "${GITHUB_REPOSITORY}" "${TAG}"
130 push_with_retry "tag ${TAG}" "refs/tags/${TAG}:refs/tags/${TAG}"
131 elif [[ "${GITHUB_REF}" == refs/heads/main ]]; then
132 # Plain --force. The CNB mirror is one-way by design —
133 # nothing else pushes to it, so there's no contributor work
134 # to protect against. `--force-with-lease` would be safer
135 # in a multi-writer scenario, but in our setup the lease
136 # check requires `refs/remotes/cnb/main` to exist in the
137 # runner's local clone, which it never does (we add `cnb`
138 # as a fresh remote in this step and don't fetch first).
139 # That made the lease check spuriously fail with
140 # `! [rejected] HEAD -> main (stale info)` even when CNB
141 # was actually behind GitHub.
142 push_with_retry "main" HEAD:refs/heads/main --force
143 else
144 # First-party fix/rebrand/release branches are first-class CNB
145 # sources for heavy Linux CI, release preflight, and
146 # Lighthouse/Feishu bootstrap.
147 # Mirror the triggering branch exactly so the CNB clone path stays
148 # useful before the branch has merged to main or become a release
149 # tag.
150 BRANCH="${GITHUB_REF#refs/heads/}"
151 push_with_retry "branch ${BRANCH}" "HEAD:refs/heads/${BRANCH}" --force
152 fi
153
153 lines YAML