Skip to content

Commit a82070f

Browse files
committed
Feat: Refactor github2gerrit workflow with test framework
- Create modular V2 workflow with 6 dedicated shell scripts for better maintainability - Add comprehensive integration testing suite with mock Gerrit simulation - Implement regression testing framework comparing V1 vs V2 workflows - Add error scenario testing and performance benchmarking - Create local testing support with act runner integration - Maintain 100% backward compatibility with original workflow - Include security validation and input sanitization tests - Add workflow outputs for change URLs and numbers Scripts created: - setup-environment.sh: Environment variable configuration - parse-gitreview.sh: .gitreview parsing with fallbacks - setup-git.sh: Git configuration for Gerrit - process-commits.sh: Commit processing (squash/individual) - submit-to-gerrit.sh: Gerrit submission and tracking - handle-pr-updates.sh: Change-ID reuse for PR updates Testing framework includes: - Mock Gerrit testing with matrix scenarios - V1 vs V2 regression validation - Error handling verification - Performance comparison metrics - Local testing with run-tests.sh script Signed-off-by: Anil Belur <abelur@linuxfoundation.org>
1 parent 3df3528 commit a82070f

File tree

10 files changed

+1703
-0
lines changed

10 files changed

+1703
-0
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
---
2+
# SPDX-License-Identifier: Apache-2.0
3+
# SPDX-FileCopyrightText: 2024 The Linux Foundation
4+
5+
# Example usage of the new modular github2gerrit-v2 workflow
6+
name: Example GitHub2Gerrit V2 Usage
7+
8+
on:
9+
pull_request_target:
10+
types: [opened, reopened, synchronize]
11+
branches: [main, master]
12+
workflow_dispatch:
13+
inputs:
14+
pr_number:
15+
description: "PR number to process"
16+
required: true
17+
type: number
18+
19+
concurrency:
20+
group: example-g2g-v2-${{ github.ref }}
21+
cancel-in-progress: true
22+
23+
jobs:
24+
submit-to-gerrit:
25+
name: "Submit PR to Gerrit (V2)"
26+
if: false # Disabled by default - remove this line to enable
27+
permissions:
28+
contents: read
29+
pull-requests: write
30+
uses: ./.github/workflows/github2gerrit-v2.yaml
31+
with:
32+
# Submission options
33+
SUBMIT_SINGLE_COMMITS: false # Default: squash commits
34+
USE_PR_AS_COMMIT: false # Default: use original commit messages
35+
FETCH_DEPTH: "0" # Fetch full history
36+
37+
# Gerrit configuration (required)
38+
GERRIT_KNOWN_HOSTS: ${{ vars.GERRIT_KNOWN_HOSTS }}
39+
GERRIT_SSH_USER_G2G: ${{ vars.GERRIT_SSH_USER_G2G }}
40+
GERRIT_SSH_USER_G2G_EMAIL: ${{ vars.GERRIT_SSH_USER_G2G_EMAIL }}
41+
42+
# Optional Gerrit overrides (use when .gitreview is missing)
43+
# GERRIT_SERVER: "review.opendev.org"
44+
# GERRIT_SERVER_PORT: "29418"
45+
# GERRIT_PROJECT: "openstack/nova"
46+
47+
# GitHub organization
48+
ORGANIZATION: ${{ vars.ORGANIZATION }}
49+
50+
# Optional: Reviewers to notify
51+
REVIEWERS_EMAIL: ${{ vars.REVIEWERS_EMAIL }}
52+
secrets:
53+
GERRIT_SSH_PRIVKEY_G2G: ${{ secrets.GERRIT_SSH_PRIVKEY_G2G }}
54+
55+
# Example: Use the outputs from the workflow
56+
follow-up-actions:
57+
name: "Follow-up Actions"
58+
needs: submit-to-gerrit
59+
if: needs.submit-to-gerrit.outputs.gerrit_change_url != ''
60+
runs-on: ubuntu-latest
61+
steps:
62+
- name: "Process Gerrit submission results"
63+
run: |
64+
echo "::notice::Gerrit change created successfully!"
65+
echo "::notice::Change URL: ${{ needs.submit-to-gerrit.outputs.gerrit_change_url }}"
66+
echo "::notice::Change Number: ${{ needs.submit-to-gerrit.outputs.gerrit_change_number }}"
67+
68+
# Example: Send notification, update issue, etc.
69+
# slack-notify, email, database update, etc.
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
---
2+
# SPDX-License-Identifier: Apache-2.0
3+
# SPDX-FileCopyrightText: 2025 The Linux Foundation
4+
5+
name: github2gerrit-reusable-workflow-v2
6+
7+
on:
8+
workflow_call:
9+
inputs:
10+
SUBMIT_SINGLE_COMMITS:
11+
description: "Submit one commit at a time to the Gerrit repository"
12+
required: false
13+
default: false
14+
type: boolean
15+
USE_PR_AS_COMMIT:
16+
description: "Use PR body and title as commit message"
17+
required: false
18+
default: false
19+
type: boolean
20+
FETCH_DEPTH:
21+
description: "fetch-depth for the clone. (Default: 10)"
22+
required: false
23+
default: "10"
24+
type: string
25+
GERRIT_KNOWN_HOSTS:
26+
description: "known hosts"
27+
required: true
28+
type: string
29+
GERRIT_SERVER:
30+
description: "Gerrit hostname ex: git.opendaylight.org"
31+
required: false
32+
default: ""
33+
type: string
34+
GERRIT_SERVER_PORT:
35+
description: "Gerrit port. (Default: 29418)"
36+
required: false
37+
default: "29418"
38+
type: string
39+
GERRIT_PROJECT:
40+
description: "Gerrit project name. ex: releng/builder"
41+
required: false
42+
default: ""
43+
type: string
44+
GERRIT_SSH_USER_G2G:
45+
description: "Gerrit user-id for SSH"
46+
required: true
47+
type: string
48+
GERRIT_SSH_USER_G2G_EMAIL:
49+
description: "Email of the SSH user"
50+
required: true
51+
type: string
52+
ORGANIZATION:
53+
description: "Organization name, e.g. opendaylight"
54+
required: false
55+
type: string
56+
default: ${{ github.repository_owner }}
57+
REVIEWERS_EMAIL:
58+
description: "Committers email list (comma separated) to notify on code-reviews"
59+
required: false
60+
default: ""
61+
type: string
62+
secrets:
63+
GERRIT_SSH_PRIVKEY_G2G:
64+
description: "SSH Private key"
65+
required: true
66+
outputs:
67+
gerrit_change_url:
68+
description: "URL of the Gerrit change request"
69+
value: ${{ jobs.github2gerrit.outputs.change_url }}
70+
gerrit_change_number:
71+
description: "Gerrit change request number"
72+
value: ${{ jobs.github2gerrit.outputs.change_number }}
73+
74+
concurrency:
75+
group: g2g-v2-${{ github.workflow }}-${{ github.run_id }}
76+
cancel-in-progress: true
77+
78+
jobs:
79+
github2gerrit:
80+
name: "Submit PR to Gerrit"
81+
runs-on: ubuntu-latest
82+
timeout-minutes: 15
83+
permissions:
84+
contents: read
85+
pull-requests: write
86+
87+
outputs:
88+
change_url: ${{ env.GERRIT_CHANGE_REQUEST_URL }}
89+
change_number: ${{ env.GERRIT_CHANGE_REQUEST_NUM }}
90+
91+
steps:
92+
- name: "Validate workflow inputs"
93+
if: ${{ inputs.USE_PR_AS_COMMIT && inputs.SUBMIT_SINGLE_COMMITS }}
94+
run: |
95+
echo "::error::USE_PR_AS_COMMIT and SUBMIT_SINGLE_COMMITS cannot be enabled simultaneously"
96+
exit 1
97+
98+
- name: "Setup Python environment"
99+
uses: actions/setup-python@v5
100+
with:
101+
python-version: "3.11"
102+
103+
- name: "Install dependencies"
104+
run: |
105+
python -m pip install --upgrade pip
106+
pip install "git-review==2.3.1" jq
107+
echo "::notice::Installed git-review $(git review --version)"
108+
echo "::notice::Installed jq $(jq --version)"
109+
110+
- name: "Checkout repository"
111+
uses: actions/checkout@v4
112+
with:
113+
fetch-depth: ${{ inputs.FETCH_DEPTH }}
114+
ref: ${{ github.event.pull_request.head.sha }}
115+
token: ${{ github.token }}
116+
117+
- name: "Setup SSH key"
118+
uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4
119+
with:
120+
key: ${{ secrets.GERRIT_SSH_PRIVKEY_G2G }}
121+
name: "id_rsa"
122+
known_hosts: ${{ inputs.GERRIT_KNOWN_HOSTS }}
123+
config: |
124+
Host ${{ env.GERRIT_SERVER }}
125+
User ${{ inputs.GERRIT_SSH_USER_G2G }}
126+
Port ${{ env.GERRIT_SERVER_PORT || '29418' }}
127+
PubkeyAcceptedKeyTypes +ssh-rsa
128+
IdentityFile ~/.ssh/id_rsa
129+
130+
- name: "Setup environment variables"
131+
run: ./scripts/setup-environment.sh
132+
env:
133+
GITHUB_EVENT_PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
134+
GITHUB_EVENT_ISSUE_NUMBER: ${{ github.event.issue.number }}
135+
GITHUB_BASE_REF: ${{ github.base_ref }}
136+
137+
- name: "Parse git review configuration"
138+
run: ./scripts/parse-gitreview.sh
139+
env:
140+
GERRIT_PROJECT_INPUT: ${{ inputs.GERRIT_PROJECT }}
141+
GERRIT_SERVER_INPUT: ${{ inputs.GERRIT_SERVER }}
142+
GERRIT_SERVER_PORT_INPUT: ${{ inputs.GERRIT_SERVER_PORT }}
143+
GITHUB_REPOSITORY: ${{ github.repository }}
144+
145+
- name: "Setup Issue ID lookup (if enabled)"
146+
if: vars.ISSUEID == 'true'
147+
run: |
148+
# Set key to use for JSON lookup
149+
ACTOR="${{ github.actor }}"
150+
ACTOR_ID="${{ github.actor_id }}"
151+
echo "::notice::Using GitHub actor as lookup key: $ACTOR [$ACTOR_ID]"
152+
echo "key=$ACTOR" >> "$GITHUB_ENV"
153+
154+
- name: "Get ticket from JSON lookup table"
155+
if: vars.ISSUEID == 'true'
156+
uses: lfit/releng-reusable-workflows/.github/actions/json-key-value-lookup-action@main
157+
with:
158+
json: ${{ vars.ISSUE_ID_LOOKUP_JSON }}
159+
key: ${{ env.key }}
160+
161+
- name: "Set Issue ID in environment"
162+
if: vars.ISSUEID == 'true'
163+
run: |
164+
if [[ -n "${{ env.value }}" ]]; then
165+
echo "SET_ISSUE_ID=${{ env.value }}" >> "$GITHUB_ENV"
166+
echo "::notice::Issue ID set: ${{ env.value }}"
167+
fi
168+
169+
- name: "Configure git for Gerrit"
170+
run: ./scripts/setup-git.sh
171+
env:
172+
GERRIT_SSH_USER_G2G: ${{ inputs.GERRIT_SSH_USER_G2G }}
173+
GERRIT_SSH_USER_G2G_EMAIL: ${{ inputs.GERRIT_SSH_USER_G2G_EMAIL }}
174+
175+
- name: "Handle PR updates and Change-ID reuse"
176+
if: >-
177+
github.event_name == 'pull_request_target' &&
178+
(github.event.action == 'reopened' || github.event.action == 'synchronize')
179+
run: ./scripts/handle-pr-updates.sh
180+
env:
181+
ORGANIZATION: ${{ inputs.ORGANIZATION }}
182+
GITHUB_EVENT_ACTION: ${{ github.event.action }}
183+
GH_TOKEN: ${{ github.token }}
184+
185+
- name: "Process PR commits"
186+
run: ./scripts/process-commits.sh
187+
env:
188+
SUBMIT_SINGLE_COMMITS: ${{ inputs.SUBMIT_SINGLE_COMMITS }}
189+
USE_PR_AS_COMMIT: ${{ inputs.USE_PR_AS_COMMIT }}
190+
GITHUB_EVENT_PULL_REQUEST_BASE_SHA: ${{ github.event.pull_request.base.sha }}
191+
GITHUB_SHA: ${{ github.sha }}
192+
GITHUB_EVENT_ACTION: ${{ github.event.action }}
193+
SET_ISSUE_ID: ${{ env.SET_ISSUE_ID }}
194+
ISSUEID_ENABLED: ${{ vars.ISSUEID }}
195+
GH_TOKEN: ${{ github.token }}
196+
197+
- name: "Handle PR title and body as commit message"
198+
if: ${{ inputs.USE_PR_AS_COMMIT && github.event_name == 'pull_request_target' }}
199+
run: |
200+
echo "::notice::Using PR title and body as commit message"
201+
202+
# Get PR title and body
203+
gh pr view "$PR_NUMBER" --json title,body > pr_data.json
204+
205+
# Extract title and body
206+
jq -r '.title // ""' pr_data.json > pr_title.txt
207+
echo "" >> pr_title.txt # Blank line between title and body
208+
jq -r '.body // ""' pr_data.json > pr_body.txt
209+
210+
# Combine title and body
211+
cat pr_title.txt pr_body.txt > pr_commit.txt
212+
213+
# Get author info and signed-off-by lines
214+
if [[ -s author-info.txt && -s signed-off-by-final.txt ]]; then
215+
author=$(cat author-info.txt)
216+
echo "" >> pr_commit.txt # Blank line before trailers
217+
cat signed-off-by-final.txt >> pr_commit.txt
218+
219+
# Amend commit with PR content
220+
git commit --amend --author "$author" -F pr_commit.txt
221+
echo "::notice::Updated commit with PR title and body"
222+
fi
223+
env:
224+
GH_TOKEN: ${{ github.token }}
225+
226+
- name: "Submit to Gerrit"
227+
run: ./scripts/submit-to-gerrit.sh
228+
env:
229+
REVIEWERS_EMAIL: ${{ inputs.REVIEWERS_EMAIL }}
230+
GERRIT_SSH_USER_G2G: ${{ inputs.GERRIT_SSH_USER_G2G }}
231+
GERRIT_SERVER: ${{ env.GERRIT_SERVER }}
232+
GITHUB_SERVER_URL: ${{ github.server_url }}
233+
GITHUB_REPOSITORY: ${{ github.repository }}
234+
GITHUB_RUN_ID: ${{ github.run_id }}
235+
236+
- name: "Add GitHub reference to PR"
237+
if: env.GERRIT_CR_URL_CID != ''
238+
uses: actions/github-script@v7
239+
with:
240+
script: |
241+
const changeUrls = `${{ env.GERRIT_CR_URL_CID }}`;
242+
const prNumber = `${{ env.PR_NUMBER }}`;
243+
const organization = `${{ inputs.ORGANIZATION }}`;
244+
const gerritServer = `${{ env.GERRIT_SERVER }}`;
245+
246+
const message = `The pull-request PR-${prNumber} has been submitted to Gerrit [${organization}](https://${gerritServer})!
247+
248+
**Gerrit Change(s):**
249+
${changeUrls}
250+
251+
**Important:** This PR will be closed automatically. Re-opening will create a new change - use the Gerrit link above for updates.`;
252+
253+
await github.rest.issues.createComment({
254+
issue_number: context.issue.number,
255+
owner: context.repo.owner,
256+
repo: context.repo.repo,
257+
body: message
258+
});
259+
260+
- name: "Close pull request"
261+
if: github.event_name == 'pull_request_target'
262+
run: |
263+
echo "::notice::Closing PR #$PR_NUMBER after successful Gerrit submission"
264+
265+
if [[ -n "${SET_ISSUE_ID:-}" ]]; then
266+
# Keep branch for issue tracking workflows
267+
gh pr close --comment "Auto-closed: Submitted to Gerrit" "$PR_NUMBER"
268+
else
269+
# Delete branch for cleaner repo
270+
gh pr close --comment "Auto-closed: Submitted to Gerrit" --delete-branch "$PR_NUMBER"
271+
fi
272+
env:
273+
GH_TOKEN: ${{ github.token }}
274+
275+
- name: "Workflow summary"
276+
if: always()
277+
run: |
278+
{
279+
echo "# GitHub2Gerrit Workflow Summary"
280+
echo ""
281+
echo "**PR Number:** $PR_NUMBER"
282+
echo "**Repository:** ${{ github.repository }}"
283+
echo "**Gerrit Server:** ${GERRIT_SERVER:-'Not set'}"
284+
echo "**Submission Mode:** ${{ inputs.SUBMIT_SINGLE_COMMITS == true && 'Individual commits' || 'Squashed commit' }}"
285+
286+
if [[ -n "${GERRIT_CHANGE_REQUEST_URL:-}" ]]; then
287+
echo ""
288+
echo "**✅ Success:** Changes submitted to Gerrit"
289+
echo "**Gerrit URL(s):** $GERRIT_CHANGE_REQUEST_URL"
290+
else
291+
echo ""
292+
echo "**❌ Status:** Workflow completed but no Gerrit URL available"
293+
fi
294+
295+
echo ""
296+
echo "**Timestamp:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
297+
} >> "$GITHUB_STEP_SUMMARY"
298+
299+
- name: "Set job outputs"
300+
if: env.GERRIT_CHANGE_REQUEST_URL != ''
301+
run: |
302+
echo "change_url=${GERRIT_CHANGE_REQUEST_URL}" >> "$GITHUB_OUTPUT"
303+
echo "change_number=${GERRIT_CHANGE_REQUEST_NUM}" >> "$GITHUB_OUTPUT"

0 commit comments

Comments
 (0)