Skip to content

Commit 226b174

Browse files
committed
Adding github action to generate a badge for first time contributor
This github action uses the certficate_generator.html from automation repo to generate the badge for first time contributors and commits the badge to the same repo which will be commented on the PR once its get merged. Signed-off-by: Mohan Boddu <[email protected]>
1 parent 43c95d2 commit 226b174

File tree

1 file changed

+269
-0
lines changed

1 file changed

+269
-0
lines changed
Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
1+
name: Generate Contributor Certificate Preview
2+
3+
# This action triggers automatically when a pull request is closed,
4+
# or can be run manually from the Actions tab.
5+
on:
6+
pull_request:
7+
types: [closed]
8+
branches:
9+
- main
10+
workflow_dispatch:
11+
inputs:
12+
contributor_username:
13+
description: 'The GitHub username of the contributor'
14+
required: true
15+
pr_number:
16+
description: 'The pull request number'
17+
required: true
18+
19+
# Permissions needed for this workflow.
20+
permissions:
21+
contents: read # Write access for certificate storage
22+
pull-requests: write # Write access to comment on PRs
23+
actions: read # Read access for workflow actions
24+
25+
jobs:
26+
screenshot_and_comment:
27+
# This job runs if the PR was merged or if it's a manual trigger.
28+
# The logic for first-time contributors is handled in a dedicated step below.
29+
if: ${{ github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true }}
30+
runs-on: ubuntu-latest
31+
steps:
32+
# Step 1: Check if this is the contributor's first merged PR.
33+
# This step is the source of truth and will control the execution of subsequent steps.
34+
- name: Check for first merged PR
35+
id: check_first_pr
36+
if: ${{ github.event_name == 'pull_request' }}
37+
uses: actions/github-script@v7
38+
with:
39+
script: |
40+
const author = context.payload.pull_request.user.login;
41+
const query = `repo:${context.repo.owner}/${context.repo.repo} is:pr is:merged author:${author}`;
42+
43+
console.log(`Searching for merged PRs from @${author} with query: "${query}"`);
44+
45+
const result = await github.rest.search.issuesAndPullRequests({ q: query });
46+
const mergedPRs = result.data.total_count;
47+
48+
if (mergedPRs === 1) {
49+
console.log(`SUCCESS: This is the first merged PR from @${author}. Proceeding...`);
50+
core.setOutput('is_first_pr', 'true');
51+
} else {
52+
console.log(`INFO: Skipping certificate generation. @${author} has ${mergedPRs} total merged PRs.`);
53+
core.setOutput('is_first_pr', 'false');
54+
}
55+
56+
# Step 2: Checkout the repository containing the certificate HTML file.
57+
- name: Checkout containers/automation repository
58+
if: ${{ github.event_name == 'workflow_dispatch' || steps.check_first_pr.outputs.is_first_pr == 'true' }}
59+
uses: actions/checkout@v4
60+
with:
61+
repository: containers/automation
62+
path: automation-repo
63+
64+
# Step 3: Update the HTML file locally
65+
- name: Update HTML file
66+
if: ${{ github.event_name == 'workflow_dispatch' || steps.check_first_pr.outputs.is_first_pr == 'true' }}
67+
run: |
68+
HTML_FILE="automation-repo/certificate-generator/certificate_generator.html"
69+
CONTRIBUTOR_NAME="${{ github.event.inputs.contributor_username || github.event.pull_request.user.login }}"
70+
PR_NUMBER="${{ github.event.inputs.pr_number || github.event.pull_request.number }}"
71+
MERGE_DATE=$(date -u +"%B %d, %Y")
72+
73+
sed -i "/id=\"contributorName\"/s/value=\"[^\"]*\"/value=\"${CONTRIBUTOR_NAME}\"/" ${HTML_FILE} || { echo "ERROR: Failed to update contributor name."; exit 1; }
74+
sed -i "/id=\"prNumber\"/s/value=\"[^\"]*\"/value=\"#${PR_NUMBER}\"/" ${HTML_FILE} || { echo "ERROR: Failed to update PR number."; exit 1; }
75+
sed -i "/id=\"mergeDate\"/s/value=\"[^\"]*\"/value=\"${MERGE_DATE}\"/" ${HTML_FILE} || { echo "ERROR: Failed to update merge date."; exit 1; }
76+
77+
# Step 4: Setup Node.js environment
78+
- name: Setup Node.js
79+
if: ${{ github.event_name == 'workflow_dispatch' || steps.check_first_pr.outputs.is_first_pr == 'true' }}
80+
uses: actions/setup-node@v4
81+
with:
82+
node-version: latest
83+
84+
# Step 5: Install Puppeteer
85+
- name: Install Puppeteer
86+
if: ${{ github.event_name == 'workflow_dispatch' || steps.check_first_pr.outputs.is_first_pr == 'true' }}
87+
run: |
88+
npm install puppeteer || { echo "ERROR: Failed to install Puppeteer."; exit 1; }
89+
90+
# Step 6: Take a screenshot of the certificate div
91+
- name: Create and run screenshot script
92+
if: ${{ github.event_name == 'workflow_dispatch' || steps.check_first_pr.outputs.is_first_pr == 'true' }}
93+
run: |
94+
cat <<'EOF' > screenshot.js
95+
const puppeteer = require('puppeteer');
96+
const path = require('path');
97+
(async () => {
98+
const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
99+
const page = await browser.newPage();
100+
const htmlPath = 'file://' + path.resolve('automation-repo/certificate-generator/certificate_generator.html');
101+
await page.goto(htmlPath, { waitUntil: 'networkidle0' });
102+
await page.setViewport({ width: 1080, height: 720 });
103+
const element = await page.$('#certificatePreview');
104+
if (!element) {
105+
console.error('Could not find element #certificatePreview.');
106+
process.exit(1);
107+
}
108+
await element.screenshot({ path: 'certificate.png' });
109+
await browser.close();
110+
console.log('Screenshot saved as certificate.png');
111+
})().catch(err => {
112+
console.error(err);
113+
process.exit(1);
114+
});
115+
EOF
116+
node screenshot.js || { echo "ERROR: Screenshot script failed."; exit 1; }
117+
118+
# Step 7: Upload certificate image to separate repository
119+
- name: Upload certificate to separate repository
120+
if: ${{ github.event_name == 'workflow_dispatch' || steps.check_first_pr.outputs.is_first_pr == 'true' }}
121+
uses: actions/github-script@v7
122+
with:
123+
github-token: ${{ secrets.CERTIFICATES_REPO_TOKEN }}
124+
script: |
125+
const fs = require('fs');
126+
127+
try {
128+
// Check if certificate.png exists
129+
if (!fs.existsSync('certificate.png')) {
130+
throw new Error('certificate.png not found!');
131+
}
132+
133+
// Debug: Check token and repository access
134+
console.log('Testing repository access...');
135+
const certificatesOwner = process.env.CERTIFICATES_REPO_OWNER || context.repo.owner;
136+
const certificatesRepo = process.env.CERTIFICATES_REPO_NAME || 'automation';
137+
138+
// Test repository access first
139+
try {
140+
await github.rest.repos.get({
141+
owner: certificatesOwner,
142+
repo: certificatesRepo
143+
});
144+
console.log(`✅ Repository access confirmed: ${certificatesOwner}/${certificatesRepo}`);
145+
} catch (accessError) {
146+
console.error(`❌ Repository access failed: ${accessError.message}`);
147+
throw new Error(`Cannot access repository ${certificatesOwner}/${certificatesRepo}. Check token permissions and repository existence.`);
148+
}
149+
150+
// Read the certificate image
151+
const imageBuffer = fs.readFileSync('certificate.png');
152+
const base64Content = imageBuffer.toString('base64');
153+
154+
console.log(`Certificate image size: ${imageBuffer.length} bytes`);
155+
156+
// Create a unique filename with timestamp
157+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
158+
const contributorName = context.eventName === 'workflow_dispatch'
159+
? '${{ github.event.inputs.contributor_username }}'
160+
: '${{ github.event.pull_request.user.login }}';
161+
const prNumber = context.eventName === 'workflow_dispatch'
162+
? '${{ github.event.inputs.pr_number }}'
163+
: context.issue.number;
164+
165+
const filename = `certificates/${contributorName}-${prNumber}-${timestamp}.png`;
166+
167+
// Configuration for the certificates repository
168+
const certificatesBranch = process.env.CERTIFICATES_REPO_BRANCH || 'main';
169+
170+
console.log(`Uploading to repository: ${certificatesOwner}/${certificatesRepo}`);
171+
console.log(`File path: ${filename}`);
172+
console.log(`Branch: ${certificatesBranch}`);
173+
174+
// Upload the file to the certificates repository
175+
await github.rest.repos.createOrUpdateFileContents({
176+
owner: certificatesOwner,
177+
repo: certificatesRepo,
178+
path: filename,
179+
message: `Add certificate for ${contributorName} from ${context.repo.owner}/${context.repo.repo} (PR #${prNumber})\n\nSigned-off-by: Podman Bot <[email protected]>`,
180+
content: base64Content,
181+
branch: certificatesBranch,
182+
author: {
183+
name: 'Podman Bot',
184+
185+
},
186+
committer: {
187+
name: 'Podman Bot',
188+
189+
}
190+
});
191+
192+
// Create the image URL
193+
const imageUrl = `https://github.com/${certificatesOwner}/${certificatesRepo}/raw/${certificatesBranch}/${filename}`;
194+
195+
console.log(`Certificate uploaded successfully: ${imageUrl}`);
196+
197+
// Store the image URL for the comment step
198+
core.exportVariable('CERTIFICATE_IMAGE_URL', imageUrl);
199+
core.exportVariable('CERTIFICATE_UPLOADED', 'true');
200+
201+
} catch (error) {
202+
console.error('Failed to upload certificate:', error);
203+
console.error('Error details:', error.message);
204+
205+
// Provide helpful error message if it's likely a permissions issue
206+
let errorMsg = error.message;
207+
if (error.status === 404) {
208+
errorMsg += ' (Repository not found - check CERTIFICATES_REPO_OWNER and CERTIFICATES_REPO_NAME environment variables, or ensure the automation repository exists and the token has access)';
209+
} else if (error.status === 403) {
210+
errorMsg += ' (Permission denied - check that CERTIFICATES_REPO_TOKEN has write access to the automation repository)';
211+
}
212+
213+
core.exportVariable('CERTIFICATE_UPLOADED', 'false');
214+
core.exportVariable('UPLOAD_ERROR', errorMsg);
215+
}
216+
217+
# Step 8: Comment on Pull Request with embedded image
218+
- name: Comment with embedded certificate image
219+
if: ${{ github.event_name == 'workflow_dispatch' || steps.check_first_pr.outputs.is_first_pr == 'true' }}
220+
uses: actions/github-script@v7
221+
with:
222+
script: |
223+
try {
224+
let body;
225+
226+
// Check if certificate was uploaded successfully
227+
if (process.env.CERTIFICATE_UPLOADED === 'true') {
228+
const imageUrl = process.env.CERTIFICATE_IMAGE_URL;
229+
console.log(`Using uploaded certificate image: ${imageUrl}`);
230+
231+
// Create the image content with the uploaded image URL
232+
const imageContent = `![Certificate Preview](${imageUrl})`;
233+
body = imageContent;
234+
} else {
235+
console.log('Certificate upload failed, providing fallback message');
236+
const errorMsg = process.env.UPLOAD_ERROR || 'Unknown error';
237+
body = `📜 **Certificate Preview**\n\n_Certificate generation completed, but there was an issue uploading the image: ${errorMsg}_\n\nPlease check the workflow logs for more details.`;
238+
}
239+
240+
if (context.eventName === 'workflow_dispatch') {
241+
// Manual trigger case
242+
const contributorName = '${{ github.event.inputs.contributor_username }}';
243+
const prNumber = '${{ github.event.inputs.pr_number }}';
244+
body = `📜 Certificate preview generated for @${contributorName} (PR #${prNumber}):\n\n${body}`;
245+
} else {
246+
// Auto trigger case for first-time contributors
247+
const username = '${{ github.event.pull_request.user.login }}';
248+
body = `🎉 Congratulations on your first merged pull request, @${username}! Thank you for your contribution.\n\nHere's a preview of your certificate:\n\n${body}`;
249+
}
250+
251+
const issueNumber = context.eventName === 'workflow_dispatch' ?
252+
parseInt('${{ github.event.inputs.pr_number }}') :
253+
context.issue.number;
254+
255+
await github.rest.issues.createComment({
256+
issue_number: issueNumber,
257+
owner: context.repo.owner,
258+
repo: context.repo.repo,
259+
body: body,
260+
});
261+
} catch (error) {
262+
core.setFailed(`ERROR: Failed to comment on PR. Details: ${error.message}`);
263+
}
264+
265+
# Step 9: Clean up temporary files
266+
- name: Clean up temporary files
267+
if: ${{ always() && (github.event_name == 'workflow_dispatch' || steps.check_first_pr.outputs.is_first_pr == 'true') }}
268+
run: |
269+
rm -f certificate.png

0 commit comments

Comments
 (0)