Skip to content

Commit 54c0343

Browse files
committed
Merge remote-tracking branch 'origin/master' into backport/ec81917-to-master
2 parents a82d55e + 6f44812 commit 54c0343

File tree

37 files changed

+2290
-105
lines changed

37 files changed

+2290
-105
lines changed

.github/scripts/backport-commit.sh

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
#!/usr/bin/env bash
2+
3+
# Safe backport helper. Creates a PR in the current repository that cherry-picks a commit from upstream.
4+
5+
set -euo pipefail
6+
7+
# ANSI colors for readability
8+
RED='\033[0;31m'
9+
GREEN='\033[0;32m'
10+
YELLOW='\033[1;33m'
11+
NC='\033[0m'
12+
13+
die() {
14+
echo -e "${RED}$1${NC}" >&2
15+
exit "${2:-1}"
16+
}
17+
18+
require_env() {
19+
local name="$1"
20+
local value="${!name:-}"
21+
if [[ -z "$value" ]]; then
22+
die "Environment variable $name is required"
23+
fi
24+
}
25+
26+
if [[ $# -ne 1 ]]; then
27+
die "Usage: $0 <commit-sha>"
28+
fi
29+
30+
COMMIT_SHA="$1"
31+
32+
if ! [[ "$COMMIT_SHA" =~ ^[0-9a-f]{40}$ ]]; then
33+
die "Invalid commit SHA: $COMMIT_SHA"
34+
fi
35+
36+
SOURCE_REPO="${SOURCE_REPO:-apache/apisix-ingress-controller}"
37+
TARGET_BRANCH="${TARGET_BRANCH:-master}"
38+
GITHUB_REPO="${GITHUB_REPOSITORY:-}"
39+
40+
require_env SOURCE_REPO
41+
require_env TARGET_BRANCH
42+
require_env GH_TOKEN
43+
44+
[[ "$SOURCE_REPO" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] || die "Invalid SOURCE_REPO: $SOURCE_REPO"
45+
[[ "$TARGET_BRANCH" =~ ^[A-Za-z0-9._/-]+$ ]] || die "Invalid TARGET_BRANCH: $TARGET_BRANCH"
46+
47+
if [[ -z "$GITHUB_REPO" ]]; then
48+
GITHUB_REPO="$(gh repo view --json nameWithOwner -q '.nameWithOwner')"
49+
fi
50+
51+
[[ "$GITHUB_REPO" =~ ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ ]] || die "Invalid target repo: $GITHUB_REPO"
52+
53+
echo -e "${YELLOW}Backporting commit ${COMMIT_SHA} from ${SOURCE_REPO}${NC}"
54+
55+
ORIGINAL_REF=""
56+
ORIGINAL_COMMIT=""
57+
if ORIGINAL_REF=$(git symbolic-ref --quiet HEAD 2>/dev/null); then
58+
ORIGINAL_REF=${ORIGINAL_REF#refs/heads/}
59+
else
60+
ORIGINAL_COMMIT=$(git rev-parse HEAD)
61+
fi
62+
63+
restore_original_ref() {
64+
if [[ -n "$ORIGINAL_REF" ]]; then
65+
git checkout "$ORIGINAL_REF" >/dev/null 2>&1 || true
66+
elif [[ -n "$ORIGINAL_COMMIT" ]]; then
67+
git checkout --detach "$ORIGINAL_COMMIT" >/dev/null 2>&1 || true
68+
fi
69+
}
70+
71+
if ! git cat-file -e "${COMMIT_SHA}^{commit}" 2>/dev/null; then
72+
die "Commit $COMMIT_SHA is not available locally - fetch upstream before running this script"
73+
fi
74+
75+
COMMIT_TITLE="$(git log --format='%s' -n 1 "$COMMIT_SHA")"
76+
COMMIT_AUTHOR="$(git log --format='%an <%ae>' -n 1 "$COMMIT_SHA")"
77+
COMMIT_URL="https://github.com/${SOURCE_REPO}/commit/${COMMIT_SHA}"
78+
SHORT_SHA="${COMMIT_SHA:0:7}"
79+
if [[ -z "$COMMIT_TITLE" ]]; then
80+
COMMIT_TITLE="Backport ${SHORT_SHA} from ${SOURCE_REPO}"
81+
fi
82+
TITLE_SUFFIX=" (${SHORT_SHA})"
83+
if [[ "$COMMIT_TITLE" == *"$SHORT_SHA"* ]]; then
84+
TITLE_SUFFIX=""
85+
fi
86+
BRANCH_NAME="backport/${SHORT_SHA}-to-${TARGET_BRANCH}"
87+
88+
[[ "$BRANCH_NAME" =~ ^[A-Za-z0-9._/-]+$ ]] || die "Generated branch name is unsafe: $BRANCH_NAME"
89+
90+
echo -e "${YELLOW}Generated branch name: ${BRANCH_NAME}${NC}"
91+
92+
EXISTING_PR="$(gh pr list --state all --head "$BRANCH_NAME" --json url --jq '.[0].url' 2>/dev/null || true)"
93+
if [[ -n "$EXISTING_PR" ]]; then
94+
echo -e "${GREEN}PR already exists: ${EXISTING_PR}. Skipping duplicate.${NC}"
95+
exit 0
96+
fi
97+
98+
git fetch origin "$TARGET_BRANCH" --quiet
99+
git checkout -B "$TARGET_BRANCH" "origin/$TARGET_BRANCH"
100+
101+
if git rev-parse --verify "$BRANCH_NAME" >/dev/null 2>&1; then
102+
git checkout "$BRANCH_NAME"
103+
git reset --hard "origin/$TARGET_BRANCH"
104+
else
105+
git checkout -b "$BRANCH_NAME"
106+
fi
107+
108+
PARENT_COUNT="$(git rev-list --parents -n 1 "$COMMIT_SHA" | awk '{print NF-1}')"
109+
HAS_CONFLICTS=false
110+
111+
echo -e "${YELLOW}Running cherry-pick...${NC}"
112+
113+
cherry_pick() {
114+
if [[ "$PARENT_COUNT" -gt 1 ]]; then
115+
git cherry-pick -x -m 1 "$COMMIT_SHA"
116+
else
117+
git cherry-pick -x "$COMMIT_SHA"
118+
fi
119+
}
120+
121+
if ! cherry_pick; then
122+
echo -e "${YELLOW}Cherry-pick reported conflicts; leaving markers for manual resolution.${NC}"
123+
HAS_CONFLICTS=true
124+
git add .
125+
git -c core.editor=true cherry-pick --continue || true
126+
fi
127+
128+
echo -e "${YELLOW}Pushing branch to origin...${NC}"
129+
if ! git push -u origin "$BRANCH_NAME"; then
130+
echo -e "${YELLOW}Push failed, trying force-with-lease...${NC}"
131+
git fetch origin "$BRANCH_NAME" || true
132+
git branch --set-upstream-to="origin/$BRANCH_NAME" "$BRANCH_NAME" || true
133+
git push -u origin "$BRANCH_NAME" --force-with-lease || {
134+
git checkout "$TARGET_BRANCH"
135+
git branch -D "$BRANCH_NAME" || true
136+
restore_original_ref
137+
die "Unable to push branch ${BRANCH_NAME}"
138+
}
139+
fi
140+
141+
echo -e "${YELLOW}Creating pull request...${NC}"
142+
143+
if [[ "$HAS_CONFLICTS" == "true" ]]; then
144+
PR_TITLE="conflict: ${COMMIT_TITLE}${TITLE_SUFFIX}"
145+
PR_BODY=$(cat <<EOF
146+
<!-- backport:${COMMIT_SHA} -->
147+
148+
## ⚠️ Backport With Conflicts
149+
150+
- Upstream commit: ${COMMIT_URL}
151+
- Original title: ${COMMIT_TITLE}
152+
- Original author: ${COMMIT_AUTHOR}
153+
154+
This PR contains unresolved conflicts. Please resolve them before merging.
155+
156+
### Suggested workflow
157+
1. \`git fetch origin ${BRANCH_NAME}\`
158+
2. \`git checkout ${BRANCH_NAME}\`
159+
3. Resolve conflicts, commit, and push updates.
160+
161+
> Created automatically by backport-bot.
162+
EOF
163+
)
164+
LABEL_FLAGS=(--label backport --label automated --label needs-manual-action --label conflicts)
165+
else
166+
PR_TITLE="${COMMIT_TITLE}${TITLE_SUFFIX}"
167+
PR_BODY=$(cat <<EOF
168+
<!-- backport:${COMMIT_SHA} -->
169+
170+
## 🔄 Automated Backport
171+
172+
- Upstream commit: ${COMMIT_URL}
173+
- Original title: ${COMMIT_TITLE}
174+
- Original author: ${COMMIT_AUTHOR}
175+
176+
Please review and run the relevant validation before merging.
177+
178+
> Created automatically by backport-bot.
179+
EOF
180+
)
181+
LABEL_FLAGS=(--label backport --label automated)
182+
fi
183+
184+
set +e
185+
PR_RESPONSE="$(gh pr create \
186+
--title "$PR_TITLE" \
187+
--body "$PR_BODY" \
188+
--head "$BRANCH_NAME" \
189+
--base "$TARGET_BRANCH" \
190+
--repo "$GITHUB_REPO" \
191+
"${LABEL_FLAGS[@]}" 2>&1)"
192+
PR_EXIT_CODE=$?
193+
set -e
194+
195+
if [[ $PR_EXIT_CODE -ne 0 ]]; then
196+
echo -e "${RED}Failed to create PR:${NC}\n${PR_RESPONSE}"
197+
if grep -q "already exists" <<<"$PR_RESPONSE"; then
198+
echo -e "${YELLOW}Detected existing PR, assuming success.${NC}"
199+
git checkout "$TARGET_BRANCH"
200+
restore_original_ref
201+
exit 0
202+
fi
203+
git checkout "$TARGET_BRANCH"
204+
git push origin --delete "$BRANCH_NAME" || true
205+
git branch -D "$BRANCH_NAME" || true
206+
restore_original_ref
207+
die "PR creation failed"
208+
fi
209+
210+
echo -e "${GREEN}Pull request created successfully:${NC} ${PR_RESPONSE}"
211+
212+
restore_original_ref
213+
214+
echo -e "${GREEN}Backport finished for ${COMMIT_SHA}.${NC}"

.github/workflows/backport.yaml

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
name: Auto Backport from Upstream
2+
3+
on:
4+
schedule:
5+
- cron: "*/30 * * * *"
6+
workflow_dispatch:
7+
inputs:
8+
force_sync:
9+
description: "Force sync all recent commits (ignores watermark)"
10+
required: false
11+
default: "false"
12+
13+
concurrency:
14+
group: auto-backport
15+
cancel-in-progress: false
16+
17+
env:
18+
SOURCE_REPO: apache/apisix-ingress-controller
19+
SOURCE_BRANCH: master
20+
TARGET_BRANCH: ${{ github.event.repository.default_branch || 'master' }}
21+
MAX_COMMITS_PER_RUN: 5
22+
23+
jobs:
24+
auto-backport:
25+
runs-on: ubuntu-latest
26+
timeout-minutes: 20
27+
permissions:
28+
actions: write
29+
contents: write
30+
pull-requests: write
31+
issues: write
32+
repository-projects: write
33+
env:
34+
GH_TOKEN: ${{ secrets.BACKPORT_PAT }}
35+
GITHUB_REPOSITORY: ${{ github.repository }}
36+
FORCE_SYNC: ${{ github.event.inputs.force_sync || 'false' }}
37+
38+
steps:
39+
- name: Checkout target repository
40+
uses: actions/checkout@v4
41+
with:
42+
fetch-depth: 0
43+
token: ${{ secrets.BACKPORT_PAT }}
44+
45+
- name: Show run configuration
46+
run: |
47+
echo "Force sync: $FORCE_SYNC"
48+
echo "Source repo: $SOURCE_REPO"
49+
echo "Source branch: $SOURCE_BRANCH"
50+
echo "Target branch: $TARGET_BRANCH"
51+
52+
- name: Configure git identity
53+
run: |
54+
git config --global user.name "backport-bot[bot]"
55+
git config --global user.email "backport-bot[bot]@users.noreply.github.com"
56+
57+
- name: Add upstream remote
58+
run: |
59+
git remote add upstream "https://github.com/${SOURCE_REPO}.git" 2>/dev/null || true
60+
git remote set-url upstream "https://github.com/${SOURCE_REPO}.git"
61+
62+
- name: Fetch upstream branch
63+
run: |
64+
git fetch --prune --no-tags upstream "${SOURCE_BRANCH}"
65+
66+
- name: Read last processed commit watermark
67+
id: watermark
68+
run: |
69+
LAST_SHA=$(gh variable get LAST_BACKPORT_SHA -R "${GITHUB_REPOSITORY}" --json value --jq '.value' 2>/dev/null || echo "")
70+
if [[ -z "$LAST_SHA" || "$FORCE_SYNC" == "true" ]]; then
71+
LAST_SHA=$(git log "upstream/${SOURCE_BRANCH}" --since="7 days ago" --format="%H" | tail -n 1)
72+
fi
73+
echo "last_sha=${LAST_SHA}" >> "$GITHUB_OUTPUT"
74+
echo "Last processed SHA: ${LAST_SHA:-<none>}"
75+
76+
- name: Collect new commits
77+
id: collect_commits
78+
run: |
79+
LAST_SHA="${{ steps.watermark.outputs.last_sha }}"
80+
if [[ -n "$LAST_SHA" ]]; then
81+
COMMITS=$(git log "upstream/${SOURCE_BRANCH}" ^"$LAST_SHA" --format="%H" --reverse | head -"${MAX_COMMITS_PER_RUN}")
82+
else
83+
COMMITS=$(git log "upstream/${SOURCE_BRANCH}" -1 --format="%H")
84+
fi
85+
{
86+
echo "commits<<EOF"
87+
printf '%s\n' "$COMMITS"
88+
echo "EOF"
89+
} >> "$GITHUB_OUTPUT"
90+
if [[ -z "$COMMITS" ]]; then
91+
COUNT=0
92+
else
93+
COUNT=$(printf '%s\n' "$COMMITS" | grep -c '[0-9a-f]')
94+
fi
95+
echo "count=${COUNT}" >> "$GITHUB_OUTPUT"
96+
echo "Commits to process: ${COUNT}"
97+
98+
- name: Ensure labels exist
99+
run: |
100+
gh label create backport --color EDEDED --description "Automated backport" -R "${GITHUB_REPOSITORY}" 2>/dev/null || true
101+
gh label create automated --color EDEDED --description "Created by automation" -R "${GITHUB_REPOSITORY}" 2>/dev/null || true
102+
gh label create backport-failed --color D73A4A --description "Backport failed" -R "${GITHUB_REPOSITORY}" 2>/dev/null || true
103+
gh label create needs-manual-action --color FBCA04 --description "Manual intervention required" -R "${GITHUB_REPOSITORY}" 2>/dev/null || true
104+
gh label create conflicts --color D93F0B --description "Contains merge conflicts" -R "${GITHUB_REPOSITORY}" 2>/dev/null || true
105+
106+
- name: Process commits
107+
if: steps.collect_commits.outputs.count != '0'
108+
env:
109+
GH_TOKEN: ${{ github.token }}
110+
run: |
111+
chmod +x .github/scripts/backport-commit.sh
112+
SUCCESS=0
113+
FAILURE=0
114+
LAST_PROCESSED=""
115+
while IFS= read -r COMMIT; do
116+
[[ -z "$COMMIT" ]] && continue
117+
if .github/scripts/backport-commit.sh "$COMMIT"; then
118+
SUCCESS=$((SUCCESS + 1))
119+
LAST_PROCESSED="$COMMIT"
120+
else
121+
echo "Commit ${COMMIT} failed to backport"
122+
FAILURE=$((FAILURE + 1))
123+
fi
124+
done <<< "${{ steps.collect_commits.outputs.commits }}"
125+
echo "SUCCESS_COUNT=$SUCCESS" >> "$GITHUB_ENV"
126+
echo "FAILURE_COUNT=$FAILURE" >> "$GITHUB_ENV"
127+
echo "LAST_PROCESSED_SHA=$LAST_PROCESSED" >> "$GITHUB_ENV"
128+
129+
- name: Update watermark
130+
if: env.LAST_PROCESSED_SHA != ''
131+
run: |
132+
if [[ "${FAILURE_COUNT:-0}" == "0" ]]; then
133+
gh variable set LAST_BACKPORT_SHA -b "${LAST_PROCESSED_SHA}" -R "${GITHUB_REPOSITORY}"
134+
else
135+
echo "Failures detected; watermark will not be updated."
136+
fi
137+
138+
- name: Summary
139+
run: |
140+
echo "Successful cherry-picks: ${SUCCESS_COUNT:-0}"
141+
echo "Failed cherry-picks: ${FAILURE_COUNT:-0}"
142+
echo "Last processed SHA: ${LAST_PROCESSED_SHA:-none}"
143+
{
144+
echo "# Backport Summary"
145+
echo
146+
echo "- Successful: ${SUCCESS_COUNT:-0}"
147+
echo "- Failed: ${FAILURE_COUNT:-0}"
148+
echo "- Last processed: ${LAST_PROCESSED_SHA:-none}"
149+
echo "- Force sync: ${FORCE_SYNC}"
150+
} >> "$GITHUB_STEP_SUMMARY"

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ GO_LDFLAGS ?= "-X=$(VERSYM)=$(VERSION) -X=$(GITSHASYM)=$(GITSHA) -X=$(BUILDOSSYM
5959
# gateway-api
6060
GATEAY_API_VERSION ?= v1.3.0
6161
## https://github.com/kubernetes-sigs/gateway-api/blob/v1.3.0/pkg/features/httproute.go
62-
SUPPORTED_EXTENDED_FEATURES = "HTTPRouteDestinationPortMatching,HTTPRouteMethodMatching,HTTPRoutePortRedirect,HTTPRouteRequestMirror,HTTPRouteSchemeRedirect,GatewayAddressEmpty,HTTPRouteResponseHeaderModification,GatewayPort8080"
62+
SUPPORTED_EXTENDED_FEATURES = "HTTPRouteDestinationPortMatching,HTTPRouteMethodMatching,HTTPRoutePortRedirect,HTTPRouteRequestMirror,HTTPRouteSchemeRedirect,GatewayAddressEmpty,HTTPRouteResponseHeaderModification,GatewayPort8080,HTTPRouteHostRewrite"
6363
CONFORMANCE_TEST_REPORT_OUTPUT ?= $(DIR)/apisix-ingress-controller-conformance-report.yaml
6464
## https://github.com/kubernetes-sigs/gateway-api/blob/v1.3.0/conformance/utils/suite/profiles.go
6565
CONFORMANCE_PROFILES ?= GATEWAY-HTTP,GATEWAY-GRPC

0 commit comments

Comments
 (0)