renovate-app-version.yml
· 7.8 KiB · YAML
Brut
name: Update app version in Renovate Branches
on:
push:
branches: [ 'renovate/*' ]
workflow_dispatch:
inputs:
manual-trigger:
description: 'Manually trigger Renovate'
default: ''
# 并发控制:确保同一时间只有一个 PR 合并操作在进行,避免竞争条件
concurrency:
group: merge-renovate-pr
cancel-in-progress: false
jobs:
update-app-version:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Check if triggered by self (prevent circular trigger)
id: check-circular
run: |
# 检查最后一个 commit 的 author,如果是自己则跳过
last_author=$(git log -1 --format='%an')
last_message=$(git log -1 --format='%s')
echo "Last commit author: $last_author"
echo "Last commit message: $last_message"
# 如果是 github-action 自己 commit 的,说明是工作流触发的,跳过避免循环
if [[ "$last_author" == "github-action update-app-version" ]] || \
[[ "$last_author" == "github-action[bot]" ]] || \
[[ "$last_message" == *"[auto-version-bump]"* ]]; then
echo "⚠️ Skipping: triggered by own commit (preventing circular trigger)"
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "✅ Proceeding: triggered by external commit"
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Configure git
if: steps.check-circular.outputs.skip != 'true'
run: |
git config --local user.email "githubaction@githubaction.com"
git config --local user.name "github-action update-app-version"
- name: Get list of updated files by the last commit
if: steps.check-circular.outputs.skip != 'true'
id: updated-files
run: |
echo "files=$(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | tr '\n' ' ')" >> $GITHUB_OUTPUT
- name: Run renovate-app-version.sh on updated files
if: steps.check-circular.outputs.skip != 'true'
id: rename
run: |
set -e
chmod +x .github/workflows/renovate-app-version.sh
files="${{ steps.updated-files.outputs.files }}"
declare -a changed_apps=()
echo "Updated files: $files"
for file in $files; do
if [[ $file == *"docker-compose.yml"* ]]; then
echo "Processing file: $file"
app_name=$(echo $file | cut -d'/' -f 2)
old_version=$(echo $file | cut -d'/' -f 3)
echo "App name: $app_name, old version: $old_version"
# 获取所有服务名
services=$(yq '.services | keys | .[]' "$file")
service=""
image_line=""
for s in $services; do
# 通过awk获取服务下的image行(包含注释)
image_line=$(awk "/services:/{flag=0} /^\s*$s:/{flag=1} flag && /^\s*image:/{print; exit}" "$file")
echo "Service $s image line: $image_line"
if [[ "$image_line" != *"[ignore]"* ]]; then
service="$s"
break
else
echo "Skipping service $s due to [ignore]"
fi
done
if [[ -z "$service" ]]; then
echo "No valid service found in $file, skipping..."
continue
fi
# 提取image纯字符串,去除注释和多余空格
image=$(echo "$image_line" | sed -E 's/^\s*image:\s*([^ #]+).*/\1/')
echo "Selected service: $service"
echo "Extracted image: $image"
if [[ "$image" == *":"* ]]; then
new_version=$(cut -d ":" -f2- <<< "$image")
trimmed_version=${new_version/#"v"/}
echo "Parsed new version: $trimmed_version"
else
trimmed_version=""
echo "No version tag found in image."
fi
changed_apps+=("${app_name}:${old_version}:${trimmed_version}")
echo "Calling renovate-app-version.sh with: $app_name, $old_version, $trimmed_version"
.github/workflows/renovate-app-version.sh "$app_name" "$old_version" "$trimmed_version"
fi
done
echo "All changed apps: ${changed_apps[*]}"
echo "apps=$(IFS=, ; echo "${changed_apps[*]}")" >> $GITHUB_OUTPUT
- name: Commit & Push Changes
if: steps.check-circular.outputs.skip != 'true'
run: |
set -e
IFS=',' read -r -a apps <<< "${{ steps.rename.outputs.apps }}"
for item in "${apps[@]}"; do
app_name=$(cut -d':' -f1 <<< "$item")
old_version=$(cut -d':' -f2 <<< "$item")
new_version=$(cut -d':' -f3 <<< "$item")
if [[ -n "$app_name" && -n "$new_version" ]]; then
git add "apps/$app_name/*"
git commit -m "📈将应用 $app_name 的版本从 $old_version 升级到 $new_version" --no-verify || echo "无内容可提交"
fi
done
git push || echo "无内容可推送"
- name: Rebase to latest main and merge PR
if: steps.check-circular.outputs.skip != 'true' && github.ref_name != 'main'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -e
branch_name=$(git rev-parse --abbrev-ref HEAD)
echo "Current branch: $branch_name"
# 获取 PR 编号
pr_number=$(gh pr list --state open --head "$branch_name" --json number -q '.[0].number')
if [ -z "$pr_number" ]; then
echo "No PR found for branch $branch_name"
exit 0
fi
echo "Found PR #$pr_number, preparing to merge..."
# Fetch 最新的 main 分支
echo "Fetching latest main branch..."
git fetch origin main
# Rebase 当前分支到最新的 main
# 由于 renovate PR 修改的是不同的应用文件夹,不会有真正的冲突
echo "Rebasing $branch_name onto origin/main..."
git rebase origin/main
# Force push 更新后的分支
echo "Force pushing rebased branch..."
git push --force-with-lease
# 等待 GitHub 重新计算 mergeable 状态
echo "Waiting for GitHub to recalculate mergeable status..."
sleep 15
# 检查 mergeable 状态,最多等待 2 分钟
max_attempts=12
attempt=0
while [ $attempt -lt $max_attempts ]; do
mergeable=$(gh pr view "$pr_number" --json mergeable -q '.mergeable' 2>/dev/null || echo "UNKNOWN")
echo "Mergeable status: $mergeable (attempt $((attempt+1))/$max_attempts)"
if [ "$mergeable" = "MERGEABLE" ]; then
echo "PR is mergeable!"
break
fi
# 如果是 CONFLICTING,说明有真正的冲突(不应该发生)
if [ "$mergeable" = "CONFLICTING" ]; then
echo "Warning: PR has conflicts, but proceeding with admin merge..."
break
fi
sleep 10
attempt=$((attempt+1))
done
# 合并 PR
echo "Merging PR #$pr_number..."
gh pr merge "$pr_number" --merge --delete-branch --admin
echo "Successfully merged PR #$pr_number!"
- name: Trigger sync to CNB mirror
if: steps.check-circular.outputs.skip != 'true' && github.ref_name != 'main'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo "Triggering sync-to-cnb workflow..."
gh workflow run sync-to-cnb.yml --ref main
echo "Sync workflow triggered successfully!"
| 1 | name: Update app version in Renovate Branches |
| 2 | |
| 3 | on: |
| 4 | push: |
| 5 | branches: [ 'renovate/*' ] |
| 6 | workflow_dispatch: |
| 7 | inputs: |
| 8 | manual-trigger: |
| 9 | description: 'Manually trigger Renovate' |
| 10 | default: '' |
| 11 | |
| 12 | # 并发控制:确保同一时间只有一个 PR 合并操作在进行,避免竞争条件 |
| 13 | concurrency: |
| 14 | group: merge-renovate-pr |
| 15 | cancel-in-progress: false |
| 16 | |
| 17 | jobs: |
| 18 | update-app-version: |
| 19 | runs-on: ubuntu-latest |
| 20 | steps: |
| 21 | - name: Checkout |
| 22 | uses: actions/checkout@v5 |
| 23 | with: |
| 24 | fetch-depth: 0 |
| 25 | |
| 26 | - name: Check if triggered by self (prevent circular trigger) |
| 27 | id: check-circular |
| 28 | run: | |
| 29 | # 检查最后一个 commit 的 author,如果是自己则跳过 |
| 30 | last_author=$(git log -1 --format='%an') |
| 31 | last_message=$(git log -1 --format='%s') |
| 32 | |
| 33 | echo "Last commit author: $last_author" |
| 34 | echo "Last commit message: $last_message" |
| 35 | |
| 36 | # 如果是 github-action 自己 commit 的,说明是工作流触发的,跳过避免循环 |
| 37 | if [[ "$last_author" == "github-action update-app-version" ]] || \ |
| 38 | [[ "$last_author" == "github-action[bot]" ]] || \ |
| 39 | [[ "$last_message" == *"[auto-version-bump]"* ]]; then |
| 40 | echo "⚠️ Skipping: triggered by own commit (preventing circular trigger)" |
| 41 | echo "skip=true" >> $GITHUB_OUTPUT |
| 42 | else |
| 43 | echo "✅ Proceeding: triggered by external commit" |
| 44 | echo "skip=false" >> $GITHUB_OUTPUT |
| 45 | fi |
| 46 | |
| 47 | - name: Configure git |
| 48 | if: steps.check-circular.outputs.skip != 'true' |
| 49 | run: | |
| 50 | git config --local user.email "githubaction@githubaction.com" |
| 51 | git config --local user.name "github-action update-app-version" |
| 52 | |
| 53 | - name: Get list of updated files by the last commit |
| 54 | if: steps.check-circular.outputs.skip != 'true' |
| 55 | id: updated-files |
| 56 | run: | |
| 57 | echo "files=$(git diff-tree --no-commit-id --name-only -r ${{ github.sha }} | tr '\n' ' ')" >> $GITHUB_OUTPUT |
| 58 | |
| 59 | - name: Run renovate-app-version.sh on updated files |
| 60 | if: steps.check-circular.outputs.skip != 'true' |
| 61 | id: rename |
| 62 | run: | |
| 63 | set -e |
| 64 | chmod +x .github/workflows/renovate-app-version.sh |
| 65 | |
| 66 | files="${{ steps.updated-files.outputs.files }}" |
| 67 | declare -a changed_apps=() |
| 68 | |
| 69 | echo "Updated files: $files" |
| 70 | |
| 71 | for file in $files; do |
| 72 | if [[ $file == *"docker-compose.yml"* ]]; then |
| 73 | echo "Processing file: $file" |
| 74 | |
| 75 | app_name=$(echo $file | cut -d'/' -f 2) |
| 76 | old_version=$(echo $file | cut -d'/' -f 3) |
| 77 | echo "App name: $app_name, old version: $old_version" |
| 78 | |
| 79 | # 获取所有服务名 |
| 80 | services=$(yq '.services | keys | .[]' "$file") |
| 81 | service="" |
| 82 | image_line="" |
| 83 | |
| 84 | for s in $services; do |
| 85 | # 通过awk获取服务下的image行(包含注释) |
| 86 | image_line=$(awk "/services:/{flag=0} /^\s*$s:/{flag=1} flag && /^\s*image:/{print; exit}" "$file") |
| 87 | echo "Service $s image line: $image_line" |
| 88 | if [[ "$image_line" != *"[ignore]"* ]]; then |
| 89 | service="$s" |
| 90 | break |
| 91 | else |
| 92 | echo "Skipping service $s due to [ignore]" |
| 93 | fi |
| 94 | done |
| 95 | |
| 96 | if [[ -z "$service" ]]; then |
| 97 | echo "No valid service found in $file, skipping..." |
| 98 | continue |
| 99 | fi |
| 100 | |
| 101 | # 提取image纯字符串,去除注释和多余空格 |
| 102 | image=$(echo "$image_line" | sed -E 's/^\s*image:\s*([^ #]+).*/\1/') |
| 103 | echo "Selected service: $service" |
| 104 | echo "Extracted image: $image" |
| 105 | |
| 106 | if [[ "$image" == *":"* ]]; then |
| 107 | new_version=$(cut -d ":" -f2- <<< "$image") |
| 108 | trimmed_version=${new_version/#"v"/} |
| 109 | echo "Parsed new version: $trimmed_version" |
| 110 | else |
| 111 | trimmed_version="" |
| 112 | echo "No version tag found in image." |
| 113 | fi |
| 114 | |
| 115 | changed_apps+=("${app_name}:${old_version}:${trimmed_version}") |
| 116 | echo "Calling renovate-app-version.sh with: $app_name, $old_version, $trimmed_version" |
| 117 | .github/workflows/renovate-app-version.sh "$app_name" "$old_version" "$trimmed_version" |
| 118 | fi |
| 119 | done |
| 120 | |
| 121 | echo "All changed apps: ${changed_apps[*]}" |
| 122 | echo "apps=$(IFS=, ; echo "${changed_apps[*]}")" >> $GITHUB_OUTPUT |
| 123 | |
| 124 | - name: Commit & Push Changes |
| 125 | if: steps.check-circular.outputs.skip != 'true' |
| 126 | run: | |
| 127 | set -e |
| 128 | IFS=',' read -r -a apps <<< "${{ steps.rename.outputs.apps }}" |
| 129 | for item in "${apps[@]}"; do |
| 130 | app_name=$(cut -d':' -f1 <<< "$item") |
| 131 | old_version=$(cut -d':' -f2 <<< "$item") |
| 132 | new_version=$(cut -d':' -f3 <<< "$item") |
| 133 | |
| 134 | if [[ -n "$app_name" && -n "$new_version" ]]; then |
| 135 | git add "apps/$app_name/*" |
| 136 | git commit -m "📈将应用 $app_name 的版本从 $old_version 升级到 $new_version" --no-verify || echo "无内容可提交" |
| 137 | fi |
| 138 | done |
| 139 | |
| 140 | git push || echo "无内容可推送" |
| 141 | |
| 142 | - name: Rebase to latest main and merge PR |
| 143 | if: steps.check-circular.outputs.skip != 'true' && github.ref_name != 'main' |
| 144 | env: |
| 145 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 146 | run: | |
| 147 | set -e |
| 148 | branch_name=$(git rev-parse --abbrev-ref HEAD) |
| 149 | echo "Current branch: $branch_name" |
| 150 | |
| 151 | # 获取 PR 编号 |
| 152 | pr_number=$(gh pr list --state open --head "$branch_name" --json number -q '.[0].number') |
| 153 | if [ -z "$pr_number" ]; then |
| 154 | echo "No PR found for branch $branch_name" |
| 155 | exit 0 |
| 156 | fi |
| 157 | |
| 158 | echo "Found PR #$pr_number, preparing to merge..." |
| 159 | |
| 160 | # Fetch 最新的 main 分支 |
| 161 | echo "Fetching latest main branch..." |
| 162 | git fetch origin main |
| 163 | |
| 164 | # Rebase 当前分支到最新的 main |
| 165 | # 由于 renovate PR 修改的是不同的应用文件夹,不会有真正的冲突 |
| 166 | echo "Rebasing $branch_name onto origin/main..." |
| 167 | git rebase origin/main |
| 168 | |
| 169 | # Force push 更新后的分支 |
| 170 | echo "Force pushing rebased branch..." |
| 171 | git push --force-with-lease |
| 172 | |
| 173 | # 等待 GitHub 重新计算 mergeable 状态 |
| 174 | echo "Waiting for GitHub to recalculate mergeable status..." |
| 175 | sleep 15 |
| 176 | |
| 177 | # 检查 mergeable 状态,最多等待 2 分钟 |
| 178 | max_attempts=12 |
| 179 | attempt=0 |
| 180 | while [ $attempt -lt $max_attempts ]; do |
| 181 | mergeable=$(gh pr view "$pr_number" --json mergeable -q '.mergeable' 2>/dev/null || echo "UNKNOWN") |
| 182 | echo "Mergeable status: $mergeable (attempt $((attempt+1))/$max_attempts)" |
| 183 | |
| 184 | if [ "$mergeable" = "MERGEABLE" ]; then |
| 185 | echo "PR is mergeable!" |
| 186 | break |
| 187 | fi |
| 188 | |
| 189 | # 如果是 CONFLICTING,说明有真正的冲突(不应该发生) |
| 190 | if [ "$mergeable" = "CONFLICTING" ]; then |
| 191 | echo "Warning: PR has conflicts, but proceeding with admin merge..." |
| 192 | break |
| 193 | fi |
| 194 | |
| 195 | sleep 10 |
| 196 | attempt=$((attempt+1)) |
| 197 | done |
| 198 | |
| 199 | # 合并 PR |
| 200 | echo "Merging PR #$pr_number..." |
| 201 | gh pr merge "$pr_number" --merge --delete-branch --admin |
| 202 | |
| 203 | echo "Successfully merged PR #$pr_number!" |
| 204 | |
| 205 | - name: Trigger sync to CNB mirror |
| 206 | if: steps.check-circular.outputs.skip != 'true' && github.ref_name != 'main' |
| 207 | env: |
| 208 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 209 | run: | |
| 210 | echo "Triggering sync-to-cnb workflow..." |
| 211 | gh workflow run sync-to-cnb.yml --ref main |
| 212 | echo "Sync workflow triggered successfully!" |
sync-to-cnb.yml
· 3.2 KiB · YAML
Brut
name: Sync commits to CNB (force)
on:
schedule:
- cron: "0 0 * * *" # 每天凌晨 0 点强制同步(兜底)
- cron: "0 */6 * * *" # 每 6 小时检查一次
push:
branches:
- main
workflow_call: # 允许被其他工作流调用
workflow_dispatch:
inputs:
reason:
description: 'Manual trigger reason'
required: false
default: 'Manual run'
force_sync:
description: 'Force sync without checking for new commits'
required: false
default: 'false'
jobs:
sync:
runs-on: ubuntu-latest
outputs:
synced: ${{ steps.sync.outputs.synced }}
steps:
- name: Checkout GitHub repository
uses: actions/checkout@v5
with:
ref: main
fetch-depth: 0
- name: Determine sync mode
id: mode
run: |
# 每天凌晨的定时任务强制同步
if [[ "${{ github.event.schedule }}" == "0 0 * * *" ]]; then
echo "Daily scheduled sync - forcing sync"
echo "force=true" >> $GITHUB_OUTPUT
exit 0
fi
# 手动触发且指定强制同步
if [[ "${{ github.event.inputs.force_sync }}" == "true" ]]; then
echo "Manual force sync requested"
echo "force=true" >> $GITHUB_OUTPUT
exit 0
fi
# 被 workflow_call 调用时强制同步
if [[ "${{ github.event_name }}" == "workflow_call" ]]; then
echo "Called by another workflow - forcing sync"
echo "force=true" >> $GITHUB_OUTPUT
exit 0
fi
echo "force=false" >> $GITHUB_OUTPUT
- name: Check if there are new commits
if: steps.mode.outputs.force != 'true'
id: check
run: |
NEW_COMMITS=$(git log --since="6 hours ago" --oneline)
if [ -z "$NEW_COMMITS" ]; then
echo "No new commits in the last 6 hours, skip sync."
echo "skip=true" >> $GITHUB_OUTPUT
else
echo "New commits found:"
echo "$NEW_COMMITS"
echo "skip=false" >> $GITHUB_OUTPUT
fi
- name: Sync to CNB
id: sync
if: steps.mode.outputs.force == 'true' || steps.check.outputs.skip == 'false'
env:
CNB_USERNAME: ${{ secrets.CNB_USERNAME }}
CNB_TOKEN: ${{ secrets.CNB_TOKEN }}
run: |
echo "Syncing to CNB mirror..."
rm -rf .git
git init
git config --global user.name 'GitHub Action'
git config --global user.email 'action@github.com'
git add .
git commit -m "Sync commit ==> [$(date +"%Y-%m-%d %H:%M:%S")]"
git branch -M main
git remote add origin "https://${{ secrets.CNB_USERNAME }}:${{ secrets.CNB_TOKEN }}@cnb.cool/liiiu/appstore.git"
git push --force --quiet origin main
echo "synced=true" >> $GITHUB_OUTPUT
- name: Report result
run: |
if [[ "${{ steps.sync.outputs.synced }}" == "true" ]]; then
echo "✅ Successfully synced to CNB mirror"
else
echo "⏭️ Skipped sync (no new commits)"
fi
| 1 | name: Sync commits to CNB (force) |
| 2 | |
| 3 | on: |
| 4 | schedule: |
| 5 | - cron: "0 0 * * *" # 每天凌晨 0 点强制同步(兜底) |
| 6 | - cron: "0 */6 * * *" # 每 6 小时检查一次 |
| 7 | push: |
| 8 | branches: |
| 9 | - main |
| 10 | workflow_call: # 允许被其他工作流调用 |
| 11 | workflow_dispatch: |
| 12 | inputs: |
| 13 | reason: |
| 14 | description: 'Manual trigger reason' |
| 15 | required: false |
| 16 | default: 'Manual run' |
| 17 | force_sync: |
| 18 | description: 'Force sync without checking for new commits' |
| 19 | required: false |
| 20 | default: 'false' |
| 21 | |
| 22 | jobs: |
| 23 | sync: |
| 24 | runs-on: ubuntu-latest |
| 25 | outputs: |
| 26 | synced: ${{ steps.sync.outputs.synced }} |
| 27 | |
| 28 | steps: |
| 29 | - name: Checkout GitHub repository |
| 30 | uses: actions/checkout@v5 |
| 31 | with: |
| 32 | ref: main |
| 33 | fetch-depth: 0 |
| 34 | |
| 35 | - name: Determine sync mode |
| 36 | id: mode |
| 37 | run: | |
| 38 | # 每天凌晨的定时任务强制同步 |
| 39 | if [[ "${{ github.event.schedule }}" == "0 0 * * *" ]]; then |
| 40 | echo "Daily scheduled sync - forcing sync" |
| 41 | echo "force=true" >> $GITHUB_OUTPUT |
| 42 | exit 0 |
| 43 | fi |
| 44 | |
| 45 | # 手动触发且指定强制同步 |
| 46 | if [[ "${{ github.event.inputs.force_sync }}" == "true" ]]; then |
| 47 | echo "Manual force sync requested" |
| 48 | echo "force=true" >> $GITHUB_OUTPUT |
| 49 | exit 0 |
| 50 | fi |
| 51 | |
| 52 | # 被 workflow_call 调用时强制同步 |
| 53 | if [[ "${{ github.event_name }}" == "workflow_call" ]]; then |
| 54 | echo "Called by another workflow - forcing sync" |
| 55 | echo "force=true" >> $GITHUB_OUTPUT |
| 56 | exit 0 |
| 57 | fi |
| 58 | |
| 59 | echo "force=false" >> $GITHUB_OUTPUT |
| 60 | |
| 61 | - name: Check if there are new commits |
| 62 | if: steps.mode.outputs.force != 'true' |
| 63 | id: check |
| 64 | run: | |
| 65 | NEW_COMMITS=$(git log --since="6 hours ago" --oneline) |
| 66 | if [ -z "$NEW_COMMITS" ]; then |
| 67 | echo "No new commits in the last 6 hours, skip sync." |
| 68 | echo "skip=true" >> $GITHUB_OUTPUT |
| 69 | else |
| 70 | echo "New commits found:" |
| 71 | echo "$NEW_COMMITS" |
| 72 | echo "skip=false" >> $GITHUB_OUTPUT |
| 73 | fi |
| 74 | |
| 75 | - name: Sync to CNB |
| 76 | id: sync |
| 77 | if: steps.mode.outputs.force == 'true' || steps.check.outputs.skip == 'false' |
| 78 | env: |
| 79 | CNB_USERNAME: ${{ secrets.CNB_USERNAME }} |
| 80 | CNB_TOKEN: ${{ secrets.CNB_TOKEN }} |
| 81 | run: | |
| 82 | echo "Syncing to CNB mirror..." |
| 83 | rm -rf .git |
| 84 | git init |
| 85 | git config --global user.name 'GitHub Action' |
| 86 | git config --global user.email 'action@github.com' |
| 87 | git add . |
| 88 | git commit -m "Sync commit ==> [$(date +"%Y-%m-%d %H:%M:%S")]" |
| 89 | git branch -M main |
| 90 | git remote add origin "https://${{ secrets.CNB_USERNAME }}:${{ secrets.CNB_TOKEN }}@cnb.cool/liiiu/appstore.git" |
| 91 | git push --force --quiet origin main |
| 92 | echo "synced=true" >> $GITHUB_OUTPUT |
| 93 | |
| 94 | - name: Report result |
| 95 | run: | |
| 96 | if [[ "${{ steps.sync.outputs.synced }}" == "true" ]]; then |
| 97 | echo "✅ Successfully synced to CNB mirror" |
| 98 | else |
| 99 | echo "⏭️ Skipped sync (no new commits)" |
| 100 | fi |
| 101 |