Skip to content

Commit 2d39837

Browse files
committed
feat: Refactor github2gerrit add tests 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 2d39837

15 files changed

+1730
-90
lines changed
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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+
# yamllint disable-line rule:truthy
9+
on:
10+
pull_request_target:
11+
types: [opened, reopened, synchronize]
12+
branches: [main, master]
13+
workflow_dispatch:
14+
inputs:
15+
pr_number:
16+
description: "PR number to process"
17+
required: true
18+
type: number
19+
20+
concurrency:
21+
group: example-g2g-v2-${{ github.ref }}
22+
cancel-in-progress: true
23+
24+
jobs:
25+
submit-to-gerrit:
26+
name: "Submit PR to Gerrit (V2)"
27+
if: false # Disabled by default - remove this line to enable
28+
permissions:
29+
contents: read
30+
pull-requests: write
31+
uses: ./.github/workflows/github2gerrit-v2.yaml
32+
with:
33+
# Submission options
34+
SUBMIT_SINGLE_COMMITS: false # Default: squash commits
35+
USE_PR_AS_COMMIT: false # Default: use original commit messages
36+
FETCH_DEPTH: "0" # Fetch full history
37+
38+
# Gerrit configuration (required)
39+
GERRIT_KNOWN_HOSTS: ${{ vars.GERRIT_KNOWN_HOSTS }}
40+
GERRIT_SSH_USER_G2G: ${{ vars.GERRIT_SSH_USER_G2G }}
41+
GERRIT_SSH_USER_G2G_EMAIL: ${{ vars.GERRIT_SSH_USER_G2G_EMAIL }}
42+
43+
# Optional Gerrit overrides (use when .gitreview is missing)
44+
# GERRIT_SERVER: "review.opendev.org"
45+
# GERRIT_SERVER_PORT: "29418"
46+
# GERRIT_PROJECT: "openstack/nova"
47+
48+
# GitHub organization
49+
ORGANIZATION: ${{ vars.ORGANIZATION }}
50+
51+
# Optional: Reviewers to notify
52+
REVIEWERS_EMAIL: ${{ vars.REVIEWERS_EMAIL }}
53+
secrets:
54+
GERRIT_SSH_PRIVKEY_G2G: ${{ secrets.GERRIT_SSH_PRIVKEY_G2G }}
55+
56+
# Example: Use the outputs from the workflow
57+
follow-up-actions:
58+
name: "Follow-up Actions"
59+
needs: submit-to-gerrit
60+
if: needs.submit-to-gerrit.outputs.gerrit_change_url != ''
61+
runs-on: ubuntu-latest
62+
steps:
63+
- name: "Process Gerrit submission results"
64+
run: |
65+
echo "::notice::Gerrit change created successfully!"
66+
echo "::notice::Change URL: ${{ needs.submit-to-gerrit.outputs.gerrit_change_url }}"
67+
echo "::notice::Change Number: ${{ needs.submit-to-gerrit.outputs.gerrit_change_number }}"
68+
69+
# Example: Send notification, update issue, etc.
70+
# slack-notify, email, database update, etc.
Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,307 @@
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+
# yamllint disable rule:line-length
239+
uses: actions/github-script@v7
240+
with:
241+
# yamllint disable rule:line-length
242+
script: |
243+
const changeUrls = `${{ env.GERRIT_CR_URL_CID }}`;
244+
const prNumber = `${{ env.PR_NUMBER }}`;
245+
const organization = `${{ inputs.ORGANIZATION }}`;
246+
const gerritServer = `${{ env.GERRIT_SERVER }}`;
247+
248+
const message = `The pull-request PR-${prNumber} has been submitted to Gerrit [${organization}](https://${gerritServer})!
249+
250+
**Gerrit Change(s):**
251+
${changeUrls}
252+
253+
**Important:** This PR will be closed automatically. Re-opening will create a new change - use the Gerrit link above for updates.`;
254+
255+
await github.rest.issues.createComment({
256+
issue_number: context.issue.number,
257+
owner: context.repo.owner,
258+
repo: context.repo.repo,
259+
body: message
260+
});
261+
# yamllint enable rule:line-length
262+
263+
- name: "Close pull request"
264+
if: github.event_name == 'pull_request_target'
265+
run: |
266+
echo "::notice::Closing PR #$PR_NUMBER after successful Gerrit submission"
267+
268+
if [[ -n "${SET_ISSUE_ID:-}" ]]; then
269+
# Keep branch for issue tracking workflows
270+
gh pr close --comment "Auto-closed: Submitted to Gerrit" "$PR_NUMBER"
271+
else
272+
# Delete branch for cleaner repo
273+
gh pr close --comment "Auto-closed: Submitted to Gerrit" --delete-branch "$PR_NUMBER"
274+
fi
275+
env:
276+
GH_TOKEN: ${{ github.token }}
277+
278+
- name: "Workflow summary"
279+
if: always()
280+
run: |
281+
{
282+
echo "# GitHub2Gerrit Workflow Summary"
283+
echo ""
284+
echo "**PR Number:** $PR_NUMBER"
285+
echo "**Repository:** ${{ github.repository }}"
286+
echo "**Gerrit Server:** ${GERRIT_SERVER:-'Not set'}"
287+
submission_mode="${{ inputs.SUBMIT_SINGLE_COMMITS == true && 'Individual commits' || 'Squashed commit' }}"
288+
echo "**Submission Mode:** $submission_mode"
289+
290+
if [[ -n "${GERRIT_CHANGE_REQUEST_URL:-}" ]]; then
291+
echo ""
292+
echo "**✅ Success:** Changes submitted to Gerrit"
293+
echo "**Gerrit URL(s):** $GERRIT_CHANGE_REQUEST_URL"
294+
else
295+
echo ""
296+
echo "**❌ Status:** Workflow completed but no Gerrit URL available"
297+
fi
298+
299+
echo ""
300+
echo "**Timestamp:** $(date -u '+%Y-%m-%d %H:%M:%S UTC')"
301+
} >> "$GITHUB_STEP_SUMMARY"
302+
303+
- name: "Set job outputs"
304+
if: env.GERRIT_CHANGE_REQUEST_URL != ''
305+
run: |
306+
echo "change_url=${GERRIT_CHANGE_REQUEST_URL}" >> "$GITHUB_OUTPUT"
307+
echo "change_number=${GERRIT_CHANGE_REQUEST_NUM}" >> "$GITHUB_OUTPUT"

.github/workflows/github2gerrit.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
name: github2gerrit-reusable-workflow
66

7+
# yamllint disable-line rule:truthy
78
on:
89
workflow_call:
910
inputs:

0 commit comments

Comments
 (0)